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

Commit b5a151f

Browse files
authored
[Impeller] Add Rect::GetNormalizingTransform to handle UV coordinate conversion (#47775)
Three places in the code were manually computing the UV coordinates relative to a texture coverage rectangle while also transforming the points. This change will make it easier both to compute the UV conversion matrix and also to consolidate it with the transform that was already being applied to streamline the total computations.
1 parent 9250de9 commit b5a151f

File tree

6 files changed

+225
-26
lines changed

6 files changed

+225
-26
lines changed

impeller/aiks/aiks_unittests.cc

+40-6
Original file line numberDiff line numberDiff line change
@@ -206,12 +206,23 @@ void CanRenderTiledTexture(AiksTest* aiks_test,
206206
canvas.DrawRect(Rect::MakeXYWH(0, 0, 600, 600), paint);
207207
}
208208

209-
// Should not change the image.
210-
PathBuilder path_builder;
211-
path_builder.AddCircle({150, 150}, 150);
212-
path_builder.AddRoundedRect(Rect::MakeLTRB(300, 300, 600, 600), 10);
213-
paint.style = Paint::Style::kFill;
214-
canvas.DrawPath(path_builder.TakePath(), paint);
209+
{
210+
// Should not change the image.
211+
PathBuilder path_builder;
212+
path_builder.AddCircle({150, 150}, 150);
213+
path_builder.AddRoundedRect(Rect::MakeLTRB(300, 300, 600, 600), 10);
214+
paint.style = Paint::Style::kFill;
215+
canvas.DrawPath(path_builder.TakePath(), paint);
216+
}
217+
218+
{
219+
// Should not change the image. Tests the Convex short-cut code.
220+
PathBuilder path_builder;
221+
path_builder.AddCircle({150, 450}, 150);
222+
path_builder.SetConvexity(Convexity::kConvex);
223+
paint.style = Paint::Style::kFill;
224+
canvas.DrawPath(path_builder.TakePath(), paint);
225+
}
215226

216227
ASSERT_TRUE(aiks_test->OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
217228
}
@@ -3744,6 +3755,29 @@ TEST_P(AiksTest, VerticesGeometryUVPositionData) {
37443755
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
37453756
}
37463757

3758+
// Regression test for https://github.com/flutter/flutter/issues/135441 .
3759+
TEST_P(AiksTest, VerticesGeometryUVPositionDataWithTranslate) {
3760+
Canvas canvas;
3761+
Paint paint;
3762+
auto texture = CreateTextureForFixture("table_mountain_nx.png");
3763+
3764+
paint.color_source = ColorSource::MakeImage(
3765+
texture, Entity::TileMode::kClamp, Entity::TileMode::kClamp, {},
3766+
Matrix::MakeTranslation({100.0, 100.0}));
3767+
3768+
auto vertices = {Point(0, 0), Point(texture->GetSize().width, 0),
3769+
Point(0, texture->GetSize().height)};
3770+
std::vector<uint16_t> indices = {0u, 1u, 2u};
3771+
std::vector<Point> texture_coordinates = {};
3772+
std::vector<Color> vertex_colors = {};
3773+
auto geometry = std::make_shared<VerticesGeometry>(
3774+
vertices, indices, texture_coordinates, vertex_colors,
3775+
Rect::MakeLTRB(0, 0, 1, 1), VerticesGeometry::VertexMode::kTriangleStrip);
3776+
3777+
canvas.DrawVertices(geometry, BlendMode::kSourceOver, paint);
3778+
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
3779+
}
3780+
37473781
TEST_P(AiksTest, ClearBlendWithBlur) {
37483782
Canvas canvas;
37493783
Paint white;

impeller/entity/geometry/fill_path_geometry.cc

+6-7
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ GeometryResult FillPathGeometry::GetPositionUVBuffer(
8282
RenderPass& pass) {
8383
using VS = TextureFillVertexShader;
8484

85+
auto uv_transform =
86+
texture_coverage.GetNormalizingTransform() * effect_transform;
87+
8588
if (path_.GetFillType() == FillType::kNonZero && //
8689
path_.IsConvex()) {
8790
auto [points, indices] = TessellateConvex(
@@ -93,9 +96,7 @@ GeometryResult FillPathGeometry::GetPositionUVBuffer(
9396
for (auto i = 0u; i < points.size(); i++) {
9497
VS::PerVertexData data;
9598
data.position = points[i];
96-
data.texture_coords = effect_transform *
97-
(points[i] - texture_coverage.origin) /
98-
texture_coverage.size;
99+
data.texture_coords = uv_transform * points[i];
99100
vertex_builder.AppendVertex(data);
100101
}
101102
for (auto i = 0u; i < indices.size(); i++) {
@@ -116,16 +117,14 @@ GeometryResult FillPathGeometry::GetPositionUVBuffer(
116117
auto tesselation_result = renderer.GetTessellator()->Tessellate(
117118
path_.GetFillType(),
118119
path_.CreatePolyline(entity.GetTransformation().GetMaxBasisLength()),
119-
[&vertex_builder, &texture_coverage, &effect_transform](
120+
[&vertex_builder, &uv_transform](
120121
const float* vertices, size_t vertices_count, const uint16_t* indices,
121122
size_t indices_count) {
122123
for (auto i = 0u; i < vertices_count * 2; i += 2) {
123124
VS::PerVertexData data;
124125
Point vtx = {vertices[i], vertices[i + 1]};
125126
data.position = vtx;
126-
data.texture_coords = effect_transform *
127-
(vtx - texture_coverage.origin) /
128-
texture_coverage.size;
127+
data.texture_coords = uv_transform * vtx;
129128
vertex_builder.AppendVertex(data);
130129
}
131130
FML_DCHECK(vertex_builder.GetVertexCount() == vertices_count);

impeller/entity/geometry/geometry.cc

+3-2
Original file line numberDiff line numberDiff line change
@@ -75,12 +75,13 @@ GeometryResult ComputeUVGeometryForRect(Rect source_rect,
7575
RenderPass& pass) {
7676
auto& host_buffer = pass.GetTransientsBuffer();
7777

78+
auto uv_transform =
79+
texture_coverage.GetNormalizingTransform() * effect_transform;
7880
std::vector<Point> data(8);
7981
auto points = source_rect.GetPoints();
8082
for (auto i = 0u, j = 0u; i < 8; i += 2, j++) {
8183
data[i] = points[j];
82-
data[i + 1] = effect_transform * (points[j] - texture_coverage.origin) /
83-
texture_coverage.size;
84+
data[i + 1] = uv_transform * points[j];
8485
}
8586

8687
return GeometryResult{

impeller/entity/geometry/vertices_geometry.cc

+3-5
Original file line numberDiff line numberDiff line change
@@ -227,18 +227,16 @@ GeometryResult VerticesGeometry::GetPositionUVBuffer(
227227

228228
auto index_count = indices_.size();
229229
auto vertex_count = vertices_.size();
230-
auto size = texture_coverage.size;
231-
auto origin = texture_coverage.origin;
230+
auto uv_transform =
231+
texture_coverage.GetNormalizingTransform() * effect_transform;
232232
auto has_texture_coordinates = HasTextureCoordinates();
233233
std::vector<VS::PerVertexData> vertex_data(vertex_count);
234234
{
235235
for (auto i = 0u; i < vertex_count; i++) {
236236
auto vertex = vertices_[i];
237237
auto texture_coord =
238238
has_texture_coordinates ? texture_coordinates_[i] : vertices_[i];
239-
auto uv =
240-
effect_transform * Point((texture_coord.x - origin.x) / size.width,
241-
(texture_coord.y - origin.y) / size.height);
239+
auto uv = uv_transform * texture_coord;
242240
// From experimentation we need to clamp these values to < 1.0 or else
243241
// there can be flickering.
244242
vertex_data[i] = {

impeller/geometry/rect.h

+29
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,35 @@ struct TRect {
214214
return TRect::MakePointBounds(points.begin(), points.end()).value();
215215
}
216216

217+
/// @brief Constructs a Matrix that will map all points in the coordinate
218+
/// space of the rectangle into a new normalized coordinate space
219+
/// where the upper left corner of the rectangle maps to (0, 0)
220+
/// and the lower right corner of the rectangle maps to (1, 1).
221+
///
222+
/// Empty and non-finite rectangles will return a zero-scaling
223+
/// transform that maps all points to (0, 0).
224+
constexpr Matrix GetNormalizingTransform() const {
225+
if (!IsEmpty()) {
226+
Scalar sx = 1.0 / size.width;
227+
Scalar sy = 1.0 / size.height;
228+
Scalar tx = origin.x * -sx;
229+
Scalar ty = origin.y * -sy;
230+
231+
// Exclude NaN and infinities and either scale underflowing to zero
232+
if (sx != 0.0 && sy != 0.0 && 0.0 * sx * sy * tx * ty == 0.0) {
233+
// clang-format off
234+
return Matrix( sx, 0.0f, 0.0f, 0.0f,
235+
0.0f, sy, 0.0f, 0.0f,
236+
0.0f, 0.0f, 1.0f, 0.0f,
237+
tx, ty, 0.0f, 1.0f);
238+
// clang-format on
239+
}
240+
}
241+
242+
// Map all coordinates to the origin.
243+
return Matrix::MakeScale({0.0f, 0.0f, 1.0f});
244+
}
245+
217246
constexpr TRect Union(const TRect& o) const {
218247
auto this_ltrb = GetLTRB();
219248
auto other_ltrb = o.GetLTRB();

impeller/geometry/rect_unittests.cc

+144-6
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,14 @@ namespace testing {
1414
TEST(RectTest, RectOriginSizeGetters) {
1515
{
1616
Rect r = Rect::MakeOriginSize({10, 20}, {50, 40});
17-
ASSERT_EQ(r.GetOrigin(), Point(10, 20));
18-
ASSERT_EQ(r.GetSize(), Size(50, 40));
17+
EXPECT_EQ(r.GetOrigin(), Point(10, 20));
18+
EXPECT_EQ(r.GetSize(), Size(50, 40));
1919
}
2020

2121
{
2222
Rect r = Rect::MakeLTRB(10, 20, 50, 40);
23-
ASSERT_EQ(r.GetOrigin(), Point(10, 20));
24-
ASSERT_EQ(r.GetSize(), Size(40, 20));
23+
EXPECT_EQ(r.GetOrigin(), Point(10, 20));
24+
EXPECT_EQ(r.GetSize(), Size(40, 20));
2525
}
2626
}
2727

@@ -44,14 +44,152 @@ TEST(RectTest, RectMakeSize) {
4444
Size s(100, 200);
4545
IRect r = IRect::MakeSize(s);
4646
IRect expected = IRect::MakeLTRB(0, 0, 100, 200);
47-
ASSERT_EQ(r, expected);
47+
EXPECT_EQ(r, expected);
4848
}
4949

5050
{
5151
ISize s(100, 200);
5252
IRect r = IRect::MakeSize(s);
5353
IRect expected = IRect::MakeLTRB(0, 0, 100, 200);
54-
ASSERT_EQ(r, expected);
54+
EXPECT_EQ(r, expected);
55+
}
56+
}
57+
58+
TEST(RectTest, RectGetNormalizingTransform) {
59+
{
60+
// Checks for expected matrix values
61+
62+
auto r = Rect::MakeXYWH(100, 200, 200, 400);
63+
64+
EXPECT_EQ(r.GetNormalizingTransform(),
65+
Matrix::MakeScale({0.005, 0.0025, 1.0}) *
66+
Matrix::MakeTranslation({-100, -200}));
67+
}
68+
69+
{
70+
// Checks for expected transformation of points relative to the rect
71+
72+
auto r = Rect::MakeLTRB(300, 500, 400, 700);
73+
auto m = r.GetNormalizingTransform();
74+
75+
// The 4 corners of the rect => (0, 0) to (1, 1)
76+
EXPECT_EQ(m * Point(300, 500), Point(0, 0));
77+
EXPECT_EQ(m * Point(400, 500), Point(1, 0));
78+
EXPECT_EQ(m * Point(400, 700), Point(1, 1));
79+
EXPECT_EQ(m * Point(300, 700), Point(0, 1));
80+
81+
// The center => (0.5, 0.5)
82+
EXPECT_EQ(m * Point(350, 600), Point(0.5, 0.5));
83+
84+
// Outside the 4 corners => (-1, -1) to (2, 2)
85+
EXPECT_EQ(m * Point(200, 300), Point(-1, -1));
86+
EXPECT_EQ(m * Point(500, 300), Point(2, -1));
87+
EXPECT_EQ(m * Point(500, 900), Point(2, 2));
88+
EXPECT_EQ(m * Point(200, 900), Point(-1, 2));
89+
}
90+
91+
{
92+
// Checks for behavior with empty rects
93+
94+
auto zero = Matrix::MakeScale({0.0, 0.0, 1.0});
95+
96+
// Empty for width and/or height == 0
97+
EXPECT_EQ(Rect::MakeXYWH(10, 10, 0, 10).GetNormalizingTransform(), zero);
98+
EXPECT_EQ(Rect::MakeXYWH(10, 10, 10, 0).GetNormalizingTransform(), zero);
99+
EXPECT_EQ(Rect::MakeXYWH(10, 10, 0, 0).GetNormalizingTransform(), zero);
100+
101+
// Empty for width and/or height < 0
102+
EXPECT_EQ(Rect::MakeXYWH(10, 10, -1, 10).GetNormalizingTransform(), zero);
103+
EXPECT_EQ(Rect::MakeXYWH(10, 10, 10, -1).GetNormalizingTransform(), zero);
104+
EXPECT_EQ(Rect::MakeXYWH(10, 10, -1, -1).GetNormalizingTransform(), zero);
105+
}
106+
107+
{
108+
// Checks for behavior with non-finite rects
109+
110+
auto z = Matrix::MakeScale({0.0, 0.0, 1.0});
111+
auto nan = std::numeric_limits<Scalar>::quiet_NaN();
112+
auto inf = std::numeric_limits<Scalar>::infinity();
113+
114+
// Non-finite for width and/or height == nan
115+
EXPECT_EQ(Rect::MakeXYWH(10, 10, nan, 10).GetNormalizingTransform(), z);
116+
EXPECT_EQ(Rect::MakeXYWH(10, 10, 10, nan).GetNormalizingTransform(), z);
117+
EXPECT_EQ(Rect::MakeXYWH(10, 10, nan, nan).GetNormalizingTransform(), z);
118+
119+
// Non-finite for width and/or height == inf
120+
EXPECT_EQ(Rect::MakeXYWH(10, 10, inf, 10).GetNormalizingTransform(), z);
121+
EXPECT_EQ(Rect::MakeXYWH(10, 10, 10, inf).GetNormalizingTransform(), z);
122+
EXPECT_EQ(Rect::MakeXYWH(10, 10, inf, inf).GetNormalizingTransform(), z);
123+
124+
// Non-finite for width and/or height == -inf
125+
EXPECT_EQ(Rect::MakeXYWH(10, 10, -inf, 10).GetNormalizingTransform(), z);
126+
EXPECT_EQ(Rect::MakeXYWH(10, 10, 10, -inf).GetNormalizingTransform(), z);
127+
EXPECT_EQ(Rect::MakeXYWH(10, 10, -inf, -inf).GetNormalizingTransform(), z);
128+
129+
// Non-finite for origin X and/or Y == nan
130+
EXPECT_EQ(Rect::MakeXYWH(nan, 10, 10, 10).GetNormalizingTransform(), z);
131+
EXPECT_EQ(Rect::MakeXYWH(10, nan, 10, 10).GetNormalizingTransform(), z);
132+
EXPECT_EQ(Rect::MakeXYWH(nan, nan, 10, 10).GetNormalizingTransform(), z);
133+
134+
// Non-finite for origin X and/or Y == inf
135+
EXPECT_EQ(Rect::MakeXYWH(inf, 10, 10, 10).GetNormalizingTransform(), z);
136+
EXPECT_EQ(Rect::MakeXYWH(10, inf, 10, 10).GetNormalizingTransform(), z);
137+
EXPECT_EQ(Rect::MakeXYWH(inf, inf, 10, 10).GetNormalizingTransform(), z);
138+
139+
// Non-finite for origin X and/or Y == -inf
140+
EXPECT_EQ(Rect::MakeXYWH(-inf, 10, 10, 10).GetNormalizingTransform(), z);
141+
EXPECT_EQ(Rect::MakeXYWH(10, -inf, 10, 10).GetNormalizingTransform(), z);
142+
EXPECT_EQ(Rect::MakeXYWH(-inf, -inf, 10, 10).GetNormalizingTransform(), z);
143+
}
144+
}
145+
146+
TEST(RectTest, IRectGetNormalizingTransform) {
147+
{
148+
// Checks for expected matrix values
149+
150+
auto r = IRect::MakeXYWH(100, 200, 200, 400);
151+
152+
EXPECT_EQ(r.GetNormalizingTransform(),
153+
Matrix::MakeScale({0.005, 0.0025, 1.0}) *
154+
Matrix::MakeTranslation({-100, -200}));
155+
}
156+
157+
{
158+
// Checks for expected transformation of points relative to the rect
159+
160+
auto r = IRect::MakeLTRB(300, 500, 400, 700);
161+
auto m = r.GetNormalizingTransform();
162+
163+
// The 4 corners of the rect => (0, 0) to (1, 1)
164+
EXPECT_EQ(m * Point(300, 500), Point(0, 0));
165+
EXPECT_EQ(m * Point(400, 500), Point(1, 0));
166+
EXPECT_EQ(m * Point(400, 700), Point(1, 1));
167+
EXPECT_EQ(m * Point(300, 700), Point(0, 1));
168+
169+
// The center => (0.5, 0.5)
170+
EXPECT_EQ(m * Point(350, 600), Point(0.5, 0.5));
171+
172+
// Outside the 4 corners => (-1, -1) to (2, 2)
173+
EXPECT_EQ(m * Point(200, 300), Point(-1, -1));
174+
EXPECT_EQ(m * Point(500, 300), Point(2, -1));
175+
EXPECT_EQ(m * Point(500, 900), Point(2, 2));
176+
EXPECT_EQ(m * Point(200, 900), Point(-1, 2));
177+
}
178+
179+
{
180+
// Checks for behavior with empty rects
181+
182+
auto zero = Matrix::MakeScale({0.0, 0.0, 1.0});
183+
184+
// Empty for width and/or height == 0
185+
EXPECT_EQ(IRect::MakeXYWH(10, 10, 0, 10).GetNormalizingTransform(), zero);
186+
EXPECT_EQ(IRect::MakeXYWH(10, 10, 10, 0).GetNormalizingTransform(), zero);
187+
EXPECT_EQ(IRect::MakeXYWH(10, 10, 0, 0).GetNormalizingTransform(), zero);
188+
189+
// Empty for width and/or height < 0
190+
EXPECT_EQ(IRect::MakeXYWH(10, 10, -1, 10).GetNormalizingTransform(), zero);
191+
EXPECT_EQ(IRect::MakeXYWH(10, 10, 10, -1).GetNormalizingTransform(), zero);
192+
EXPECT_EQ(IRect::MakeXYWH(10, 10, -1, -1).GetNormalizingTransform(), zero);
55193
}
56194
}
57195

0 commit comments

Comments
 (0)