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..f47bd9be4ed58 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,36 @@ 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 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); +} + +/// 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 = radius == 0.0 ? 0.0 : 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/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; } diff --git a/engine/src/flutter/impeller/display_list/canvas.cc b/engine/src/flutter/impeller/display_list/canvas.cc index 5b74b64767530..fda9f88c4b351 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" @@ -266,6 +268,82 @@ static std::pair ExpandRectToPixelMinimum(const Rect& rect, return {expanded.value(), color.WithAlpha(color.alpha * alpha_scaling)}; } +/// @brief Attempts to create UberSDF GradientParameters from a DlColorSource, +/// if supported by UberSDF (e.g. for linear and radial gradients). +/// +/// @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; + } + + 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; + } + + GradientData gradient_data = CreateGradientBuffer(colors, stops); + std::shared_ptr texture = + CreateGradientTexture(gradient_data, renderer.GetContext()); + if (!texture) { + return std::nullopt; + } + + gradient.texture = std::move(texture); + return gradient; +} + } // namespace class Canvas::RRectBlurShape : public BlurShape { @@ -2214,22 +2292,32 @@ void Canvas::AddRenderSDFEntityToCurrentPass( 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. - params.color = Color::White(); + params.gradient = CreateUberSDFGradientParameters( + renderer_, *paint.color_source, shape_transform); } - auto geometry = std::make_unique(params); - auto contents = UberSDFContents::Make(params, std::move(geometry)); - const Geometry* geom = contents->GetGeometry(); - if (paint.color_source) { - // UberSDF doesn't perform things like gradients so we blend the SDF - // with the color source. + 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(); + 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 uber_sdf_contents = UberSDFContents::Make(params, std::move(geometry)); + const Geometry* geom = uber_sdf_contents->GetGeometry(); + 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; @@ -2238,10 +2326,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 71528fefeded2..be7b69fb962f5 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,47 @@ 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); + 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; + 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 +111,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; @@ -84,6 +127,16 @@ bool UberSDFContents::Render(const ContentContext& renderer, frag_info.circle_center_right = params_.circle_center_right; 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); @@ -96,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; @@ -123,6 +179,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; } @@ -130,7 +189,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..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 @@ -29,6 +29,30 @@ 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 = @@ -100,5 +124,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 c2b35718b66be..16fa2bef01b0d 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, @@ -69,6 +96,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/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()); } 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 0a9c6a71a7332..a67679dcadbb1 100644 --- a/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag +++ b/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag @@ -5,12 +5,16 @@ precision mediump float; #include +#include +#include #include #include "rse_sdf.glsl" #include "sdf_functions.glsl" #include "sdf_utils.glsl" +uniform sampler2D color_source_sampler; + uniform FragInfo { // FragInfo fields are sorted by size (vec4 -> vec2 -> float) to optimize // uniform register usage. @@ -19,7 +23,7 @@ uniform FragInfo { // vec4 fields // =========================================================================== - /// The RGBA color of the shape. + /// 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 the circular cap radii for rounded superellipses in @@ -49,6 +53,17 @@ uniform FragInfo { /// rounded superellipse. vec2 circle_center_right; + // --- 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 fields // =========================================================================== @@ -61,6 +76,11 @@ uniform FragInfo { /// 3: RoundRect /// 4: Rounded Superellipse (must have uniform circular corner radii) 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; @@ -74,6 +94,14 @@ uniform FragInfo { /// 1: Bevel /// 2: Round float stroke_join; + + // --- Gradient Parameters --- + /// The tile mode for gradient sampling: + /// 0: Clamp + /// 1: Repeat + /// 2: Mirror + /// 3: Decal + float tile_mode; } frag_info; @@ -81,6 +109,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; } @@ -304,8 +355,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; @@ -315,8 +367,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); } 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..9aabf8bdc9170 100644 --- a/engine/src/flutter/impeller/geometry/gradient.cc +++ b/engine/src/flutter/impeller/geometry/gradient.cc @@ -9,6 +9,10 @@ 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) { auto converted = color.ToR8G8B8A8(); data->color_bytes.push_back(converted[0]); @@ -26,22 +30,20 @@ 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. diff --git a/engine/src/flutter/impeller/tools/malioc.json b/engine/src/flutter/impeller/tools/malioc.json index bccca2a408164..06f515a142b13 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, @@ -5067,7 +5069,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 +5077,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 +5098,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 +5111,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 +5122,7 @@ }, "stack_spill_bytes": 0, "thread_occupancy": 100, - "uniform_registers_used": 18, + "uniform_registers_used": 16, "work_registers_used": 20 } } @@ -5138,7 +5140,7 @@ "arithmetic" ], "longest_path_cycles": [ - 6.599999904632568, + 6.929999828338623, 1.0, 1.0 ], @@ -5151,21 +5153,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 +5811,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 +5819,9 @@ "arith_cvt" ], "longest_path_cycles": [ - 0.359375, + 0.375, 0.296875, - 0.359375, + 0.375, 0.1875, 0.0, 0.25, @@ -5835,15 +5837,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 +5854,9 @@ "arith_cvt" ], "total_cycles": [ - 0.484375, + 0.5, 0.328125, - 0.484375, + 0.5, 0.1875, 0.0, 0.25, @@ -5862,7 +5865,7 @@ }, "stack_spill_bytes": 0, "thread_occupancy": 100, - "uniform_registers_used": 16, + "uniform_registers_used": 14, "work_registers_used": 20 } } @@ -5880,7 +5883,7 @@ "arithmetic" ], "longest_path_cycles": [ - 6.599999904632568, + 7.260000228881836, 1.0, 1.0 ], @@ -5893,15 +5896,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 +8600,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 +8608,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 +8630,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 +8643,18 @@ "arith_fma" ], "total_cycles": [ - 13.625, - 13.625, - 4.5, - 13.4375, + 14.0625, + 14.0625, + 5.550000190734863, + 13.75, 0.0, 0.25, - 0.0 + 0.5 ] }, "stack_spill_bytes": 0, "thread_occupancy": 100, - "uniform_registers_used": 44, + "uniform_registers_used": 62, "work_registers_used": 32 } } @@ -8669,9 +8672,9 @@ "arithmetic" ], "longest_path_cycles": [ - 44.220001220703125, - 3.0, - 4.0 + 52.13999938964844, + 6.0, + 5.0 ], "pipelines": [ "arithmetic", @@ -8682,21 +8685,21 @@ "arithmetic" ], "shortest_path_cycles": [ - 6.269999980926514, - 1.0, + 6.599999904632568, + 3.0, 2.0 ], "total_bound_pipelines": [ "arithmetic" ], "total_cycles": [ - 106.0, - 3.0, - 24.0 + 122.0, + 7.0, + 26.0 ] }, "thread_occupancy": 100, - "uniform_registers_used": 6, + "uniform_registers_used": 9, "work_registers_used": 4 } } @@ -9361,17 +9364,19 @@ "uses_late_zs_update": false, "variants": { "Main": { - "fp16_arithmetic": 28, + "fp16_arithmetic": 35, "has_stack_spilling": false, "performance": { "longest_path_bound_pipelines": [ + "arith_total", + "arith_cvt", "varying", "texture" ], "longest_path_cycles": [ + 0.25, 0.234375, - 0.234375, - 0.234375, + 0.25, 0.0625, 0.0, 0.25, @@ -9390,9 +9395,9 @@ "varying" ], "shortest_path_cycles": [ - 0.171875, + 0.1875, 0.140625, - 0.171875, + 0.1875, 0.0625, 0.0, 0.25, @@ -9403,9 +9408,9 @@ "arith_cvt" ], "total_cycles": [ - 0.328125, + 0.34375, 0.265625, - 0.328125, + 0.34375, 0.0625, 0.0, 0.25, @@ -9901,17 +9906,17 @@ "uses_late_zs_update": false, "variants": { "Main": { - "fp16_arithmetic": 16, + "fp16_arithmetic": 21, "has_stack_spilling": false, "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, @@ -9930,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, @@ -9943,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, @@ -10884,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, @@ -10903,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, @@ -10920,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, @@ -11823,7 +11827,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 +11835,13 @@ "arith_sfu" ], "longest_path_cycles": [ - 4.0, - 3.387500047683716, - 1.9249999523162842, - 4.0, + 4.125, + 3.65625, + 2.28125, + 4.125, 0.0, 0.25, - 0.0 + 0.25 ], "pipelines": [ "arith_total", @@ -11850,12 +11854,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 +11871,18 @@ "arith_sfu" ], "total_cycles": [ - 13.4375, - 12.4375, - 4.84375, - 13.4375, + 13.625, + 12.875, + 5.59375, + 13.625, 0.0, 0.25, - 0.0 + 0.5 ] }, "stack_spill_bytes": 0, "thread_occupancy": 100, - "uniform_registers_used": 46, + "uniform_registers_used": 64, "work_registers_used": 32 } } @@ -12289,4 +12294,4 @@ } } } -} \ No newline at end of file +}