Sitelet https://github.com/flutter/flutter/pull/190874/files
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Comment on lines +164 to +166

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To prevent potential division-by-zero or overflow issues on some GPUs when the start and end points of a linear gradient are extremely close (but not exactly identical), consider using a small epsilon check (e.g., 1e-6) instead of comparing start_to_end_squared directly to 0.0.

  float t = start_to_end_squared < 1e-6
                ? 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;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Similarly, to avoid division by an extremely small radius which could lead to overflow or rendering artifacts, consider using a small epsilon check instead of comparing radius directly to 0.0.

  float t = radius < 1e-6 ? 0.0 : length(pos - center) / radius;

return IPSampleLinearWithTileMode(tex, vec2(t, 0.5), half_texel, tile_mode,
decal_border_color);
}
Comment thread
b-luk marked this conversation as resolved.

#endif
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
114 changes: 99 additions & 15 deletions engine/src/flutter/impeller/display_list/canvas.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand Down Expand Up @@ -266,6 +268,82 @@ static std::pair<Rect, Color> 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<UberSDFParameters::GradientParameters>
CreateUberSDFGradientParameters(const ContentContext& renderer,
const flutter::DlColorSource& color_source,
const std::optional<Matrix>& shape_transform) {
if (!color_source.isGradient()) {
// Color source not supported by UberSDF.
return std::nullopt;
}

UberSDFParameters::GradientParameters gradient;

std::vector<Color> colors;
std::vector<float> 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<Entity::TileMode>(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<Entity::TileMode>(radial->tile_mode());
} else {
// Gradient type not supported by UberSDF.
return std::nullopt;
}

GradientData gradient_data = CreateGradientBuffer(colors, stops);
std::shared_ptr<Texture> texture =
CreateGradientTexture(gradient_data, renderer.GetContext());
if (!texture) {
return std::nullopt;
}

gradient.texture = std::move(texture);
return gradient;
}

} // namespace

class Canvas::RRectBlurShape : public BlurShape {
Expand Down Expand Up @@ -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<UberSDFGeometry>(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<UberSDFGeometry>(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<UberSDFGeometry>(params);
auto uber_sdf_contents = UberSDFContents::Make(params, std::move(geometry));
const Geometry* geom = uber_sdf_contents->GetGeometry();

std::shared_ptr<ColorSourceContents> color_source_contents =
paint.CreateContents(renderer_, geom, shape_transform);
std::shared_ptr<Contents> 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;
Expand All @@ -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));
}
}

Expand Down
64 changes: 62 additions & 2 deletions engine/src/flutter/impeller/entity/contents/uber_sdf_contents.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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;
}
Comment thread
b-luk marked this conversation as resolved.
}
Comment thread
b-luk marked this conversation as resolved.

struct SamplerBinding {
std::shared_ptr<Texture> texture;
raw_ptr<const Sampler> 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<Scalar>(gradient.tile_mode);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we have a function for this conversion too?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As stated in the other comment, the enum value is expected to always exactly match the shader float value, and this is made explicit in https://github.com/flutter/flutter/blob/e6aa89eea34850cb1fd25d9f4d0b2fad379b00bf/engine/src/flutter/impeller/compiler/shader_lib/impeller/tile_mode.glsl. So I don't think an explicit conversion function here is necessary. This matches how it's used in our existing shaders, like

frag_info.tile_mode = static_cast<Scalar>(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<const Sampler> sampler =
renderer.GetContext()->GetSamplerLibrary()->GetSampler(sampler_desc);

return {gradient.texture, sampler};
}

} // namespace

std::unique_ptr<UberSDFContents> UberSDFContents::Make(
Expand All @@ -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;
Expand All @@ -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);

Expand All @@ -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;
Expand All @@ -123,14 +179,18 @@ 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;
}

std::optional<Color> 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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<UberSDFGeometry>(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 =
Expand Down Expand Up @@ -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<UberSDFGeometry>(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
Loading