Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 2c87ebf

Browse files
author
Jonah Williams
authored
[Impeller] Reland: hash less stuff per frame. (#55742)
Reverted due to missing glyph when rolling into the framework. Problem was that we should be always using the unrounded rounded scale to compute subpixel position but we sometimes used the rounded scale. --- Currently we have multiple stages of hashing while font rendering, which is relatively expensive for the actualy required workload. First, we hash the contents of all text frames to compute the unique set of glyphs per frame. Then we diff this hash set against the hashmap of glyphs within the atlas. Finally we hash and lookup the final rendered bounds for each glyph. We can simplify this to 2. hash lookups for glyphs not yet in the atlas and 1. hash lookup for glyphs that are in the atlas. This is done by combing the step where we uniquely compute glyphs per frame with the diff against the current atlas. When this lookup is performed, we also store the glyph position (if found) in the text_frame itself - which allows text contents to skip the last hash, as long as the glyph has already been rendered. See #55092
1 parent ecfdd29 commit 2c87ebf

File tree

15 files changed

+331
-170
lines changed

15 files changed

+331
-170
lines changed

impeller/display_list/dl_dispatcher.cc

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1112,10 +1112,13 @@ void TextFrameDispatcher::drawTextFrame(
11121112
}
11131113
auto scale =
11141114
(matrix_ * Matrix::MakeTranslation(Point(x, y))).GetMaxBasisLengthXY();
1115-
renderer_.GetLazyGlyphAtlas()->AddTextFrame(*text_frame, //
1116-
scale, //
1117-
Point(x, y), //
1118-
properties //
1115+
renderer_.GetLazyGlyphAtlas()->AddTextFrame(
1116+
text_frame, //
1117+
scale, //
1118+
Point(x, y), //
1119+
(properties.stroke || text_frame->HasColor()) //
1120+
? std::optional<GlyphProperties>(properties) //
1121+
: std::nullopt //
11191122
);
11201123
}
11211124

impeller/entity/contents/text_contents.cc

Lines changed: 49 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
#include "impeller/geometry/point.h"
1818
#include "impeller/renderer/render_pass.h"
1919
#include "impeller/typographer/glyph_atlas.h"
20-
#include "impeller/typographer/lazy_glyph_atlas.h"
2120

2221
namespace impeller {
2322

@@ -90,6 +89,10 @@ bool TextContents::Render(const ContentContext& renderer,
9089
VALIDATION_LOG << "Cannot render glyphs without prepared atlas.";
9190
return false;
9291
}
92+
if (!frame_->IsFrameComplete()) {
93+
VALIDATION_LOG << "Failed to find font glyph bounds.";
94+
return false;
95+
}
9396

9497
// Information shared by all glyph draw calls.
9598
pass.SetCommandLabel("TextFrame");
@@ -169,16 +172,12 @@ bool TextContents::Render(const ContentContext& renderer,
169172
VS::PerVertexData* vtx_contents =
170173
reinterpret_cast<VS::PerVertexData*>(contents);
171174
size_t i = 0u;
175+
size_t bounds_offset = 0u;
172176
for (const TextRun& run : frame_->GetRuns()) {
173177
const Font& font = run.GetFont();
174178
Scalar rounded_scale = TextFrame::RoundScaledFontSize(
175179
scale_, font.GetMetrics().point_size);
176-
const FontGlyphAtlas* font_atlas =
177-
atlas->GetFontGlyphAtlas(font, rounded_scale);
178-
if (!font_atlas) {
179-
VALIDATION_LOG << "Could not find font in the atlas.";
180-
continue;
181-
}
180+
FontGlyphAtlas* font_atlas = nullptr;
182181

183182
// Adjust glyph position based on the subpixel rounding
184183
// used by the font.
@@ -201,22 +200,44 @@ bool TextContents::Render(const ContentContext& renderer,
201200
Point screen_offset = (entity_transform * Point(0, 0));
202201
for (const TextRun::GlyphPosition& glyph_position :
203202
run.GetGlyphPositions()) {
204-
// Note: uses unrounded scale for more accurate subpixel position.
205-
Point subpixel = TextFrame::ComputeSubpixelPosition(
206-
glyph_position, font.GetAxisAlignment(), offset_, scale_);
207-
std::optional<std::pair<Rect, Rect>> maybe_atlas_glyph_bounds =
208-
font_atlas->FindGlyphBounds(SubpixelGlyph{
209-
glyph_position.glyph, subpixel,
210-
(properties_.stroke || frame_->HasColor())
211-
? std::optional<GlyphProperties>(properties_)
212-
: std::nullopt});
213-
if (!maybe_atlas_glyph_bounds.has_value()) {
214-
VALIDATION_LOG << "Could not find glyph position in the atlas.";
215-
continue;
203+
const FrameBounds& frame_bounds =
204+
frame_->GetFrameBounds(bounds_offset);
205+
bounds_offset++;
206+
auto atlas_glyph_bounds = frame_bounds.atlas_bounds;
207+
auto glyph_bounds = frame_bounds.glyph_bounds;
208+
209+
// If frame_bounds.is_placeholder is true, this is the first frame
210+
// the glyph has been rendered and so its atlas position was not
211+
// known when the glyph was recorded. Perform a slow lookup into the
212+
// glyph atlas hash table.
213+
if (frame_bounds.is_placeholder) {
214+
if (!font_atlas) {
215+
font_atlas = atlas->GetOrCreateFontGlyphAtlas(
216+
ScaledFont{font, rounded_scale});
217+
}
218+
219+
if (!font_atlas) {
220+
VALIDATION_LOG << "Could not find font in the atlas.";
221+
continue;
222+
}
223+
// Note: uses unrounded scale for more accurate subpixel position.
224+
Point subpixel = TextFrame::ComputeSubpixelPosition(
225+
glyph_position, font.GetAxisAlignment(), offset_, scale_);
226+
227+
std::optional<FrameBounds> maybe_atlas_glyph_bounds =
228+
font_atlas->FindGlyphBounds(SubpixelGlyph{
229+
glyph_position.glyph, //
230+
subpixel, //
231+
GetGlyphProperties() //
232+
});
233+
if (!maybe_atlas_glyph_bounds.has_value()) {
234+
VALIDATION_LOG << "Could not find glyph position in the atlas.";
235+
continue;
236+
}
237+
atlas_glyph_bounds =
238+
maybe_atlas_glyph_bounds.value().atlas_bounds;
216239
}
217-
const Rect& atlas_glyph_bounds =
218-
maybe_atlas_glyph_bounds.value().first;
219-
Rect glyph_bounds = maybe_atlas_glyph_bounds.value().second;
240+
220241
Rect scaled_bounds = glyph_bounds.Scale(1.0 / rounded_scale);
221242
// For each glyph, we compute two rectangles. One for the vertex
222243
// positions and one for the texture coordinates (UVs). The atlas
@@ -263,4 +284,10 @@ bool TextContents::Render(const ContentContext& renderer,
263284
return pass.Draw().ok();
264285
}
265286

287+
std::optional<GlyphProperties> TextContents::GetGlyphProperties() const {
288+
return (properties_.stroke || frame_->HasColor())
289+
? std::optional<GlyphProperties>(properties_)
290+
: std::nullopt;
291+
}
292+
266293
} // namespace impeller

impeller/entity/contents/text_contents.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ class TextContents final : public Contents {
6262
RenderPass& pass) const override;
6363

6464
private:
65+
std::optional<GlyphProperties> GetGlyphProperties() const;
66+
6567
std::shared_ptr<TextFrame> frame_;
6668
Scalar scale_ = 1.0;
6769
Scalar inherited_opacity_ = 1.0;

impeller/typographer/backends/skia/typographer_context_skia.cc

Lines changed: 75 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,8 @@ static bool BulkUpdateAtlasBitmap(const GlyphAtlas& atlas,
283283
if (!data.has_value()) {
284284
continue;
285285
}
286-
auto [pos, bounds] = data.value();
286+
auto [pos, bounds, placeholder] = data.value();
287+
FML_DCHECK(!placeholder);
287288
Size size = pos.GetSize();
288289
if (size.IsEmpty()) {
289290
continue;
@@ -326,7 +327,9 @@ static bool UpdateAtlasBitmap(const GlyphAtlas& atlas,
326327
if (!data.has_value()) {
327328
continue;
328329
}
329-
auto [pos, bounds] = data.value();
330+
auto [pos, bounds, placeholder] = data.value();
331+
FML_DCHECK(!placeholder);
332+
330333
Size size = pos.GetSize();
331334
if (size.IsEmpty()) {
332335
continue;
@@ -404,61 +407,88 @@ static Rect ComputeGlyphSize(const SkFont& font,
404407
scaled_bounds.fBottom);
405408
};
406409

407-
static void CollectNewGlyphs(const std::shared_ptr<GlyphAtlas>& atlas,
408-
const FontGlyphMap& font_glyph_map,
409-
std::vector<FontGlyphPair>& new_glyphs,
410-
std::vector<Rect>& glyph_sizes) {
411-
for (const auto& font_value : font_glyph_map) {
412-
const ScaledFont& scaled_font = font_value.first;
413-
const FontGlyphAtlas* font_glyph_atlas =
414-
atlas->GetFontGlyphAtlas(scaled_font.font, scaled_font.scale);
415-
416-
auto metrics = scaled_font.font.GetMetrics();
417-
418-
SkFont sk_font(
419-
TypefaceSkia::Cast(*scaled_font.font.GetTypeface()).GetSkiaTypeface(),
420-
metrics.point_size, metrics.scaleX, metrics.skewX);
421-
sk_font.setEdging(SkFont::Edging::kAntiAlias);
422-
sk_font.setHinting(SkFontHinting::kSlight);
423-
sk_font.setEmbolden(metrics.embolden);
424-
// Rather than computing the bounds at the requested point size and scaling
425-
// up the bounds, we scale up the font size and request the bounds. This
426-
// seems to give more accurate bounds information.
427-
sk_font.setSize(sk_font.getSize() * scaled_font.scale);
428-
sk_font.setSubpixel(true);
429-
430-
if (font_glyph_atlas) {
431-
for (const SubpixelGlyph& glyph : font_value.second) {
432-
if (!font_glyph_atlas->FindGlyphBounds(glyph)) {
433-
new_glyphs.emplace_back(scaled_font, glyph);
434-
glyph_sizes.push_back(
435-
ComputeGlyphSize(sk_font, glyph, scaled_font.scale));
410+
std::pair<std::vector<FontGlyphPair>, std::vector<Rect>>
411+
TypographerContextSkia::CollectNewGlyphs(
412+
const std::shared_ptr<GlyphAtlas>& atlas,
413+
const std::vector<std::shared_ptr<TextFrame>>& text_frames) {
414+
std::vector<FontGlyphPair> new_glyphs;
415+
std::vector<Rect> glyph_sizes;
416+
for (const auto& frame : text_frames) {
417+
// TODO(jonahwilliams): unless we destroy the atlas (which we know about),
418+
// we could probably guarantee that a text frame that is complete does not
419+
// need to be processed unless the scale or properties changed. I'm leaving
420+
// this as a future optimization.
421+
frame->ClearFrameBounds();
422+
423+
for (const auto& run : frame->GetRuns()) {
424+
auto metrics = run.GetFont().GetMetrics();
425+
426+
auto rounded_scale =
427+
TextFrame::RoundScaledFontSize(frame->GetScale(), metrics.point_size);
428+
ScaledFont scaled_font{.font = run.GetFont(), .scale = rounded_scale};
429+
430+
FontGlyphAtlas* font_glyph_atlas =
431+
atlas->GetOrCreateFontGlyphAtlas(scaled_font);
432+
FML_DCHECK(!!font_glyph_atlas);
433+
434+
SkFont sk_font(
435+
TypefaceSkia::Cast(*scaled_font.font.GetTypeface()).GetSkiaTypeface(),
436+
metrics.point_size, metrics.scaleX, metrics.skewX);
437+
sk_font.setEdging(SkFont::Edging::kAntiAlias);
438+
sk_font.setHinting(SkFontHinting::kSlight);
439+
sk_font.setEmbolden(metrics.embolden);
440+
// Rather than computing the bounds at the requested point size and
441+
// scaling up the bounds, we scale up the font size and request the
442+
// bounds. This seems to give more accurate bounds information.
443+
sk_font.setSize(sk_font.getSize() * scaled_font.scale);
444+
sk_font.setSubpixel(true);
445+
446+
for (const auto& glyph_position : run.GetGlyphPositions()) {
447+
Point subpixel = TextFrame::ComputeSubpixelPosition(
448+
glyph_position, scaled_font.font.GetAxisAlignment(),
449+
frame->GetOffset(), frame->GetScale());
450+
SubpixelGlyph subpixel_glyph(glyph_position.glyph, subpixel,
451+
frame->GetProperties());
452+
const auto& font_glyph_bounds =
453+
font_glyph_atlas->FindGlyphBounds(subpixel_glyph);
454+
455+
if (!font_glyph_bounds.has_value()) {
456+
new_glyphs.push_back(FontGlyphPair{scaled_font, subpixel_glyph});
457+
auto glyph_bounds =
458+
ComputeGlyphSize(sk_font, subpixel_glyph, scaled_font.scale);
459+
glyph_sizes.push_back(glyph_bounds);
460+
461+
auto frame_bounds = FrameBounds{
462+
Rect::MakeLTRB(0, 0, 0, 0), //
463+
glyph_bounds, //
464+
/*placeholder=*/true //
465+
};
466+
467+
frame->AppendFrameBounds(frame_bounds);
468+
font_glyph_atlas->AppendGlyph(subpixel_glyph, frame_bounds);
469+
} else {
470+
frame->AppendFrameBounds(font_glyph_bounds.value());
436471
}
437472
}
438-
} else {
439-
for (const SubpixelGlyph& glyph : font_value.second) {
440-
new_glyphs.emplace_back(scaled_font, glyph);
441-
glyph_sizes.push_back(
442-
ComputeGlyphSize(sk_font, glyph, scaled_font.scale));
443-
}
444473
}
445474
}
475+
return {std::move(new_glyphs), std::move(glyph_sizes)};
446476
}
447477

448478
std::shared_ptr<GlyphAtlas> TypographerContextSkia::CreateGlyphAtlas(
449479
Context& context,
450480
GlyphAtlas::Type type,
451481
HostBuffer& host_buffer,
452482
const std::shared_ptr<GlyphAtlasContext>& atlas_context,
453-
const FontGlyphMap& font_glyph_map) const {
483+
const std::vector<std::shared_ptr<TextFrame>>& text_frames) const {
454484
TRACE_EVENT0("impeller", __FUNCTION__);
455485
if (!IsValid()) {
456486
return nullptr;
457487
}
458488
std::shared_ptr<GlyphAtlas> last_atlas = atlas_context->GetGlyphAtlas();
459489
FML_DCHECK(last_atlas->GetType() == type);
460490

461-
if (font_glyph_map.empty()) {
491+
if (text_frames.empty()) {
462492
return last_atlas;
463493
}
464494

@@ -467,9 +497,7 @@ std::shared_ptr<GlyphAtlas> TypographerContextSkia::CreateGlyphAtlas(
467497
// with the current atlas and reuse if possible. For each new font and
468498
// glyph pair, compute the glyph size at scale.
469499
// ---------------------------------------------------------------------------
470-
std::vector<FontGlyphPair> new_glyphs;
471-
std::vector<Rect> glyph_sizes;
472-
CollectNewGlyphs(last_atlas, font_glyph_map, new_glyphs, glyph_sizes);
500+
auto [new_glyphs, glyph_sizes] = CollectNewGlyphs(last_atlas, text_frames);
473501
if (new_glyphs.size() == 0) {
474502
return last_atlas;
475503
}
@@ -540,9 +568,11 @@ std::shared_ptr<GlyphAtlas> TypographerContextSkia::CreateGlyphAtlas(
540568
blit_old_atlas = false;
541569
new_atlas = std::make_shared<GlyphAtlas>(type);
542570

543-
new_glyphs.clear();
544-
glyph_sizes.clear();
545-
CollectNewGlyphs(new_atlas, font_glyph_map, new_glyphs, glyph_sizes);
571+
auto [update_glyphs, update_sizes] =
572+
CollectNewGlyphs(new_atlas, text_frames);
573+
new_glyphs = std::move(update_glyphs);
574+
glyph_sizes = std::move(update_sizes);
575+
546576
glyph_positions.clear();
547577
glyph_positions.reserve(new_glyphs.size());
548578
first_missing_index = 0;

impeller/typographer/backends/skia/typographer_context_skia.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,14 @@ class TypographerContextSkia : public TypographerContext {
2727
GlyphAtlas::Type type,
2828
HostBuffer& host_buffer,
2929
const std::shared_ptr<GlyphAtlasContext>& atlas_context,
30-
const FontGlyphMap& font_glyph_map) const override;
30+
const std::vector<std::shared_ptr<TextFrame>>& text_frames)
31+
const override;
3132

3233
private:
34+
static std::pair<std::vector<FontGlyphPair>, std::vector<Rect>>
35+
CollectNewGlyphs(const std::shared_ptr<GlyphAtlas>& atlas,
36+
const std::vector<std::shared_ptr<TextFrame>>& text_frames);
37+
3338
TypographerContextSkia(const TypographerContextSkia&) = delete;
3439

3540
TypographerContextSkia& operator=(const TypographerContextSkia&) = delete;

impeller/typographer/font_glyph_pair.h

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,6 @@
55
#ifndef FLUTTER_IMPELLER_TYPOGRAPHER_FONT_GLYPH_PAIR_H_
66
#define FLUTTER_IMPELLER_TYPOGRAPHER_FONT_GLYPH_PAIR_H_
77

8-
#include <unordered_map>
9-
#include <unordered_set>
10-
11-
#include "fml/hash_combine.h"
128
#include "impeller/geometry/color.h"
139
#include "impeller/geometry/path.h"
1410
#include "impeller/geometry/point.h"
@@ -99,23 +95,15 @@ struct SubpixelGlyph {
9995
};
10096
};
10197

102-
using FontGlyphMap =
103-
std::unordered_map<ScaledFont,
104-
std::unordered_set<SubpixelGlyph,
105-
SubpixelGlyph::Hash,
106-
SubpixelGlyph::Equal>,
107-
ScaledFont::Hash,
108-
ScaledFont::Equal>;
109-
11098
//------------------------------------------------------------------------------
11199
/// @brief A font along with a glyph in that font rendered at a particular
112100
/// scale and subpixel position.
113101
///
114102
struct FontGlyphPair {
115103
FontGlyphPair(const ScaledFont& sf, const SubpixelGlyph& g)
116104
: scaled_font(sf), glyph(g) {}
117-
const ScaledFont& scaled_font;
118-
const SubpixelGlyph& glyph;
105+
ScaledFont scaled_font;
106+
SubpixelGlyph glyph;
119107
};
120108

121109
} // namespace impeller

0 commit comments

Comments
 (0)