From 0238b98a03ef8c85cd6bdd1e942915b6527fb4de Mon Sep 17 00:00:00 2001 From: Benson Luk <97480502+b-luk@users.noreply.github.com> Date: Mon, 10 Aug 2026 12:23:33 -0700 Subject: [PATCH 01/15] initial commit --- .../shader_lib/impeller/gradient.glsl | 29 ++++++ .../flutter/impeller/display_list/canvas.cc | 89 +++++++++++++++---- .../entity/contents/uber_sdf_contents.cc | 58 +++++++++++- .../contents/uber_sdf_contents_unittests.cc | 24 +++++ .../entity/contents/uber_sdf_parameters.h | 30 +++++++ .../gradients/linear_gradient_fill.frag | 13 +-- .../gradients/radial_gradient_fill.frag | 11 +-- .../impeller/entity/shaders/uber_sdf.frag | 39 +++++++- 8 files changed, 256 insertions(+), 37 deletions(-) diff --git a/engine/src/flutter/impeller/compiler/shader_lib/impeller/gradient.glsl b/engine/src/flutter/impeller/compiler/shader_lib/impeller/gradient.glsl index 6f2b87f24a8f3..193dd6548feb0 100644 --- a/engine/src/flutter/impeller/compiler/shader_lib/impeller/gradient.glsl +++ b/engine/src/flutter/impeller/compiler/shader_lib/impeller/gradient.glsl @@ -149,4 +149,33 @@ vec3 IPComputeFixedGradientValues(float t, float colors_length) { return vec3(lower_index, upper_index, scale); } +/// Samples a linear gradient from a 1D gradient ramp texture. +vec4 IPSampleLinearGradient(sampler2D tex, + vec2 start_point, + vec2 end_point, + vec2 pos, + vec2 half_texel, + float tile_mode, + vec4 decal_border_color) { + vec2 start_to_end = end_point - start_point; + vec2 start_to_position = pos - start_point; + float t = + dot(start_to_position, start_to_end) / dot(start_to_end, start_to_end); + return IPSampleLinearWithTileMode(tex, vec2(t, 0.5), half_texel, tile_mode, + decal_border_color); +} + +/// Samples a radial gradient from a 1D gradient ramp texture. +vec4 IPSampleRadialGradient(sampler2D tex, + vec2 center, + float radius, + vec2 pos, + vec2 half_texel, + float tile_mode, + vec4 decal_border_color) { + float t = length(pos - center) / radius; + return IPSampleLinearWithTileMode(tex, vec2(t, 0.5), half_texel, tile_mode, + decal_border_color); +} + #endif diff --git a/engine/src/flutter/impeller/display_list/canvas.cc b/engine/src/flutter/impeller/display_list/canvas.cc index 02b6dd130c8dc..aa1a1ff12df08 100644 --- a/engine/src/flutter/impeller/display_list/canvas.cc +++ b/engine/src/flutter/impeller/display_list/canvas.cc @@ -34,6 +34,7 @@ #include "impeller/entity/contents/content_context.h" #include "impeller/entity/contents/filters/filter_contents.h" #include "impeller/entity/contents/framebuffer_blend_contents.h" +#include "impeller/entity/contents/gradient_generator.h" #include "impeller/entity/contents/shadow_vertices_contents.h" #include "impeller/entity/contents/solid_color_contents.h" #include "impeller/entity/contents/solid_rrect_blur_contents.h" @@ -59,6 +60,7 @@ #include "impeller/entity/save_layer_utils.h" #include "impeller/geometry/color.h" #include "impeller/geometry/constants.h" +#include "impeller/geometry/gradient.h" #include "impeller/geometry/round_superellipse_param.h" #include "impeller/geometry/rounding_radii.h" #include "impeller/geometry/rstransform.h" @@ -224,6 +226,59 @@ static std::pair ExpandRectToPixelMinimum(const Rect& rect, return {expanded.value(), color.WithAlpha(color.alpha * alpha_scaling)}; } +/// @brief Attempts to populate `params` with the color source properties, +/// if supported by UberSDF (e.g. for linear and radial gradients). +/// +/// @return Whether the color source was successfully populated into `params`. +static bool PopulateUberSDFColorSource( + const flutter::DlColorSource& color_source, + const ContentContext& renderer, + UberSDFParameters& params) { + if (color_source.isGradient()) { + UberSDFParameters::GradientParameters gradient; + + std::vector colors; + std::vector stops; + + if (color_source.type() == flutter::DlColorSourceType::kLinearGradient) { + const auto* linear = color_source.asLinearGradient(); + FML_DCHECK(linear); + Paint::ConvertStops(linear, colors, stops); + gradient.type = UberSDFParameters::GradientParameters::Type::kLinear; + gradient.start = linear->start_point(); + gradient.end = linear->end_point(); + gradient.tile_mode = static_cast(linear->tile_mode()); + } else if (color_source.type() == + flutter::DlColorSourceType::kRadialGradient) { + const auto* radial = color_source.asRadialGradient(); + FML_DCHECK(radial); + Paint::ConvertStops(radial, colors, stops); + gradient.type = UberSDFParameters::GradientParameters::Type::kRadial; + gradient.start = radial->center(); + // For radial gradients, gradient.end.x stores the radius. + gradient.end = Point(radial->radius(), 0.0f); + gradient.tile_mode = static_cast(radial->tile_mode()); + } else { + // Gradient type not supported by UberSDF. + return false; + } + + GradientData gradient_data = CreateGradientBuffer(colors, stops); + std::shared_ptr texture = + CreateGradientTexture(gradient_data, renderer.GetContext()); + if (!texture) { + return false; + } + + gradient.texture = std::move(texture); + params.gradient = std::move(gradient); + return true; + } + + // Color source not supported by UberSDF. + return false; +} + } // namespace class Canvas::RRectBlurShape : public BlurShape { @@ -2180,23 +2235,29 @@ void Canvas::AddRenderSDFEntityToCurrentPass( entity.SetTransform(transform); entity.SetBlendMode(paint.blend_mode); - if (paint.color_source) { - // Since we are going to use BlendMode::kSrcIn to implement the color_source - // the SDF portion of the blend should just be solid white to get the - // correct color from the color_source. + if (!paint.color_source || + PopulateUberSDFColorSource(*paint.color_source, renderer_, params)) { + // No color source (solid paint color), or a supported color source was + // populated into UberSDFParams. + auto geometry = std::make_unique(params); + auto contents = UberSDFContents::Make(params, std::move(geometry)); + const Geometry* geom = contents->GetGeometry(); + AddRenderEntityWithFiltersToCurrentPass(entity, geom, paint, reuse_depth, + /*override_contents=*/ + std::move(contents)); + } else { + // Color source not directly supported by UberSDF (e.g. image, runtime + // effect, or an unsupported gradient type). Render a solid white mask with + // UberSDF and blend with ColorSourceContents. params.color = Color::White(); - } - auto geometry = std::make_unique(params); - auto contents = UberSDFContents::Make(params, std::move(geometry)); - const Geometry* geom = contents->GetGeometry(); + auto geometry = std::make_unique(params); + auto uber_sdf_contents = UberSDFContents::Make(params, std::move(geometry)); + const Geometry* geom = uber_sdf_contents->GetGeometry(); - if (paint.color_source) { - // UberSDF doesn't perform things like gradients so we blend the SDF - // with the color source. std::shared_ptr color_source_contents = paint.CreateContents(renderer_, geom, shape_transform); std::shared_ptr final_contents = ColorFilterContents::MakeBlend( - BlendMode::kSrcIn, {FilterInput::Make(std::move(contents)), + BlendMode::kSrcIn, {FilterInput::Make(std::move(uber_sdf_contents)), FilterInput::Make(color_source_contents)}); Paint new_paint = paint; @@ -2205,10 +2266,6 @@ void Canvas::AddRenderSDFEntityToCurrentPass( reuse_depth, /*override_contents=*/ std::move(final_contents)); - } else { - AddRenderEntityWithFiltersToCurrentPass(entity, geom, paint, reuse_depth, - /*override_contents=*/ - std::move(contents)); } } diff --git a/engine/src/flutter/impeller/entity/contents/uber_sdf_contents.cc b/engine/src/flutter/impeller/entity/contents/uber_sdf_contents.cc index 5bec7103c92b5..0d6a505c64525 100644 --- a/engine/src/flutter/impeller/entity/contents/uber_sdf_contents.cc +++ b/engine/src/flutter/impeller/entity/contents/uber_sdf_contents.cc @@ -4,6 +4,7 @@ #include "impeller/entity/contents/uber_sdf_contents.h" +#include "fml/logging.h" #include "impeller/entity/contents/color_source_contents.h" #include "impeller/entity/contents/content_context.h" #include "impeller/entity/contents/pipelines.h" @@ -46,6 +47,44 @@ Scalar ToShaderStrokeJoin(Join join) { } } +Scalar ToShaderColorSourceType(const UberSDFParameters& params) { + if (!params.gradient.has_value()) { + return 0.0f; + } + switch (params.gradient->type) { + case UberSDFParameters::GradientParameters::Type::kLinear: + return 1.0f; + case UberSDFParameters::GradientParameters::Type::kRadial: + return 2.0f; + } +} + +struct SamplerBinding { + std::shared_ptr texture; + raw_ptr sampler; +}; + +/// @brief Populates the gradient uniform fields in `frag_info` and returns +/// the texture and sampler for the gradient. +SamplerBinding SetupGradientParameters( + const UberSDFParameters::GradientParameters& gradient, + const ContentContext& renderer, + FS::FragInfo& frag_info) { + FML_DCHECK(gradient.texture); + frag_info.gradient_start = gradient.start; + frag_info.gradient_end = gradient.end; + frag_info.tile_mode = static_cast(gradient.tile_mode); + frag_info.half_texel = Point(0.5f, 0.5f) / gradient.texture->GetSize(); + + SamplerDescriptor sampler_desc; + sampler_desc.min_filter = MinMagFilter::kLinear; + sampler_desc.mag_filter = MinMagFilter::kLinear; + raw_ptr sampler = + renderer.GetContext()->GetSamplerLibrary()->GetSampler(sampler_desc); + + return {gradient.texture, sampler}; +} + } // namespace std::unique_ptr UberSDFContents::Make( @@ -69,6 +108,7 @@ bool UberSDFContents::Render(const ContentContext& renderer, VS::FrameInfo frame_info; FS::FragInfo frag_info; frag_info.type = ToShaderType(params_.type); + frag_info.color_source_type = ToShaderColorSourceType(params_); frag_info.color = params_.color.WithAlpha(params_.color.alpha * GetOpacityFactor()); frag_info.center = params_.center; @@ -87,6 +127,16 @@ bool UberSDFContents::Render(const ContentContext& renderer, frag_info.superellipse_scale = params_.superellipse_scale; frag_info.radii = params_.radii; + SamplerBinding sampler_binding; + if (params_.gradient) { + sampler_binding = + SetupGradientParameters(params_.gradient.value(), renderer, frag_info); + } else { + sampler_binding.texture = renderer.GetEmptyTexture(); + sampler_binding.sampler = + renderer.GetContext()->GetSamplerLibrary()->GetSampler({}); + } + auto geometry_result = GetGeometry()->GetPositionBuffer(renderer, entity, pass); @@ -99,7 +149,10 @@ bool UberSDFContents::Render(const ContentContext& renderer, this, GetGeometry(), renderer, entity, pass, pipeline_callback, frame_info, /*bind_fragment_callback=*/ - [&frag_info, &data_host_buffer](RenderPass& pass) { + [&frag_info, &data_host_buffer, + sampler_binding = std::move(sampler_binding)](RenderPass& pass) { + FS::BindColorSourceSampler(pass, sampler_binding.texture, + sampler_binding.sampler); FS::BindFragInfo(pass, data_host_buffer.EmplaceUniform(frag_info)); pass.SetCommandLabel("UberSDF"); return true; @@ -133,7 +186,8 @@ bool UberSDFContents::ApplyColorFilter( std::optional UberSDFContents::AsBackgroundColor( const Entity& entity, ISize target_size) const { - if (params_.type != UberSDFParameters::Type::kRect) { + if (params_.type != UberSDFParameters::Type::kRect || + params_.gradient.has_value()) { return std::nullopt; } const Geometry* geometry = GetGeometry(); diff --git a/engine/src/flutter/impeller/entity/contents/uber_sdf_contents_unittests.cc b/engine/src/flutter/impeller/entity/contents/uber_sdf_contents_unittests.cc index 00522e087d572..e862b42d8df34 100644 --- a/engine/src/flutter/impeller/entity/contents/uber_sdf_contents_unittests.cc +++ b/engine/src/flutter/impeller/entity/contents/uber_sdf_contents_unittests.cc @@ -100,5 +100,29 @@ TEST(UberSDFContentsTest, AsBackgroundColorStrokedRect) { EXPECT_FALSE(bg_color.has_value()); } +TEST(UberSDFContentsTest, AsBackgroundColorGradientReturnsNullopt) { + auto rect = Rect::MakeXYWH(-2, -2, 504, 504); + auto params = + UberSDFParameters::MakeRect(Color::Red(), rect, /*stroke=*/std::nullopt); + + UberSDFParameters::GradientParameters gradient; + gradient.type = UberSDFParameters::GradientParameters::Type::kLinear; + gradient.start = Point(0, 0); + gradient.end = Point(500, 500); + gradient.tile_mode = Entity::TileMode::kClamp; + params.gradient = gradient; + + auto geometry = std::make_unique(params); + auto contents = UberSDFContents::Make(params, std::move(geometry)); + + Entity entity; + entity.SetTransform(Matrix()); + + // Even though the rect covers the entire area, a gradient is not a single + // solid background color, so AsBackgroundColor must return nullopt. + auto bg_color = contents->AsBackgroundColor(entity, ISize(500, 500)); + EXPECT_FALSE(bg_color.has_value()); +} + } // namespace testing } // namespace impeller diff --git a/engine/src/flutter/impeller/entity/contents/uber_sdf_parameters.h b/engine/src/flutter/impeller/entity/contents/uber_sdf_parameters.h index 942d6ccf5db3e..3e9520f9cc4fd 100644 --- a/engine/src/flutter/impeller/entity/contents/uber_sdf_parameters.h +++ b/engine/src/flutter/impeller/entity/contents/uber_sdf_parameters.h @@ -7,6 +7,8 @@ #include +#include "impeller/core/texture.h" +#include "impeller/entity/entity.h" #include "impeller/geometry/color.h" #include "impeller/geometry/point.h" #include "impeller/geometry/rect.h" @@ -32,6 +34,31 @@ struct UberSDFParameters { kRoundedSuperellipseSymmetric, }; + /// The gradient properties applied to the shape. + struct GradientParameters { + enum class Type { + kLinear, + kRadial, + }; + + /// The type of gradient. + Type type; + + /// Gradient start point (for linear gradients) or center (for radial + /// gradients). + Point start; + + /// Gradient end point (for linear gradients) or `(radius, 0)` (for radial + /// gradients). + Point end; + + /// Tile mode for the gradient. + Entity::TileMode tile_mode = Entity::TileMode::kClamp; + + /// Texture for gradient ramp. + std::shared_ptr texture; + }; + /// Creates UberSDFParameters for a rectangle. static UberSDFParameters MakeRect(Color color, const Rect& rect, @@ -68,6 +95,9 @@ struct UberSDFParameters { /// The color used for filling or stroking the shape. Color color; + /// Gradient properties. Populated when using a gradient color source. + std::optional gradient; + /// The center point of the shape in local coordinates. Point center; diff --git a/engine/src/flutter/impeller/entity/shaders/gradients/linear_gradient_fill.frag b/engine/src/flutter/impeller/entity/shaders/gradients/linear_gradient_fill.frag index 4c12983c34a1a..a62b77acf6f62 100644 --- a/engine/src/flutter/impeller/entity/shaders/gradients/linear_gradient_fill.frag +++ b/engine/src/flutter/impeller/entity/shaders/gradients/linear_gradient_fill.frag @@ -5,6 +5,7 @@ precision mediump float; #include +#include #include #include @@ -25,14 +26,8 @@ highp in vec2 v_position; out vec4 frag_color; void main() { - vec2 start_to_end = frag_info.end_point - frag_info.start_point; - vec2 start_to_position = v_position - frag_info.start_point; - float t = - dot(start_to_position, start_to_end) / dot(start_to_end, start_to_end); - frag_color = IPSampleLinearWithTileMode(texture_sampler, // - vec2(t, 0.5), // - frag_info.half_texel, // - frag_info.tile_mode, // - frag_info.decal_border_color); + frag_color = IPSampleLinearGradient( + texture_sampler, frag_info.start_point, frag_info.end_point, v_position, + frag_info.half_texel, frag_info.tile_mode, frag_info.decal_border_color); frag_color = IPPremultiply(frag_color) * frag_info.alpha; } diff --git a/engine/src/flutter/impeller/entity/shaders/gradients/radial_gradient_fill.frag b/engine/src/flutter/impeller/entity/shaders/gradients/radial_gradient_fill.frag index 866ebfaeeed26..596acfab6d42d 100644 --- a/engine/src/flutter/impeller/entity/shaders/gradients/radial_gradient_fill.frag +++ b/engine/src/flutter/impeller/entity/shaders/gradients/radial_gradient_fill.frag @@ -5,6 +5,7 @@ precision mediump float; #include +#include #include #include @@ -25,12 +26,8 @@ highp in vec2 v_position; out vec4 frag_color; void main() { - float len = length(v_position - frag_info.center); - float t = len / frag_info.radius; - frag_color = IPSampleLinearWithTileMode(texture_sampler, // - vec2(t, 0.5), // - frag_info.half_texel, // - frag_info.tile_mode, // - frag_info.decal_border_color); + frag_color = IPSampleRadialGradient( + texture_sampler, frag_info.center, frag_info.radius, v_position, + frag_info.half_texel, frag_info.tile_mode, frag_info.decal_border_color); frag_color = IPPremultiply(frag_color) * frag_info.alpha; } diff --git a/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag b/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag index 97a5f5b715928..0ff413f14cc55 100644 --- a/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag +++ b/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag @@ -5,11 +5,15 @@ precision mediump float; #include +#include +#include #include #include "sdf_functions.glsl" #include "sdf_utils.glsl" +uniform sampler2D color_source_sampler; + uniform FragInfo { vec4 color; vec2 center; @@ -27,6 +31,11 @@ uniform FragInfo { vec2 circle_center_right; vec2 superellipse_scale; vec4 radii; + vec2 gradient_start; + vec2 gradient_end; + vec2 half_texel; + float tile_mode; + float color_source_type; } frag_info; @@ -34,6 +43,29 @@ out vec4 frag_color; highp in vec2 v_position; +// Gets the color to use at v_position based on frag_info properties. +vec4 getColor() { + vec4 color; + if (frag_info.color_source_type < 0.5) { + // Solid color + color = frag_info.color; + } else if (frag_info.color_source_type < 1.5) { + // Linear gradient + vec4 gradient_color = IPSampleLinearGradient( + color_source_sampler, frag_info.gradient_start, frag_info.gradient_end, + v_position, frag_info.half_texel, frag_info.tile_mode, vec4(0.0)); + color = vec4(gradient_color.rgb, gradient_color.a * frag_info.color.a); + } else { + // Radial gradient + vec4 gradient_color = IPSampleRadialGradient( + color_source_sampler, frag_info.gradient_start, + frag_info.gradient_end.x, v_position, frag_info.half_texel, + frag_info.tile_mode, vec4(0.0)); + color = vec4(gradient_color.rgb, gradient_color.a * frag_info.color.a); + } + return color; +} + float distanceFromCircle(vec2 p, float radius) { return length(p) - radius; } @@ -276,8 +308,9 @@ float gammaCorrectedAlpha(float alpha, vec3 foreground_rgb) { } void main() { - vec2 p = v_position - frag_info.center; + vec4 color = getColor(); + vec2 p = v_position - frag_info.center; vec2 sdf_and_pixel_size = (frag_info.stroked < 0.5) ? filledSDF(p) : strokedSDF(p); float sdf = sdf_and_pixel_size.x; @@ -287,8 +320,8 @@ void main() { // Clamp alpha in case floating point precision errors cause it to be outside // [0.0, 1.0]. alpha = clamp(alpha, 0.0, 1.0); - alpha = gammaCorrectedAlpha(alpha, frag_info.color.rgb); + alpha = gammaCorrectedAlpha(alpha, color.rgb); - frag_color = vec4(frag_info.color.rgb, frag_info.color.a * alpha); + frag_color = vec4(color.rgb, color.a * alpha); frag_color = IPPremultiply(frag_color); } From 704e3985e7b2d5df7ee92a129f7a0f0b1d88632a Mon Sep 17 00:00:00 2001 From: Benson Luk <97480502+b-luk@users.noreply.github.com> Date: Mon, 10 Aug 2026 13:31:49 -0700 Subject: [PATCH 02/15] gemini comments --- .../impeller/compiler/shader_lib/impeller/gradient.glsl | 9 ++++++--- .../impeller/entity/contents/uber_sdf_contents.cc | 5 ++++- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/engine/src/flutter/impeller/compiler/shader_lib/impeller/gradient.glsl b/engine/src/flutter/impeller/compiler/shader_lib/impeller/gradient.glsl index 193dd6548feb0..f47bd9be4ed58 100644 --- a/engine/src/flutter/impeller/compiler/shader_lib/impeller/gradient.glsl +++ b/engine/src/flutter/impeller/compiler/shader_lib/impeller/gradient.glsl @@ -159,8 +159,11 @@ vec4 IPSampleLinearGradient(sampler2D tex, vec4 decal_border_color) { vec2 start_to_end = end_point - start_point; vec2 start_to_position = pos - start_point; - float t = - dot(start_to_position, start_to_end) / dot(start_to_end, start_to_end); + float start_to_end_squared = dot(start_to_end, start_to_end); + + float t = start_to_end_squared == 0.0 + ? 0.0 + : dot(start_to_position, start_to_end) / start_to_end_squared; return IPSampleLinearWithTileMode(tex, vec2(t, 0.5), half_texel, tile_mode, decal_border_color); } @@ -173,7 +176,7 @@ vec4 IPSampleRadialGradient(sampler2D tex, vec2 half_texel, float tile_mode, vec4 decal_border_color) { - float t = length(pos - center) / radius; + float t = radius == 0.0 ? 0.0 : length(pos - center) / radius; return IPSampleLinearWithTileMode(tex, vec2(t, 0.5), half_texel, tile_mode, decal_border_color); } diff --git a/engine/src/flutter/impeller/entity/contents/uber_sdf_contents.cc b/engine/src/flutter/impeller/entity/contents/uber_sdf_contents.cc index 0d6a505c64525..88f801c2a5693 100644 --- a/engine/src/flutter/impeller/entity/contents/uber_sdf_contents.cc +++ b/engine/src/flutter/impeller/entity/contents/uber_sdf_contents.cc @@ -74,7 +74,10 @@ SamplerBinding SetupGradientParameters( frag_info.gradient_start = gradient.start; frag_info.gradient_end = gradient.end; frag_info.tile_mode = static_cast(gradient.tile_mode); - frag_info.half_texel = Point(0.5f, 0.5f) / gradient.texture->GetSize(); + auto texture_size = gradient.texture->GetSize(); + FML_DCHECK(!texture_size.IsEmpty()); + frag_info.half_texel = + Point(0.5f, 0.5f) / Point(texture_size.width, texture_size.height); SamplerDescriptor sampler_desc; sampler_desc.min_filter = MinMagFilter::kLinear; From c4f1565b41e916271c77724d0915645c26526873 Mon Sep 17 00:00:00 2001 From: Benson Luk <97480502+b-luk@users.noreply.github.com> Date: Tue, 11 Aug 2026 13:40:49 -0700 Subject: [PATCH 03/15] malioc --- engine/src/flutter/impeller/tools/malioc.json | 164 +++++++++--------- 1 file changed, 83 insertions(+), 81 deletions(-) diff --git a/engine/src/flutter/impeller/tools/malioc.json b/engine/src/flutter/impeller/tools/malioc.json index d93c1e6ad6f2d..2539a10e56cdc 100644 --- a/engine/src/flutter/impeller/tools/malioc.json +++ b/engine/src/flutter/impeller/tools/malioc.json @@ -5067,7 +5067,7 @@ "uses_late_zs_update": false, "variants": { "Main": { - "fp16_arithmetic": 0, + "fp16_arithmetic": 13, "has_stack_spilling": false, "performance": { "longest_path_bound_pipelines": [ @@ -5075,9 +5075,9 @@ "arith_cvt" ], "longest_path_cycles": [ - 0.328125, - 0.28125, - 0.328125, + 0.375, + 0.265625, + 0.375, 0.125, 0.0, 0.25, @@ -5096,9 +5096,9 @@ "varying" ], "shortest_path_cycles": [ - 0.203125, - 0.1875, - 0.203125, + 0.21875, + 0.171875, + 0.21875, 0.125, 0.0, 0.25, @@ -5109,9 +5109,9 @@ "arith_cvt" ], "total_cycles": [ - 0.453125, - 0.3125, - 0.453125, + 0.46875, + 0.296875, + 0.46875, 0.125, 0.0, 0.25, @@ -5120,7 +5120,7 @@ }, "stack_spill_bytes": 0, "thread_occupancy": 100, - "uniform_registers_used": 18, + "uniform_registers_used": 16, "work_registers_used": 20 } } @@ -5138,7 +5138,7 @@ "arithmetic" ], "longest_path_cycles": [ - 6.599999904632568, + 6.929999828338623, 1.0, 1.0 ], @@ -5151,21 +5151,21 @@ "arithmetic" ], "shortest_path_cycles": [ - 2.309999942779541, - 1.0, + 1.649999976158142, + 0.0, 0.0 ], "total_bound_pipelines": [ "arithmetic" ], "total_cycles": [ - 7.333333492279053, + 7.666666507720947, 1.0, 1.0 ] }, "thread_occupancy": 100, - "uniform_registers_used": 4, + "uniform_registers_used": 3, "work_registers_used": 2 } } @@ -5809,7 +5809,7 @@ "uses_late_zs_update": false, "variants": { "Main": { - "fp16_arithmetic": 0, + "fp16_arithmetic": 3, "has_stack_spilling": false, "performance": { "longest_path_bound_pipelines": [ @@ -5817,9 +5817,9 @@ "arith_cvt" ], "longest_path_cycles": [ - 0.359375, + 0.375, 0.296875, - 0.359375, + 0.375, 0.1875, 0.0, 0.25, @@ -5835,15 +5835,16 @@ "texture" ], "shortest_path_bound_pipelines": [ - "varying" + "arith_total", + "arith_cvt" ], "shortest_path_cycles": [ 0.234375, - 0.203125, + 0.109375, 0.234375, - 0.1875, + 0.125, + 0.0, 0.0, - 0.25, 0.0 ], "total_bound_pipelines": [ @@ -5851,9 +5852,9 @@ "arith_cvt" ], "total_cycles": [ - 0.484375, + 0.5, 0.328125, - 0.484375, + 0.5, 0.1875, 0.0, 0.25, @@ -5862,7 +5863,7 @@ }, "stack_spill_bytes": 0, "thread_occupancy": 100, - "uniform_registers_used": 16, + "uniform_registers_used": 14, "work_registers_used": 20 } } @@ -5880,7 +5881,7 @@ "arithmetic" ], "longest_path_cycles": [ - 6.599999904632568, + 7.260000228881836, 1.0, 1.0 ], @@ -5893,15 +5894,15 @@ "arithmetic" ], "shortest_path_cycles": [ - 2.309999942779541, - 1.0, + 1.649999976158142, + 0.0, 0.0 ], "total_bound_pipelines": [ "arithmetic" ], "total_cycles": [ - 7.333333492279053, + 8.0, 1.0, 1.0 ] @@ -8597,7 +8598,7 @@ "uses_late_zs_update": false, "variants": { "Main": { - "fp16_arithmetic": 32, + "fp16_arithmetic": 31, "has_stack_spilling": false, "performance": { "longest_path_bound_pipelines": [ @@ -8605,13 +8606,13 @@ "arith_sfu" ], "longest_path_cycles": [ - 4.0, - 3.875, - 1.65625, - 4.0, + 4.1875, + 4.125, + 2.125, + 4.1875, 0.0, 0.25, - 0.0 + 0.25 ], "pipelines": [ "arith_total", @@ -8627,9 +8628,9 @@ "arith_fma" ], "shortest_path_cycles": [ - 0.53125, - 0.53125, - 0.15625, + 0.578125, + 0.578125, + 0.234375, 0.5, 0.0, 0.25, @@ -8640,18 +8641,18 @@ "arith_fma" ], "total_cycles": [ - 13.625, - 13.625, - 4.53125, - 13.4375, + 14.0625, + 14.0625, + 5.574999809265137, + 13.75, 0.0, 0.25, - 0.0 + 0.5 ] }, "stack_spill_bytes": 0, "thread_occupancy": 100, - "uniform_registers_used": 46, + "uniform_registers_used": 62, "work_registers_used": 32 } } @@ -8669,9 +8670,9 @@ "arithmetic" ], "longest_path_cycles": [ - 43.88999938964844, - 3.0, - 4.0 + 52.13999938964844, + 6.0, + 5.0 ], "pipelines": [ "arithmetic", @@ -8682,21 +8683,21 @@ "arithmetic" ], "shortest_path_cycles": [ - 6.269999980926514, - 1.0, + 6.929999828338623, + 3.0, 2.0 ], "total_bound_pipelines": [ "arithmetic" ], "total_cycles": [ - 105.66666412353516, - 3.0, - 24.0 + 122.33333587646484, + 7.0, + 26.0 ] }, "thread_occupancy": 100, - "uniform_registers_used": 7, + "uniform_registers_used": 9, "work_registers_used": 4 } } @@ -9361,17 +9362,17 @@ "uses_late_zs_update": false, "variants": { "Main": { - "fp16_arithmetic": 28, + "fp16_arithmetic": 35, "has_stack_spilling": false, "performance": { "longest_path_bound_pipelines": [ - "varying", - "texture" + "arith_total", + "arith_cvt" ], "longest_path_cycles": [ - 0.234375, - 0.234375, - 0.234375, + 0.265625, + 0.21875, + 0.265625, 0.0625, 0.0, 0.25, @@ -9390,9 +9391,9 @@ "varying" ], "shortest_path_cycles": [ - 0.171875, - 0.140625, - 0.171875, + 0.203125, + 0.125, + 0.203125, 0.0625, 0.0, 0.25, @@ -9403,9 +9404,9 @@ "arith_cvt" ], "total_cycles": [ - 0.328125, - 0.265625, - 0.328125, + 0.359375, + 0.25, + 0.359375, 0.0625, 0.0, 0.25, @@ -9901,7 +9902,7 @@ "uses_late_zs_update": false, "variants": { "Main": { - "fp16_arithmetic": 16, + "fp16_arithmetic": 21, "has_stack_spilling": false, "performance": { "longest_path_bound_pipelines": [ @@ -11823,7 +11824,7 @@ "uses_late_zs_update": false, "variants": { "Main": { - "fp16_arithmetic": 35, + "fp16_arithmetic": 34, "has_stack_spilling": false, "performance": { "longest_path_bound_pipelines": [ @@ -11831,13 +11832,13 @@ "arith_sfu" ], "longest_path_cycles": [ - 4.0, - 3.387500047683716, - 1.9375, - 4.0, + 4.125, + 3.637500047683716, + 2.299999952316284, + 4.125, 0.0, 0.25, - 0.0 + 0.25 ], "pipelines": [ "arith_total", @@ -11850,12 +11851,13 @@ ], "shortest_path_bound_pipelines": [ "arith_total", + "arith_fma", "arith_sfu" ], "shortest_path_cycles": [ 0.5, - 0.453125, - 0.171875, + 0.5, + 0.25, 0.5, 0.0, 0.25, @@ -11866,18 +11868,18 @@ "arith_sfu" ], "total_cycles": [ - 13.4375, - 12.4375, - 4.875, - 13.4375, + 13.625, + 12.875, + 5.65625, + 13.625, 0.0, 0.25, - 0.0 + 0.5 ] }, "stack_spill_bytes": 0, "thread_occupancy": 100, - "uniform_registers_used": 48, + "uniform_registers_used": 66, "work_registers_used": 32 } } From 1096e810653d90b8d0bc557e89ec5209debfa41f Mon Sep 17 00:00:00 2001 From: Benson Luk <97480502+b-luk@users.noreply.github.com> Date: Tue, 11 Aug 2026 13:41:12 -0700 Subject: [PATCH 04/15] Fix incorrect ending coord check in IPSampleLinearWithTileMode for decal mode --- .../impeller/compiler/shader_lib/impeller/texture.glsl | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/engine/src/flutter/impeller/compiler/shader_lib/impeller/texture.glsl b/engine/src/flutter/impeller/compiler/shader_lib/impeller/texture.glsl index bab5a65e45ba2..6e4ef63d03401 100644 --- a/engine/src/flutter/impeller/compiler/shader_lib/impeller/texture.glsl +++ b/engine/src/flutter/impeller/compiler/shader_lib/impeller/texture.glsl @@ -98,8 +98,12 @@ vec4 IPSampleLinearWithTileMode(sampler2D tex, float x_tile_mode, float y_tile_mode, vec4 decal_border_color) { - if (x_tile_mode == kTileModeDecal && (coords.x < 0 || coords.x >= 1) || - y_tile_mode == kTileModeDecal && (coords.y < 0 || coords.y >= 1)) { + // Unlike 2D image sampling which uses a half-open interval [0, 1) where 1.0 + // is outside the texture boundary, linear sampling uses a closed interval + // [0, 1]. Therefore, decal mode checks the ending coordinate using > 1 rather + // than >= 1. + if (x_tile_mode == kTileModeDecal && (coords.x < 0.0 || coords.x > 1.0) || + y_tile_mode == kTileModeDecal && (coords.y < 0.0 || coords.y > 1.0)) { return decal_border_color; } From 8bb2c2a62c54b49370d43ff040b90262a52e9069 Mon Sep 17 00:00:00 2001 From: Benson Luk <97480502+b-luk@users.noreply.github.com> Date: Tue, 11 Aug 2026 13:42:12 -0700 Subject: [PATCH 05/15] UberSDFContents::ApplyColorFilter does not work with gradients --- .../entity/contents/uber_sdf_contents.cc | 3 +++ .../contents/uber_sdf_contents_unittests.cc | 23 +++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/engine/src/flutter/impeller/entity/contents/uber_sdf_contents.cc b/engine/src/flutter/impeller/entity/contents/uber_sdf_contents.cc index 88f801c2a5693..407729fbe8b7a 100644 --- a/engine/src/flutter/impeller/entity/contents/uber_sdf_contents.cc +++ b/engine/src/flutter/impeller/entity/contents/uber_sdf_contents.cc @@ -182,6 +182,9 @@ Color UberSDFContents::GetColor() const { bool UberSDFContents::ApplyColorFilter( const ColorFilterProc& color_filter_proc) { + if (params_.gradient.has_value()) { + return false; + } params_.color = color_filter_proc(params_.color); return true; } diff --git a/engine/src/flutter/impeller/entity/contents/uber_sdf_contents_unittests.cc b/engine/src/flutter/impeller/entity/contents/uber_sdf_contents_unittests.cc index e862b42d8df34..b1a057f0a277a 100644 --- a/engine/src/flutter/impeller/entity/contents/uber_sdf_contents_unittests.cc +++ b/engine/src/flutter/impeller/entity/contents/uber_sdf_contents_unittests.cc @@ -29,6 +29,29 @@ TEST(UberSDFContentsTest, ApplyColorFilter) { EXPECT_EQ(contents->GetColor(), Color::Blue()); } +TEST(UberSDFContentsTest, ApplyColorFilterWithGradient) { + auto rect = Rect::MakeXYWH(100, 100, 200, 200); + auto params = + UberSDFParameters::MakeRect(Color::Red(), rect, /*stroke=*/std::nullopt); + UberSDFParameters::GradientParameters gradient; + gradient.type = UberSDFParameters::GradientParameters::Type::kLinear; + gradient.start = Point(0, 0); + gradient.end = Point(200, 200); + params.gradient = gradient; + + auto geometry = std::make_unique(params); + auto contents = UberSDFContents::Make(params, std::move(geometry)); + + // Color filters cannot be applied on CPU to pre-baked gradients in UberSDFContents, + // so ApplyColorFilter must return false to trigger GPU filter wrapping. + bool result = + contents->ApplyColorFilter([](Color color) { return Color::Blue(); }); + + EXPECT_FALSE(result); + // The color should remain unchanged. + EXPECT_EQ(contents->GetColor(), Color::Red()); +} + TEST(UberSDFContentsTest, AsBackgroundColor) { auto rect = Rect::MakeXYWH(-2, -2, 504, 504); auto params = From 69cda7b8e2cf551dbb0cf1eed17c446996ea8299 Mon Sep 17 00:00:00 2001 From: Benson Luk <97480502+b-luk@users.noreply.github.com> Date: Tue, 11 Aug 2026 13:43:27 -0700 Subject: [PATCH 06/15] Make CreateGradientBuffer compatible with overlapping color stops --- .../impeller/geometry/geometry_unittests.cc | 19 ++++++++-- .../src/flutter/impeller/geometry/gradient.cc | 36 +++++++++---------- 2 files changed, 35 insertions(+), 20 deletions(-) diff --git a/engine/src/flutter/impeller/geometry/geometry_unittests.cc b/engine/src/flutter/impeller/geometry/geometry_unittests.cc index 0e85fb6c6fc20..d805a595f6207 100644 --- a/engine/src/flutter/impeller/geometry/geometry_unittests.cc +++ b/engine/src/flutter/impeller/geometry/geometry_unittests.cc @@ -1834,13 +1834,28 @@ TEST(GeometryTest, Gradient) { } { - // Gradient with duplicate stops does not create an empty texture. + // Gradient with duplicate stops creates a 1024 texture. std::vector colors = {Color::Red(), Color::Yellow(), Color::Black(), Color::Blue()}; std::vector stops = {0.0, 0.25, 0.25, 1.0}; auto gradient = CreateGradientBuffer(colors, stops); - ASSERT_EQ(gradient.texture_size, 5u); + ASSERT_EQ(gradient.texture_size, 1024u); + + std::vector expected_colors(1024); + for (size_t i = 0; i < 1024; i++) { + double t = i / 1023.0; + if (i < 256) { + // Interval 1: texels 0 to 255 (stop interval 0 to 0.25) + expected_colors[i] = + Color::Lerp(Color::Red(), Color::Yellow(), t / 0.25); + } else { + // Interval 2: texels 256 to 1023 (stop interval 0.25 to 1.0) + expected_colors[i] = + Color::Lerp(Color::Black(), Color::Blue(), (t - 0.25) / 0.75); + } + } + ASSERT_COLOR_BUFFER_NEAR(gradient.color_bytes, expected_colors); } { diff --git a/engine/src/flutter/impeller/geometry/gradient.cc b/engine/src/flutter/impeller/geometry/gradient.cc index 8bdfbe2cb2507..13967ebfd03d2 100644 --- a/engine/src/flutter/impeller/geometry/gradient.cc +++ b/engine/src/flutter/impeller/geometry/gradient.cc @@ -9,6 +9,9 @@ namespace impeller { +// TODO(b-luk): this should use a platform specific max texture size. +static constexpr uint32_t kMaxGradientTextureSize = 1024; + static void AppendColor(const Color& color, GradientData* data) { auto converted = color.ToR8G8B8A8(); data->color_bytes.push_back(converted[0]); @@ -26,22 +29,21 @@ GradientData CreateGradientBuffer(const std::vector& colors, texture_size = colors.size(); } else { auto minimum_delta = 1.0; + // Avoid creating textures that are absurdly large due to stops that are + // very close together. + auto minimum_allowable_delta = 1.0 / (kMaxGradientTextureSize - 1.0); for (size_t i = 1; i < stops.size(); i++) { auto value = stops[i] - stops[i - 1]; - // Smaller than kEhCloseEnough - if (value < 0.0001) { - continue; + if (value <= minimum_allowable_delta) { + minimum_delta = minimum_allowable_delta; + break; } if (value < minimum_delta) { minimum_delta = value; } } - // Avoid creating textures that are absurdly large due to stops that are - // very close together. - // TODO(jonahwilliams): this should use a platform specific max texture - // size. - texture_size = std::min( - static_cast(std::round(1.0 / minimum_delta)) + 1, 1024u); + texture_size = + static_cast(std::round(1.0 / minimum_delta)) + 1; } GradientData data = { .color_bytes = {}, @@ -49,7 +51,8 @@ GradientData CreateGradientBuffer(const std::vector& colors, }; data.color_bytes.reserve(texture_size * 4); - if (texture_size == colors.size() && colors.size() <= 1024) { + if (texture_size == colors.size() && + colors.size() <= kMaxGradientTextureSize) { for (auto i = 0u; i < colors.size(); i++) { AppendColor(colors[i], &data); } @@ -79,17 +82,14 @@ GradientData CreateGradientBuffer(const std::vector& colors, AppendColor(mixed_color, &data); } else { - // We've slightly overshot the previous stop. + // We've passed the next stop. Advance to the next stop interval. + // Decrement `i` to re-evaluate the current texel against the new + // interval. When there are overlapping or zero-delta stops, this branch + // evaluates repeatedly until the overlap is passed. previous_color = next_color; previous_stop = next_stop; previous_color_index += 1; - next_color = colors[previous_color_index + 1]; - auto next_stop = stops[previous_color_index + 1]; - - auto t = (scaled_i - previous_stop) / (next_stop - previous_stop); - auto mixed_color = Color::Lerp(previous_color, next_color, t); - - AppendColor(mixed_color, &data); + i--; } } // The last index is always equal to the last color, exactly. From 5b5fcdc07b0fe9585be8b525e5ecfab1530bef69 Mon Sep 17 00:00:00 2001 From: Benson Luk <97480502+b-luk@users.noreply.github.com> Date: Tue, 11 Aug 2026 13:44:21 -0700 Subject: [PATCH 07/15] Make PopulateUberSDFColorSource account for matrix transformations --- .../flutter/impeller/display_list/canvas.cc | 37 ++++++++++++++++--- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/engine/src/flutter/impeller/display_list/canvas.cc b/engine/src/flutter/impeller/display_list/canvas.cc index aa1a1ff12df08..8e77d72cd826c 100644 --- a/engine/src/flutter/impeller/display_list/canvas.cc +++ b/engine/src/flutter/impeller/display_list/canvas.cc @@ -231,8 +231,9 @@ static std::pair ExpandRectToPixelMinimum(const Rect& rect, /// /// @return Whether the color source was successfully populated into `params`. static bool PopulateUberSDFColorSource( - const flutter::DlColorSource& color_source, const ContentContext& renderer, + const flutter::DlColorSource& color_source, + const std::optional& shape_transform, UberSDFParameters& params) { if (color_source.isGradient()) { UberSDFParameters::GradientParameters gradient; @@ -240,23 +241,46 @@ static bool PopulateUberSDFColorSource( std::vector colors; std::vector stops; + // When a shape transform is applied to transform the SDF shape from local + // space into canvas space, the gradient parameters must be mapped from + // canvas space into local space via the inverse shape transform. + Matrix inverted_shape_transform = + shape_transform.has_value() ? shape_transform->Invert() : Matrix(); + if (color_source.type() == flutter::DlColorSourceType::kLinearGradient) { const auto* linear = color_source.asLinearGradient(); FML_DCHECK(linear); + Matrix gradient_transform = inverted_shape_transform * linear->matrix(); + if (!gradient_transform.IsAffine()) { + // Non-affine matrix transformation not supported by UberSDF. + return false; + } Paint::ConvertStops(linear, colors, stops); gradient.type = UberSDFParameters::GradientParameters::Type::kLinear; - gradient.start = linear->start_point(); - gradient.end = linear->end_point(); + gradient.start = gradient_transform * linear->start_point(); + gradient.end = gradient_transform * linear->end_point(); gradient.tile_mode = static_cast(linear->tile_mode()); } else if (color_source.type() == flutter::DlColorSourceType::kRadialGradient) { const auto* radial = color_source.asRadialGradient(); FML_DCHECK(radial); + Matrix gradient_transform = inverted_shape_transform * radial->matrix(); + if (!gradient_transform.IsAffine()) { + // Non-affine matrix transformation not supported by UberSDF. + return false; + } + auto scales = gradient_transform.GetScales2D(); + if (!scales.has_value() || + !ScalarNearlyEqual(scales->first, scales->second)) { + // Non-uniform scaling on a radial gradient creates an ellipse, which is + // not supported by UberSDF. + return false; + } Paint::ConvertStops(radial, colors, stops); gradient.type = UberSDFParameters::GradientParameters::Type::kRadial; - gradient.start = radial->center(); + gradient.start = gradient_transform * radial->center(); // For radial gradients, gradient.end.x stores the radius. - gradient.end = Point(radial->radius(), 0.0f); + gradient.end = Point(radial->radius() * scales->first, 0.0f); gradient.tile_mode = static_cast(radial->tile_mode()); } else { // Gradient type not supported by UberSDF. @@ -2236,7 +2260,8 @@ void Canvas::AddRenderSDFEntityToCurrentPass( entity.SetBlendMode(paint.blend_mode); if (!paint.color_source || - PopulateUberSDFColorSource(*paint.color_source, renderer_, params)) { + PopulateUberSDFColorSource(renderer_, *paint.color_source, + shape_transform, params)) { // No color source (solid paint color), or a supported color source was // populated into UberSDFParams. auto geometry = std::make_unique(params); From ab93ec4cb08cc973a6c048f505fc133cf6965249 Mon Sep 17 00:00:00 2001 From: Benson Luk <97480502+b-luk@users.noreply.github.com> Date: Tue, 11 Aug 2026 13:45:25 -0700 Subject: [PATCH 08/15] et format --- .../impeller/entity/contents/uber_sdf_contents_unittests.cc | 5 +++-- engine/src/flutter/impeller/geometry/gradient.cc | 3 +-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/engine/src/flutter/impeller/entity/contents/uber_sdf_contents_unittests.cc b/engine/src/flutter/impeller/entity/contents/uber_sdf_contents_unittests.cc index b1a057f0a277a..fd82167da8e88 100644 --- a/engine/src/flutter/impeller/entity/contents/uber_sdf_contents_unittests.cc +++ b/engine/src/flutter/impeller/entity/contents/uber_sdf_contents_unittests.cc @@ -42,8 +42,9 @@ TEST(UberSDFContentsTest, ApplyColorFilterWithGradient) { auto geometry = std::make_unique(params); auto contents = UberSDFContents::Make(params, std::move(geometry)); - // Color filters cannot be applied on CPU to pre-baked gradients in UberSDFContents, - // so ApplyColorFilter must return false to trigger GPU filter wrapping. + // Color filters cannot be applied on CPU to pre-baked gradients in + // UberSDFContents, so ApplyColorFilter must return false to trigger GPU + // filter wrapping. bool result = contents->ApplyColorFilter([](Color color) { return Color::Blue(); }); diff --git a/engine/src/flutter/impeller/geometry/gradient.cc b/engine/src/flutter/impeller/geometry/gradient.cc index 13967ebfd03d2..3ed83d6cb5a6a 100644 --- a/engine/src/flutter/impeller/geometry/gradient.cc +++ b/engine/src/flutter/impeller/geometry/gradient.cc @@ -42,8 +42,7 @@ GradientData CreateGradientBuffer(const std::vector& colors, minimum_delta = value; } } - texture_size = - static_cast(std::round(1.0 / minimum_delta)) + 1; + texture_size = static_cast(std::round(1.0 / minimum_delta)) + 1; } GradientData data = { .color_bytes = {}, From cef74280cb700eae0418f9d6a298a943380d3436 Mon Sep 17 00:00:00 2001 From: Benson Luk <97480502+b-luk@users.noreply.github.com> Date: Tue, 11 Aug 2026 14:58:06 -0700 Subject: [PATCH 09/15] malioc --- engine/src/flutter/impeller/tools/malioc.json | 93 ++++++++++--------- 1 file changed, 48 insertions(+), 45 deletions(-) diff --git a/engine/src/flutter/impeller/tools/malioc.json b/engine/src/flutter/impeller/tools/malioc.json index 2539a10e56cdc..a16d8ac41a980 100644 --- a/engine/src/flutter/impeller/tools/malioc.json +++ b/engine/src/flutter/impeller/tools/malioc.json @@ -744,7 +744,7 @@ ], "longest_path_cycles": [ 0.675000011920929, - 0.46875, + 0.484375, 0.675000011920929, 0.1875, 0.0, @@ -778,9 +778,9 @@ "arith_cvt" ], "total_cycles": [ - 0.862500011920929, + 0.84375, 0.59375, - 0.862500011920929, + 0.84375, 0.3125, 0.0, 0.25, @@ -816,8 +816,8 @@ "arith_fma" ], "longest_path_cycles": [ - 0.328125, - 0.328125, + 0.34375, + 0.34375, 0.296875, 0.125, 0.0, @@ -834,12 +834,14 @@ "texture" ], "shortest_path_bound_pipelines": [ + "arith_total", + "arith_fma", "varying" ], "shortest_path_cycles": [ - 0.234375, - 0.234375, - 0.203125, + 0.25, + 0.25, + 0.1875, 0.125, 0.0, 0.25, @@ -850,9 +852,9 @@ "arith_cvt" ], "total_cycles": [ - 0.421875, + 0.40625, 0.359375, - 0.421875, + 0.40625, 0.125, 0.0, 0.25, @@ -889,7 +891,7 @@ ], "longest_path_cycles": [ 0.421875, - 0.328125, + 0.34375, 0.421875, 0.125, 0.0, @@ -922,9 +924,9 @@ "arith_cvt" ], "total_cycles": [ - 0.546875, + 0.53125, 0.359375, - 0.546875, + 0.53125, 0.125, 0.0, 0.25, @@ -9367,12 +9369,14 @@ "performance": { "longest_path_bound_pipelines": [ "arith_total", - "arith_cvt" + "arith_cvt", + "varying", + "texture" ], "longest_path_cycles": [ - 0.265625, - 0.21875, - 0.265625, + 0.25, + 0.234375, + 0.25, 0.0625, 0.0, 0.25, @@ -9391,9 +9395,9 @@ "varying" ], "shortest_path_cycles": [ - 0.203125, - 0.125, - 0.203125, + 0.1875, + 0.140625, + 0.1875, 0.0625, 0.0, 0.25, @@ -9404,9 +9408,9 @@ "arith_cvt" ], "total_cycles": [ - 0.359375, - 0.25, - 0.359375, + 0.34375, + 0.265625, + 0.34375, 0.0625, 0.0, 0.25, @@ -9907,12 +9911,12 @@ "performance": { "longest_path_bound_pipelines": [ "arith_total", - "arith_cvt" + "arith_fma" ], "longest_path_cycles": [ 0.265625, - 0.25, 0.265625, + 0.25, 0.125, 0.0, 0.25, @@ -9931,9 +9935,9 @@ "varying" ], "shortest_path_cycles": [ - 0.203125, - 0.15625, - 0.203125, + 0.1875, + 0.171875, + 0.1875, 0.125, 0.0, 0.25, @@ -9944,9 +9948,9 @@ "arith_cvt" ], "total_cycles": [ - 0.359375, - 0.28125, - 0.359375, + 0.34375, + 0.296875, + 0.34375, 0.125, 0.0, 0.25, @@ -10885,9 +10889,9 @@ "arith_fma" ], "longest_path_cycles": [ - 0.40625, - 0.40625, - 0.296875, + 0.421875, + 0.421875, + 0.28125, 0.3125, 0.0, 0.25, @@ -10904,13 +10908,12 @@ ], "shortest_path_bound_pipelines": [ "arith_total", - "arith_fma", - "arith_sfu" + "arith_fma" ], "shortest_path_cycles": [ - 0.3125, - 0.3125, - 0.234375, + 0.328125, + 0.328125, + 0.21875, 0.3125, 0.0, 0.25, @@ -10921,9 +10924,9 @@ "arith_fma" ], "total_cycles": [ - 0.4375, - 0.4375, - 0.390625, + 0.453125, + 0.453125, + 0.375, 0.3125, 0.0, 0.25, @@ -11833,8 +11836,8 @@ ], "longest_path_cycles": [ 4.125, - 3.637500047683716, - 2.299999952316284, + 3.65625, + 2.28125, 4.125, 0.0, 0.25, @@ -11870,7 +11873,7 @@ "total_cycles": [ 13.625, 12.875, - 5.65625, + 5.625, 13.625, 0.0, 0.25, From 2770ec3486ee4dae308546b03b14ea708d4e1b78 Mon Sep 17 00:00:00 2001 From: Benson Luk <97480502+b-luk@users.noreply.github.com> Date: Wed, 12 Aug 2026 10:50:19 -0700 Subject: [PATCH 10/15] Reorder and document UberSDF FragInfo fields --- .../impeller/entity/shaders/uber_sdf.frag | 92 +++++++++++++++++-- 1 file changed, 83 insertions(+), 9 deletions(-) diff --git a/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag b/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag index 0ff413f14cc55..af1162ac30e45 100644 --- a/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag +++ b/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag @@ -15,27 +15,101 @@ precision mediump float; uniform sampler2D color_source_sampler; uniform FragInfo { + // FragInfo fields are sorted by size (vec4 -> vec2 -> float) to optimize + // uniform register usage. + + // =========================================================================== + // vec4 fields + // =========================================================================== + + /// The RGBA color of the shape (or paint opacity in color.a for gradients). vec4 color; + /// Corner radii for rounded rects (top-left, top-right, bottom-left, + /// bottom-right) or rounded superellipses (xy). + vec4 radii; + + // =========================================================================== + // vec2 fields + // =========================================================================== + + // --- General Shape Geometry --- + /// The center position of the shape in local coordinates. vec2 center; + /// The half-dimensions of the shape (half-width, half-height). vec2 size; - float stroke_width; - float stroke_join; - float aa_pixels; - float stroked; - float type; + + // --- Superellipse Parameters --- + /// The exponent degree (n_x, n_y) of the superellipse curvature. vec2 superellipse_degree; + /// The semi-axis lengths (a, b) of the superellipse. vec2 superellipse_semi_axis; + /// Normalization scale factor mapping the superellipse quadrant to a unit + /// square. + vec2 superellipse_scale; + /// The angular span of the corner circular arc transitions for rounded + /// superellipses. vec2 angle_span; - float octant_offset_c; + /// The center of the corner transition circle for the top octant of a + /// rounded superellipse. vec2 circle_center_top; + /// The center of the corner transition circle for the right octant of a + /// rounded superellipse. vec2 circle_center_right; - vec2 superellipse_scale; - vec4 radii; + + // --- Gradient Parameters --- + /// The starting point of a linear gradient, or the center point of a radial + /// gradient. vec2 gradient_start; + /// The ending point of a linear gradient, or (radius, 0.0) for a radial + /// gradient. vec2 gradient_end; + /// Half the size of a single gradient texel in normalized texture + /// coordinates. vec2 half_texel; - float tile_mode; + + // =========================================================================== + // float fields + // =========================================================================== + + // --- General Configuration --- + /// The shape type: + /// 0: Circle + /// 1: Rect + /// 2: Oval + /// 3: RoundRect + /// 4: Superellipse + float type; + /// The type of color source: + /// 0: Solid color + /// 1: Linear gradient + /// 2: Radial gradient float color_source_type; + /// The width in device pixels over which to apply antialiasing. + float aa_pixels; + + // --- Stroke Parameters --- + /// Whether the shape is stroked (1.0) or filled (0.0). + float stroked; + /// The width of the stroke. + float stroke_width; + /// The join style for the stroke: + /// 0: Miter + /// 1: Bevel + /// 2: Round + float stroke_join; + + // --- Superellipse Parameters --- + /// Transition line offset dividing top and right octants for rounded + /// superellipses. + float octant_offset_c; + + // --- Gradient Parameters --- + /// The tile mode for gradient sampling: + /// 0: Clamp + /// 1: Repeat + /// 2: Mirror + /// 3: Decal + float tile_mode; } frag_info; From 228eebd85491adc956c6964c86fc8e8399848318 Mon Sep 17 00:00:00 2001 From: Benson Luk <97480502+b-luk@users.noreply.github.com> Date: Wed, 12 Aug 2026 18:03:05 +0000 Subject: [PATCH 11/15] malioc --- engine/src/flutter/impeller/tools/malioc.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/engine/src/flutter/impeller/tools/malioc.json b/engine/src/flutter/impeller/tools/malioc.json index a16d8ac41a980..4f8480ed4321e 100644 --- a/engine/src/flutter/impeller/tools/malioc.json +++ b/engine/src/flutter/impeller/tools/malioc.json @@ -8672,7 +8672,7 @@ "arithmetic" ], "longest_path_cycles": [ - 52.13999938964844, + 51.47999954223633, 6.0, 5.0 ], @@ -8685,7 +8685,7 @@ "arithmetic" ], "shortest_path_cycles": [ - 6.929999828338623, + 6.599999904632568, 3.0, 2.0 ], @@ -8693,7 +8693,7 @@ "arithmetic" ], "total_cycles": [ - 122.33333587646484, + 121.0, 7.0, 26.0 ] From 7f4eae884d1f26a4c02601259ad3dc4bf31117d9 Mon Sep 17 00:00:00 2001 From: Benson Luk <97480502+b-luk@users.noreply.github.com> Date: Mon, 24 Aug 2026 17:50:39 +0000 Subject: [PATCH 12/15] malioc --- engine/src/flutter/impeller/tools/malioc.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/engine/src/flutter/impeller/tools/malioc.json b/engine/src/flutter/impeller/tools/malioc.json index fedd4d4c89985..46ecb48e49508 100644 --- a/engine/src/flutter/impeller/tools/malioc.json +++ b/engine/src/flutter/impeller/tools/malioc.json @@ -8654,7 +8654,7 @@ }, "stack_spill_bytes": 0, "thread_occupancy": 100, - "uniform_registers_used": 44, + "uniform_registers_used": 60, "work_registers_used": 32 } } @@ -8693,13 +8693,13 @@ "arithmetic" ], "total_cycles": [ - 121.0, + 121.33333587646484, 7.0, 26.0 ] }, "thread_occupancy": 100, - "uniform_registers_used": 5, + "uniform_registers_used": 9, "work_registers_used": 4 } } From e877b1b9aeb4d97d1f88ee4f9292de85495e9e2b Mon Sep 17 00:00:00 2001 From: Benson Luk <97480502+b-luk@users.noreply.github.com> Date: Mon, 24 Aug 2026 18:02:43 +0000 Subject: [PATCH 13/15] Remove octant_offset_c uniform from ubersdf --- .../impeller/entity/contents/uber_sdf_contents.cc | 1 - .../impeller/entity/contents/uber_sdf_parameters.cc | 1 - .../impeller/entity/contents/uber_sdf_parameters.h | 3 --- .../contents/uber_sdf_parameters_unittests.cc | 6 ------ .../flutter/impeller/entity/shaders/uber_sdf.frag | 13 +++++-------- engine/src/flutter/impeller/tools/malioc.json | 12 ++++++------ 6 files changed, 11 insertions(+), 25 deletions(-) diff --git a/engine/src/flutter/impeller/entity/contents/uber_sdf_contents.cc b/engine/src/flutter/impeller/entity/contents/uber_sdf_contents.cc index 20913fca7d722..be7b69fb962f5 100644 --- a/engine/src/flutter/impeller/entity/contents/uber_sdf_contents.cc +++ b/engine/src/flutter/impeller/entity/contents/uber_sdf_contents.cc @@ -123,7 +123,6 @@ bool UberSDFContents::Render(const ContentContext& renderer, frag_info.aa_pixels = UberSDFParameters::kAntialiasPixels; frag_info.superellipse_degree = params_.superellipse_degree; frag_info.angle_span = params_.angle_span; - frag_info.octant_offset_c = params_.octant_offset_c; frag_info.circle_center_top = params_.circle_center_top; frag_info.circle_center_right = params_.circle_center_right; frag_info.radii = params_.radii; diff --git a/engine/src/flutter/impeller/entity/contents/uber_sdf_parameters.cc b/engine/src/flutter/impeller/entity/contents/uber_sdf_parameters.cc index f349bb74fa277..6569c6c1cd8ab 100644 --- a/engine/src/flutter/impeller/entity/contents/uber_sdf_parameters.cc +++ b/engine/src/flutter/impeller/entity/contents/uber_sdf_parameters.cc @@ -105,7 +105,6 @@ std::optional UberSDFParameters::MakeRoundedSuperellipse( .superellipse_degree = Point(top_right.top.se_n, top_right.right.se_n), .angle_span = Point(top_right.top.circle_max_angle.radians, top_right.right.circle_max_angle.radians), - .octant_offset_c = top_right.top.se_a - top_right.right.se_a, .circle_center_top = top_right.top.circle_center, .circle_center_right = top_right.right.circle_center, .radii = Vector4(top_right.top.circle_radius, diff --git a/engine/src/flutter/impeller/entity/contents/uber_sdf_parameters.h b/engine/src/flutter/impeller/entity/contents/uber_sdf_parameters.h index d09ede41a7e02..16fa2bef01b0d 100644 --- a/engine/src/flutter/impeller/entity/contents/uber_sdf_parameters.h +++ b/engine/src/flutter/impeller/entity/contents/uber_sdf_parameters.h @@ -116,9 +116,6 @@ struct UberSDFParameters { /// The angular span of the circular cap for the top and right octants. Point angle_span; - /// The geometric offset 'c' used to connect the two octants of each quadrant. - float octant_offset_c; - /// The circular cap center for the top octant of each /// quadrant. Point circle_center_top; diff --git a/engine/src/flutter/impeller/entity/contents/uber_sdf_parameters_unittests.cc b/engine/src/flutter/impeller/entity/contents/uber_sdf_parameters_unittests.cc index 1e906a30be451..c470cf863a500 100644 --- a/engine/src/flutter/impeller/entity/contents/uber_sdf_parameters_unittests.cc +++ b/engine/src/flutter/impeller/entity/contents/uber_sdf_parameters_unittests.cc @@ -169,10 +169,6 @@ TEST(UberSDFParametersTest, MakeRoundedSuperellipse) { params.angle_span.y, round_superellipse_params.top_right.right.circle_max_angle.radians); - EXPECT_EQ(params.octant_offset_c, - round_superellipse_params.top_right.top.se_a - - round_superellipse_params.top_right.right.se_a); - EXPECT_EQ(params.circle_center_top, round_superellipse_params.top_right.top.circle_center); EXPECT_EQ(params.circle_center_right, @@ -204,8 +200,6 @@ TEST(UberSDFParametersTest, MakeRectangularRoundedSuperellipse) { EXPECT_EQ(params.center, Point(60, 120)); EXPECT_EQ(params.size, Point(50, 100)); EXPECT_FALSE(params.stroke.has_value()); - - EXPECT_EQ(params.octant_offset_c, -50.0f); } } diff --git a/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag b/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag index 38b59b10315c4..a67679dcadbb1 100644 --- a/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag +++ b/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag @@ -95,11 +95,6 @@ uniform FragInfo { /// 2: Round float stroke_join; - // --- Superellipse Parameters --- - /// Transition line offset dividing top and right octants for rounded - /// superellipses. - float octant_offset_c; - // --- Gradient Parameters --- /// The tile mode for gradient sampling: /// 0: Clamp @@ -181,11 +176,13 @@ float distanceFromRoundedSuperellipse(vec2 p, vec2 radii, vec2 angle_span, vec2 circle_center_top, - vec2 circle_center_right, - float c) { + vec2 circle_center_right) { // Do work in the first quadrant to simply things. p = abs(p); + // Transition line offset dividing top and right octants. + float c = size.x - size.y; + // Declare all RSE params for a single octant. float se_degree, span, radius, axis_length; vec2 circle_center; @@ -299,7 +296,7 @@ vec2 filledSDF(vec2 p) { sdf = distanceFromRoundedSuperellipse( p, frag_info.superellipse_degree, frag_info.size, frag_info.radii.xy, frag_info.angle_span, frag_info.circle_center_top, - frag_info.circle_center_right, frag_info.octant_offset_c); + frag_info.circle_center_right); pixel_size = pixelSize(sdf); } return vec2(sdf, pixel_size); diff --git a/engine/src/flutter/impeller/tools/malioc.json b/engine/src/flutter/impeller/tools/malioc.json index 46ecb48e49508..5674d6d3d3ac9 100644 --- a/engine/src/flutter/impeller/tools/malioc.json +++ b/engine/src/flutter/impeller/tools/malioc.json @@ -8645,7 +8645,7 @@ "total_cycles": [ 14.0625, 14.0625, - 5.574999809265137, + 5.550000190734863, 13.75, 0.0, 0.25, @@ -8654,7 +8654,7 @@ }, "stack_spill_bytes": 0, "thread_occupancy": 100, - "uniform_registers_used": 60, + "uniform_registers_used": 62, "work_registers_used": 32 } } @@ -8672,7 +8672,7 @@ "arithmetic" ], "longest_path_cycles": [ - 51.47999954223633, + 52.13999938964844, 6.0, 5.0 ], @@ -8693,7 +8693,7 @@ "arithmetic" ], "total_cycles": [ - 121.33333587646484, + 122.0, 7.0, 26.0 ] @@ -11873,7 +11873,7 @@ "total_cycles": [ 13.625, 12.875, - 5.625, + 5.59375, 13.625, 0.0, 0.25, @@ -11882,7 +11882,7 @@ }, "stack_spill_bytes": 0, "thread_occupancy": 100, - "uniform_registers_used": 66, + "uniform_registers_used": 64, "work_registers_used": 32 } } From a0363dcdd3200e0f5cb1c0b1a558654250ae4cd9 Mon Sep 17 00:00:00 2001 From: Benson Luk <97480502+b-luk@users.noreply.github.com> Date: Tue, 25 Aug 2026 11:26:09 -0700 Subject: [PATCH 14/15] Don't use auto for non-obvious types, and use a const reference to address clang-tidy warning --- .../contents/uber_sdf_parameters_unittests.cc | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/engine/src/flutter/impeller/entity/contents/uber_sdf_parameters_unittests.cc b/engine/src/flutter/impeller/entity/contents/uber_sdf_parameters_unittests.cc index c470cf863a500..69223cfff4fd3 100644 --- a/engine/src/flutter/impeller/entity/contents/uber_sdf_parameters_unittests.cc +++ b/engine/src/flutter/impeller/entity/contents/uber_sdf_parameters_unittests.cc @@ -137,14 +137,15 @@ TEST(UberSDFParametersTest, MakeRoundedSuperellipse) { auto round_superellipse = RoundSuperellipse::MakeRectRadii(rect, radii); auto round_superellipse_params = RoundSuperellipseParam::MakeBoundsRadii(rect, radii); - auto maybe_params = UberSDFParameters::MakeRoundedSuperellipse( - /*color=*/Color::Red(), /*round_superellipse=*/round_superellipse, - /*stroke=*/std::nullopt); + std::optional maybe_params = + UberSDFParameters::MakeRoundedSuperellipse( + /*color=*/Color::Red(), /*round_superellipse=*/round_superellipse, + /*stroke=*/std::nullopt); ASSERT_TRUE(maybe_params.has_value()); if (maybe_params.has_value()) { - auto params = maybe_params.value(); + const UberSDFParameters& params = maybe_params.value(); EXPECT_EQ(params.type, UberSDFParameters::Type::kRoundedSuperellipseSymmetric); @@ -185,14 +186,15 @@ TEST(UberSDFParametersTest, MakeRectangularRoundedSuperellipse) { .bottom_right = Size(10.0f, 10.0f), }; auto round_superellipse = RoundSuperellipse::MakeRectRadii(rect, radii); - auto maybe_params = UberSDFParameters::MakeRoundedSuperellipse( - /*color=*/Color::Red(), /*round_superellipse=*/round_superellipse, - /*stroke=*/std::nullopt); + std::optional maybe_params = + UberSDFParameters::MakeRoundedSuperellipse( + /*color=*/Color::Red(), /*round_superellipse=*/round_superellipse, + /*stroke=*/std::nullopt); ASSERT_TRUE(maybe_params.has_value()); if (maybe_params.has_value()) { - auto params = maybe_params.value(); + const UberSDFParameters& params = maybe_params.value(); EXPECT_EQ(params.type, UberSDFParameters::Type::kRoundedSuperellipseSymmetric); @@ -212,9 +214,10 @@ TEST(UberSDFParametersTest, MakeRoundedSuperellipseRejectsNonSymmetric) { .bottom_right = Size(10.0f, 10.0f), }; auto round_superellipse = RoundSuperellipse::MakeRectRadii(rect, radii); - auto params = UberSDFParameters::MakeRoundedSuperellipse( - /*color=*/Color::Red(), /*round_superellipse=*/round_superellipse, - /*stroke=*/std::nullopt); + std::optional params = + UberSDFParameters::MakeRoundedSuperellipse( + /*color=*/Color::Red(), /*round_superellipse=*/round_superellipse, + /*stroke=*/std::nullopt); EXPECT_FALSE(params.has_value()); } @@ -228,9 +231,10 @@ TEST(UberSDFParametersTest, MakeRoundedSuperellipseRejectsNonCircular) { .bottom_right = Size(9.0f, 10.0f), }; auto round_superellipse = RoundSuperellipse::MakeRectRadii(rect, radii); - auto params = UberSDFParameters::MakeRoundedSuperellipse( - /*color=*/Color::Red(), /*round_superellipse=*/round_superellipse, - /*stroke=*/std::nullopt); + std::optional params = + UberSDFParameters::MakeRoundedSuperellipse( + /*color=*/Color::Red(), /*round_superellipse=*/round_superellipse, + /*stroke=*/std::nullopt); EXPECT_FALSE(params.has_value()); } From 643f07dcd838eb47cf2b1a2b5f25653351a95dab Mon Sep 17 00:00:00 2001 From: Benson Luk <97480502+b-luk@users.noreply.github.com> Date: Wed, 26 Aug 2026 10:28:12 -0700 Subject: [PATCH 15/15] address comments --- .../flutter/impeller/display_list/canvas.cc | 148 +++++++++--------- .../src/flutter/impeller/geometry/gradient.cc | 1 + 2 files changed, 76 insertions(+), 73 deletions(-) diff --git a/engine/src/flutter/impeller/display_list/canvas.cc b/engine/src/flutter/impeller/display_list/canvas.cc index 010182108da36..fda9f88c4b351 100644 --- a/engine/src/flutter/impeller/display_list/canvas.cc +++ b/engine/src/flutter/impeller/display_list/canvas.cc @@ -268,81 +268,80 @@ static std::pair ExpandRectToPixelMinimum(const Rect& rect, return {expanded.value(), color.WithAlpha(color.alpha * alpha_scaling)}; } -/// @brief Attempts to populate `params` with the color source properties, +/// @brief Attempts to create UberSDF GradientParameters from a DlColorSource, /// if supported by UberSDF (e.g. for linear and radial gradients). /// -/// @return Whether the color source was successfully populated into `params`. -static bool PopulateUberSDFColorSource( - const ContentContext& renderer, - const flutter::DlColorSource& color_source, - const std::optional& shape_transform, - UberSDFParameters& params) { - if (color_source.isGradient()) { - UberSDFParameters::GradientParameters gradient; - - std::vector colors; - std::vector stops; - - // When a shape transform is applied to transform the SDF shape from local - // space into canvas space, the gradient parameters must be mapped from - // canvas space into local space via the inverse shape transform. - Matrix inverted_shape_transform = - shape_transform.has_value() ? shape_transform->Invert() : Matrix(); - - if (color_source.type() == flutter::DlColorSourceType::kLinearGradient) { - const auto* linear = color_source.asLinearGradient(); - FML_DCHECK(linear); - Matrix gradient_transform = inverted_shape_transform * linear->matrix(); - if (!gradient_transform.IsAffine()) { - // Non-affine matrix transformation not supported by UberSDF. - return false; - } - Paint::ConvertStops(linear, colors, stops); - gradient.type = UberSDFParameters::GradientParameters::Type::kLinear; - gradient.start = gradient_transform * linear->start_point(); - gradient.end = gradient_transform * linear->end_point(); - gradient.tile_mode = static_cast(linear->tile_mode()); - } else if (color_source.type() == - flutter::DlColorSourceType::kRadialGradient) { - const auto* radial = color_source.asRadialGradient(); - FML_DCHECK(radial); - Matrix gradient_transform = inverted_shape_transform * radial->matrix(); - if (!gradient_transform.IsAffine()) { - // Non-affine matrix transformation not supported by UberSDF. - return false; - } - auto scales = gradient_transform.GetScales2D(); - if (!scales.has_value() || - !ScalarNearlyEqual(scales->first, scales->second)) { - // Non-uniform scaling on a radial gradient creates an ellipse, which is - // not supported by UberSDF. - return false; - } - Paint::ConvertStops(radial, colors, stops); - gradient.type = UberSDFParameters::GradientParameters::Type::kRadial; - gradient.start = gradient_transform * radial->center(); - // For radial gradients, gradient.end.x stores the radius. - gradient.end = Point(radial->radius() * scales->first, 0.0f); - gradient.tile_mode = static_cast(radial->tile_mode()); - } else { - // Gradient type not supported by UberSDF. - return false; - } +/// @return The GradientParameters if the color source is a supported gradient, +/// otherwise std::nullopt. +static std::optional +CreateUberSDFGradientParameters(const ContentContext& renderer, + const flutter::DlColorSource& color_source, + const std::optional& shape_transform) { + if (!color_source.isGradient()) { + // Color source not supported by UberSDF. + return std::nullopt; + } - GradientData gradient_data = CreateGradientBuffer(colors, stops); - std::shared_ptr texture = - CreateGradientTexture(gradient_data, renderer.GetContext()); - if (!texture) { - return false; + UberSDFParameters::GradientParameters gradient; + + std::vector colors; + std::vector stops; + + // When a shape transform is applied to transform the SDF shape from local + // space into canvas space, the gradient parameters must be mapped from + // canvas space into local space via the inverse shape transform. + Matrix inverted_shape_transform = + shape_transform.has_value() ? shape_transform->Invert() : Matrix(); + + if (color_source.type() == flutter::DlColorSourceType::kLinearGradient) { + const auto* linear = color_source.asLinearGradient(); + FML_DCHECK(linear); + Matrix gradient_transform = inverted_shape_transform * linear->matrix(); + if (!gradient_transform.IsAffine()) { + // Non-affine matrix transformation not supported by UberSDF. + return std::nullopt; + } + Paint::ConvertStops(linear, colors, stops); + gradient.type = UberSDFParameters::GradientParameters::Type::kLinear; + gradient.start = gradient_transform * linear->start_point(); + gradient.end = gradient_transform * linear->end_point(); + gradient.tile_mode = static_cast(linear->tile_mode()); + } else if (color_source.type() == + flutter::DlColorSourceType::kRadialGradient) { + const auto* radial = color_source.asRadialGradient(); + FML_DCHECK(radial); + Matrix gradient_transform = inverted_shape_transform * radial->matrix(); + if (!gradient_transform.IsAffine()) { + // Non-affine matrix transformation not supported by UberSDF. + return std::nullopt; + } + auto scales = gradient_transform.GetScales2D(); + if (!scales.has_value() || + !ScalarNearlyEqual(scales->first, scales->second)) { + // Non-uniform scaling on a radial gradient creates an ellipse, which is + // not supported by UberSDF. + return std::nullopt; } + Paint::ConvertStops(radial, colors, stops); + gradient.type = UberSDFParameters::GradientParameters::Type::kRadial; + gradient.start = gradient_transform * radial->center(); + // For radial gradients, gradient.end.x stores the radius. + gradient.end = Point(radial->radius() * scales->first, 0.0f); + gradient.tile_mode = static_cast(radial->tile_mode()); + } else { + // Gradient type not supported by UberSDF. + return std::nullopt; + } - gradient.texture = std::move(texture); - params.gradient = std::move(gradient); - return true; + GradientData gradient_data = CreateGradientBuffer(colors, stops); + std::shared_ptr texture = + CreateGradientTexture(gradient_data, renderer.GetContext()); + if (!texture) { + return std::nullopt; } - // Color source not supported by UberSDF. - return false; + gradient.texture = std::move(texture); + return gradient; } } // namespace @@ -2292,11 +2291,14 @@ void Canvas::AddRenderSDFEntityToCurrentPass( entity.SetTransform(transform); entity.SetBlendMode(paint.blend_mode); - if (!paint.color_source || - PopulateUberSDFColorSource(renderer_, *paint.color_source, - shape_transform, params)) { - // No color source (solid paint color), or a supported color source was - // populated into UberSDFParams. + if (paint.color_source) { + params.gradient = CreateUberSDFGradientParameters( + renderer_, *paint.color_source, shape_transform); + } + + if (!paint.color_source || params.gradient.has_value()) { + // No color source (solid paint color), or a supported gradient color + // source. auto geometry = std::make_unique(params); auto contents = UberSDFContents::Make(params, std::move(geometry)); const Geometry* geom = contents->GetGeometry(); diff --git a/engine/src/flutter/impeller/geometry/gradient.cc b/engine/src/flutter/impeller/geometry/gradient.cc index 3ed83d6cb5a6a..9aabf8bdc9170 100644 --- a/engine/src/flutter/impeller/geometry/gradient.cc +++ b/engine/src/flutter/impeller/geometry/gradient.cc @@ -10,6 +10,7 @@ namespace impeller { // TODO(b-luk): this should use a platform specific max texture size. +// https://github.com/flutter/flutter/issues/191820 static constexpr uint32_t kMaxGradientTextureSize = 1024; static void AppendColor(const Color& color, GradientData* data) {