Visitar URL original
uber_sdf: removes derivatives from rect path (#192267) · flutter/flutter@4e6768e · GitHub
Skip to content

Commit 4e6768e

Browse files
uber_sdf: removes derivatives from rect path (#192267)
This removes derivative calls from the rect renderer in uber_sdf. It rejiggers uniforms for gradients as well to make space for the new uniform. It also removes uber_sdf from the codepath for rendering perspective matrices. That allows us to have a stable derivative when rendering with uber_sdf. spawned from #192204 Testing: see malioc results for better static analysis Local performance testing on macos where I removed the derivatives from rects, rrects and circles then drew a lot of those gave this about an 8% improvement in raster time. ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [AI contribution guidelines] and understand my responsibilities, or I am not using AI tools. - [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 in-code documentation (doc comments with `///`). - [x] If this PR introduces a new feature or capability, I created and linked a website documentation issue or PR in [flutter/website] (or verified none is needed). - [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]. If this change needs to override an active code freeze, provide a comment explaining why. The code freeze workflow can be overridden by code reviewers. See pinned issues for any active code freezes with guidance. **Note**: The Flutter team is currently trialing the use of [Gemini Code Assist for GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code). Comments from the `gemini-code-assist` bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [AI contribution guidelines]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines [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 [flutter/website]: https://github.com/flutter/website [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 --------- Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
1 parent ccf262e commit 4e6768e

11 files changed

Lines changed: 285 additions & 115 deletions

File tree

‎engine/src/flutter/impeller/compiler/shader_lib/impeller/gradient.glsl‎

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,19 @@ vec3 IPComputeFixedGradientValues(float t, float colors_length) {
149149
return vec3(lower_index, upper_index, scale);
150150
}
151151

152+
float IPComputeLinearGradientT(vec2 positon,
153+
vec2 start,
154+
vec2 delta,
155+
float inv_gradient_length) {
156+
return dot(positon - start, delta) * inv_gradient_length;
157+
}
158+
159+
float IPComputeRadialGradientT(vec2 position,
160+
vec2 center,
161+
float inv_gradient_length) {
162+
return length(position - center) * inv_gradient_length;
163+
}
164+
152165
/// Samples a linear gradient from a 1D gradient ramp texture.
153166
vec4 IPSampleLinearGradient(sampler2D tex,
154167
vec2 start_point,
@@ -163,7 +176,8 @@ vec4 IPSampleLinearGradient(sampler2D tex,
163176

164177
float t = start_to_end_squared == 0.0
165178
? 0.0
166-
: dot(start_to_position, start_to_end) / start_to_end_squared;
179+
: IPComputeLinearGradientT(pos, start_point, start_to_end,
180+
1.0 / start_to_end_squared);
167181
return IPSampleLinearWithTileMode(tex, vec2(t, 0.5), half_texel, tile_mode,
168182
decal_border_color);
169183
}
@@ -176,7 +190,7 @@ vec4 IPSampleRadialGradient(sampler2D tex,
176190
vec2 half_texel,
177191
float tile_mode,
178192
vec4 decal_border_color) {
179-
float t = radius == 0.0 ? 0.0 : length(pos - center) / radius;
193+
float t = IPComputeRadialGradientT(pos, center, 1.0 / radius);
180194
return IPSampleLinearWithTileMode(tex, vec2(t, 0.5), half_texel, tile_mode,
181195
decal_border_color);
182196
}

‎engine/src/flutter/impeller/display_list/aiks_dl_primitive_shape_unittests.cc‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,5 +222,21 @@ TEST_P(AiksTest, CanRenderSkewedCircleHairline) {
222222
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
223223
}
224224

225+
TEST_P(AiksTest, CanRenderSkewedRectHairline) {
226+
DisplayListBuilder builder;
227+
builder.Scale(GetContentScale().x, GetContentScale().y);
228+
builder.DrawColor(DlColor::kBlack(), DlBlendMode::kSrc);
229+
230+
RenderParameters params{
231+
.render_type = RenderType::kRectangle,
232+
.center = GetWindowBounds().GetCenter(),
233+
.skew_x = 0.75f,
234+
.skew_y = 0.5f,
235+
};
236+
RenderPrimitiveWithStroke(builder, params);
237+
238+
ASSERT_TRUE(OpenPlaygroundHere(builder.Build()));
239+
}
240+
225241
} // namespace testing
226242
} // namespace impeller

‎engine/src/flutter/impeller/display_list/canvas.cc‎

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -930,7 +930,7 @@ bool Canvas::AttemptDrawLineSDF(const Point& p0,
930930
const Paint& paint,
931931
bool reuse_depth) {
932932
if (!renderer_.GetContext()->GetFlags().use_sdfs ||
933-
!IsCompatibleWithSDFRendering(paint)) {
933+
!IsCompatibleWithSDFRendering(paint, GetCurrentTransform())) {
934934
return false;
935935
}
936936
// Draw the line as a filled rectangle with width=line_length and
@@ -1069,7 +1069,7 @@ void Canvas::DrawRect(const Rect& rect, const Paint& paint) {
10691069
}
10701070

10711071
if (renderer_.GetContext()->GetFlags().use_sdfs &&
1072-
IsCompatibleWithSDFRendering(paint)) {
1072+
IsCompatibleWithSDFRendering(paint, GetCurrentTransform())) {
10731073
Rect effective_rect = rect;
10741074
Color effective_color = paint.color;
10751075

@@ -1142,7 +1142,7 @@ void Canvas::DrawOval(const Rect& rect, const Paint& paint) {
11421142
entity.SetBlendMode(paint.blend_mode);
11431143

11441144
if (renderer_.GetContext()->GetFlags().use_sdfs &&
1145-
IsCompatibleWithSDFRendering(paint)) {
1145+
IsCompatibleWithSDFRendering(paint, GetCurrentTransform())) {
11461146
UberSDFParameters params;
11471147

11481148
if (paint.style == Paint::Style::kStroke) {
@@ -1230,7 +1230,8 @@ void Canvas::DrawRoundRect(const RoundRect& round_rect, const Paint& paint) {
12301230
const RoundingRadii& radii = round_rect.GetRadii();
12311231

12321232
if (renderer_.GetContext()->GetFlags().use_sdfs &&
1233-
IsCompatibleWithSDFRendering(paint) && radii.AreAllCornersCircular()) {
1233+
IsCompatibleWithSDFRendering(paint, GetCurrentTransform()) &&
1234+
radii.AreAllCornersCircular()) {
12341235
Color effective_color = paint.color;
12351236
Rect bounds = round_rect.GetBounds();
12361237

@@ -1314,7 +1315,7 @@ void Canvas::DrawRoundSuperellipse(const RoundSuperellipse& round_superellipse,
13141315
entity.SetBlendMode(paint.blend_mode);
13151316

13161317
if (renderer_.GetContext()->GetFlags().use_sdfs &&
1317-
IsCompatibleWithSDFRendering(paint)) {
1318+
IsCompatibleWithSDFRendering(paint, GetCurrentTransform())) {
13181319
// Try to draw using UberSDF.
13191320
auto params = UberSDFParameters::MakeRoundedSuperellipse(
13201321
/*color=*/paint.color, /*round_superellipse=*/round_superellipse,
@@ -1378,7 +1379,7 @@ void Canvas::DrawCircle(const Point& center,
13781379
}
13791380

13801381
if (renderer_.GetContext()->GetFlags().use_sdfs &&
1381-
IsCompatibleWithSDFRendering(paint)) {
1382+
IsCompatibleWithSDFRendering(paint, GetCurrentTransform())) {
13821383
auto params = UberSDFParameters::MakeCircle(
13831384
/*color=*/paint.color, /*center=*/center, /*radius=*/radius,
13841385
/*stroke=*/paint.GetStroke());
@@ -2752,13 +2753,17 @@ void Canvas::EndReplay() {
27522753
Initialize(initial_cull_rect_);
27532754
}
27542755

2755-
bool Canvas::IsCompatibleWithSDFRendering(const Paint& paint) {
2756+
bool Canvas::IsCompatibleWithSDFRendering(const Paint& paint,
2757+
const Matrix& transform) {
27562758
if (!paint.anti_alias) {
27572759
return false;
27582760
}
27592761
if (paint.mask_blur_descriptor.has_value()) {
27602762
return false;
27612763
}
2764+
if (transform.HasPerspective()) {
2765+
return false;
2766+
}
27622767
switch (paint.blend_mode) {
27632768
// Incompatible blend modes:
27642769
case BlendMode::kClear:

‎engine/src/flutter/impeller/display_list/canvas.h‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -287,8 +287,8 @@ class Canvas {
287287

288288
/// Returns true if the paint is compatible with SDF rendering.
289289
///
290-
/// Visible for testing.
291-
static bool IsCompatibleWithSDFRendering(const Paint& paint);
290+
static bool IsCompatibleWithSDFRendering(const Paint& paint,
291+
const Matrix& transform = Matrix());
292292

293293
private:
294294
class BlurShape {

‎engine/src/flutter/impeller/entity/contents/uber_sdf_contents.cc‎

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,23 @@ SamplerBinding SetupGradientParameters(
7171
const ContentContext& renderer,
7272
FS::FragInfo& frag_info) {
7373
FML_DCHECK(gradient.texture);
74-
frag_info.gradient_start = gradient.start;
75-
frag_info.gradient_end = gradient.end;
7674
frag_info.tile_mode = static_cast<Scalar>(gradient.tile_mode);
7775
auto texture_size = gradient.texture->GetSize();
7876
FML_DCHECK(!texture_size.IsEmpty());
79-
frag_info.half_texel =
80-
Point(0.5f, 0.5f) / Point(texture_size.width, texture_size.height);
77+
frag_info.half_texel = 0.5f / texture_size.width;
78+
79+
if (gradient.type == UberSDFParameters::GradientParameters::Type::kLinear) {
80+
Point delta = gradient.end - gradient.start;
81+
Scalar length_sq = delta.x * delta.x + delta.y * delta.y;
82+
frag_info.gradient_coords =
83+
Vector4(gradient.start.x, gradient.start.y, delta.x, delta.y);
84+
frag_info.inv_gradient_length = length_sq > 0.0f ? 1.0f / length_sq : 0.0f;
85+
} else {
86+
frag_info.gradient_coords =
87+
Vector4(gradient.start.x, gradient.start.y, 0.0f, 0.0f);
88+
frag_info.inv_gradient_length =
89+
gradient.end.x > 0.0f ? 1.0f / gradient.end.x : 0.0f;
90+
}
8191

8292
SamplerDescriptor sampler_desc;
8393
sampler_desc.min_filter = MinMagFilter::kLinear;
@@ -116,6 +126,8 @@ bool UberSDFContents::Render(const ContentContext& renderer,
116126
params_.color.WithAlpha(params_.color.alpha * GetOpacityFactor());
117127
frag_info.center = params_.center;
118128
frag_info.size = params_.size;
129+
Vector2 pixel_size = entity.GetTransform().GetTransformedPixelSize();
130+
frag_info.pixel_size = Point(pixel_size.x, pixel_size.y);
119131
frag_info.stroked = params_.stroke ? 1.0f : 0.0f;
120132
frag_info.stroke_width = params_.stroke ? params_.stroke->width : 0.0f;
121133
frag_info.stroke_join =
@@ -126,6 +138,10 @@ bool UberSDFContents::Render(const ContentContext& renderer,
126138
frag_info.circle_center_top = params_.circle_center_top;
127139
frag_info.circle_center_right = params_.circle_center_right;
128140
frag_info.radii = params_.radii;
141+
frag_info.gradient_coords = Vector4();
142+
frag_info.half_texel = 0.0f;
143+
frag_info.tile_mode = 0.0f;
144+
frag_info.inv_gradient_length = 0.0f;
129145

130146
SamplerBinding sampler_binding;
131147
if (params_.gradient) {

‎engine/src/flutter/impeller/entity/geometry/geometry_unittests.cc‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,21 @@ TEST(EntityGeometryTest, UberSDFGeometryPaddingIsAdjustedByInverseMaxBasis) {
117117
.TransformAndClipBounds(matrix));
118118
}
119119
}
120+
121+
// Under skew, padding should account for non-orthogonal axis scaling and
122+
// determinant.
123+
{
124+
auto matrix = Matrix::MakeSkew(0.75f, 0.5f);
125+
auto coverage = geometry.GetCoverage(matrix);
126+
EXPECT_TRUE(coverage.has_value());
127+
if (coverage.has_value()) {
128+
Vector2 pixel_size = matrix.GetTransformedPixelSize();
129+
EXPECT_EQ(coverage.value(),
130+
Rect::MakeLTRB(-pixel_size.x, -pixel_size.y,
131+
100.0f + pixel_size.x, 100.0f + pixel_size.y)
132+
.TransformAndClipBounds(matrix));
133+
}
134+
}
120135
}
121136

122137
TEST(EntityGeometryTest, FillPathGeometryCoversArea) {

‎engine/src/flutter/impeller/entity/geometry/uber_sdf_geometry.cc‎

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -65,16 +65,9 @@ bool UberSDFGeometry::IsAxisAlignedRect() const {
6565
}
6666

6767
Rect UberSDFGeometry::GetExpandedBounds(const Matrix& transform) const {
68-
// Get the scaling factor of the transform in the X and Y directions.
69-
Vector2 transform_scaling = transform.GetBasisScaleXY();
70-
71-
// Get the device pixel size in local space units. This is the inverse of the
72-
// transform scaling. E.g. if the transform performs a scale of 4 in the X
73-
// direction and 0.5 in the Y direction, then 1 device pixel is size
74-
// {0.25, 2.0} in local space units.
75-
Size device_pixel_size = {
76-
transform_scaling.x != 0 ? 1.0f / transform_scaling.x : 0,
77-
transform_scaling.y != 0 ? 1.0f / transform_scaling.y : 0};
68+
// Get the device pixel size in local space units.
69+
Vector2 pixel_size = transform.GetTransformedPixelSize();
70+
Size device_pixel_size(pixel_size.x, pixel_size.y);
7871

7972
// The stroke padding is half the stroke width, if the shape is stroked.
8073
Size stroke_padding;

‎engine/src/flutter/impeller/entity/shaders/uber_sdf.frag‎

Lines changed: 37 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
precision mediump float;
66

77
#include <impeller/color.glsl>
8+
#include <impeller/constants.glsl>
89
#include <impeller/gradient.glsl>
910
#include <impeller/texture.glsl>
1011
#include <impeller/types.glsl>
@@ -29,6 +30,14 @@ uniform FragInfo {
2930
/// bottom-right), or the circular cap radii for rounded superellipses in
3031
/// radii.xy (top octant in x, right octant in y).
3132
vec4 radii;
33+
/// Gradient parameters:
34+
/// - Linear gradient:
35+
/// xy: Start point in local coordinates.
36+
/// zw: Delta vector (end - start) in local coordinates.
37+
/// - Radial gradient:
38+
/// xy: Center point in local coordinates.
39+
/// zw: Unused (0.0).
40+
vec4 gradient_coords;
3241

3342
// ===========================================================================
3443
// vec2 fields
@@ -39,6 +48,8 @@ uniform FragInfo {
3948
vec2 center;
4049
/// The half-dimensions of the shape (half-width, half-height).
4150
vec2 size;
51+
/// The size of a device pixel in local coordinates.
52+
vec2 pixel_size;
4253

4354
// --- Superellipse Parameters ---
4455
/// The exponent degree (n_x, n_y) of the superellipse curvature.
@@ -53,17 +64,6 @@ uniform FragInfo {
5364
/// rounded superellipse.
5465
vec2 circle_center_right;
5566

56-
// --- Gradient Parameters ---
57-
/// The starting point of a linear gradient, or the center point of a radial
58-
/// gradient.
59-
vec2 gradient_start;
60-
/// The ending point of a linear gradient, or (radius, 0.0) for a radial
61-
/// gradient.
62-
vec2 gradient_end;
63-
/// Half the size of a single gradient texel in normalized texture
64-
/// coordinates.
65-
vec2 half_texel;
66-
6767
// ===========================================================================
6868
// float fields
6969
// ===========================================================================
@@ -102,6 +102,13 @@ uniform FragInfo {
102102
/// 2: Mirror
103103
/// 3: Decal
104104
float tile_mode;
105+
/// Half the size of a single gradient texel in normalized texture
106+
/// coordinates along the gradient ramp (x axis).
107+
float half_texel;
108+
/// Inverse gradient length:
109+
/// - Linear gradient: 1.0 / dot(delta, delta)
110+
/// - Radial gradient: 1.0 / radius
111+
float inv_gradient_length;
105112
}
106113
frag_info;
107114

@@ -115,17 +122,20 @@ vec4 getColor() {
115122
if (frag_info.color_source_type < 0.5) {
116123
// Solid color
117124
color = frag_info.color;
118-
} else if (frag_info.color_source_type < 1.5) {
119-
// Linear gradient
120-
vec4 gradient_color = IPSampleLinearGradient(
121-
color_source_sampler, frag_info.gradient_start, frag_info.gradient_end,
122-
v_position, frag_info.half_texel, frag_info.tile_mode, vec4(0.0));
123-
color = vec4(gradient_color.rgb, gradient_color.a * frag_info.color.a);
124125
} else {
125-
// Radial gradient
126-
vec4 gradient_color = IPSampleRadialGradient(
127-
color_source_sampler, frag_info.gradient_start,
128-
frag_info.gradient_end.x, v_position, frag_info.half_texel,
126+
float t;
127+
if (frag_info.color_source_type < 1.5) {
128+
// Linear gradient
129+
t = IPComputeLinearGradientT(v_position, frag_info.gradient_coords.xy,
130+
frag_info.gradient_coords.zw,
131+
frag_info.inv_gradient_length);
132+
} else {
133+
// Radial gradient
134+
t = IPComputeRadialGradientT(v_position, frag_info.gradient_coords.xy,
135+
frag_info.inv_gradient_length);
136+
}
137+
vec4 gradient_color = IPSampleLinearWithTileMode(
138+
color_source_sampler, vec2(t, 0.5), vec2(frag_info.half_texel, 0.5),
129139
frag_info.tile_mode, vec4(0.0));
130140
color = vec4(gradient_color.rgb, gradient_color.a * frag_info.color.a);
131141
}
@@ -166,7 +176,7 @@ float distanceFromChamferRect(vec2 p, vec2 half_size, float chamfer_size) {
166176
p = abs(p);
167177
float d1 = max(p.x - half_size.x, p.y - half_size.y);
168178
float d2 =
169-
(p.x + p.y - half_size.x - half_size.y + chamfer_size) * 0.70710678;
179+
(p.x + p.y - half_size.x - half_size.y + chamfer_size) * kHalfSqrtTwo;
170180
return max(d1, d2);
171181
}
172182

@@ -215,25 +225,14 @@ float distanceFromRoundedSuperellipse(vec2 p,
215225
se_degree);
216226
}
217227

218-
// Special case pixel size calculation for rectangles. The standard `pixelSize`
219-
// function uses SDF derivatives, which gives invalid results for very small
220-
// shapes, where adjacent device pixels span across opposing edges of the shape.
221-
// This function calculates pixel size for rectangles without using SDF
222-
// derivatives.
228+
// Calculates pixel size for rectangles using frag_info.pixel_size.
223229
float rectPixelSize(vec2 p) {
224-
// The change in local coordinates per horizontal device pixel (device_dx)
225-
// and vertical device pixel (device_dy).
226-
vec2 device_dx = dFdx(v_position);
227-
vec2 device_dy = dFdy(v_position);
228-
// The size of a device pixel in terms of local coordinates.
229-
vec2 device_pixel_size = vec2(length(vec2(device_dx.x, device_dy.x)),
230-
length(vec2(device_dx.y, device_dy.y)));
231-
232230
// Get pixel size in the direction perpendicular to the closest edge of the
233-
// rectangle: device_pixel_size.x when closer to a vertical edge, and
234-
// pixel_size.y when closer to a horizontal edge.
231+
// rectangle: frag_info.pixel_size.x when closer to a vertical edge, and
232+
// frag_info.pixel_size.y when closer to a horizontal edge.
235233
vec2 distance = abs(abs(p) - frag_info.size);
236-
return (distance.x < distance.y) ? device_pixel_size.x : device_pixel_size.y;
234+
return (distance.x < distance.y) ? frag_info.pixel_size.x
235+
: frag_info.pixel_size.y;
237236
}
238237

239238
// Special case pixel size calculation for rounded rectangles, similar to

0 commit comments

Comments
 (0)