[stable] Removes assumption that basis scalar and rounded_scalar match (#165428)
## Impacted Users
Impeller, all platforms
## Impact Description
Text that is scaled over 48x will render at the incorrect size and position
## Workaround
Change the font size instead of using a scale, but this isn't always clear from the framework usage.
## Risk
If it is wrong, text will be rendered incorrectly.
## Test Coverage
A golden test was written but they aren't automatically executed on release branches.
## Validation Steps
https://github.com/flutter/flutter/issues/165130 has reproduction code
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2d0ea8d..61338d2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -26,6 +26,10 @@
-->
## Flutter 3.29 Changes
+### [3.29.3](https://github.com/flutter/flutter/releases/tag/3.29.3)
+- [flutter/165166](https://github.com/flutter/flutter/pull/165166) - Impeller,
+ All platforms, Text that is scaled over 48x renders incorrectly.
+
### [3.29.2](https://github.com/flutter/flutter/releases/tag/3.29.2)
- [dart 3.7.2 changelog](https://github.com/dart-lang/sdk/blob/stable/CHANGELOG.md#372)
diff --git a/engine/src/flutter/impeller/display_list/aiks_dl_text_unittests.cc b/engine/src/flutter/impeller/display_list/aiks_dl_text_unittests.cc
index 8ad7ac1..2244489 100644
--- a/engine/src/flutter/impeller/display_list/aiks_dl_text_unittests.cc
+++ b/engine/src/flutter/impeller/display_list/aiks_dl_text_unittests.cc
@@ -160,6 +160,35 @@
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
}
+TEST_P(AiksTest, CanRenderTextFrameWithScalingOverflow) {
+ Scalar scale = 60.0;
+ Scalar offsetx = -500.0;
+ Scalar offsety = 700.0;
+ auto callback = [&]() -> sk_sp<DisplayList> {
+ if (AiksTest::ImGuiBegin("Controls", nullptr,
+ ImGuiWindowFlags_AlwaysAutoResize)) {
+ ImGui::SliderFloat("scale", &scale, 1.f, 300.f);
+ ImGui::SliderFloat("offsetx", &offsetx, -600.f, 100.f);
+ ImGui::SliderFloat("offsety", &offsety, 600.f, 2048.f);
+ ImGui::End();
+ }
+ DisplayListBuilder builder;
+ builder.Scale(GetContentScale().x, GetContentScale().y);
+ DlPaint paint;
+ paint.setColor(DlColor::ARGB(1, 0.1, 0.1, 0.1));
+ builder.DrawPaint(paint);
+ builder.Scale(scale, scale);
+
+ RenderTextInCanvasSkia(
+ GetContext(), builder, "test", "Roboto-Regular.ttf",
+ TextRenderOptions{
+ .position = SkPoint::Make(offsetx / scale, offsety / scale),
+ });
+ return builder.Build();
+ };
+ ASSERT_TRUE(OpenPlaygroundHere(callback));
+}
+
TEST_P(AiksTest, CanRenderTextFrameWithFractionScaling) {
Scalar fine_scale = 0.f;
bool is_subpixel = false;
diff --git a/engine/src/flutter/impeller/entity/contents/text_contents.cc b/engine/src/flutter/impeller/entity/contents/text_contents.cc
index 6ed000c..27bb4a6 100644
--- a/engine/src/flutter/impeller/entity/contents/text_contents.cc
+++ b/engine/src/flutter/impeller/entity/contents/text_contents.cc
@@ -77,6 +77,22 @@
}
}
+namespace {
+Scalar AttractToOne(Scalar x) {
+ // Epsilon was decided by looking at the floating point inaccuracies in
+ // the ScaledK test.
+ const Scalar epsilon = 0.005f;
+ if (std::abs(x - 1.f) < epsilon) {
+ return 1.f;
+ }
+ if (std::abs(x + 1.f) < epsilon) {
+ return -1.f;
+ }
+ return x;
+}
+
+} // namespace
+
void TextContents::ComputeVertexData(
VS::PerVertexData* vtx_contents,
const std::shared_ptr<TextFrame>& frame,
@@ -165,7 +181,8 @@
atlas_glyph_bounds = maybe_atlas_glyph_bounds.value().atlas_bounds;
}
- Rect scaled_bounds = glyph_bounds.Scale(1.0 / rounded_scale);
+ Scalar inverted_rounded_scale = 1.f / rounded_scale;
+ Rect scaled_bounds = glyph_bounds.Scale(inverted_rounded_scale);
// For each glyph, we compute two rectangles. One for the vertex
// positions and one for the texture coordinates (UVs). The atlas
// glyph bounds are used to compute UVs in cases where the
@@ -175,8 +192,14 @@
Point uv_size = SizeToPoint(atlas_glyph_bounds.GetSize()) / atlas_size;
Matrix unscaled_basis =
- Matrix::MakeScale({basis_transform.m[0] > 0 ? 1.f : -1.f,
- basis_transform.m[5] > 0 ? 1.f : -1.f, 1.f});
+ basis_transform * Matrix::MakeScale({inverted_rounded_scale,
+ inverted_rounded_scale, 1});
+
+ // In typical scales < 48x these values should be -1 or 1. We round to
+ // those to avoid inaccuracies.
+ unscaled_basis.m[0] = AttractToOne(unscaled_basis.m[0]);
+ unscaled_basis.m[5] = AttractToOne(unscaled_basis.m[5]);
+
Point unrounded_glyph_position =
// This is for RTL text.
unscaled_basis * glyph_bounds.GetLeftTop() +
diff --git a/engine/src/flutter/testing/impeller_golden_tests_output.txt b/engine/src/flutter/testing/impeller_golden_tests_output.txt
index 79cc526..af7e23b 100644
--- a/engine/src/flutter/testing/impeller_golden_tests_output.txt
+++ b/engine/src/flutter/testing/impeller_golden_tests_output.txt
@@ -487,6 +487,9 @@
impeller_Play_AiksTest_CanRenderTextFrameWithInvertedTransform_Metal.png
impeller_Play_AiksTest_CanRenderTextFrameWithInvertedTransform_OpenGLES.png
impeller_Play_AiksTest_CanRenderTextFrameWithInvertedTransform_Vulkan.png
+impeller_Play_AiksTest_CanRenderTextFrameWithScalingOverflow_Metal.png
+impeller_Play_AiksTest_CanRenderTextFrameWithScalingOverflow_OpenGLES.png
+impeller_Play_AiksTest_CanRenderTextFrameWithScalingOverflow_Vulkan.png
impeller_Play_AiksTest_CanRenderTextFrame_Metal.png
impeller_Play_AiksTest_CanRenderTextFrame_OpenGLES.png
impeller_Play_AiksTest_CanRenderTextFrame_Vulkan.png