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

Commit bf6d4bf

Browse files
authored
[Impeller] Fix text scaling issues again, this time with perspective when a save layer is involved (#43695)
Alternative to #43662 Records the basis vector of the current CTM into text contents and reuses that rather than trying to get it again at render time. That method breaks if perspective is involved in the CTM and a subpass gets translated, which can modify the scale (and rotation) of the matrix. We're definitely not doing things quite right with perspective here, but the real fix to that is to either record the fully transformed glyph into the atlas or to use SDF/path based rendering. Fixes flutter/flutter#130476 I still have some concerns about how `EntityPass::Render` is a mix of mutations and constness but we can try to address that independently. I expect this to re-improve the regression noted in flutter/flutter#130594
1 parent eb57d70 commit bf6d4bf

28 files changed

+286
-199
lines changed

impeller/aiks/aiks_unittests.cc

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2877,6 +2877,35 @@ TEST_P(AiksTest, CanCanvasDrawPicture) {
28772877
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
28782878
}
28792879

2880+
TEST_P(AiksTest, DrawPictureWithText) {
2881+
Canvas subcanvas;
2882+
ASSERT_TRUE(RenderTextInCanvas(
2883+
GetContext(), subcanvas,
2884+
"the quick brown fox jumped over the lazy dog!.?", "Roboto-Regular.ttf"));
2885+
subcanvas.Translate({0, 10});
2886+
subcanvas.Scale(Vector2(3, 3));
2887+
ASSERT_TRUE(RenderTextInCanvas(
2888+
GetContext(), subcanvas,
2889+
"the quick brown fox jumped over the very big lazy dog!.?",
2890+
"Roboto-Regular.ttf"));
2891+
auto picture = subcanvas.EndRecordingAsPicture();
2892+
2893+
Canvas canvas;
2894+
canvas.Scale(Vector2(.2, .2));
2895+
canvas.Save();
2896+
canvas.Translate({200, 200});
2897+
canvas.Scale(Vector2(3.5, 3.5)); // The text must not be blurry after this.
2898+
canvas.DrawPicture(picture);
2899+
canvas.Restore();
2900+
2901+
canvas.Scale(Vector2(1.5, 1.5));
2902+
ASSERT_TRUE(RenderTextInCanvas(
2903+
GetContext(), canvas,
2904+
"the quick brown fox jumped over the smaller lazy dog!.?",
2905+
"Roboto-Regular.ttf"));
2906+
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2907+
}
2908+
28802909
TEST_P(AiksTest, MatrixBackdropFilter) {
28812910
Canvas canvas;
28822911
canvas.SaveLayer({}, std::nullopt,
@@ -2923,5 +2952,41 @@ APPLY_COLOR_FILTER_GRADIENT_TEST(Radial);
29232952
APPLY_COLOR_FILTER_GRADIENT_TEST(Conical);
29242953
APPLY_COLOR_FILTER_GRADIENT_TEST(Sweep);
29252954

2955+
TEST_P(AiksTest, DrawScaledTextWithPerspectiveNoSaveLayer) {
2956+
Canvas canvas;
2957+
// clang-format off
2958+
canvas.Transform(Matrix(
2959+
2.000000, 0.000000, 0.000000, 0.000000,
2960+
1.445767, 2.637070, -0.507928, 0.001524,
2961+
-2.451887, -0.534662, 0.861399, -0.002584,
2962+
1063.481934, 1025.951416, -48.300270, 1.144901
2963+
));
2964+
// clang-format on
2965+
2966+
ASSERT_TRUE(RenderTextInCanvas(GetContext(), canvas, "Hello world",
2967+
"Roboto-Regular.ttf"));
2968+
2969+
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2970+
}
2971+
2972+
TEST_P(AiksTest, DrawScaledTextWithPerspectiveSaveLayer) {
2973+
Canvas canvas;
2974+
Paint save_paint;
2975+
canvas.SaveLayer(save_paint);
2976+
// clang-format off
2977+
canvas.Transform(Matrix(
2978+
2.000000, 0.000000, 0.000000, 0.000000,
2979+
1.445767, 2.637070, -0.507928, 0.001524,
2980+
-2.451887, -0.534662, 0.861399, -0.002584,
2981+
1063.481934, 1025.951416, -48.300270, 1.144901
2982+
));
2983+
// clang-format on
2984+
2985+
ASSERT_TRUE(RenderTextInCanvas(GetContext(), canvas, "Hello world",
2986+
"Roboto-Regular.ttf"));
2987+
2988+
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
2989+
}
2990+
29262991
} // namespace testing
29272992
} // namespace impeller

impeller/aiks/canvas.cc

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ void Canvas::Initialize(std::optional<Rect> cull_rect) {
4242
base_pass_ = std::make_unique<EntityPass>();
4343
current_pass_ = base_pass_.get();
4444
xformation_stack_.emplace_back(CanvasStackEntry{.cull_rect = cull_rect});
45-
lazy_glyph_atlas_ = std::make_shared<LazyGlyphAtlas>();
4645
FML_DCHECK(GetSaveCount() == 1u);
4746
FML_DCHECK(base_pass_->GetSubpassesDepth() == 1u);
4847
}
@@ -51,7 +50,6 @@ void Canvas::Reset() {
5150
base_pass_ = nullptr;
5251
current_pass_ = nullptr;
5352
xformation_stack_ = {};
54-
lazy_glyph_atlas_ = nullptr;
5553
}
5654

5755
void Canvas::Save() {
@@ -516,15 +514,12 @@ void Canvas::SaveLayer(const Paint& paint,
516514
void Canvas::DrawTextFrame(const TextFrame& text_frame,
517515
Point position,
518516
const Paint& paint) {
519-
lazy_glyph_atlas_->AddTextFrame(text_frame);
520-
521517
Entity entity;
522518
entity.SetStencilDepth(GetStencilDepth());
523519
entity.SetBlendMode(paint.blend_mode);
524520

525521
auto text_contents = std::make_shared<TextContents>();
526522
text_contents->SetTextFrame(text_frame);
527-
text_contents->SetGlyphAtlas(lazy_glyph_atlas_);
528523

529524
if (paint.color_source.GetType() != ColorSource::Type::kColor) {
530525
auto color_text_contents = std::make_shared<ColorSourceTextContents>();

impeller/aiks/canvas.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,6 @@ class Canvas {
162162
std::unique_ptr<EntityPass> base_pass_;
163163
EntityPass* current_pass_ = nullptr;
164164
std::deque<CanvasStackEntry> xformation_stack_;
165-
std::shared_ptr<LazyGlyphAtlas> lazy_glyph_atlas_;
166165
std::optional<Rect> initial_cull_rect_;
167166

168167
void Initialize(std::optional<Rect> cull_rect);

impeller/display_list/dl_dispatcher.cc

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1104,8 +1104,7 @@ void DlDispatcher::drawDisplayList(
11041104
void DlDispatcher::drawTextBlob(const sk_sp<SkTextBlob> blob,
11051105
SkScalar x,
11061106
SkScalar y) {
1107-
Scalar scale = canvas_.GetCurrentTransformation().GetMaxBasisLengthXY();
1108-
const auto text_frame = TextFrameFromTextBlob(blob, scale);
1107+
const auto text_frame = TextFrameFromTextBlob(blob);
11091108
if (paint_.style == Paint::Style::kStroke) {
11101109
auto path = skia_conversions::PathDataFromTextBlob(blob);
11111110
auto bounds = text_frame.GetBounds();

impeller/entity/contents/color_source_text_contents.cc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
#include "impeller/entity/contents/color_source_text_contents.h"
66

7+
#include "color_source_text_contents.h"
78
#include "impeller/entity/contents/content_context.h"
89
#include "impeller/entity/contents/texture_contents.h"
910
#include "impeller/renderer/render_pass.h"
@@ -33,6 +34,12 @@ void ColorSourceTextContents::SetTextPosition(Point position) {
3334
position_ = position;
3435
}
3536

37+
void ColorSourceTextContents::PopulateGlyphAtlas(
38+
const std::shared_ptr<LazyGlyphAtlas>& lazy_glyph_atlas,
39+
Scalar scale) {
40+
text_contents_->PopulateGlyphAtlas(lazy_glyph_atlas, scale);
41+
}
42+
3643
bool ColorSourceTextContents::Render(const ContentContext& renderer,
3744
const Entity& entity,
3845
RenderPass& pass) const {

impeller/entity/contents/color_source_text_contents.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ class ColorSourceTextContents final : public Contents {
3232
// |Contents|
3333
std::optional<Rect> GetCoverage(const Entity& entity) const override;
3434

35+
// |Contents|
36+
void PopulateGlyphAtlas(
37+
const std::shared_ptr<LazyGlyphAtlas>& lazy_glyph_atlas,
38+
Scalar scale) override;
39+
3540
// |Contents|
3641
bool Render(const ContentContext& renderer,
3742
const Entity& entity,

impeller/entity/contents/content_context.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -696,8 +696,18 @@ class ContentContext {
696696
const SubpassCallback& subpass_callback,
697697
bool msaa_enabled = true) const;
698698

699+
void SetLazyGlyphAtlas(
700+
const std::shared_ptr<LazyGlyphAtlas>& lazy_glyph_atlas) {
701+
lazy_glyph_atlas_ = lazy_glyph_atlas;
702+
}
703+
704+
std::shared_ptr<LazyGlyphAtlas> GetLazyGlyphAtlas() const {
705+
return lazy_glyph_atlas_;
706+
}
707+
699708
private:
700709
std::shared_ptr<Context> context_;
710+
std::shared_ptr<LazyGlyphAtlas> lazy_glyph_atlas_;
701711

702712
template <class T>
703713
using Variants = std::unordered_map<ContentContextOptions,

impeller/entity/contents/contents.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "impeller/geometry/color.h"
1515
#include "impeller/geometry/rect.h"
1616
#include "impeller/renderer/snapshot.h"
17+
#include "impeller/typographer/lazy_glyph_atlas.h"
1718

1819
namespace impeller {
1920

@@ -53,6 +54,12 @@ class Contents {
5354

5455
virtual ~Contents();
5556

57+
/// @brief Add any text data to the specified lazy atlas. The scale parameter
58+
/// must be used again later when drawing the text.
59+
virtual void PopulateGlyphAtlas(
60+
const std::shared_ptr<LazyGlyphAtlas>& lazy_glyph_atlas,
61+
Scalar scale) {}
62+
5663
virtual bool Render(const ContentContext& renderer,
5764
const Entity& entity,
5865
RenderPass& pass) const = 0;

impeller/entity/contents/text_contents.cc

Lines changed: 46 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -29,18 +29,15 @@ void TextContents::SetTextFrame(const TextFrame& frame) {
2929
frame_ = frame;
3030
}
3131

32-
void TextContents::SetGlyphAtlas(std::shared_ptr<LazyGlyphAtlas> atlas) {
33-
lazy_atlas_ = std::move(atlas);
34-
}
35-
3632
std::shared_ptr<GlyphAtlas> TextContents::ResolveAtlas(
3733
GlyphAtlas::Type type,
34+
const std::shared_ptr<LazyGlyphAtlas>& lazy_atlas,
3835
std::shared_ptr<GlyphAtlasContext> atlas_context,
3936
std::shared_ptr<Context> context) const {
40-
FML_DCHECK(lazy_atlas_);
41-
if (lazy_atlas_) {
42-
return lazy_atlas_->CreateOrGetGlyphAtlas(type, std::move(atlas_context),
43-
std::move(context));
37+
FML_DCHECK(lazy_atlas);
38+
if (lazy_atlas) {
39+
return lazy_atlas->CreateOrGetGlyphAtlas(type, std::move(atlas_context),
40+
std::move(context));
4441
}
4542

4643
return nullptr;
@@ -78,14 +75,43 @@ std::optional<Rect> TextContents::GetCoverage(const Entity& entity) const {
7875
return bounds->TransformBounds(entity.GetTransformation());
7976
}
8077

81-
static bool CommonRender(const ContentContext& renderer,
82-
const Entity& entity,
83-
RenderPass& pass,
84-
const Color& color,
85-
const TextFrame& frame,
86-
Vector2 offset,
87-
const std::shared_ptr<GlyphAtlas>& atlas,
88-
Command& cmd) {
78+
void TextContents::PopulateGlyphAtlas(
79+
const std::shared_ptr<LazyGlyphAtlas>& lazy_glyph_atlas,
80+
Scalar scale) {
81+
lazy_glyph_atlas->AddTextFrame(frame_, scale);
82+
scale_ = scale;
83+
}
84+
85+
bool TextContents::Render(const ContentContext& renderer,
86+
const Entity& entity,
87+
RenderPass& pass) const {
88+
auto color = GetColor();
89+
if (color.IsTransparent()) {
90+
return true;
91+
}
92+
93+
auto type = frame_.GetAtlasType();
94+
auto atlas =
95+
ResolveAtlas(type, renderer.GetLazyGlyphAtlas(),
96+
renderer.GetGlyphAtlasContext(type), renderer.GetContext());
97+
98+
if (!atlas || !atlas->IsValid()) {
99+
VALIDATION_LOG << "Cannot render glyphs without prepared atlas.";
100+
return false;
101+
}
102+
103+
// Information shared by all glyph draw calls.
104+
Command cmd;
105+
cmd.label = "TextFrame";
106+
auto opts = OptionsFromPassAndEntity(pass, entity);
107+
opts.primitive_type = PrimitiveType::kTriangle;
108+
if (type == GlyphAtlas::Type::kAlphaBitmap) {
109+
cmd.pipeline = renderer.GetGlyphAtlasPipeline(opts);
110+
} else {
111+
cmd.pipeline = renderer.GetGlyphAtlasColorPipeline(opts);
112+
}
113+
cmd.stencil_reference = entity.GetStencilDepth();
114+
89115
using VS = GlyphAtlasPipeline::VertexShader;
90116
using FS = GlyphAtlasPipeline::FragmentShader;
91117

@@ -95,7 +121,7 @@ static bool CommonRender(const ContentContext& renderer,
95121
frame_info.atlas_size =
96122
Vector2{static_cast<Scalar>(atlas->GetTexture()->GetSize().width),
97123
static_cast<Scalar>(atlas->GetTexture()->GetSize().height)};
98-
frame_info.offset = offset;
124+
frame_info.offset = offset_;
99125
frame_info.is_translation_scale =
100126
entity.GetTransformation().IsTranslationScaleOnly();
101127
frame_info.entity_transform = entity.GetTransformation();
@@ -141,7 +167,7 @@ static bool CommonRender(const ContentContext& renderer,
141167

142168
auto& host_buffer = pass.GetTransientsBuffer();
143169
size_t vertex_count = 0;
144-
for (const auto& run : frame.GetRuns()) {
170+
for (const auto& run : frame_.GetRuns()) {
145171
vertex_count += run.GetGlyphPositions().size();
146172
}
147173
vertex_count *= 6;
@@ -151,10 +177,10 @@ static bool CommonRender(const ContentContext& renderer,
151177
[&](uint8_t* contents) {
152178
VS::PerVertexData vtx;
153179
size_t vertex_offset = 0;
154-
for (const auto& run : frame.GetRuns()) {
180+
for (const auto& run : frame_.GetRuns()) {
155181
const Font& font = run.GetFont();
156182
for (const auto& glyph_position : run.GetGlyphPositions()) {
157-
FontGlyphPair font_glyph_pair{font, glyph_position.glyph};
183+
FontGlyphPair font_glyph_pair{font, glyph_position.glyph, scale_};
158184
auto maybe_atlas_glyph_bounds =
159185
atlas->FindFontGlyphBounds(font_glyph_pair);
160186
if (!maybe_atlas_glyph_bounds.has_value()) {
@@ -191,37 +217,4 @@ static bool CommonRender(const ContentContext& renderer,
191217
return pass.AddCommand(cmd);
192218
}
193219

194-
bool TextContents::Render(const ContentContext& renderer,
195-
const Entity& entity,
196-
RenderPass& pass) const {
197-
auto color = GetColor();
198-
if (color.IsTransparent()) {
199-
return true;
200-
}
201-
202-
auto type = frame_.GetAtlasType();
203-
auto atlas = ResolveAtlas(type, renderer.GetGlyphAtlasContext(type),
204-
renderer.GetContext());
205-
206-
if (!atlas || !atlas->IsValid()) {
207-
VALIDATION_LOG << "Cannot render glyphs without prepared atlas.";
208-
return false;
209-
}
210-
211-
// Information shared by all glyph draw calls.
212-
Command cmd;
213-
cmd.label = "TextFrame";
214-
auto opts = OptionsFromPassAndEntity(pass, entity);
215-
opts.primitive_type = PrimitiveType::kTriangle;
216-
if (type == GlyphAtlas::Type::kAlphaBitmap) {
217-
cmd.pipeline = renderer.GetGlyphAtlasPipeline(opts);
218-
} else {
219-
cmd.pipeline = renderer.GetGlyphAtlasColorPipeline(opts);
220-
}
221-
cmd.stencil_reference = entity.GetStencilDepth();
222-
223-
return CommonRender(renderer, entity, pass, color, frame_, offset_, atlas,
224-
cmd);
225-
}
226-
227220
} // namespace impeller

impeller/entity/contents/text_contents.h

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,14 @@ class TextContents final : public Contents {
2828

2929
void SetTextFrame(const TextFrame& frame);
3030

31-
void SetGlyphAtlas(std::shared_ptr<LazyGlyphAtlas> atlas);
32-
3331
void SetColor(Color color);
3432

3533
Color GetColor() const;
3634

35+
// |Contents|
3736
bool CanInheritOpacity(const Entity& entity) const override;
3837

38+
// |Contents|
3939
void SetInheritedOpacity(Scalar opacity) override;
4040

4141
void SetOffset(Vector2 offset);
@@ -45,20 +45,26 @@ class TextContents final : public Contents {
4545
// |Contents|
4646
std::optional<Rect> GetCoverage(const Entity& entity) const override;
4747

48+
// |Contents|
49+
void PopulateGlyphAtlas(
50+
const std::shared_ptr<LazyGlyphAtlas>& lazy_glyph_atlas,
51+
Scalar scale) override;
52+
4853
// |Contents|
4954
bool Render(const ContentContext& renderer,
5055
const Entity& entity,
5156
RenderPass& pass) const override;
5257

5358
private:
5459
TextFrame frame_;
60+
Scalar scale_ = 1.0;
5561
Color color_;
5662
Scalar inherited_opacity_ = 1.0;
57-
mutable std::shared_ptr<LazyGlyphAtlas> lazy_atlas_;
5863
Vector2 offset_;
5964

6065
std::shared_ptr<GlyphAtlas> ResolveAtlas(
6166
GlyphAtlas::Type type,
67+
const std::shared_ptr<LazyGlyphAtlas>& lazy_atlas,
6268
std::shared_ptr<GlyphAtlasContext> atlas_context,
6369
std::shared_ptr<Context> context) const;
6470

0 commit comments

Comments
 (0)