Skip to content

Commit 61a5fe7

Browse files
authored
Switched the font atlas to discrete math for hash keys (#164822)
issue: flutter/flutter#164606 This is a refactor of how we store font atlas data. Previously we were using floating point values with custom hash functions as keys. This was very hard to debug. This change instead makes all keys used in the font atlas discrete. The hope was that this might fix flutter/flutter#164606, or at least make it easier to debug. It didn't fix the issue. ## 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 1cfbc3c commit 61a5fe7

File tree

16 files changed

+340
-109
lines changed

16 files changed

+340
-109
lines changed

engine/src/flutter/ci/licenses_golden/excluded_files

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@
174174
../../../flutter/impeller/geometry/geometry_unittests.cc
175175
../../../flutter/impeller/geometry/matrix_unittests.cc
176176
../../../flutter/impeller/geometry/path_unittests.cc
177+
../../../flutter/impeller/geometry/rational_unittests.cc
177178
../../../flutter/impeller/geometry/rect_unittests.cc
178179
../../../flutter/impeller/geometry/round_rect_unittests.cc
179180
../../../flutter/impeller/geometry/round_superellipse_unittests.cc

engine/src/flutter/ci/licenses_golden/licenses_flutter

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41860,6 +41860,8 @@ ORIGIN: ../../../flutter/impeller/geometry/point.cc + ../../../flutter/LICENSE
4186041860
ORIGIN: ../../../flutter/impeller/geometry/point.h + ../../../flutter/LICENSE
4186141861
ORIGIN: ../../../flutter/impeller/geometry/quaternion.cc + ../../../flutter/LICENSE
4186241862
ORIGIN: ../../../flutter/impeller/geometry/quaternion.h + ../../../flutter/LICENSE
41863+
ORIGIN: ../../../flutter/impeller/geometry/rational.cc + ../../../flutter/LICENSE
41864+
ORIGIN: ../../../flutter/impeller/geometry/rational.h + ../../../flutter/LICENSE
4186341865
ORIGIN: ../../../flutter/impeller/geometry/rect.cc + ../../../flutter/LICENSE
4186441866
ORIGIN: ../../../flutter/impeller/geometry/rect.h + ../../../flutter/LICENSE
4186541867
ORIGIN: ../../../flutter/impeller/geometry/round_rect.cc + ../../../flutter/LICENSE
@@ -44821,6 +44823,8 @@ FILE: ../../../flutter/impeller/geometry/point.cc
4482144823
FILE: ../../../flutter/impeller/geometry/point.h
4482244824
FILE: ../../../flutter/impeller/geometry/quaternion.cc
4482344825
FILE: ../../../flutter/impeller/geometry/quaternion.h
44826+
FILE: ../../../flutter/impeller/geometry/rational.cc
44827+
FILE: ../../../flutter/impeller/geometry/rational.h
4482444828
FILE: ../../../flutter/impeller/geometry/rect.cc
4482544829
FILE: ../../../flutter/impeller/geometry/rect.h
4482644830
FILE: ../../../flutter/impeller/geometry/round_rect.cc

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ void TextContents::ComputeVertexData(
105105
size_t bounds_offset = 0u;
106106
for (const TextRun& run : frame->GetRuns()) {
107107
const Font& font = run.GetFont();
108-
Scalar rounded_scale = frame->GetScale();
108+
Rational rounded_scale = frame->GetScale();
109109
const Matrix transform = frame->GetOffsetTransform();
110110
FontGlyphAtlas* font_atlas = nullptr;
111111

@@ -149,7 +149,7 @@ void TextContents::ComputeVertexData(
149149
VALIDATION_LOG << "Could not find font in the atlas.";
150150
continue;
151151
}
152-
Point subpixel = TextFrame::ComputeSubpixelPosition(
152+
SubpixelPosition subpixel = TextFrame::ComputeSubpixelPosition(
153153
glyph_position, font.GetAxisAlignment(), transform);
154154

155155
std::optional<FrameBounds> maybe_atlas_glyph_bounds =
@@ -165,7 +165,8 @@ void TextContents::ComputeVertexData(
165165
atlas_glyph_bounds = maybe_atlas_glyph_bounds.value().atlas_bounds;
166166
}
167167

168-
Rect scaled_bounds = glyph_bounds.Scale(1.0 / rounded_scale);
168+
Rect scaled_bounds =
169+
glyph_bounds.Scale(static_cast<Scalar>(rounded_scale.Invert()));
169170
// For each glyph, we compute two rectangles. One for the vertex
170171
// positions and one for the texture coordinates (UVs). The atlas
171172
// glyph bounds are used to compute UVs in cases where the

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

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,15 @@ std::shared_ptr<GlyphAtlas> CreateGlyphAtlas(
5454
const TypographerContext* typographer_context,
5555
HostBuffer& host_buffer,
5656
GlyphAtlas::Type type,
57-
Scalar scale,
57+
Rational scale,
5858
const std::shared_ptr<GlyphAtlasContext>& atlas_context,
5959
const std::shared_ptr<TextFrame>& frame,
6060
Point offset) {
6161
frame->SetPerFrameData(
6262
TextFrame::RoundScaledFontSize(scale), /*offset=*/offset,
63-
/*transform=*/Matrix::MakeScale(Vector3{scale, scale, 1}),
63+
/*transform=*/
64+
Matrix::MakeScale(
65+
Vector3{static_cast<Scalar>(scale), static_cast<Scalar>(scale), 1}),
6466
/*properties=*/std::nullopt);
6567
return typographer_context->CreateGlyphAtlas(context, type, host_buffer,
6668
atlas_context, {frame});
@@ -122,7 +124,7 @@ TEST_P(TextContentsTest, SimpleComputeVertexData) {
122124
ASSERT_TRUE(context && context->IsValid());
123125
std::shared_ptr<GlyphAtlas> atlas =
124126
CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer,
125-
GlyphAtlas::Type::kAlphaBitmap, /*scale=*/1.0f,
127+
GlyphAtlas::Type::kAlphaBitmap, /*scale=*/Rational(1, 1),
126128
atlas_context, text_frame, /*offset=*/{0, 0});
127129

128130
ISize texture_size = atlas->GetTexture()->GetSize();
@@ -156,16 +158,18 @@ TEST_P(TextContentsTest, SimpleComputeVertexData2x) {
156158
std::shared_ptr<HostBuffer> host_buffer = HostBuffer::Create(
157159
GetContext()->GetResourceAllocator(), GetContext()->GetIdleWaiter());
158160
ASSERT_TRUE(context && context->IsValid());
159-
Scalar font_scale = 2.f;
161+
Rational font_scale(2, 1);
160162
std::shared_ptr<GlyphAtlas> atlas =
161163
CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer,
162164
GlyphAtlas::Type::kAlphaBitmap, font_scale,
163165
atlas_context, text_frame, /*offset=*/{0, 0});
164166

165167
ISize texture_size = atlas->GetTexture()->GetSize();
166168
TextContents::ComputeVertexData(
167-
data, text_frame, font_scale,
168-
/*entity_transform=*/Matrix::MakeScale({font_scale, font_scale, 1}),
169+
data, text_frame, static_cast<Scalar>(font_scale),
170+
/*entity_transform=*/
171+
Matrix::MakeScale({static_cast<Scalar>(font_scale),
172+
static_cast<Scalar>(font_scale), 1}),
169173
/*offset=*/Vector2(0, 0),
170174
/*glyph_properties=*/std::nullopt, atlas);
171175

@@ -187,7 +191,7 @@ TEST_P(TextContentsTest, MaintainsShape) {
187191
ASSERT_TRUE(context && context->IsValid());
188192

189193
for (int i = 0; i <= 1000; ++i) {
190-
Scalar font_scale = 0.440 + (i / 1000.0);
194+
Rational font_scale(440 + i, 1000.0);
191195
Rect position_rect[2];
192196
Rect uv_rect[2];
193197

@@ -200,8 +204,10 @@ TEST_P(TextContentsTest, MaintainsShape) {
200204
ISize texture_size = atlas->GetTexture()->GetSize();
201205

202206
TextContents::ComputeVertexData(
203-
data, text_frame, font_scale,
204-
/*entity_transform=*/Matrix::MakeScale({font_scale, font_scale, 1}),
207+
data, text_frame, static_cast<Scalar>(font_scale),
208+
/*entity_transform=*/
209+
Matrix::MakeScale({static_cast<Scalar>(font_scale),
210+
static_cast<Scalar>(font_scale), 1}),
205211
/*offset=*/Vector2(0, 0),
206212
/*glyph_properties=*/std::nullopt, atlas);
207213
position_rect[0] = PerVertexDataPositionToRect(data);
@@ -234,7 +240,7 @@ TEST_P(TextContentsTest, SimpleSubpixel) {
234240
Point offset = Point(0.5, 0);
235241
std::shared_ptr<GlyphAtlas> atlas =
236242
CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer,
237-
GlyphAtlas::Type::kAlphaBitmap, /*scale=*/1.0f,
243+
GlyphAtlas::Type::kAlphaBitmap, /*scale=*/Rational(1),
238244
atlas_context, text_frame, offset);
239245

240246
ISize texture_size = atlas->GetTexture()->GetSize();
@@ -268,7 +274,7 @@ TEST_P(TextContentsTest, SimpleSubpixel3x) {
268274
std::shared_ptr<HostBuffer> host_buffer = HostBuffer::Create(
269275
GetContext()->GetResourceAllocator(), GetContext()->GetIdleWaiter());
270276
ASSERT_TRUE(context && context->IsValid());
271-
Scalar font_scale = 3.f;
277+
Rational font_scale(3, 1);
272278
Point offset = {0.16667, 0};
273279
std::shared_ptr<GlyphAtlas> atlas =
274280
CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer,
@@ -277,10 +283,11 @@ TEST_P(TextContentsTest, SimpleSubpixel3x) {
277283

278284
ISize texture_size = atlas->GetTexture()->GetSize();
279285
TextContents::ComputeVertexData(
280-
data, text_frame, font_scale,
286+
data, text_frame, static_cast<Scalar>(font_scale),
281287
/*entity_transform=*/
282288
Matrix::MakeTranslation(offset) *
283-
Matrix::MakeScale({font_scale, font_scale, 1}),
289+
Matrix::MakeScale({static_cast<Scalar>(font_scale),
290+
static_cast<Scalar>(font_scale), 1}),
284291
offset,
285292
/*glyph_properties=*/std::nullopt, atlas);
286293

@@ -314,7 +321,7 @@ TEST_P(TextContentsTest, SimpleSubpixel26) {
314321
Point offset = Point(0.26, 0);
315322
std::shared_ptr<GlyphAtlas> atlas =
316323
CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer,
317-
GlyphAtlas::Type::kAlphaBitmap, /*scale=*/1.0f,
324+
GlyphAtlas::Type::kAlphaBitmap, /*scale=*/Rational(1),
318325
atlas_context, text_frame, offset);
319326

320327
ISize texture_size = atlas->GetTexture()->GetSize();
@@ -351,7 +358,7 @@ TEST_P(TextContentsTest, SimpleSubpixel80) {
351358
Point offset = Point(0.80, 0);
352359
std::shared_ptr<GlyphAtlas> atlas =
353360
CreateGlyphAtlas(*GetContext(), context.get(), *host_buffer,
354-
GlyphAtlas::Type::kAlphaBitmap, /*scale=*/1.0f,
361+
GlyphAtlas::Type::kAlphaBitmap, /*scale=*/Rational(1),
355362
atlas_context, text_frame, offset);
356363

357364
ISize texture_size = atlas->GetTexture()->GetSize();

engine/src/flutter/impeller/entity/entity_unittests.cc

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2110,11 +2110,11 @@ TEST_P(EntityTest, ColorFilterContentsWithLargeGeometry) {
21102110
}
21112111

21122112
TEST_P(EntityTest, TextContentsCeilsGlyphScaleToDecimal) {
2113-
ASSERT_EQ(TextFrame::RoundScaledFontSize(0.4321111f), 0.43f);
2114-
ASSERT_EQ(TextFrame::RoundScaledFontSize(0.5321111f), 0.53f);
2115-
ASSERT_EQ(TextFrame::RoundScaledFontSize(2.1f), 2.1f);
2116-
ASSERT_EQ(TextFrame::RoundScaledFontSize(0.0f), 0.0f);
2117-
ASSERT_EQ(TextFrame::RoundScaledFontSize(100000000.0f), 48.0f);
2113+
ASSERT_EQ(TextFrame::RoundScaledFontSize(0.4321111f), Rational(43, 100));
2114+
ASSERT_EQ(TextFrame::RoundScaledFontSize(0.5321111f), Rational(53, 100));
2115+
ASSERT_EQ(TextFrame::RoundScaledFontSize(2.1f), Rational(21, 10));
2116+
ASSERT_EQ(TextFrame::RoundScaledFontSize(0.0f), Rational(0, 1));
2117+
ASSERT_EQ(TextFrame::RoundScaledFontSize(100000000.0f), Rational(48, 1));
21182118
}
21192119

21202120
TEST_P(EntityTest, SpecializationConstantsAreAppliedToVariants) {

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ impeller_component("geometry") {
2727
"point.h",
2828
"quaternion.cc",
2929
"quaternion.h",
30+
"rational.cc",
31+
"rational.h",
3032
"rect.cc",
3133
"rect.h",
3234
"round_rect.cc",
@@ -80,6 +82,7 @@ impeller_component("geometry_unittests") {
8082
"geometry_unittests.cc",
8183
"matrix_unittests.cc",
8284
"path_unittests.cc",
85+
"rational_unittests.cc",
8386
"rect_unittests.cc",
8487
"round_rect_unittests.cc",
8588
"round_superellipse_unittests.cc",
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#include "flutter/impeller/geometry/rational.h"
6+
7+
#include <cmath>
8+
#include <cstdlib>
9+
#include <numeric>
10+
11+
namespace impeller {
12+
namespace {
13+
uint32_t AbsToUnsigned(int32_t x) {
14+
return static_cast<uint32_t>(std::abs(x));
15+
}
16+
} // namespace
17+
18+
bool Rational::operator==(const Rational& that) const {
19+
if (den_ == that.den_) {
20+
return num_ == that.num_;
21+
} else if ((num_ >= 0) != (that.num_ >= 0)) {
22+
return false;
23+
} else {
24+
return AbsToUnsigned(num_) * that.den_ == AbsToUnsigned(that.num_) * den_;
25+
}
26+
}
27+
28+
bool Rational::operator!=(const Rational& that) const {
29+
return !(*this == that);
30+
}
31+
32+
bool Rational::operator<(const Rational& that) const {
33+
if (den_ == that.den_) {
34+
return num_ < that.num_;
35+
} else if ((num_ >= 0) != (that.num_ >= 0)) {
36+
return num_ < that.num_;
37+
} else {
38+
return AbsToUnsigned(num_) * that.den_ < AbsToUnsigned(that.num_) * den_;
39+
}
40+
}
41+
42+
uint64_t Rational::GetHash() const {
43+
if (num_ == 0) {
44+
return 0;
45+
}
46+
uint64_t gcd = std::gcd(num_, den_);
47+
return ((num_ / gcd) << 32) | (den_ / gcd);
48+
}
49+
50+
Rational Rational::Invert() const {
51+
if (num_ >= 0) {
52+
return Rational(den_, num_);
53+
} else {
54+
return Rational(-1 * static_cast<int32_t>(den_), std::abs(num_));
55+
}
56+
}
57+
58+
} // namespace impeller
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#ifndef FLUTTER_IMPELLER_GEOMETRY_RATIONAL_H_
6+
#define FLUTTER_IMPELLER_GEOMETRY_RATIONAL_H_
7+
8+
#include <cstdint>
9+
#include "impeller/geometry/scalar.h"
10+
11+
namespace impeller {
12+
13+
class Rational {
14+
public:
15+
constexpr explicit Rational(int32_t num) : num_(num), den_(1) {}
16+
17+
constexpr Rational(int32_t num, uint32_t den) : num_(num), den_(den) {}
18+
19+
int32_t GetNumerator() const { return num_; }
20+
21+
uint32_t GetDenominator() const { return den_; }
22+
23+
bool operator==(const Rational& that) const;
24+
25+
bool operator!=(const Rational& that) const;
26+
27+
bool operator<(const Rational& that) const;
28+
29+
uint64_t GetHash() const;
30+
31+
explicit operator Scalar() const { return static_cast<float>(num_) / den_; }
32+
33+
Rational Invert() const;
34+
35+
private:
36+
int32_t num_;
37+
uint32_t den_;
38+
};
39+
40+
} // namespace impeller
41+
42+
#endif // FLUTTER_IMPELLER_GEOMETRY_RATIONAL_H_
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#include "flutter/impeller/geometry/rational.h"
6+
7+
#include "gtest/gtest.h"
8+
9+
namespace impeller {
10+
11+
TEST(RationalTest, Make) {
12+
Rational value(1, 2);
13+
EXPECT_EQ(value.GetNumerator(), 1);
14+
EXPECT_EQ(value.GetDenominator(), 2u);
15+
}
16+
17+
TEST(RationalTest, EqualsSameDen) {
18+
EXPECT_TRUE(Rational(1, 2) == Rational(1, 2));
19+
}
20+
21+
TEST(RationalTest, NotEqualsSameDen) {
22+
EXPECT_FALSE(Rational(3, 2) == Rational(1, 2));
23+
}
24+
25+
TEST(RationalTest, EqualsDifferentDen) {
26+
EXPECT_TRUE(Rational(1, 2) == Rational(2, 4));
27+
}
28+
29+
TEST(RationalTest, NegationNotEquals) {
30+
EXPECT_FALSE(Rational(1, 2) == Rational(-1, 2));
31+
}
32+
33+
TEST(RationalTest, LessThanSameDen) {
34+
EXPECT_TRUE(Rational(1, 2) < Rational(2, 2));
35+
}
36+
37+
TEST(RationalTest, LessThanNegation) {
38+
EXPECT_TRUE(Rational(-1, 2) < Rational(2, 23));
39+
}
40+
41+
TEST(RationalTest, LessThanDifferentDen) {
42+
EXPECT_TRUE(Rational(1, 2) < Rational(25, 23));
43+
}
44+
45+
TEST(RationalTest, NotLessThanDifferentDen) {
46+
EXPECT_FALSE(Rational(25, 23) < Rational(1, 2));
47+
}
48+
49+
TEST(RationalTest, SameHashes) {
50+
EXPECT_EQ(Rational(1, 2).GetHash(), Rational(2, 4).GetHash());
51+
}
52+
53+
TEST(RationalTest, DifferentHashes) {
54+
EXPECT_NE(Rational(2, 2).GetHash(), Rational(2, 4).GetHash());
55+
}
56+
57+
} // namespace impeller

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy