Skip to content

Commit 52cfc8b

Browse files
authored
[Impeller] backfilling TextContents unit tests (#161625)
issue: flutter/flutter#149652 ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
1 parent bdecbae commit 52cfc8b

File tree

7 files changed

+312
-121
lines changed

7 files changed

+312
-121
lines changed

engine/src/flutter/ci/licenses_golden/excluded_files

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,7 @@
160160
../../../flutter/impeller/entity/contents/filters/matrix_filter_contents_unittests.cc
161161
../../../flutter/impeller/entity/contents/host_buffer_unittests.cc
162162
../../../flutter/impeller/entity/contents/test
163+
../../../flutter/impeller/entity/contents/text_contents_unittests.cc
163164
../../../flutter/impeller/entity/contents/tiled_texture_contents_unittests.cc
164165
../../../flutter/impeller/entity/draw_order_resolver_unittests.cc
165166
../../../flutter/impeller/entity/entity_pass_target_unittests.cc

engine/src/flutter/impeller/entity/BUILD.gn

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@ impeller_component("entity_unittests") {
248248
"contents/filters/inputs/filter_input_unittests.cc",
249249
"contents/filters/matrix_filter_contents_unittests.cc",
250250
"contents/host_buffer_unittests.cc",
251+
"contents/text_contents_unittests.cc",
251252
"contents/tiled_texture_contents_unittests.cc",
252253
"draw_order_resolver_unittests.cc",
253254
"entity_pass_target_unittests.cc",
@@ -266,5 +267,6 @@ impeller_component("entity_unittests") {
266267
"../playground:playground_test",
267268
"//flutter/display_list/testing:display_list_testing",
268269
"//flutter/impeller/typographer/backends/skia:typographer_skia_backend",
270+
"//flutter/third_party/txt",
269271
]
270272
}

engine/src/flutter/impeller/entity/contents/text_contents.cc

Lines changed: 130 additions & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
#include "impeller/core/buffer_view.h"
1212
#include "impeller/core/formats.h"
1313
#include "impeller/core/sampler_descriptor.h"
14-
#include "impeller/entity/contents/content_context.h"
1514
#include "impeller/entity/entity.h"
1615
#include "impeller/geometry/color.h"
1716
#include "impeller/geometry/point.h"
@@ -20,6 +19,9 @@
2019

2120
namespace impeller {
2221

22+
using VS = GlyphAtlasPipeline::VertexShader;
23+
using FS = GlyphAtlasPipeline::FragmentShader;
24+
2325
TextContents::TextContents() = default;
2426

2527
TextContents::~TextContents() = default;
@@ -72,6 +74,130 @@ void TextContents::SetTextProperties(Color color,
7274
}
7375
}
7476

77+
void TextContents::ComputeVertexData(
78+
VS::PerVertexData* vtx_contents,
79+
const std::shared_ptr<TextFrame>& frame,
80+
Scalar scale,
81+
const Matrix& entity_transform,
82+
Vector2 offset,
83+
std::optional<GlyphProperties> glyph_properties,
84+
const std::shared_ptr<GlyphAtlas>& atlas) {
85+
// Common vertex information for all glyphs.
86+
// All glyphs are given the same vertex information in the form of a
87+
// unit-sized quad. The size of the glyph is specified in per instance data
88+
// and the vertex shader uses this to size the glyph correctly. The
89+
// interpolated vertex information is also used in the fragment shader to
90+
// sample from the glyph atlas.
91+
92+
constexpr std::array<Point, 6> unit_points = {Point{0, 0}, Point{1, 0},
93+
Point{0, 1}, Point{1, 0},
94+
Point{0, 1}, Point{1, 1}};
95+
96+
ISize atlas_size = atlas->GetTexture()->GetSize();
97+
bool is_translation_scale = entity_transform.IsTranslationScaleOnly();
98+
Matrix basis_transform = entity_transform.Basis();
99+
100+
VS::PerVertexData vtx;
101+
size_t i = 0u;
102+
size_t bounds_offset = 0u;
103+
for (const TextRun& run : frame->GetRuns()) {
104+
const Font& font = run.GetFont();
105+
Scalar rounded_scale = TextFrame::RoundScaledFontSize(scale);
106+
FontGlyphAtlas* font_atlas = nullptr;
107+
108+
// Adjust glyph position based on the subpixel rounding
109+
// used by the font.
110+
Point subpixel_adjustment(0.5, 0.5);
111+
switch (font.GetAxisAlignment()) {
112+
case AxisAlignment::kNone:
113+
break;
114+
case AxisAlignment::kX:
115+
subpixel_adjustment.x = 0.125;
116+
break;
117+
case AxisAlignment::kY:
118+
subpixel_adjustment.y = 0.125;
119+
break;
120+
case AxisAlignment::kAll:
121+
subpixel_adjustment.x = 0.125;
122+
subpixel_adjustment.y = 0.125;
123+
break;
124+
}
125+
126+
Point screen_offset = (entity_transform * Point(0, 0));
127+
for (const TextRun::GlyphPosition& glyph_position :
128+
run.GetGlyphPositions()) {
129+
const FrameBounds& frame_bounds = frame->GetFrameBounds(bounds_offset);
130+
bounds_offset++;
131+
auto atlas_glyph_bounds = frame_bounds.atlas_bounds;
132+
auto glyph_bounds = frame_bounds.glyph_bounds;
133+
134+
// If frame_bounds.is_placeholder is true, this is the first frame
135+
// the glyph has been rendered and so its atlas position was not
136+
// known when the glyph was recorded. Perform a slow lookup into the
137+
// glyph atlas hash table.
138+
if (frame_bounds.is_placeholder) {
139+
if (!font_atlas) {
140+
font_atlas =
141+
atlas->GetOrCreateFontGlyphAtlas(ScaledFont{font, rounded_scale});
142+
}
143+
144+
if (!font_atlas) {
145+
VALIDATION_LOG << "Could not find font in the atlas.";
146+
continue;
147+
}
148+
Point subpixel = TextFrame::ComputeSubpixelPosition(
149+
glyph_position, font.GetAxisAlignment(), offset, rounded_scale);
150+
151+
std::optional<FrameBounds> maybe_atlas_glyph_bounds =
152+
font_atlas->FindGlyphBounds(SubpixelGlyph{
153+
glyph_position.glyph, //
154+
subpixel, //
155+
glyph_properties //
156+
});
157+
if (!maybe_atlas_glyph_bounds.has_value()) {
158+
VALIDATION_LOG << "Could not find glyph position in the atlas.";
159+
continue;
160+
}
161+
atlas_glyph_bounds = maybe_atlas_glyph_bounds.value().atlas_bounds;
162+
}
163+
164+
Rect scaled_bounds = glyph_bounds.Scale(1.0 / rounded_scale);
165+
// For each glyph, we compute two rectangles. One for the vertex
166+
// positions and one for the texture coordinates (UVs). The atlas
167+
// glyph bounds are used to compute UVs in cases where the
168+
// destination and source sizes may differ due to clamping the sizes
169+
// of large glyphs.
170+
Point uv_origin =
171+
(atlas_glyph_bounds.GetLeftTop() - Point(0.5, 0.5)) / atlas_size;
172+
Point uv_size = (atlas_glyph_bounds.GetSize() + Point(1, 1)) / atlas_size;
173+
174+
Point unrounded_glyph_position =
175+
basis_transform *
176+
(glyph_position.position + scaled_bounds.GetLeftTop());
177+
178+
Point screen_glyph_position =
179+
(screen_offset + unrounded_glyph_position + subpixel_adjustment)
180+
.Floor();
181+
182+
for (const Point& point : unit_points) {
183+
Point position;
184+
if (is_translation_scale) {
185+
position = (screen_glyph_position +
186+
(basis_transform * point * scaled_bounds.GetSize()))
187+
.Round();
188+
} else {
189+
position = entity_transform *
190+
(glyph_position.position + scaled_bounds.GetLeftTop() +
191+
point * scaled_bounds.GetSize());
192+
}
193+
vtx.uv = uv_origin + (uv_size * point);
194+
vtx.position = position;
195+
vtx_contents[i++] = vtx;
196+
}
197+
}
198+
}
199+
}
200+
75201
bool TextContents::Render(const ContentContext& renderer,
76202
const Entity& entity,
77203
RenderPass& pass) const {
@@ -100,17 +226,12 @@ bool TextContents::Render(const ContentContext& renderer,
100226
opts.primitive_type = PrimitiveType::kTriangle;
101227
pass.SetPipeline(renderer.GetGlyphAtlasPipeline(opts));
102228

103-
using VS = GlyphAtlasPipeline::VertexShader;
104-
using FS = GlyphAtlasPipeline::FragmentShader;
105-
106229
// Common vertex uniforms for all glyphs.
107230
VS::FrameInfo frame_info;
108231
frame_info.mvp =
109232
Entity::GetShaderTransform(entity.GetShaderClipDepth(), pass, Matrix());
110-
ISize atlas_size = atlas->GetTexture()->GetSize();
111233
bool is_translation_scale = entity.GetTransform().IsTranslationScaleOnly();
112234
Matrix entity_transform = entity.GetTransform();
113-
Matrix basis_transform = entity_transform.Basis();
114235

115236
VS::BindFrameInfo(pass,
116237
renderer.GetTransientsBuffer().EmplaceUniform(frame_info));
@@ -147,17 +268,6 @@ bool TextContents::Render(const ContentContext& renderer,
147268
sampler_desc) // sampler
148269
);
149270

150-
// Common vertex information for all glyphs.
151-
// All glyphs are given the same vertex information in the form of a
152-
// unit-sized quad. The size of the glyph is specified in per instance data
153-
// and the vertex shader uses this to size the glyph correctly. The
154-
// interpolated vertex information is also used in the fragment shader to
155-
// sample from the glyph atlas.
156-
157-
constexpr std::array<Point, 6> unit_points = {Point{0, 0}, Point{1, 0},
158-
Point{0, 1}, Point{1, 0},
159-
Point{0, 1}, Point{1, 1}};
160-
161271
auto& host_buffer = renderer.GetTransientsBuffer();
162272
size_t vertex_count = 0;
163273
for (const auto& run : frame_->GetRuns()) {
@@ -168,112 +278,11 @@ bool TextContents::Render(const ContentContext& renderer,
168278
BufferView buffer_view = host_buffer.Emplace(
169279
vertex_count * sizeof(VS::PerVertexData), alignof(VS::PerVertexData),
170280
[&](uint8_t* contents) {
171-
VS::PerVertexData vtx;
172281
VS::PerVertexData* vtx_contents =
173282
reinterpret_cast<VS::PerVertexData*>(contents);
174-
size_t i = 0u;
175-
size_t bounds_offset = 0u;
176-
for (const TextRun& run : frame_->GetRuns()) {
177-
const Font& font = run.GetFont();
178-
Scalar rounded_scale = TextFrame::RoundScaledFontSize(scale_);
179-
FontGlyphAtlas* font_atlas = nullptr;
180-
181-
// Adjust glyph position based on the subpixel rounding
182-
// used by the font.
183-
Point subpixel_adjustment(0.5, 0.5);
184-
switch (font.GetAxisAlignment()) {
185-
case AxisAlignment::kNone:
186-
break;
187-
case AxisAlignment::kX:
188-
subpixel_adjustment.x = 0.125;
189-
break;
190-
case AxisAlignment::kY:
191-
subpixel_adjustment.y = 0.125;
192-
break;
193-
case AxisAlignment::kAll:
194-
subpixel_adjustment.x = 0.125;
195-
subpixel_adjustment.y = 0.125;
196-
break;
197-
}
198-
199-
Point screen_offset = (entity_transform * Point(0, 0));
200-
for (const TextRun::GlyphPosition& glyph_position :
201-
run.GetGlyphPositions()) {
202-
const FrameBounds& frame_bounds =
203-
frame_->GetFrameBounds(bounds_offset);
204-
bounds_offset++;
205-
auto atlas_glyph_bounds = frame_bounds.atlas_bounds;
206-
auto glyph_bounds = frame_bounds.glyph_bounds;
207-
208-
// If frame_bounds.is_placeholder is true, this is the first frame
209-
// the glyph has been rendered and so its atlas position was not
210-
// known when the glyph was recorded. Perform a slow lookup into the
211-
// glyph atlas hash table.
212-
if (frame_bounds.is_placeholder) {
213-
if (!font_atlas) {
214-
font_atlas = atlas->GetOrCreateFontGlyphAtlas(
215-
ScaledFont{font, rounded_scale});
216-
}
217-
218-
if (!font_atlas) {
219-
VALIDATION_LOG << "Could not find font in the atlas.";
220-
continue;
221-
}
222-
Point subpixel = TextFrame::ComputeSubpixelPosition(
223-
glyph_position, font.GetAxisAlignment(), offset_,
224-
rounded_scale);
225-
226-
std::optional<FrameBounds> maybe_atlas_glyph_bounds =
227-
font_atlas->FindGlyphBounds(SubpixelGlyph{
228-
glyph_position.glyph, //
229-
subpixel, //
230-
GetGlyphProperties() //
231-
});
232-
if (!maybe_atlas_glyph_bounds.has_value()) {
233-
VALIDATION_LOG << "Could not find glyph position in the atlas.";
234-
continue;
235-
}
236-
atlas_glyph_bounds =
237-
maybe_atlas_glyph_bounds.value().atlas_bounds;
238-
}
239-
240-
Rect scaled_bounds = glyph_bounds.Scale(1.0 / rounded_scale);
241-
// For each glyph, we compute two rectangles. One for the vertex
242-
// positions and one for the texture coordinates (UVs). The atlas
243-
// glyph bounds are used to compute UVs in cases where the
244-
// destination and source sizes may differ due to clamping the sizes
245-
// of large glyphs.
246-
Point uv_origin =
247-
(atlas_glyph_bounds.GetLeftTop() - Point(0.5, 0.5)) /
248-
atlas_size;
249-
Point uv_size =
250-
(atlas_glyph_bounds.GetSize() + Point(1, 1)) / atlas_size;
251-
252-
Point unrounded_glyph_position =
253-
basis_transform *
254-
(glyph_position.position + scaled_bounds.GetLeftTop());
255-
256-
Point screen_glyph_position =
257-
(screen_offset + unrounded_glyph_position + subpixel_adjustment)
258-
.Floor();
259-
260-
for (const Point& point : unit_points) {
261-
Point position;
262-
if (is_translation_scale) {
263-
position = (screen_glyph_position +
264-
(basis_transform * point * scaled_bounds.GetSize()))
265-
.Round();
266-
} else {
267-
position = entity_transform * (glyph_position.position +
268-
scaled_bounds.GetLeftTop() +
269-
point * scaled_bounds.GetSize());
270-
}
271-
vtx.uv = uv_origin + (uv_size * point);
272-
vtx.position = position;
273-
vtx_contents[i++] = vtx;
274-
}
275-
}
276-
}
283+
ComputeVertexData(vtx_contents, frame_, scale_,
284+
/*entity_transform=*/entity_transform, offset_,
285+
GetGlyphProperties(), atlas);
277286
});
278287

279288
pass.SetVertexBuffer(std::move(buffer_view));

engine/src/flutter/impeller/entity/contents/text_contents.h

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

88
#include <memory>
99

10+
#include "impeller/entity/contents/content_context.h"
1011
#include "impeller/entity/contents/contents.h"
1112
#include "impeller/geometry/color.h"
1213
#include "impeller/typographer/font_glyph_pair.h"
@@ -61,6 +62,15 @@ class TextContents final : public Contents {
6162
const Entity& entity,
6263
RenderPass& pass) const override;
6364

65+
static void ComputeVertexData(
66+
GlyphAtlasPipeline::VertexShader::PerVertexData* vtx_contents,
67+
const std::shared_ptr<TextFrame>& frame,
68+
Scalar scale,
69+
const Matrix& entity_transform,
70+
Vector2 offset,
71+
std::optional<GlyphProperties> glyph_properties,
72+
const std::shared_ptr<GlyphAtlas>& atlas);
73+
6474
private:
6575
std::optional<GlyphProperties> GetGlyphProperties() const;
6676

0 commit comments

Comments
 (0)