From 5460f5ec50a2182bfeed95b30a18c08d552b4598 Mon Sep 17 00:00:00 2001 From: Evan Walley Date: Thu, 7 May 2026 10:25:26 -0700 Subject: [PATCH 01/15] initial implementation --- .../flutter/impeller/display_list/canvas.cc | 136 +++++++++-- .../entity/contents/uber_sdf_contents.cc | 25 +- .../entity/contents/uber_sdf_parameters.cc | 52 ++-- .../entity/contents/uber_sdf_parameters.h | 69 +++--- .../impeller/entity/shaders/uber_sdf.frag | 229 +++++++++++++----- 5 files changed, 379 insertions(+), 132 deletions(-) diff --git a/engine/src/flutter/impeller/display_list/canvas.cc b/engine/src/flutter/impeller/display_list/canvas.cc index e82a5a2b0e8f6..831057a59f5e9 100644 --- a/engine/src/flutter/impeller/display_list/canvas.cc +++ b/engine/src/flutter/impeller/display_list/canvas.cc @@ -1043,33 +1043,127 @@ void Canvas::DrawRoundSuperellipse(const RoundSuperellipse& round_superellipse, entity.SetBlendMode(paint.blend_mode); if (renderer_.GetContext()->GetFlags().use_sdfs && - !paint.mask_blur_descriptor.has_value() && - round_superellipse.GetRadii().AreAllCornersSame()) { + !paint.mask_blur_descriptor.has_value()) { + auto split_proc = [](Scalar left, Scalar right, Scalar ratio_left, + Scalar ratio_right) -> Scalar { + if (ratio_left == 0 && ratio_right == 0) { + return (left + right) / 2; + } + return (left * ratio_right + right * ratio_left) / + (ratio_left + ratio_right); + }; + auto round_superellipse_params = RoundSuperellipseParam::MakeBoundsRadii( round_superellipse.GetBounds(), round_superellipse.GetRadii()); - RoundSuperellipseParam::Octant octant_top = - round_superellipse_params.top_right.top; - RoundSuperellipseParam::Octant octant_right = - round_superellipse_params.top_right.right; - - auto adjusted_radii = RoundingRadii::MakeRadii( - Size(octant_top.circle_radius, octant_right.circle_radius)); + RoundSuperellipseParam::Quadrant tr = round_superellipse_params.top_right; + RoundSuperellipseParam::Quadrant br = + round_superellipse_params.all_corners_same + ? tr + : round_superellipse_params.bottom_right; + RoundSuperellipseParam::Quadrant bl = + round_superellipse_params.all_corners_same + ? tr + : round_superellipse_params.bottom_left; + RoundSuperellipseParam::Quadrant tl = + round_superellipse_params.all_corners_same + ? tr + : round_superellipse_params.top_left; + + Rect bounds = round_superellipse.GetBounds(); + Point center = bounds.GetCenter(); + RoundingRadii radii = round_superellipse.GetRadii(); + + Scalar top_split = split_proc(bounds.GetLeft(), bounds.GetRight(), + radii.top_left.width, radii.top_right.width) - + center.x; + Scalar bottom_split = + split_proc(bounds.GetLeft(), bounds.GetRight(), radii.bottom_left.width, + radii.bottom_right.width) - + center.x; + Scalar left_split = + split_proc(bounds.GetTop(), bounds.GetBottom(), radii.top_left.height, + radii.bottom_left.height) - + center.y; + Scalar right_split = + split_proc(bounds.GetTop(), bounds.GetBottom(), radii.top_right.height, + radii.bottom_right.height) - + center.y; + + Point tr_center_relative = tr.offset - center; + Point br_center_relative = br.offset - center; + Point bl_center_relative = bl.offset - center; + Point tl_center_relative = tl.offset - center; + + Vector4 superellipse_degrees_top(tr.top.se_n, br.top.se_n, bl.top.se_n, + tl.top.se_n); + Vector4 superellipse_degrees_right(tr.right.se_n, br.right.se_n, + bl.right.se_n, tl.right.se_n); + Vector4 superellipse_semi_axes_top(tr.top.se_a, br.top.se_a, bl.top.se_a, + tl.top.se_a); + Vector4 superellipse_semi_axes_right(tr.right.se_a, br.right.se_a, + bl.right.se_a, tl.right.se_a); + Vector4 angle_spans_top(tr.top.circle_max_angle.radians, + br.top.circle_max_angle.radians, + bl.top.circle_max_angle.radians, + tl.top.circle_max_angle.radians); + Vector4 angle_spans_right(tr.right.circle_max_angle.radians, + br.right.circle_max_angle.radians, + bl.right.circle_max_angle.radians, + tl.right.circle_max_angle.radians); + Vector4 octant_offsets_c(tr.top.se_a - tr.right.se_a, + br.top.se_a - br.right.se_a, + bl.top.se_a - bl.right.se_a, + tl.top.se_a - tl.right.se_a); + Vector4 radii_width(tr.top.circle_radius, br.top.circle_radius, + bl.top.circle_radius, tl.top.circle_radius); + Vector4 radii_height(tr.right.circle_radius, br.right.circle_radius, + bl.right.circle_radius, tl.right.circle_radius); + Vector4 circle_centers_top_x(tr.top.circle_center.x, br.top.circle_center.x, + bl.top.circle_center.x, + tl.top.circle_center.x); + Vector4 circle_centers_top_y(tr.top.circle_center.y, br.top.circle_center.y, + bl.top.circle_center.y, + tl.top.circle_center.y); + Vector4 circle_centers_right_x( + tr.right.circle_center.x, br.right.circle_center.x, + bl.right.circle_center.x, tl.right.circle_center.x); + Vector4 circle_centers_right_y( + tr.right.circle_center.y, br.right.circle_center.y, + bl.right.circle_center.y, tl.right.circle_center.y); + Vector4 superellipse_scales_x( + tr.signed_scale.Abs().x, br.signed_scale.Abs().x, + bl.signed_scale.Abs().x, tl.signed_scale.Abs().x); + Vector4 superellipse_scales_y( + tr.signed_scale.Abs().y, br.signed_scale.Abs().y, + bl.signed_scale.Abs().y, tl.signed_scale.Abs().y); + Vector4 quadrant_centers_x(tr_center_relative.x, br_center_relative.x, + bl_center_relative.x, tl_center_relative.x); + Vector4 quadrant_centers_y(tr_center_relative.y, br_center_relative.y, + bl_center_relative.y, tl_center_relative.y); + Vector4 quadrant_splits(top_split, bottom_split, left_split, right_split); auto params = UberSDFParameters::MakeRoundedSuperellipse( /*color=*/paint.color, - /*bounds=*/round_superellipse.GetBounds(), - /*superellipse_degree=*/Point(octant_top.se_n, octant_right.se_n), - /*superellipse_a=*/Point(octant_top.se_a, octant_right.se_a), - /*radii=*/adjusted_radii, - /*corner_angle_span=*/ - Point(octant_top.circle_max_angle.radians, - octant_right.circle_max_angle.radians), - /*corner_circle_center_top=*/octant_top.circle_center, - /*corner_circle_center_right=*/octant_right.circle_center, - /*superellipse_c=*/octant_top.se_a - octant_right.se_a, - /*superellipse_scale=*/ - Point(round_superellipse_params.top_right.signed_scale.Abs()), + /*bounds=*/bounds, + /*superellipse_degrees_top=*/superellipse_degrees_top, + /*superellipse_degrees_right=*/superellipse_degrees_right, + /*superellipse_semi_axes_top=*/superellipse_semi_axes_top, + /*superellipse_semi_axes_right=*/superellipse_semi_axes_right, + /*angle_spans_top=*/angle_spans_top, + /*angle_spans_right=*/angle_spans_right, + /*octant_offsets_c=*/octant_offsets_c, + /*radii_width=*/radii_width, + /*radii_height=*/radii_height, + /*circle_centers_top_x=*/circle_centers_top_x, + /*circle_centers_top_y=*/circle_centers_top_y, + /*circle_centers_right_x=*/circle_centers_right_x, + /*circle_centers_right_y=*/circle_centers_right_y, + /*superellipse_scales_x=*/superellipse_scales_x, + /*superellipse_scales_y=*/superellipse_scales_y, + /*quadrant_centers_x=*/quadrant_centers_x, + /*quadrant_centers_y=*/quadrant_centers_y, + /*quadrant_splits=*/quadrant_splits, /*stroke=*/paint.GetStroke()); AddRenderSDFEntityToCurrentPass(paint, params); 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 d1518e7ee4f28..ab817517875e0 100644 --- a/engine/src/flutter/impeller/entity/contents/uber_sdf_contents.cc +++ b/engine/src/flutter/impeller/entity/contents/uber_sdf_contents.cc @@ -84,13 +84,24 @@ bool UberSDFContents::Render(const ContentContext& renderer, frag_info.stroke_join = params_.stroke ? ToShaderStrokeJoin(params_.stroke->join) : 0.0f; frag_info.aa_pixels = UberSDFParameters::kAntialiasPixels; - frag_info.superellipse_degree = params_.superellipse_degree; - frag_info.superellipse_a = params_.superellipse_a; - frag_info.corner_angle_span = params_.corner_angle_span; - frag_info.corner_circle_center_top = params_.corner_circle_center_top; - frag_info.corner_circle_center_right = params_.corner_circle_center_right; - frag_info.superellipse_c = params_.superellipse_c; - frag_info.superellipse_scale = params_.superellipse_scale; + frag_info.superellipse_degrees_top = params_.superellipse_degrees_top; + frag_info.superellipse_degrees_right = params_.superellipse_degrees_right; + frag_info.superellipse_semi_axes_top = params_.superellipse_semi_axes_top; + frag_info.superellipse_semi_axes_right = params_.superellipse_semi_axes_right; + frag_info.angle_spans_top = params_.angle_spans_top; + frag_info.angle_spans_right = params_.angle_spans_right; + frag_info.octant_offsets_c = params_.octant_offsets_c; + frag_info.radii_width = params_.radii_width; + frag_info.radii_height = params_.radii_height; + frag_info.circle_centers_top_x = params_.circle_centers_top_x; + frag_info.circle_centers_top_y = params_.circle_centers_top_y; + frag_info.circle_centers_right_x = params_.circle_centers_right_x; + frag_info.circle_centers_right_y = params_.circle_centers_right_y; + frag_info.superellipse_scales_x = params_.superellipse_scales_x; + frag_info.superellipse_scales_y = params_.superellipse_scales_y; + frag_info.quadrant_centers_x = params_.quadrant_centers_x; + frag_info.quadrant_centers_y = params_.quadrant_centers_y; + frag_info.quadrant_splits = params_.quadrant_splits; auto geometry_result = GetGeometry()->GetPositionBuffer(renderer, entity, pass); 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 6108ae3547441..edc958ebbb9bb 100644 --- a/engine/src/flutter/impeller/entity/contents/uber_sdf_parameters.cc +++ b/engine/src/flutter/impeller/entity/contents/uber_sdf_parameters.cc @@ -74,14 +74,24 @@ UberSDFParameters UberSDFParameters::MakeRoundedRect( UberSDFParameters UberSDFParameters::MakeRoundedSuperellipse( Color color, Rect rect, - Point superellipse_degree, - Point superellipse_a, - RoundingRadii radii, - Point corner_angle_span, - Point corner_circle_center_top, - Point corner_circle_center_right, - Scalar superellipse_c, - Point superellipse_scale, + Vector4 superellipse_degrees_top, + Vector4 superellipse_degrees_right, + Vector4 superellipse_semi_axes_top, + Vector4 superellipse_semi_axes_right, + Vector4 angle_spans_top, + Vector4 angle_spans_right, + Vector4 octant_offsets_c, + Vector4 radii_width, + Vector4 radii_height, + Vector4 circle_centers_top_x, + Vector4 circle_centers_top_y, + Vector4 circle_centers_right_x, + Vector4 circle_centers_right_y, + Vector4 superellipse_scales_x, + Vector4 superellipse_scales_y, + Vector4 quadrant_centers_x, + Vector4 quadrant_centers_y, + Vector4 quadrant_splits, std::optional stroke) { Point size = Point(rect.GetSize() * 0.5f); return UberSDFParameters{ @@ -90,14 +100,24 @@ UberSDFParameters UberSDFParameters::MakeRoundedSuperellipse( .center = rect.GetCenter(), .size = size, .stroke = stroke, - .radii = radii, - .superellipse_degree = superellipse_degree, - .superellipse_a = superellipse_a, - .corner_angle_span = corner_angle_span, - .corner_circle_center_top = corner_circle_center_top, - .corner_circle_center_right = corner_circle_center_right, - .superellipse_c = superellipse_c, - .superellipse_scale = superellipse_scale}; + .superellipse_degrees_top = superellipse_degrees_top, + .superellipse_degrees_right = superellipse_degrees_right, + .superellipse_semi_axes_top = superellipse_semi_axes_top, + .superellipse_semi_axes_right = superellipse_semi_axes_right, + .angle_spans_top = angle_spans_top, + .angle_spans_right = angle_spans_right, + .octant_offsets_c = octant_offsets_c, + .radii_width = radii_width, + .radii_height = radii_height, + .circle_centers_top_x = circle_centers_top_x, + .circle_centers_top_y = circle_centers_top_y, + .circle_centers_right_x = circle_centers_right_x, + .circle_centers_right_y = circle_centers_right_y, + .superellipse_scales_x = superellipse_scales_x, + .superellipse_scales_y = superellipse_scales_y, + .quadrant_centers_x = quadrant_centers_x, + .quadrant_centers_y = quadrant_centers_y, + .quadrant_splits = quadrant_splits}; } } // 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 e5c36973936d8..8323c57a59c4a 100644 --- a/engine/src/flutter/impeller/entity/contents/uber_sdf_parameters.h +++ b/engine/src/flutter/impeller/entity/contents/uber_sdf_parameters.h @@ -54,18 +54,28 @@ struct UberSDFParameters { const RoundingRadii& radii, std::optional stroke); - /// Creates UberSDFParameters for a symmetric round superellipse. + /// Creates UberSDFParameters for an asymmetric round superellipse. static UberSDFParameters MakeRoundedSuperellipse( Color color, Rect bounds, - Point superellipse_degree, - Point superellipse_a, - RoundingRadii radii, - Point corner_angle_span, - Point corner_circle_center_top, - Point corner_circle_center_right, - Scalar superellipse_c, - Point superellipse_scale, + Vector4 superellipse_degrees_top, + Vector4 superellipse_degrees_right, + Vector4 superellipse_semi_axes_top, + Vector4 superellipse_semi_axes_right, + Vector4 angle_spans_top, + Vector4 angle_spans_right, + Vector4 octant_offsets_c, + Vector4 radii_width, + Vector4 radii_height, + Vector4 circle_centers_top_x, + Vector4 circle_centers_top_y, + Vector4 circle_centers_right_x, + Vector4 circle_centers_right_y, + Vector4 superellipse_scales_x, + Vector4 superellipse_scales_y, + Vector4 quadrant_centers_x, + Vector4 quadrant_centers_y, + Vector4 quadrant_splits, std::optional stroke); /// The type of shape to render. @@ -86,31 +96,26 @@ struct UberSDFParameters { std::optional stroke; /// The corner radii for a rounded shapes. - /// - /// For RoundSuperellipse, the 'width' component holds the top-octant radius - /// and the 'height' component holds the right-octant radius. RoundingRadii radii; - /// The degrees of the top (.x) and right (.y) octants of a RoundSuperellipse. - Point superellipse_degree; - - /// The semi-axes of the top (.x) and right (.y) superellipse segments. - Point superellipse_a; - - /// The spans of the top (.x) and right (.y) circular arcs. - Point corner_angle_span; - - /// The center of the top circular arc in a RoundSuperellipse. - Point corner_circle_center_top; - - /// The center of the right circular arc in a RoundSuperellipse. - Point corner_circle_center_right; - - /// The offset of the octants in a RoundSuperellipse. - Scalar superellipse_c; - - /// The scale of the superellipse. - Point superellipse_scale; + Vector4 superellipse_degrees_top; + Vector4 superellipse_degrees_right; + Vector4 superellipse_semi_axes_top; + Vector4 superellipse_semi_axes_right; + Vector4 angle_spans_top; + Vector4 angle_spans_right; + Vector4 octant_offsets_c; + Vector4 radii_width; + Vector4 radii_height; + Vector4 circle_centers_top_x; + Vector4 circle_centers_top_y; + Vector4 circle_centers_right_x; + Vector4 circle_centers_right_y; + Vector4 superellipse_scales_x; + Vector4 superellipse_scales_y; + Vector4 quadrant_centers_x; + Vector4 quadrant_centers_y; + Vector4 quadrant_splits; }; } // namespace impeller diff --git a/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag b/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag index 3c001e63099db..a61ab00ec1f35 100644 --- a/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag +++ b/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag @@ -18,13 +18,24 @@ uniform FragInfo { float type; vec4 radii; vec4 radii_right; - vec2 superellipse_degree; - vec2 superellipse_a; - vec2 corner_angle_span; - vec2 corner_circle_center_top; - vec2 corner_circle_center_right; - float superellipse_c; - vec2 superellipse_scale; + vec4 superellipse_degrees_top; + vec4 superellipse_degrees_right; + vec4 superellipse_semi_axes_top; + vec4 superellipse_semi_axes_right; + vec4 angle_spans_top; + vec4 angle_spans_right; + vec4 octant_offsets_c; + vec4 radii_width; + vec4 radii_height; + vec4 circle_centers_top_x; + vec4 circle_centers_top_y; + vec4 circle_centers_right_x; + vec4 circle_centers_right_y; + vec4 superellipse_scales_x; + vec4 superellipse_scales_y; + vec4 quadrant_centers_x; + vec4 quadrant_centers_y; + vec4 quadrant_splits; } frag_info; @@ -102,70 +113,174 @@ float sdSuperellipse(vec2 p, float n) { return length(pa - ba * h) * sign(pa.x * ba.y - pa.y * ba.x); } -float distanceFromRoundedSuperellipse(vec2 p, - vec2 degree, - vec2 se_a, - vec4 radii_top, - vec2 angle_span, - vec2 circle_center_top, - vec4 radii_right, - vec2 circle_center_right, - float c, - vec2 scale) { - // Do work in the first quadrant to simply things. - p = abs(p); - // Map p in to a square. - vec2 p_norm = p / scale; +float getComponent(vec4 v, int index) { + if (index == 0) return v.x; + if (index == 1) return v.y; + if (index == 2) return v.z; + return v.w; +} - // Declare all RSE params for a single octant. - float se_degree, span, radius, axis_length; - vec2 circle_center; +float smax(float a, float b, float k) { + float m = max(a, b); + // Taper the smoothing factor to 0 outside the shape to prevent bloat/overdraw. + float effective_k = k * smoothstep(0.0, -k, m); + if (effective_k < 0.001) return m; + + float h = clamp(0.5 + 0.5 * (b - a) / effective_k, 0.0, 1.0); + return mix(a, b, h) + effective_k * h * (1.0 - h); +} + +float getQuadrantDistance(vec2 p, int quadrant_index) { + float se_degree_top = getComponent(frag_info.superellipse_degrees_top, quadrant_index); + float se_degree_right = getComponent(frag_info.superellipse_degrees_right, quadrant_index); + float se_a_top = getComponent(frag_info.superellipse_semi_axes_top, quadrant_index); + float se_a_right = getComponent(frag_info.superellipse_semi_axes_right, quadrant_index); + float angle_span_top = getComponent(frag_info.angle_spans_top, quadrant_index); + float angle_span_right = getComponent(frag_info.angle_spans_right, quadrant_index); + float c = getComponent(frag_info.octant_offsets_c, quadrant_index); + float radius_top = getComponent(frag_info.radii_width, quadrant_index); + float radius_right = getComponent(frag_info.radii_height, quadrant_index); + + vec2 circle_center_top = vec2( + getComponent(frag_info.circle_centers_top_x, quadrant_index), + getComponent(frag_info.circle_centers_top_y, quadrant_index) + ); + vec2 circle_center_right = vec2( + getComponent(frag_info.circle_centers_right_x, quadrant_index), + getComponent(frag_info.circle_centers_right_y, quadrant_index) + ); + + vec2 scale = vec2( + getComponent(frag_info.superellipse_scales_x, quadrant_index), + getComponent(frag_info.superellipse_scales_y, quadrant_index) + ); + + vec2 q_center = vec2( + getComponent(frag_info.quadrant_centers_x, quadrant_index), + getComponent(frag_info.quadrant_centers_y, quadrant_index) + ); + + vec2 q_sign = vec2(1.0); + if (quadrant_index == 0) q_sign = vec2(1.0, -1.0); + else if (quadrant_index == 1) q_sign = vec2(1.0, 1.0); + else if (quadrant_index == 2) q_sign = vec2(-1.0, 1.0); + else q_sign = vec2(-1.0, -1.0); + + vec2 p_local = (p - q_center) * q_sign; + vec2 extents = vec2(scale.x * se_a_top, scale.y * se_a_right); + + vec2 d_rect = p_local - extents; + float dist_rect_local = length(max(d_rect, 0.0)) + min(max(d_rect.x, d_rect.y), 0.0); + + if (p_local.x <= 0.0 || p_local.y <= 0.0) { + return dist_rect_local; + } + + vec2 p_norm = p_local / scale; - // 'p' in the coordinate system of the octant. + float se_degree; + float span; + float radius; + vec2 circle_center; + float axis_length; vec2 p_oct; - // We split the quadrant along the diagonal of the transition (p_norm.y + c == - // p_norm.x). This allows us to grab the correct set of parameters for the - // "top" and "right" halves of the corner. if (p_norm.y + c > p_norm.x) { p_oct = p_norm + vec2(0.0, c); - se_degree = degree.x; - span = angle_span.x; - radius = radii_top.x; + se_degree = se_degree_top; + span = angle_span_top; + radius = radius_top; circle_center = circle_center_top; - axis_length = se_a.x; + axis_length = se_a_top; } else { - // For the 'right' octant, we flip the point and shift it according to - // the CPU's OctantContains/Flip logic. p_oct = p_norm.yx - vec2(0.0, c); - se_degree = degree.y; - span = angle_span.y; - radius = radii_right.x; + se_degree = se_degree_right; + span = angle_span_right; + radius = radius_right; circle_center = circle_center_right; - axis_length = se_a.y; + axis_length = se_a_right; } - // Move the point to the corner circle's coordinate system. - vec2 p_rel = p_oct - circle_center; + if (se_degree < 1.9) { // Sharp rectangular corner + return dist_rect_local; + } - // Grab the angle offset of the point. + vec2 p_rel = p_oct - circle_center; float theta = atan(p_rel.y, p_rel.x); - // The angular distance between the point and the 45 degree midline. float d_theta = theta - PI_OVER_FOUR; d_theta = mod(d_theta + PI, TWO_PI) - PI; - // If the point is within the span of the corner circle's arc, - // use a circle SDF. - // This works because the normals of the circular and superelliptical sections - // agree at the transition angle, the total RSE curve is continuous and - // the closest point on a continuous curve to a point lies along the normal. + float d_scaled; + vec2 grad_oct; if (abs(d_theta) < abs(span)) { - return distanceFromCircle(p_rel, radius); + d_scaled = distanceFromCircle(p_rel, radius); + grad_oct = normalize(p_rel); + } else { + d_scaled = sdSuperellipse(p_oct / axis_length, se_degree) * axis_length; + vec2 p_oct_clamped = max(p_oct, vec2(0.001)); + float max_p = max(p_oct_clamped.x, p_oct_clamped.y); + vec2 p_safe = p_oct_clamped / max_p; + grad_oct = normalize(pow(p_safe, vec2(se_degree - 1.0))); + } + + vec2 grad_norm; + if (p_norm.y + c > p_norm.x) { + grad_norm = grad_oct; + } else { + grad_norm = grad_oct.yx; } - return sdSuperellipse(p_oct / axis_length, se_degree) * axis_length; + + float corner_dist = d_scaled / length(grad_norm / scale); + + return max(dist_rect_local, corner_dist); } +float distanceFromRoundedSuperellipse(vec2 p) { + vec2 T = vec2(frag_info.quadrant_splits.x, -frag_info.size.y); + vec2 R = vec2(frag_info.size.x, frag_info.quadrant_splits.w); + vec2 B = vec2(frag_info.quadrant_splits.y, frag_info.size.y); + vec2 L = vec2(-frag_info.size.x, frag_info.quadrant_splits.z); + + // Exact Wedge Partition from Bounding Box Center (0,0) + // Using (0,0) guarantees that the rays to symmetric split points are perfectly horizontal/vertical, + // preventing diagonal hairline discontinuities between symmetric quadrants. + float cT = T.x * p.y - T.y * p.x; + float cR = R.x * p.y - R.y * p.x; + float cB = B.x * p.y - B.y * p.x; + float cL = L.x * p.y - L.y * p.x; + + int quadrant_index = 0; + if (cT >= 0.0 && cR <= 0.0 && dot(p, T + R) >= 0.0) { + quadrant_index = 0; // TR + } else if (cR >= 0.0 && cB <= 0.0 && dot(p, R + B) >= 0.0) { + quadrant_index = 1; // BR + } else if (cB >= 0.0 && cL <= 0.0 && dot(p, B + L) >= 0.0) { + quadrant_index = 2; // BL + } else { + quadrant_index = 3; // TL + } + + // When two split points meet at a corner (zero radius), we reduce the + // partitions to perfectly partition the shape without evaluating the degenerate corner. + bool TR_zero = distance(T, R) < 0.01; + bool BR_zero = distance(R, B) < 0.01; + bool BL_zero = distance(B, L) < 0.01; + bool TL_zero = distance(L, T) < 0.01; + + if (TR_zero && quadrant_index == 0) quadrant_index = 3; + if (BR_zero && quadrant_index == 1) quadrant_index = 0; + if (BL_zero && quadrant_index == 2) quadrant_index = 1; + if (TL_zero && quadrant_index == 3) quadrant_index = 2; + + // Second pass in case of multiple zero-radius corners + if (TR_zero && quadrant_index == 0) quadrant_index = 3; + if (BR_zero && quadrant_index == 1) quadrant_index = 0; + if (BL_zero && quadrant_index == 2) quadrant_index = 1; + if (TL_zero && quadrant_index == 3) quadrant_index = 2; + + return getQuadrantDistance(p, quadrant_index); +} // Define an ellipse as q(w) = (a*cos(w), b*sin(w)), and p = (x, y) on the // plane. Let q(w0) be the closest point on q to p, then q(w0) - p is tangent to // q(w0), and (q(w0) - p) dot q'(w0) = 0. This function uses the Newton-Raphson @@ -342,12 +457,7 @@ vec2 filledSDF(vec2 p) { } else if (frag_info.type < 3.5) { // Rounded Rect sdf = distanceFromRoundedRect(p, frag_info.size, frag_info.radii); } else { - sdf = distanceFromRoundedSuperellipse( - p, frag_info.superellipse_degree, frag_info.superellipse_a, - frag_info.radii, frag_info.corner_angle_span, - frag_info.corner_circle_center_top, frag_info.radii_right, - frag_info.corner_circle_center_right, frag_info.superellipse_c, - frag_info.superellipse_scale); + sdf = distanceFromRoundedSuperellipse(p); } return vec2(sdf, pixelSize(sdf)); } @@ -404,6 +514,12 @@ vec2 strokedSDF(vec2 p) { return vec2(sdf, base_pixel_size); } +float distanceToSegment(vec2 p, vec2 a, vec2 b) { + vec2 pa = p - a, ba = b - a; + float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0); + return length(pa - ba * h); +} + void main() { vec2 p = v_position - frag_info.center; @@ -417,7 +533,8 @@ void main() { // (pixel_size * aa_pixels) in each direction. float fade_size = pixel_size * frag_info.aa_pixels * 0.5; float alpha = 1.0 - smoothstep(-fade_size, fade_size, sdf); - + frag_color = vec4(frag_info.color.rgb, frag_info.color.a * alpha); frag_color = IPPremultiply(frag_color); + } From 51862408ceaa97455347fa256fc664b26320907a Mon Sep 17 00:00:00 2001 From: Evan Walley Date: Mon, 11 May 2026 21:18:31 -0700 Subject: [PATCH 02/15] compute all quadrants --- .../flutter/impeller/display_list/canvas.cc | 116 +----------------- .../entity/contents/uber_sdf_contents.cc | 6 - .../entity/contents/uber_sdf_parameters.cc | 99 ++++++++------- .../entity/contents/uber_sdf_parameters.h | 25 +--- .../contents/uber_sdf_parameters_unittests.cc | 8 +- .../impeller/entity/shaders/uber_sdf.frag | 105 +++++++--------- .../geometry/round_superellipse_param.cc | 8 ++ .../geometry/round_superellipse_param.h | 5 + 8 files changed, 121 insertions(+), 251 deletions(-) diff --git a/engine/src/flutter/impeller/display_list/canvas.cc b/engine/src/flutter/impeller/display_list/canvas.cc index 831057a59f5e9..02c77fa93e8a5 100644 --- a/engine/src/flutter/impeller/display_list/canvas.cc +++ b/engine/src/flutter/impeller/display_list/canvas.cc @@ -1044,126 +1044,14 @@ void Canvas::DrawRoundSuperellipse(const RoundSuperellipse& round_superellipse, if (renderer_.GetContext()->GetFlags().use_sdfs && !paint.mask_blur_descriptor.has_value()) { - auto split_proc = [](Scalar left, Scalar right, Scalar ratio_left, - Scalar ratio_right) -> Scalar { - if (ratio_left == 0 && ratio_right == 0) { - return (left + right) / 2; - } - return (left * ratio_right + right * ratio_left) / - (ratio_left + ratio_right); - }; auto round_superellipse_params = RoundSuperellipseParam::MakeBoundsRadii( round_superellipse.GetBounds(), round_superellipse.GetRadii()); - RoundSuperellipseParam::Quadrant tr = round_superellipse_params.top_right; - RoundSuperellipseParam::Quadrant br = - round_superellipse_params.all_corners_same - ? tr - : round_superellipse_params.bottom_right; - RoundSuperellipseParam::Quadrant bl = - round_superellipse_params.all_corners_same - ? tr - : round_superellipse_params.bottom_left; - RoundSuperellipseParam::Quadrant tl = - round_superellipse_params.all_corners_same - ? tr - : round_superellipse_params.top_left; - - Rect bounds = round_superellipse.GetBounds(); - Point center = bounds.GetCenter(); - RoundingRadii radii = round_superellipse.GetRadii(); - - Scalar top_split = split_proc(bounds.GetLeft(), bounds.GetRight(), - radii.top_left.width, radii.top_right.width) - - center.x; - Scalar bottom_split = - split_proc(bounds.GetLeft(), bounds.GetRight(), radii.bottom_left.width, - radii.bottom_right.width) - - center.x; - Scalar left_split = - split_proc(bounds.GetTop(), bounds.GetBottom(), radii.top_left.height, - radii.bottom_left.height) - - center.y; - Scalar right_split = - split_proc(bounds.GetTop(), bounds.GetBottom(), radii.top_right.height, - radii.bottom_right.height) - - center.y; - - Point tr_center_relative = tr.offset - center; - Point br_center_relative = br.offset - center; - Point bl_center_relative = bl.offset - center; - Point tl_center_relative = tl.offset - center; - - Vector4 superellipse_degrees_top(tr.top.se_n, br.top.se_n, bl.top.se_n, - tl.top.se_n); - Vector4 superellipse_degrees_right(tr.right.se_n, br.right.se_n, - bl.right.se_n, tl.right.se_n); - Vector4 superellipse_semi_axes_top(tr.top.se_a, br.top.se_a, bl.top.se_a, - tl.top.se_a); - Vector4 superellipse_semi_axes_right(tr.right.se_a, br.right.se_a, - bl.right.se_a, tl.right.se_a); - Vector4 angle_spans_top(tr.top.circle_max_angle.radians, - br.top.circle_max_angle.radians, - bl.top.circle_max_angle.radians, - tl.top.circle_max_angle.radians); - Vector4 angle_spans_right(tr.right.circle_max_angle.radians, - br.right.circle_max_angle.radians, - bl.right.circle_max_angle.radians, - tl.right.circle_max_angle.radians); - Vector4 octant_offsets_c(tr.top.se_a - tr.right.se_a, - br.top.se_a - br.right.se_a, - bl.top.se_a - bl.right.se_a, - tl.top.se_a - tl.right.se_a); - Vector4 radii_width(tr.top.circle_radius, br.top.circle_radius, - bl.top.circle_radius, tl.top.circle_radius); - Vector4 radii_height(tr.right.circle_radius, br.right.circle_radius, - bl.right.circle_radius, tl.right.circle_radius); - Vector4 circle_centers_top_x(tr.top.circle_center.x, br.top.circle_center.x, - bl.top.circle_center.x, - tl.top.circle_center.x); - Vector4 circle_centers_top_y(tr.top.circle_center.y, br.top.circle_center.y, - bl.top.circle_center.y, - tl.top.circle_center.y); - Vector4 circle_centers_right_x( - tr.right.circle_center.x, br.right.circle_center.x, - bl.right.circle_center.x, tl.right.circle_center.x); - Vector4 circle_centers_right_y( - tr.right.circle_center.y, br.right.circle_center.y, - bl.right.circle_center.y, tl.right.circle_center.y); - Vector4 superellipse_scales_x( - tr.signed_scale.Abs().x, br.signed_scale.Abs().x, - bl.signed_scale.Abs().x, tl.signed_scale.Abs().x); - Vector4 superellipse_scales_y( - tr.signed_scale.Abs().y, br.signed_scale.Abs().y, - bl.signed_scale.Abs().y, tl.signed_scale.Abs().y); - Vector4 quadrant_centers_x(tr_center_relative.x, br_center_relative.x, - bl_center_relative.x, tl_center_relative.x); - Vector4 quadrant_centers_y(tr_center_relative.y, br_center_relative.y, - bl_center_relative.y, tl_center_relative.y); - Vector4 quadrant_splits(top_split, bottom_split, left_split, right_split); - auto params = UberSDFParameters::MakeRoundedSuperellipse( /*color=*/paint.color, - /*bounds=*/bounds, - /*superellipse_degrees_top=*/superellipse_degrees_top, - /*superellipse_degrees_right=*/superellipse_degrees_right, - /*superellipse_semi_axes_top=*/superellipse_semi_axes_top, - /*superellipse_semi_axes_right=*/superellipse_semi_axes_right, - /*angle_spans_top=*/angle_spans_top, - /*angle_spans_right=*/angle_spans_right, - /*octant_offsets_c=*/octant_offsets_c, - /*radii_width=*/radii_width, - /*radii_height=*/radii_height, - /*circle_centers_top_x=*/circle_centers_top_x, - /*circle_centers_top_y=*/circle_centers_top_y, - /*circle_centers_right_x=*/circle_centers_right_x, - /*circle_centers_right_y=*/circle_centers_right_y, - /*superellipse_scales_x=*/superellipse_scales_x, - /*superellipse_scales_y=*/superellipse_scales_y, - /*quadrant_centers_x=*/quadrant_centers_x, - /*quadrant_centers_y=*/quadrant_centers_y, - /*quadrant_splits=*/quadrant_splits, + /*bounds=*/round_superellipse.GetBounds(), + /*round_superellipse_params=*/round_superellipse_params, /*stroke=*/paint.GetStroke()); AddRenderSDFEntityToCurrentPass(paint, params); 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 ab817517875e0..d4b6123cc0f58 100644 --- a/engine/src/flutter/impeller/entity/contents/uber_sdf_contents.cc +++ b/engine/src/flutter/impeller/entity/contents/uber_sdf_contents.cc @@ -73,12 +73,6 @@ bool UberSDFContents::Render(const ContentContext& renderer, params_.color.WithAlpha(params_.color.alpha * GetOpacityFactor()); frag_info.center = params_.center; frag_info.size = params_.size; - frag_info.radii = - Vector4(params_.radii.bottom_right.width, params_.radii.top_right.width, - params_.radii.bottom_left.width, params_.radii.top_left.width); - frag_info.radii_right = - Vector4(params_.radii.bottom_right.height, params_.radii.top_right.height, - params_.radii.bottom_left.height, params_.radii.top_left.height); frag_info.stroked = params_.stroke ? 1.0f : 0.0f; frag_info.stroke_width = params_.stroke ? params_.stroke->width : 0.0f; frag_info.stroke_join = 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 edc958ebbb9bb..9ec9a797ee563 100644 --- a/engine/src/flutter/impeller/entity/contents/uber_sdf_parameters.cc +++ b/engine/src/flutter/impeller/entity/contents/uber_sdf_parameters.cc @@ -63,61 +63,70 @@ UberSDFParameters UberSDFParameters::MakeRoundedRect( const RoundingRadii& radii, std::optional stroke) { Point size = Point(rect.GetSize() * 0.5f); - return UberSDFParameters{.type = Type::kRoundedRect, - .color = color, - .center = rect.GetCenter(), - .size = size, - .stroke = stroke, - .radii = radii}; + return UberSDFParameters{ + .type = Type::kRoundedRect, + .color = color, + .center = rect.GetCenter(), + .size = size, + .stroke = stroke, + .radii_width = Vector4(radii.bottom_right.width, radii.top_right.width, + radii.bottom_left.width, radii.top_left.width)}; } UberSDFParameters UberSDFParameters::MakeRoundedSuperellipse( Color color, - Rect rect, - Vector4 superellipse_degrees_top, - Vector4 superellipse_degrees_right, - Vector4 superellipse_semi_axes_top, - Vector4 superellipse_semi_axes_right, - Vector4 angle_spans_top, - Vector4 angle_spans_right, - Vector4 octant_offsets_c, - Vector4 radii_width, - Vector4 radii_height, - Vector4 circle_centers_top_x, - Vector4 circle_centers_top_y, - Vector4 circle_centers_right_x, - Vector4 circle_centers_right_y, - Vector4 superellipse_scales_x, - Vector4 superellipse_scales_y, - Vector4 quadrant_centers_x, - Vector4 quadrant_centers_y, - Vector4 quadrant_splits, + const Rect& bounds, + const RoundSuperellipseParam& round_superellipse_params, std::optional stroke) { - Point size = Point(rect.GetSize() * 0.5f); + Point center = bounds.GetCenter(); + + RoundSuperellipseParam::Quadrant top_right = round_superellipse_params.top_right; + RoundSuperellipseParam::Quadrant bottom_right = + round_superellipse_params.all_corners_same + ? top_right + : round_superellipse_params.bottom_right; + RoundSuperellipseParam::Quadrant bottom_left = + round_superellipse_params.all_corners_same + ? top_right + : round_superellipse_params.bottom_left; + RoundSuperellipseParam::Quadrant top_left = + round_superellipse_params.all_corners_same + ? top_right + : round_superellipse_params.top_left; + + Point top_right_center_relative = top_right.offset - center; + Point bottom_right_center_relative = bottom_right.offset - center; + Point bottom_left_center_relative = bottom_left.offset - center; + Point top_left_center_relative = top_left.offset - center; + + Point size = Point(bounds.GetSize() * 0.5f); return UberSDFParameters{ .type = Type::kRoundedSuperellipse, .color = color, - .center = rect.GetCenter(), + .center = center, .size = size, .stroke = stroke, - .superellipse_degrees_top = superellipse_degrees_top, - .superellipse_degrees_right = superellipse_degrees_right, - .superellipse_semi_axes_top = superellipse_semi_axes_top, - .superellipse_semi_axes_right = superellipse_semi_axes_right, - .angle_spans_top = angle_spans_top, - .angle_spans_right = angle_spans_right, - .octant_offsets_c = octant_offsets_c, - .radii_width = radii_width, - .radii_height = radii_height, - .circle_centers_top_x = circle_centers_top_x, - .circle_centers_top_y = circle_centers_top_y, - .circle_centers_right_x = circle_centers_right_x, - .circle_centers_right_y = circle_centers_right_y, - .superellipse_scales_x = superellipse_scales_x, - .superellipse_scales_y = superellipse_scales_y, - .quadrant_centers_x = quadrant_centers_x, - .quadrant_centers_y = quadrant_centers_y, - .quadrant_splits = quadrant_splits}; + .superellipse_degrees_top = Vector4(top_right.top.se_n, bottom_right.top.se_n, bottom_left.top.se_n, top_left.top.se_n), + .superellipse_degrees_right = Vector4(top_right.right.se_n, bottom_right.right.se_n, bottom_left.right.se_n, top_left.right.se_n), + .superellipse_semi_axes_top = Vector4(top_right.top.se_a, bottom_right.top.se_a, bottom_left.top.se_a, top_left.top.se_a), + .superellipse_semi_axes_right = Vector4(top_right.right.se_a, bottom_right.right.se_a, bottom_left.right.se_a, top_left.right.se_a), + .angle_spans_top = Vector4(top_right.top.circle_max_angle.radians, bottom_right.top.circle_max_angle.radians, bottom_left.top.circle_max_angle.radians, top_left.top.circle_max_angle.radians), + .angle_spans_right = Vector4(top_right.right.circle_max_angle.radians, bottom_right.right.circle_max_angle.radians, bottom_left.right.circle_max_angle.radians, top_left.right.circle_max_angle.radians), + .octant_offsets_c = Vector4(top_right.top.se_a - top_right.right.se_a, bottom_right.top.se_a - bottom_right.right.se_a, bottom_left.top.se_a - bottom_left.right.se_a, top_left.top.se_a - top_left.right.se_a), + .radii_width = Vector4(top_right.top.circle_radius, bottom_right.top.circle_radius, bottom_left.top.circle_radius, top_left.top.circle_radius), + .radii_height = Vector4(top_right.right.circle_radius, bottom_right.right.circle_radius, bottom_left.right.circle_radius, top_left.right.circle_radius), + .circle_centers_top_x = Vector4(top_right.top.circle_center.x, bottom_right.top.circle_center.x, bottom_left.top.circle_center.x, top_left.top.circle_center.x), + .circle_centers_top_y = Vector4(top_right.top.circle_center.y, bottom_right.top.circle_center.y, bottom_left.top.circle_center.y, top_left.top.circle_center.y), + .circle_centers_right_x = Vector4(top_right.right.circle_center.x, bottom_right.right.circle_center.x, bottom_left.right.circle_center.x, top_left.right.circle_center.x), + .circle_centers_right_y = Vector4(top_right.right.circle_center.y, bottom_right.right.circle_center.y, bottom_left.right.circle_center.y, top_left.right.circle_center.y), + .superellipse_scales_x = Vector4(top_right.signed_scale.Abs().x, bottom_right.signed_scale.Abs().x, bottom_left.signed_scale.Abs().x, top_left.signed_scale.Abs().x), + .superellipse_scales_y = Vector4(top_right.signed_scale.Abs().y, bottom_right.signed_scale.Abs().y, bottom_left.signed_scale.Abs().y, top_left.signed_scale.Abs().y), + .quadrant_centers_x = Vector4(top_right_center_relative.x, bottom_right_center_relative.x, bottom_left_center_relative.x, top_left_center_relative.x), + .quadrant_centers_y = Vector4(top_right_center_relative.y, bottom_right_center_relative.y, bottom_left_center_relative.y, top_left_center_relative.y), + .quadrant_splits = Vector4(round_superellipse_params.top_split - center.x, + round_superellipse_params.bottom_split - center.x, + round_superellipse_params.left_split - center.y, + round_superellipse_params.right_split - center.y)}; } } // 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 8323c57a59c4a..d7aac91732ead 100644 --- a/engine/src/flutter/impeller/entity/contents/uber_sdf_parameters.h +++ b/engine/src/flutter/impeller/entity/contents/uber_sdf_parameters.h @@ -11,6 +11,7 @@ #include "impeller/geometry/point.h" #include "impeller/geometry/rect.h" #include "impeller/geometry/round_rect.h" +#include "impeller/geometry/round_superellipse_param.h" #include "impeller/geometry/stroke_parameters.h" #include "impeller/geometry/vector.h" @@ -57,25 +58,8 @@ struct UberSDFParameters { /// Creates UberSDFParameters for an asymmetric round superellipse. static UberSDFParameters MakeRoundedSuperellipse( Color color, - Rect bounds, - Vector4 superellipse_degrees_top, - Vector4 superellipse_degrees_right, - Vector4 superellipse_semi_axes_top, - Vector4 superellipse_semi_axes_right, - Vector4 angle_spans_top, - Vector4 angle_spans_right, - Vector4 octant_offsets_c, - Vector4 radii_width, - Vector4 radii_height, - Vector4 circle_centers_top_x, - Vector4 circle_centers_top_y, - Vector4 circle_centers_right_x, - Vector4 circle_centers_right_y, - Vector4 superellipse_scales_x, - Vector4 superellipse_scales_y, - Vector4 quadrant_centers_x, - Vector4 quadrant_centers_y, - Vector4 quadrant_splits, + const Rect& bounds, + const RoundSuperellipseParam& round_superellipse_params, std::optional stroke); /// The type of shape to render. @@ -95,9 +79,6 @@ struct UberSDFParameters { /// The stroke parameters. If std::nullopt, the shape is filled. std::optional stroke; - /// The corner radii for a rounded shapes. - RoundingRadii radii; - Vector4 superellipse_degrees_top; Vector4 superellipse_degrees_right; Vector4 superellipse_semi_axes_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 56cafc90d0720..f78504a7e7da4 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 @@ -116,10 +116,10 @@ TEST(UberSDFParametersTest, MakeRoundedRect) { EXPECT_EQ(params.color, Color::Red()); EXPECT_EQ(params.center, Point(60, 70)); EXPECT_EQ(params.size, Point(50, 50)); - EXPECT_EQ(params.radii.top_left.width, 1.0f); - EXPECT_EQ(params.radii.top_right.width, 2.0f); - EXPECT_EQ(params.radii.bottom_left.width, 3.0f); - EXPECT_EQ(params.radii.bottom_right.width, 4.0f); + EXPECT_EQ(params.radii_width.w, 1.0f); + EXPECT_EQ(params.radii_width.y, 2.0f); + EXPECT_EQ(params.radii_width.z, 3.0f); + EXPECT_EQ(params.radii_width.x, 4.0f); EXPECT_FALSE(params.stroke.has_value()); } diff --git a/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag b/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag index a61ab00ec1f35..f8f7e75418028 100644 --- a/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag +++ b/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag @@ -16,8 +16,6 @@ uniform FragInfo { float aa_pixels; float stroked; float type; - vec4 radii; - vec4 radii_right; vec4 superellipse_degrees_top; vec4 superellipse_degrees_right; vec4 superellipse_semi_axes_top; @@ -167,16 +165,10 @@ float getQuadrantDistance(vec2 p, int quadrant_index) { else q_sign = vec2(-1.0, -1.0); vec2 p_local = (p - q_center) * q_sign; - vec2 extents = vec2(scale.x * se_a_top, scale.y * se_a_right); - vec2 d_rect = p_local - extents; - float dist_rect_local = length(max(d_rect, 0.0)) + min(max(d_rect.x, d_rect.y), 0.0); + vec2 p_clamped = max(p_local, 0.0); - if (p_local.x <= 0.0 || p_local.y <= 0.0) { - return dist_rect_local; - } - - vec2 p_norm = p_local / scale; + vec2 p_norm = p_clamped / scale; float se_degree; float span; @@ -201,9 +193,7 @@ float getQuadrantDistance(vec2 p, int quadrant_index) { axis_length = se_a_right; } - if (se_degree < 1.9) { // Sharp rectangular corner - return dist_rect_local; - } + vec2 p_rel = p_oct - circle_center; float theta = atan(p_rel.y, p_rel.x); @@ -233,53 +223,15 @@ float getQuadrantDistance(vec2 p, int quadrant_index) { float corner_dist = d_scaled / length(grad_norm / scale); - return max(dist_rect_local, corner_dist); + return corner_dist; } float distanceFromRoundedSuperellipse(vec2 p) { - vec2 T = vec2(frag_info.quadrant_splits.x, -frag_info.size.y); - vec2 R = vec2(frag_info.size.x, frag_info.quadrant_splits.w); - vec2 B = vec2(frag_info.quadrant_splits.y, frag_info.size.y); - vec2 L = vec2(-frag_info.size.x, frag_info.quadrant_splits.z); - - // Exact Wedge Partition from Bounding Box Center (0,0) - // Using (0,0) guarantees that the rays to symmetric split points are perfectly horizontal/vertical, - // preventing diagonal hairline discontinuities between symmetric quadrants. - float cT = T.x * p.y - T.y * p.x; - float cR = R.x * p.y - R.y * p.x; - float cB = B.x * p.y - B.y * p.x; - float cL = L.x * p.y - L.y * p.x; - - int quadrant_index = 0; - if (cT >= 0.0 && cR <= 0.0 && dot(p, T + R) >= 0.0) { - quadrant_index = 0; // TR - } else if (cR >= 0.0 && cB <= 0.0 && dot(p, R + B) >= 0.0) { - quadrant_index = 1; // BR - } else if (cB >= 0.0 && cL <= 0.0 && dot(p, B + L) >= 0.0) { - quadrant_index = 2; // BL - } else { - quadrant_index = 3; // TL - } - - // When two split points meet at a corner (zero radius), we reduce the - // partitions to perfectly partition the shape without evaluating the degenerate corner. - bool TR_zero = distance(T, R) < 0.01; - bool BR_zero = distance(R, B) < 0.01; - bool BL_zero = distance(B, L) < 0.01; - bool TL_zero = distance(L, T) < 0.01; - - if (TR_zero && quadrant_index == 0) quadrant_index = 3; - if (BR_zero && quadrant_index == 1) quadrant_index = 0; - if (BL_zero && quadrant_index == 2) quadrant_index = 1; - if (TL_zero && quadrant_index == 3) quadrant_index = 2; - - // Second pass in case of multiple zero-radius corners - if (TR_zero && quadrant_index == 0) quadrant_index = 3; - if (BR_zero && quadrant_index == 1) quadrant_index = 0; - if (BL_zero && quadrant_index == 2) quadrant_index = 1; - if (TL_zero && quadrant_index == 3) quadrant_index = 2; - - return getQuadrantDistance(p, quadrant_index); + float d0 = getQuadrantDistance(p, 0); + float d1 = getQuadrantDistance(p, 1); + float d2 = getQuadrantDistance(p, 2); + float d3 = getQuadrantDistance(p, 3); + return max(max(d0, d1), max(d2, d3)); } // Define an ellipse as q(w) = (a*cos(w), b*sin(w)), and p = (x, y) on the // plane. Let q(w0) be the closest point on q to p, then q(w0) - p is tangent to @@ -455,7 +407,7 @@ vec2 filledSDF(vec2 p) { } else if (frag_info.type < 2.5) { // Oval sdf = distanceFromOval(p, frag_info.size); } else if (frag_info.type < 3.5) { // Rounded Rect - sdf = distanceFromRoundedRect(p, frag_info.size, frag_info.radii); + sdf = distanceFromRoundedRect(p, frag_info.size, frag_info.radii_width); } else { sdf = distanceFromRoundedSuperellipse(p); } @@ -534,7 +486,40 @@ void main() { float fade_size = pixel_size * frag_info.aa_pixels * 0.5; float alpha = 1.0 - smoothstep(-fade_size, fade_size, sdf); - frag_color = vec4(frag_info.color.rgb, frag_info.color.a * alpha); - frag_color = IPPremultiply(frag_color); + if (frag_info.type > 3.5) { // Rounded Superellipse + // Base visualizer + vec3 col = (sdf < 0.0) ? vec3(0.9, 0.6, 0.3) : vec3(0.4, 0.7, 0.85); + col *= 1.0 - exp(-3.0 * abs(sdf / 100.0)); + col *= 0.8 + 0.2 * cos(1.2 * sdf); + col = mix(col, vec3(1.0), 1.0 - smoothstep(0.0, 1.5, abs(sdf))); + + // 1. Quadrant centers TR (Red), BR (Green), BL (Blue), TL (Yellow) + float dot_radius = 3.5; + vec2 C_TR = vec2(frag_info.quadrant_centers_x[0], frag_info.quadrant_centers_y[0]); + vec2 C_BR = vec2(frag_info.quadrant_centers_x[1], frag_info.quadrant_centers_y[1]); + vec2 C_BL = vec2(frag_info.quadrant_centers_x[2], frag_info.quadrant_centers_y[2]); + vec2 C_TL = vec2(frag_info.quadrant_centers_x[3], frag_info.quadrant_centers_y[3]); + + float dt_ctr_TR = length(p - C_TR) - dot_radius; + float dt_ctr_BR = length(p - C_BR) - dot_radius; + float dt_ctr_BL = length(p - C_BL) - dot_radius; + float dt_ctr_TL = length(p - C_TL) - dot_radius; + + float alpha_ctr_TR = max(0.0, 1.0 - smoothstep(-1.0, 1.0, dt_ctr_TR)); + float alpha_ctr_BR = max(0.0, 1.0 - smoothstep(-1.0, 1.0, dt_ctr_BR)); + float alpha_ctr_BL = max(0.0, 1.0 - smoothstep(-1.0, 1.0, dt_ctr_BL)); + float alpha_ctr_TL = max(0.0, 1.0 - smoothstep(-1.0, 1.0, dt_ctr_TL)); + + col = mix(col, vec3(1.0, 0.0, 0.0), alpha_ctr_TR); // Red + col = mix(col, vec3(0.0, 1.0, 0.0), alpha_ctr_BR); // Green + col = mix(col, vec3(0.0, 0.0, 1.0), alpha_ctr_BL); // Blue + col = mix(col, vec3(1.0, 1.0, 0.0), alpha_ctr_TL); // Yellow + + frag_color = vec4(col, frag_info.color.a); + frag_color = IPPremultiply(frag_color); + } { + frag_color = vec4(frag_info.color.rgb, frag_info.color.a * alpha); + frag_color = IPPremultiply(frag_color); + } } diff --git a/engine/src/flutter/impeller/geometry/round_superellipse_param.cc b/engine/src/flutter/impeller/geometry/round_superellipse_param.cc index f3595b318b87f..d1c659d0ca13d 100644 --- a/engine/src/flutter/impeller/geometry/round_superellipse_param.cc +++ b/engine/src/flutter/impeller/geometry/round_superellipse_param.cc @@ -583,6 +583,10 @@ RoundSuperellipseParam RoundSuperellipseParam::MakeBoundsRadii( .top_right = ComputeQuadrant(bounds.GetCenter(), bounds.GetRightTop(), radii.top_right, {-1, 1}), .all_corners_same = true, + .top_split = bounds.GetCenter().x, + .bottom_split = bounds.GetCenter().x, + .left_split = bounds.GetCenter().y, + .right_split = bounds.GetCenter().y, }; } Scalar top_split = Split(bounds.GetLeft(), bounds.GetRight(), @@ -609,6 +613,10 @@ RoundSuperellipseParam RoundSuperellipseParam::MakeBoundsRadii( ComputeQuadrant(Point{top_split, left_split}, bounds.GetLeftTop(), radii.top_left, {-1, -1}), .all_corners_same = false, + .top_split = top_split, + .bottom_split = bottom_split, + .left_split = left_split, + .right_split = right_split, }; } diff --git a/engine/src/flutter/impeller/geometry/round_superellipse_param.h b/engine/src/flutter/impeller/geometry/round_superellipse_param.h index adad852cdd88c..6ca7df1fc70e4 100644 --- a/engine/src/flutter/impeller/geometry/round_superellipse_param.h +++ b/engine/src/flutter/impeller/geometry/round_superellipse_param.h @@ -96,6 +96,11 @@ struct RoundSuperellipseParam { // If true, all corners are the same and only `top_right` is popularized. bool all_corners_same; + Scalar top_split; + Scalar bottom_split; + Scalar left_split; + Scalar right_split; + // Create a param for a rounded superellipse with the specific bounds and // radii. [[nodiscard]] static RoundSuperellipseParam MakeBoundsRadii( From c4dbac31949324c0c12db5346d1de6df11b4e46c Mon Sep 17 00:00:00 2001 From: Evan Walley Date: Tue, 12 May 2026 19:45:30 -0700 Subject: [PATCH 03/15] add tests --- .../display_list/aiks_dl_basic_unittests.cc | 53 ++++++++ .../flutter/impeller/display_list/canvas.cc | 1 - .../entity/contents/uber_sdf_parameters.cc | 84 +++++++++---- .../contents/uber_sdf_parameters_unittests.cc | 22 ++++ .../impeller/entity/shaders/uber_sdf.frag | 115 ++++++++++-------- 5 files changed, 201 insertions(+), 74 deletions(-) diff --git a/engine/src/flutter/impeller/display_list/aiks_dl_basic_unittests.cc b/engine/src/flutter/impeller/display_list/aiks_dl_basic_unittests.cc index 44cf95758e354..67d2182119ba4 100644 --- a/engine/src/flutter/impeller/display_list/aiks_dl_basic_unittests.cc +++ b/engine/src/flutter/impeller/display_list/aiks_dl_basic_unittests.cc @@ -2499,6 +2499,59 @@ TEST_P(AiksTest, CanRenderFilledRoundSuperellipses) { ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); } +TEST_P(AiksTest, CanRenderAsymmetricRoundSuperellipses) { + DisplayListBuilder builder; + builder.DrawColor(DlColor::kWhite(), DlBlendMode::kSrc); + DlPaint paint; + paint.setColor(DlColor::kBlue()); + + builder.DrawRoundSuperellipse( + DlRoundSuperellipse::MakeRectRadii( + /*rect=*/DlRect::MakeXYWH(50, 50, 220, 220), /*radii=*/ + { + .top_left = Size(30.0f, 120.0f), + .top_right = Size(100.0f, 20.0f), + .bottom_left = Size(90.0f, 10.0f), + .bottom_right = Size(20.0f, 100.0f), + }), + paint); + + builder.DrawRoundSuperellipse( + DlRoundSuperellipse::MakeRectRadii( + /*rect=*/DlRect::MakeXYWH(350, 50, 220, 220), /*radii=*/ + { + .top_left = Size(120.0f, 20.0f), + .top_right = Size(20.0f, 120.0f), + .bottom_left = Size(20.0f, 120.0f), + .bottom_right = Size(120.0f, 20.0f), + }), + paint); + + builder.DrawRoundSuperellipse( + DlRoundSuperellipse::MakeRectRadii( + /*rect=*/DlRect::MakeXYWH(50, 350, 200, 200), /*radii=*/ + { + .top_left = Size(120.0f, 120.0f), + .top_right = Size(20.0f, 20.0f), + .bottom_left = Size(20.0f, 20.0f), + .bottom_right = Size(20.0f, 20.0f), + }), + paint); + + builder.DrawRoundSuperellipse( + DlRoundSuperellipse::MakeRectRadii( + /*rect=*/DlRect::MakeXYWH(350, 350, 200, 200), /*radii=*/ + { + .top_left = Size(120.0f, 120.0f), + .top_right = Size(20.0f, 20.0f), + .bottom_left = Size(20.0f, 20.0f), + .bottom_right = Size(120.0f, 120.0f), + }), + paint); + + ASSERT_TRUE(OpenPlaygroundHere(builder.Build())); +} + TEST_P(AiksTest, CanRenderStrokedRoundSuperellipses) { DisplayListBuilder builder; builder.DrawColor(DlColor::kWhite(), DlBlendMode::kSrc); diff --git a/engine/src/flutter/impeller/display_list/canvas.cc b/engine/src/flutter/impeller/display_list/canvas.cc index 02c77fa93e8a5..ce5aa146e1c26 100644 --- a/engine/src/flutter/impeller/display_list/canvas.cc +++ b/engine/src/flutter/impeller/display_list/canvas.cc @@ -1044,7 +1044,6 @@ void Canvas::DrawRoundSuperellipse(const RoundSuperellipse& round_superellipse, if (renderer_.GetContext()->GetFlags().use_sdfs && !paint.mask_blur_descriptor.has_value()) { - auto round_superellipse_params = RoundSuperellipseParam::MakeBoundsRadii( round_superellipse.GetBounds(), round_superellipse.GetRadii()); 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 9ec9a797ee563..b28b8b30699de 100644 --- a/engine/src/flutter/impeller/entity/contents/uber_sdf_parameters.cc +++ b/engine/src/flutter/impeller/entity/contents/uber_sdf_parameters.cc @@ -80,7 +80,8 @@ UberSDFParameters UberSDFParameters::MakeRoundedSuperellipse( std::optional stroke) { Point center = bounds.GetCenter(); - RoundSuperellipseParam::Quadrant top_right = round_superellipse_params.top_right; + RoundSuperellipseParam::Quadrant top_right = + round_superellipse_params.top_right; RoundSuperellipseParam::Quadrant bottom_right = round_superellipse_params.all_corners_same ? top_right @@ -106,27 +107,66 @@ UberSDFParameters UberSDFParameters::MakeRoundedSuperellipse( .center = center, .size = size, .stroke = stroke, - .superellipse_degrees_top = Vector4(top_right.top.se_n, bottom_right.top.se_n, bottom_left.top.se_n, top_left.top.se_n), - .superellipse_degrees_right = Vector4(top_right.right.se_n, bottom_right.right.se_n, bottom_left.right.se_n, top_left.right.se_n), - .superellipse_semi_axes_top = Vector4(top_right.top.se_a, bottom_right.top.se_a, bottom_left.top.se_a, top_left.top.se_a), - .superellipse_semi_axes_right = Vector4(top_right.right.se_a, bottom_right.right.se_a, bottom_left.right.se_a, top_left.right.se_a), - .angle_spans_top = Vector4(top_right.top.circle_max_angle.radians, bottom_right.top.circle_max_angle.radians, bottom_left.top.circle_max_angle.radians, top_left.top.circle_max_angle.radians), - .angle_spans_right = Vector4(top_right.right.circle_max_angle.radians, bottom_right.right.circle_max_angle.radians, bottom_left.right.circle_max_angle.radians, top_left.right.circle_max_angle.radians), - .octant_offsets_c = Vector4(top_right.top.se_a - top_right.right.se_a, bottom_right.top.se_a - bottom_right.right.se_a, bottom_left.top.se_a - bottom_left.right.se_a, top_left.top.se_a - top_left.right.se_a), - .radii_width = Vector4(top_right.top.circle_radius, bottom_right.top.circle_radius, bottom_left.top.circle_radius, top_left.top.circle_radius), - .radii_height = Vector4(top_right.right.circle_radius, bottom_right.right.circle_radius, bottom_left.right.circle_radius, top_left.right.circle_radius), - .circle_centers_top_x = Vector4(top_right.top.circle_center.x, bottom_right.top.circle_center.x, bottom_left.top.circle_center.x, top_left.top.circle_center.x), - .circle_centers_top_y = Vector4(top_right.top.circle_center.y, bottom_right.top.circle_center.y, bottom_left.top.circle_center.y, top_left.top.circle_center.y), - .circle_centers_right_x = Vector4(top_right.right.circle_center.x, bottom_right.right.circle_center.x, bottom_left.right.circle_center.x, top_left.right.circle_center.x), - .circle_centers_right_y = Vector4(top_right.right.circle_center.y, bottom_right.right.circle_center.y, bottom_left.right.circle_center.y, top_left.right.circle_center.y), - .superellipse_scales_x = Vector4(top_right.signed_scale.Abs().x, bottom_right.signed_scale.Abs().x, bottom_left.signed_scale.Abs().x, top_left.signed_scale.Abs().x), - .superellipse_scales_y = Vector4(top_right.signed_scale.Abs().y, bottom_right.signed_scale.Abs().y, bottom_left.signed_scale.Abs().y, top_left.signed_scale.Abs().y), - .quadrant_centers_x = Vector4(top_right_center_relative.x, bottom_right_center_relative.x, bottom_left_center_relative.x, top_left_center_relative.x), - .quadrant_centers_y = Vector4(top_right_center_relative.y, bottom_right_center_relative.y, bottom_left_center_relative.y, top_left_center_relative.y), - .quadrant_splits = Vector4(round_superellipse_params.top_split - center.x, - round_superellipse_params.bottom_split - center.x, - round_superellipse_params.left_split - center.y, - round_superellipse_params.right_split - center.y)}; + .superellipse_degrees_top = + Vector4(top_right.top.se_n, bottom_right.top.se_n, + bottom_left.top.se_n, top_left.top.se_n), + .superellipse_degrees_right = + Vector4(top_right.right.se_n, bottom_right.right.se_n, + bottom_left.right.se_n, top_left.right.se_n), + .superellipse_semi_axes_top = + Vector4(top_right.top.se_a, bottom_right.top.se_a, + bottom_left.top.se_a, top_left.top.se_a), + .superellipse_semi_axes_right = + Vector4(top_right.right.se_a, bottom_right.right.se_a, + bottom_left.right.se_a, top_left.right.se_a), + .angle_spans_top = Vector4(top_right.top.circle_max_angle.radians, + bottom_right.top.circle_max_angle.radians, + bottom_left.top.circle_max_angle.radians, + top_left.top.circle_max_angle.radians), + .angle_spans_right = Vector4(top_right.right.circle_max_angle.radians, + bottom_right.right.circle_max_angle.radians, + bottom_left.right.circle_max_angle.radians, + top_left.right.circle_max_angle.radians), + .octant_offsets_c = + Vector4(top_right.top.se_a - top_right.right.se_a, + bottom_right.top.se_a - bottom_right.right.se_a, + bottom_left.top.se_a - bottom_left.right.se_a, + top_left.top.se_a - top_left.right.se_a), + .radii_width = + Vector4(top_right.top.circle_radius, bottom_right.top.circle_radius, + bottom_left.top.circle_radius, top_left.top.circle_radius), + .radii_height = Vector4( + top_right.right.circle_radius, bottom_right.right.circle_radius, + bottom_left.right.circle_radius, top_left.right.circle_radius), + .circle_centers_top_x = Vector4( + top_right.top.circle_center.x, bottom_right.top.circle_center.x, + bottom_left.top.circle_center.x, top_left.top.circle_center.x), + .circle_centers_top_y = Vector4( + top_right.top.circle_center.y, bottom_right.top.circle_center.y, + bottom_left.top.circle_center.y, top_left.top.circle_center.y), + .circle_centers_right_x = Vector4( + top_right.right.circle_center.x, bottom_right.right.circle_center.x, + bottom_left.right.circle_center.x, top_left.right.circle_center.x), + .circle_centers_right_y = Vector4( + top_right.right.circle_center.y, bottom_right.right.circle_center.y, + bottom_left.right.circle_center.y, top_left.right.circle_center.y), + .superellipse_scales_x = Vector4( + top_right.signed_scale.Abs().x, bottom_right.signed_scale.Abs().x, + bottom_left.signed_scale.Abs().x, top_left.signed_scale.Abs().x), + .superellipse_scales_y = Vector4( + top_right.signed_scale.Abs().y, bottom_right.signed_scale.Abs().y, + bottom_left.signed_scale.Abs().y, top_left.signed_scale.Abs().y), + .quadrant_centers_x = + Vector4(top_right_center_relative.x, bottom_right_center_relative.x, + bottom_left_center_relative.x, top_left_center_relative.x), + .quadrant_centers_y = + Vector4(top_right_center_relative.y, bottom_right_center_relative.y, + bottom_left_center_relative.y, top_left_center_relative.y), + .quadrant_splits = + Vector4(round_superellipse_params.top_split - center.x, + round_superellipse_params.bottom_split - center.x, + round_superellipse_params.left_split - center.y, + round_superellipse_params.right_split - center.y)}; } } // namespace impeller 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 f78504a7e7da4..de52948f8698c 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 @@ -123,5 +123,27 @@ TEST(UberSDFParametersTest, MakeRoundedRect) { EXPECT_FALSE(params.stroke.has_value()); } +TEST(UberSDFParametersTest, MakeRoundedSuperellipse) { + Rect rect = Rect::MakeXYWH(10, 20, 100, 100); + RoundingRadii radii = { + .top_left = Size(10.0f, 10.0f), + .top_right = Size(20.0f, 20.0f), + .bottom_left = Size(40.0f, 40.0f), + .bottom_right = Size(30.0f, 30.0f), + }; + auto round_superellipse_params = + RoundSuperellipseParam::MakeBoundsRadii(rect, radii); + auto params = UberSDFParameters::MakeRoundedSuperellipse( + /*color=*/Color::Red(), /*bounds=*/rect, + /*round_superellipse_params=*/round_superellipse_params, + /*stroke=*/std::nullopt); + + EXPECT_EQ(params.type, UberSDFParameters::Type::kRoundedSuperellipse); + EXPECT_EQ(params.color, Color::Red()); + EXPECT_EQ(params.center, Point(60, 70)); + EXPECT_EQ(params.size, Point(50, 50)); + EXPECT_FALSE(params.stroke.has_value()); +} + } // namespace testing } // namespace impeller diff --git a/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag b/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag index f8f7e75418028..4cfc7fc1c79c5 100644 --- a/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag +++ b/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag @@ -112,57 +112,68 @@ float sdSuperellipse(vec2 p, float n) { } float getComponent(vec4 v, int index) { - if (index == 0) return v.x; - if (index == 1) return v.y; - if (index == 2) return v.z; + if (index == 0) + return v.x; + if (index == 1) + return v.y; + if (index == 2) + return v.z; return v.w; } float smax(float a, float b, float k) { - float m = max(a, b); - // Taper the smoothing factor to 0 outside the shape to prevent bloat/overdraw. - float effective_k = k * smoothstep(0.0, -k, m); - if (effective_k < 0.001) return m; - - float h = clamp(0.5 + 0.5 * (b - a) / effective_k, 0.0, 1.0); - return mix(a, b, h) + effective_k * h * (1.0 - h); + float m = max(a, b); + // Taper the smoothing factor to 0 outside the shape to prevent + // bloat/overdraw. + float effective_k = k * smoothstep(0.0, -k, m); + if (effective_k < 0.001) + return m; + + float h = clamp(0.5 + 0.5 * (b - a) / effective_k, 0.0, 1.0); + return mix(a, b, h) + effective_k * h * (1.0 - h); } float getQuadrantDistance(vec2 p, int quadrant_index) { - float se_degree_top = getComponent(frag_info.superellipse_degrees_top, quadrant_index); - float se_degree_right = getComponent(frag_info.superellipse_degrees_right, quadrant_index); - float se_a_top = getComponent(frag_info.superellipse_semi_axes_top, quadrant_index); - float se_a_right = getComponent(frag_info.superellipse_semi_axes_right, quadrant_index); - float angle_span_top = getComponent(frag_info.angle_spans_top, quadrant_index); - float angle_span_right = getComponent(frag_info.angle_spans_right, quadrant_index); + float se_degree_top = + getComponent(frag_info.superellipse_degrees_top, quadrant_index); + float se_degree_right = + getComponent(frag_info.superellipse_degrees_right, quadrant_index); + float se_a_top = + getComponent(frag_info.superellipse_semi_axes_top, quadrant_index); + float se_a_right = + getComponent(frag_info.superellipse_semi_axes_right, quadrant_index); + float angle_span_top = + getComponent(frag_info.angle_spans_top, quadrant_index); + float angle_span_right = + getComponent(frag_info.angle_spans_right, quadrant_index); float c = getComponent(frag_info.octant_offsets_c, quadrant_index); float radius_top = getComponent(frag_info.radii_width, quadrant_index); float radius_right = getComponent(frag_info.radii_height, quadrant_index); - vec2 circle_center_top = vec2( - getComponent(frag_info.circle_centers_top_x, quadrant_index), - getComponent(frag_info.circle_centers_top_y, quadrant_index) - ); - vec2 circle_center_right = vec2( - getComponent(frag_info.circle_centers_right_x, quadrant_index), - getComponent(frag_info.circle_centers_right_y, quadrant_index) - ); - - vec2 scale = vec2( - getComponent(frag_info.superellipse_scales_x, quadrant_index), - getComponent(frag_info.superellipse_scales_y, quadrant_index) - ); - - vec2 q_center = vec2( - getComponent(frag_info.quadrant_centers_x, quadrant_index), - getComponent(frag_info.quadrant_centers_y, quadrant_index) - ); + vec2 circle_center_top = + vec2(getComponent(frag_info.circle_centers_top_x, quadrant_index), + getComponent(frag_info.circle_centers_top_y, quadrant_index)); + vec2 circle_center_right = + vec2(getComponent(frag_info.circle_centers_right_x, quadrant_index), + getComponent(frag_info.circle_centers_right_y, quadrant_index)); + + vec2 scale = + vec2(getComponent(frag_info.superellipse_scales_x, quadrant_index), + getComponent(frag_info.superellipse_scales_y, quadrant_index)); + + vec2 q_center = + vec2(getComponent(frag_info.quadrant_centers_x, quadrant_index), + getComponent(frag_info.quadrant_centers_y, quadrant_index)); vec2 q_sign = vec2(1.0); - if (quadrant_index == 0) q_sign = vec2(1.0, -1.0); - else if (quadrant_index == 1) q_sign = vec2(1.0, 1.0); - else if (quadrant_index == 2) q_sign = vec2(-1.0, 1.0); - else q_sign = vec2(-1.0, -1.0); + if (quadrant_index == 0) + q_sign = vec2(1.0, -1.0); + else if (quadrant_index == 1) + q_sign = vec2(1.0, 1.0); + else if (quadrant_index == 2) + q_sign = vec2(-1.0, 1.0); + else + q_sign = vec2(-1.0, -1.0); vec2 p_local = (p - q_center) * q_sign; @@ -193,8 +204,6 @@ float getQuadrantDistance(vec2 p, int quadrant_index) { axis_length = se_a_right; } - - vec2 p_rel = p_oct - circle_center; float theta = atan(p_rel.y, p_rel.x); @@ -485,7 +494,7 @@ void main() { // (pixel_size * aa_pixels) in each direction. float fade_size = pixel_size * frag_info.aa_pixels * 0.5; float alpha = 1.0 - smoothstep(-fade_size, fade_size, sdf); - + if (frag_info.type > 3.5) { // Rounded Superellipse // Base visualizer vec3 col = (sdf < 0.0) ? vec3(0.9, 0.6, 0.3) : vec3(0.4, 0.7, 0.85); @@ -495,10 +504,14 @@ void main() { // 1. Quadrant centers TR (Red), BR (Green), BL (Blue), TL (Yellow) float dot_radius = 3.5; - vec2 C_TR = vec2(frag_info.quadrant_centers_x[0], frag_info.quadrant_centers_y[0]); - vec2 C_BR = vec2(frag_info.quadrant_centers_x[1], frag_info.quadrant_centers_y[1]); - vec2 C_BL = vec2(frag_info.quadrant_centers_x[2], frag_info.quadrant_centers_y[2]); - vec2 C_TL = vec2(frag_info.quadrant_centers_x[3], frag_info.quadrant_centers_y[3]); + vec2 C_TR = + vec2(frag_info.quadrant_centers_x[0], frag_info.quadrant_centers_y[0]); + vec2 C_BR = + vec2(frag_info.quadrant_centers_x[1], frag_info.quadrant_centers_y[1]); + vec2 C_BL = + vec2(frag_info.quadrant_centers_x[2], frag_info.quadrant_centers_y[2]); + vec2 C_TL = + vec2(frag_info.quadrant_centers_x[3], frag_info.quadrant_centers_y[3]); float dt_ctr_TR = length(p - C_TR) - dot_radius; float dt_ctr_BR = length(p - C_BR) - dot_radius; @@ -510,16 +523,16 @@ void main() { float alpha_ctr_BL = max(0.0, 1.0 - smoothstep(-1.0, 1.0, dt_ctr_BL)); float alpha_ctr_TL = max(0.0, 1.0 - smoothstep(-1.0, 1.0, dt_ctr_TL)); - col = mix(col, vec3(1.0, 0.0, 0.0), alpha_ctr_TR); // Red - col = mix(col, vec3(0.0, 1.0, 0.0), alpha_ctr_BR); // Green - col = mix(col, vec3(0.0, 0.0, 1.0), alpha_ctr_BL); // Blue - col = mix(col, vec3(1.0, 1.0, 0.0), alpha_ctr_TL); // Yellow + col = mix(col, vec3(1.0, 0.0, 0.0), alpha_ctr_TR); // Red + col = mix(col, vec3(0.0, 1.0, 0.0), alpha_ctr_BR); // Green + col = mix(col, vec3(0.0, 0.0, 1.0), alpha_ctr_BL); // Blue + col = mix(col, vec3(1.0, 1.0, 0.0), alpha_ctr_TL); // Yellow frag_color = vec4(col, frag_info.color.a); frag_color = IPPremultiply(frag_color); - } { + } + { frag_color = vec4(frag_info.color.rgb, frag_info.color.a * alpha); frag_color = IPPremultiply(frag_color); } - } From 99b7bfda718af891ceec795f163da5000126aaff Mon Sep 17 00:00:00 2001 From: Evan Walley Date: Tue, 12 May 2026 20:38:05 -0700 Subject: [PATCH 04/15] Add comments --- .../display_list/aiks_dl_basic_unittests.cc | 40 ++++---- .../impeller/entity/shaders/uber_sdf.frag | 98 +++++++------------ 2 files changed, 58 insertions(+), 80 deletions(-) diff --git a/engine/src/flutter/impeller/display_list/aiks_dl_basic_unittests.cc b/engine/src/flutter/impeller/display_list/aiks_dl_basic_unittests.cc index 67d2182119ba4..6ca747d96c95b 100644 --- a/engine/src/flutter/impeller/display_list/aiks_dl_basic_unittests.cc +++ b/engine/src/flutter/impeller/display_list/aiks_dl_basic_unittests.cc @@ -2507,45 +2507,45 @@ TEST_P(AiksTest, CanRenderAsymmetricRoundSuperellipses) { builder.DrawRoundSuperellipse( DlRoundSuperellipse::MakeRectRadii( - /*rect=*/DlRect::MakeXYWH(50, 50, 220, 220), /*radii=*/ + /*rect=*/DlRect::MakeXYWH(50, 50, 440, 440), /*radii=*/ { - .top_left = Size(30.0f, 120.0f), - .top_right = Size(100.0f, 20.0f), - .bottom_left = Size(90.0f, 10.0f), - .bottom_right = Size(20.0f, 100.0f), + .top_left = Size(60.0f, 240.0f), + .top_right = Size(200.0f, 40.0f), + .bottom_left = Size(180.0f, 20.0f), + .bottom_right = Size(40.0f, 200.0f), }), paint); builder.DrawRoundSuperellipse( DlRoundSuperellipse::MakeRectRadii( - /*rect=*/DlRect::MakeXYWH(350, 50, 220, 220), /*radii=*/ + /*rect=*/DlRect::MakeXYWH(550, 50, 440, 440), /*radii=*/ { - .top_left = Size(120.0f, 20.0f), - .top_right = Size(20.0f, 120.0f), - .bottom_left = Size(20.0f, 120.0f), - .bottom_right = Size(120.0f, 20.0f), + .top_left = Size(240.0f, 40.0f), + .top_right = Size(40.0f, 240.0f), + .bottom_left = Size(40.0f, 240.0f), + .bottom_right = Size(240.0f, 40.0f), }), paint); builder.DrawRoundSuperellipse( DlRoundSuperellipse::MakeRectRadii( - /*rect=*/DlRect::MakeXYWH(50, 350, 200, 200), /*radii=*/ + /*rect=*/DlRect::MakeXYWH(50, 550, 400, 400), /*radii=*/ { - .top_left = Size(120.0f, 120.0f), - .top_right = Size(20.0f, 20.0f), - .bottom_left = Size(20.0f, 20.0f), - .bottom_right = Size(20.0f, 20.0f), + .top_left = Size(240.0f, 240.0f), + .top_right = Size(40.0f, 40.0f), + .bottom_left = Size(40.0f, 40.0f), + .bottom_right = Size(40.0f, 40.0f), }), paint); builder.DrawRoundSuperellipse( DlRoundSuperellipse::MakeRectRadii( - /*rect=*/DlRect::MakeXYWH(350, 350, 200, 200), /*radii=*/ + /*rect=*/DlRect::MakeXYWH(550, 550, 400, 400), /*radii=*/ { - .top_left = Size(120.0f, 120.0f), - .top_right = Size(20.0f, 20.0f), - .bottom_left = Size(20.0f, 20.0f), - .bottom_right = Size(120.0f, 120.0f), + .top_left = Size(240.0f, 240.0f), + .top_right = Size(40.0f, 40.0f), + .bottom_left = Size(40.0f, 40.0f), + .bottom_right = Size(240.0f, 240.0f), }), paint); diff --git a/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag b/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag index 4cfc7fc1c79c5..b1b8187735722 100644 --- a/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag +++ b/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag @@ -121,19 +121,8 @@ float getComponent(vec4 v, int index) { return v.w; } -float smax(float a, float b, float k) { - float m = max(a, b); - // Taper the smoothing factor to 0 outside the shape to prevent - // bloat/overdraw. - float effective_k = k * smoothstep(0.0, -k, m); - if (effective_k < 0.001) - return m; - - float h = clamp(0.5 + 0.5 * (b - a) / effective_k, 0.0, 1.0); - return mix(a, b, h) + effective_k * h * (1.0 - h); -} - float getQuadrantDistance(vec2 p, int quadrant_index) { + // Unpack the parameters for the quadrant. float se_degree_top = getComponent(frag_info.superellipse_degrees_top, quadrant_index); float se_degree_right = @@ -175,19 +164,30 @@ float getQuadrantDistance(vec2 p, int quadrant_index) { else q_sign = vec2(-1.0, -1.0); + // Transform the point into the quadrant's local space vec2 p_local = (p - q_center) * q_sign; + // Clamp the point to positive values - this avoids issues with the sdf + // calculations below. This also means that interior distances for this + // function are not totally accurate. vec2 p_clamped = max(p_local, 0.0); + // Map p in to a square. vec2 p_norm = p_clamped / scale; + // Declare all RSE params for a single octant. float se_degree; float span; float radius; vec2 circle_center; float axis_length; + + // 'p_norm' in the coordinate system of the octant. vec2 p_oct; + // We split the quadrant along the diagonal of the transition (p_norm.y + c == + // p_norm.x). This allows us to grab the correct set of parameters for the + // "top" and "right" halves of the corner. if (p_norm.y + c > p_norm.x) { p_oct = p_norm + vec2(0.0, c); se_degree = se_degree_top; @@ -204,38 +204,54 @@ float getQuadrantDistance(vec2 p, int quadrant_index) { axis_length = se_a_right; } + // Move the point to the corner circle's coordinate system. vec2 p_rel = p_oct - circle_center; + // Grab the angle offset of the point. float theta = atan(p_rel.y, p_rel.x); + // The angular distance between the point and the 45 degree midline. float d_theta = theta - PI_OVER_FOUR; d_theta = mod(d_theta + PI, TWO_PI) - PI; - float d_scaled; + float dist_raw; vec2 grad_oct; + + // If the point is within the span of the corner circle's arc, + // use a circle SDF. + // This works because the normals of the circular and superelliptical sections + // agree at the transition angle, the total RSE curve is continuous and + // the closest point on a continuous curve to a point lies along the normal. + + // We also compute the gradient of the distance function for normalization. if (abs(d_theta) < abs(span)) { - d_scaled = distanceFromCircle(p_rel, radius); + dist_raw = distanceFromCircle(p_rel, radius); grad_oct = normalize(p_rel); } else { - d_scaled = sdSuperellipse(p_oct / axis_length, se_degree) * axis_length; + dist_raw = sdSuperellipse(p_oct / axis_length, se_degree) * axis_length; + // Clamp the coordinate to avoid division by zero vec2 p_oct_clamped = max(p_oct, vec2(0.001)); float max_p = max(p_oct_clamped.x, p_oct_clamped.y); vec2 p_safe = p_oct_clamped / max_p; + // Approximation of the gradient grad_oct = normalize(pow(p_safe, vec2(se_degree - 1.0))); } vec2 grad_norm; - if (p_norm.y + c > p_norm.x) { - grad_norm = grad_oct; - } else { - grad_norm = grad_oct.yx; + if (p_norm.y + c <= p_norm.x) { + grad_oct = grad_oct.yx; } - float corner_dist = d_scaled / length(grad_norm / scale); + // Divide the distance by the length of the gradient. + // This ensures that the resulting distance has a gradient magnitude of 1 + // everywhere, allowing to be mixed cleanly with other SDFs. + float corner_dist = dist_raw / length(grad_norm / scale); return corner_dist; } float distanceFromRoundedSuperellipse(vec2 p) { + // An RSE is constructed by four different quadrant curves, + // take the max distance from all four curves. float d0 = getQuadrantDistance(p, 0); float d1 = getQuadrantDistance(p, 1); float d2 = getQuadrantDistance(p, 2); @@ -495,44 +511,6 @@ void main() { float fade_size = pixel_size * frag_info.aa_pixels * 0.5; float alpha = 1.0 - smoothstep(-fade_size, fade_size, sdf); - if (frag_info.type > 3.5) { // Rounded Superellipse - // Base visualizer - vec3 col = (sdf < 0.0) ? vec3(0.9, 0.6, 0.3) : vec3(0.4, 0.7, 0.85); - col *= 1.0 - exp(-3.0 * abs(sdf / 100.0)); - col *= 0.8 + 0.2 * cos(1.2 * sdf); - col = mix(col, vec3(1.0), 1.0 - smoothstep(0.0, 1.5, abs(sdf))); - - // 1. Quadrant centers TR (Red), BR (Green), BL (Blue), TL (Yellow) - float dot_radius = 3.5; - vec2 C_TR = - vec2(frag_info.quadrant_centers_x[0], frag_info.quadrant_centers_y[0]); - vec2 C_BR = - vec2(frag_info.quadrant_centers_x[1], frag_info.quadrant_centers_y[1]); - vec2 C_BL = - vec2(frag_info.quadrant_centers_x[2], frag_info.quadrant_centers_y[2]); - vec2 C_TL = - vec2(frag_info.quadrant_centers_x[3], frag_info.quadrant_centers_y[3]); - - float dt_ctr_TR = length(p - C_TR) - dot_radius; - float dt_ctr_BR = length(p - C_BR) - dot_radius; - float dt_ctr_BL = length(p - C_BL) - dot_radius; - float dt_ctr_TL = length(p - C_TL) - dot_radius; - - float alpha_ctr_TR = max(0.0, 1.0 - smoothstep(-1.0, 1.0, dt_ctr_TR)); - float alpha_ctr_BR = max(0.0, 1.0 - smoothstep(-1.0, 1.0, dt_ctr_BR)); - float alpha_ctr_BL = max(0.0, 1.0 - smoothstep(-1.0, 1.0, dt_ctr_BL)); - float alpha_ctr_TL = max(0.0, 1.0 - smoothstep(-1.0, 1.0, dt_ctr_TL)); - - col = mix(col, vec3(1.0, 0.0, 0.0), alpha_ctr_TR); // Red - col = mix(col, vec3(0.0, 1.0, 0.0), alpha_ctr_BR); // Green - col = mix(col, vec3(0.0, 0.0, 1.0), alpha_ctr_BL); // Blue - col = mix(col, vec3(1.0, 1.0, 0.0), alpha_ctr_TL); // Yellow - - frag_color = vec4(col, frag_info.color.a); - frag_color = IPPremultiply(frag_color); - } - { - frag_color = vec4(frag_info.color.rgb, frag_info.color.a * alpha); - frag_color = IPPremultiply(frag_color); - } + frag_color = vec4(frag_info.color.rgb, frag_info.color.a * alpha); + frag_color = IPPremultiply(frag_color); } From 96c5935da1468dcf70ce399d262abfbf3ef43778 Mon Sep 17 00:00:00 2001 From: Evan Walley Date: Tue, 12 May 2026 20:42:02 -0700 Subject: [PATCH 05/15] More comments --- .../entity/contents/uber_sdf_parameters.h | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) 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 d7aac91732ead..e487a275b932a 100644 --- a/engine/src/flutter/impeller/entity/contents/uber_sdf_parameters.h +++ b/engine/src/flutter/impeller/entity/contents/uber_sdf_parameters.h @@ -79,23 +79,74 @@ struct UberSDFParameters { /// The stroke parameters. If std::nullopt, the shape is filled. std::optional stroke; + /// The degree (n) of the superellipse curve for the top octant of each + /// quadrant. Vector4 superellipse_degrees_top; + + /// The degree (n) of the superellipse curve for the right octant of each + /// quadrant. Vector4 superellipse_degrees_right; + + /// The semi-axis length of the superellipse curve for the top octant of each + /// quadrant. Vector4 superellipse_semi_axes_top; + + /// The semi-axis length of the superellipse curve for the right octant of + /// each quadrant. Vector4 superellipse_semi_axes_right; + + /// The angular span of the circular cap for the top octant of each quadrant. Vector4 angle_spans_top; + + /// The angular span of the circular cap for the right octant of each + /// quadrant. Vector4 angle_spans_right; + + /// The geometric offset 'c' used to connect the two octants of each quadrant. Vector4 octant_offsets_c; + + /// The horizontal corner radii for rounded shapes and circular caps of + /// superellipses. Vector4 radii_width; + + /// The vertical corner radii for rounded shapes and circular caps of + /// superellipses. Vector4 radii_height; + + /// The X coordinates of the circular cap centers for the top octant of each + /// quadrant. Vector4 circle_centers_top_x; + + /// The Y coordinates of the circular cap centers for the top octant of each + /// quadrant. Vector4 circle_centers_top_y; + + /// The X coordinates of the circular cap centers for the right octant of each + /// quadrant. Vector4 circle_centers_right_x; + + /// The Y coordinates of the circular cap centers for the right octant of each + /// quadrant. Vector4 circle_centers_right_y; + + /// The X scaling factors used to transform normalized superellipses to their + /// true size. Vector4 superellipse_scales_x; + + /// The Y scaling factors used to transform normalized superellipses to their + /// true size. Vector4 superellipse_scales_y; + + /// The X coordinates of the geometric centers for each of the four corner + /// quadrants. Vector4 quadrant_centers_x; + + /// The Y coordinates of the geometric centers for each of the four corner + /// quadrants. Vector4 quadrant_centers_y; + + /// The local coordinate split points (top, bottom, left, right) dividing the + /// quadrants. Vector4 quadrant_splits; }; From e6ff93ca6e62be463c9adf5df96b8d00913ce04b Mon Sep 17 00:00:00 2001 From: Evan Walley Date: Tue, 12 May 2026 22:39:14 -0700 Subject: [PATCH 06/15] comments --- engine/src/flutter/impeller/entity/shaders/uber_sdf.frag | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag b/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag index b1b8187735722..e68a62120eade 100644 --- a/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag +++ b/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag @@ -236,7 +236,6 @@ float getQuadrantDistance(vec2 p, int quadrant_index) { grad_oct = normalize(pow(p_safe, vec2(se_degree - 1.0))); } - vec2 grad_norm; if (p_norm.y + c <= p_norm.x) { grad_oct = grad_oct.yx; } @@ -244,7 +243,7 @@ float getQuadrantDistance(vec2 p, int quadrant_index) { // Divide the distance by the length of the gradient. // This ensures that the resulting distance has a gradient magnitude of 1 // everywhere, allowing to be mixed cleanly with other SDFs. - float corner_dist = dist_raw / length(grad_norm / scale); + float corner_dist = dist_raw / length(grad_oct / scale); return corner_dist; } From 02f098eb262a195c0194513fd64ee31d62b95c32 Mon Sep 17 00:00:00 2001 From: Evan Walley Date: Wed, 13 May 2026 13:09:27 -0700 Subject: [PATCH 07/15] Go back to split logic, clean up --- .../impeller/entity/shaders/uber_sdf.frag | 172 ++++++++++++------ 1 file changed, 113 insertions(+), 59 deletions(-) diff --git a/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag b/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag index e68a62120eade..685a3e36be7ca 100644 --- a/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag +++ b/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag @@ -111,49 +111,21 @@ float sdSuperellipse(vec2 p, float n) { return length(pa - ba * h) * sign(pa.x * ba.y - pa.y * ba.x); } -float getComponent(vec4 v, int index) { - if (index == 0) - return v.x; - if (index == 1) - return v.y; - if (index == 2) - return v.z; - return v.w; -} - -float getQuadrantDistance(vec2 p, int quadrant_index) { - // Unpack the parameters for the quadrant. - float se_degree_top = - getComponent(frag_info.superellipse_degrees_top, quadrant_index); - float se_degree_right = - getComponent(frag_info.superellipse_degrees_right, quadrant_index); - float se_a_top = - getComponent(frag_info.superellipse_semi_axes_top, quadrant_index); - float se_a_right = - getComponent(frag_info.superellipse_semi_axes_right, quadrant_index); - float angle_span_top = - getComponent(frag_info.angle_spans_top, quadrant_index); - float angle_span_right = - getComponent(frag_info.angle_spans_right, quadrant_index); - float c = getComponent(frag_info.octant_offsets_c, quadrant_index); - float radius_top = getComponent(frag_info.radii_width, quadrant_index); - float radius_right = getComponent(frag_info.radii_height, quadrant_index); - - vec2 circle_center_top = - vec2(getComponent(frag_info.circle_centers_top_x, quadrant_index), - getComponent(frag_info.circle_centers_top_y, quadrant_index)); - vec2 circle_center_right = - vec2(getComponent(frag_info.circle_centers_right_x, quadrant_index), - getComponent(frag_info.circle_centers_right_y, quadrant_index)); - - vec2 scale = - vec2(getComponent(frag_info.superellipse_scales_x, quadrant_index), - getComponent(frag_info.superellipse_scales_y, quadrant_index)); - - vec2 q_center = - vec2(getComponent(frag_info.quadrant_centers_x, quadrant_index), - getComponent(frag_info.quadrant_centers_y, quadrant_index)); - +float getQuadrantDistance(vec2 p, + float se_degree_top, + float se_degree_right, + float se_a_top, + float se_a_right, + float angle_span_top, + float angle_span_right, + float c, + float radius_top, + float radius_right, + vec2 circle_center_top, + vec2 circle_center_right, + vec2 scale, + vec2 q_center, + int quadrant_index) { vec2 q_sign = vec2(1.0); if (quadrant_index == 0) q_sign = vec2(1.0, -1.0); @@ -172,6 +144,16 @@ float getQuadrantDistance(vec2 p, int quadrant_index) { // function are not totally accurate. vec2 p_clamped = max(p_local, 0.0); + // For points that we clamped, return an approximate interior distance + vec2 extents = vec2(scale.x * se_a_top, scale.y * se_a_right); + vec2 d_rect = p_local - extents; + float dist_rect_local = + length(max(d_rect, 0.0)) + min(max(d_rect.x, d_rect.y), 0.0); + + if (p_local.x <= 0.0 || p_local.y <= 0.0) { + return dist_rect_local; + } + // Map p in to a square. vec2 p_norm = p_clamped / scale; @@ -245,17 +227,84 @@ float getQuadrantDistance(vec2 p, int quadrant_index) { // everywhere, allowing to be mixed cleanly with other SDFs. float corner_dist = dist_raw / length(grad_oct / scale); - return corner_dist; + return max(corner_dist, dist_rect_local); } -float distanceFromRoundedSuperellipse(vec2 p) { - // An RSE is constructed by four different quadrant curves, - // take the max distance from all four curves. - float d0 = getQuadrantDistance(p, 0); - float d1 = getQuadrantDistance(p, 1); - float d2 = getQuadrantDistance(p, 2); - float d3 = getQuadrantDistance(p, 3); - return max(max(d0, d1), max(d2, d3)); +float distanceFromRoundedSuperellipse(vec2 p, + vec4 quadrant_splits, + vec2 size, + vec4 superellipse_degrees_top, + vec4 superellipse_degrees_right, + vec4 superellipse_semi_axes_top, + vec4 superellipse_semi_axes_right, + vec4 angle_spans_top, + vec4 angle_spans_right, + vec4 octant_offsets_c, + vec4 radii_width, + vec4 radii_height, + vec4 circle_centers_top_x, + vec4 circle_centers_top_y, + vec4 circle_centers_right_x, + vec4 circle_centers_right_y, + vec4 superellipse_scales_x, + vec4 superellipse_scales_y, + vec4 quadrant_centers_x, + vec4 quadrant_centers_y) { + vec2 T = vec2(quadrant_splits.x, -size.y); + vec2 R = vec2(size.x, quadrant_splits.w); + vec2 B = vec2(quadrant_splits.y, size.y); + vec2 L = vec2(-size.x, quadrant_splits.z); + + // Grab the 2d cross products between p and the split points. + // Imagine drawing a line L from the center of the shape to each split point, + // p x L tells us whether p is clockwise or counterclockwise relative to L. + float cT = T.x * p.y - T.y * p.x; + float cR = R.x * p.y - R.y * p.x; + float cB = B.x * p.y - B.y * p.x; + float cL = L.x * p.y - L.y * p.x; + + int quadrant_index = 0; + // cR = p x R <= 0 -> p is counterclockwise relative to R. + // cT = p x T > 0 -> p is clockwise relative to T. + // If p is clockwise relative to T and counterclockwise relative to R, + // p must lie in the TL quadrant. + // If cT = p x T == 0, p is parallel to T, which can misidentify points in the + // BR quadrant. + if (cR <= 0.0 && (cT > 0.0 || cT == 0.0 && p.x > 0.0)) { + quadrant_index = 0; // TR + } else if (cB <= 0.0 && (cR > 0.0 || cR == 0.0 && p.x > 0.0)) { + quadrant_index = 1; // BR + } else if (cB >= 0.0 && cL <= 0.0) { + quadrant_index = 2; // BL + } else { + quadrant_index = 3; // TL + } + + float se_degree_top = superellipse_degrees_top[quadrant_index]; + float se_degree_right = superellipse_degrees_right[quadrant_index]; + float se_a_top = superellipse_semi_axes_top[quadrant_index]; + float se_a_right = superellipse_semi_axes_right[quadrant_index]; + float angle_span_top = angle_spans_top[quadrant_index]; + float angle_span_right = angle_spans_right[quadrant_index]; + float c = octant_offsets_c[quadrant_index]; + float radius_top = radii_width[quadrant_index]; + float radius_right = radii_height[quadrant_index]; + + vec2 circle_center_top = vec2(circle_centers_top_x[quadrant_index], + circle_centers_top_y[quadrant_index]); + vec2 circle_center_right = vec2(circle_centers_right_x[quadrant_index], + circle_centers_right_y[quadrant_index]); + + vec2 scale = vec2(superellipse_scales_x[quadrant_index], + superellipse_scales_y[quadrant_index]); + + vec2 q_center = vec2(quadrant_centers_x[quadrant_index], + quadrant_centers_y[quadrant_index]); + + return getQuadrantDistance( + p, se_degree_top, se_degree_right, se_a_top, se_a_right, angle_span_top, + angle_span_right, c, radius_top, radius_right, circle_center_top, + circle_center_right, scale, q_center, quadrant_index); } // Define an ellipse as q(w) = (a*cos(w), b*sin(w)), and p = (x, y) on the // plane. Let q(w0) be the closest point on q to p, then q(w0) - p is tangent to @@ -433,7 +482,18 @@ vec2 filledSDF(vec2 p) { } else if (frag_info.type < 3.5) { // Rounded Rect sdf = distanceFromRoundedRect(p, frag_info.size, frag_info.radii_width); } else { - sdf = distanceFromRoundedSuperellipse(p); + sdf = distanceFromRoundedSuperellipse( + p, frag_info.quadrant_splits, frag_info.size, + frag_info.superellipse_degrees_top, + frag_info.superellipse_degrees_right, + frag_info.superellipse_semi_axes_top, + frag_info.superellipse_semi_axes_right, frag_info.angle_spans_top, + frag_info.angle_spans_right, frag_info.octant_offsets_c, + frag_info.radii_width, frag_info.radii_height, + frag_info.circle_centers_top_x, frag_info.circle_centers_top_y, + frag_info.circle_centers_right_x, frag_info.circle_centers_right_y, + frag_info.superellipse_scales_x, frag_info.superellipse_scales_y, + frag_info.quadrant_centers_x, frag_info.quadrant_centers_y); } return vec2(sdf, pixelSize(sdf)); } @@ -490,12 +550,6 @@ vec2 strokedSDF(vec2 p) { return vec2(sdf, base_pixel_size); } -float distanceToSegment(vec2 p, vec2 a, vec2 b) { - vec2 pa = p - a, ba = b - a; - float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0); - return length(pa - ba * h); -} - void main() { vec2 p = v_position - frag_info.center; From 9e074bdebe8676dcd632a9959599891dcc7d31d8 Mon Sep 17 00:00:00 2001 From: Evan Walley Date: Wed, 13 May 2026 13:36:01 -0700 Subject: [PATCH 08/15] break out complex RSE into a different type --- .../entity/contents/uber_sdf_contents.cc | 4 +- .../entity/contents/uber_sdf_parameters.cc | 6 +- .../entity/contents/uber_sdf_parameters.h | 1 + .../impeller/entity/shaders/uber_sdf.frag | 66 ++++++++++++++++++- 4 files changed, 74 insertions(+), 3 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 1cdda9688b23d..eca59b31cc5c7 100644 --- a/engine/src/flutter/impeller/entity/contents/uber_sdf_contents.cc +++ b/engine/src/flutter/impeller/entity/contents/uber_sdf_contents.cc @@ -30,8 +30,10 @@ Scalar ToShaderType(UberSDFParameters::Type type) { return 2.0f; case UberSDFParameters::Type::kRoundedRect: return 3.0f; - case UberSDFParameters::Type::kRoundedSuperellipse: + case UberSDFParameters::Type::kRoundedSuperellipseSymmetric: return 4.0f; + case UberSDFParameters::Type::kRoundedSuperellipse: + return 5.0f; } } 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 b28b8b30699de..0f9bb3184e108 100644 --- a/engine/src/flutter/impeller/entity/contents/uber_sdf_parameters.cc +++ b/engine/src/flutter/impeller/entity/contents/uber_sdf_parameters.cc @@ -101,8 +101,12 @@ UberSDFParameters UberSDFParameters::MakeRoundedSuperellipse( Point top_left_center_relative = top_left.offset - center; Point size = Point(bounds.GetSize() * 0.5f); + Type type = round_superellipse_params.all_corners_same + ? Type::kRoundedSuperellipseSymmetric + : Type::kRoundedSuperellipse; + return UberSDFParameters{ - .type = Type::kRoundedSuperellipse, + .type = type, .color = color, .center = center, .size = size, 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 e487a275b932a..ae248643f97bf 100644 --- a/engine/src/flutter/impeller/entity/contents/uber_sdf_parameters.h +++ b/engine/src/flutter/impeller/entity/contents/uber_sdf_parameters.h @@ -29,6 +29,7 @@ struct UberSDFParameters { kRect, kOval, kRoundedRect, + kRoundedSuperellipseSymmetric, kRoundedSuperellipse, }; diff --git a/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag b/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag index 685a3e36be7ca..b0dc72cc8ce23 100644 --- a/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag +++ b/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag @@ -464,6 +464,58 @@ float pixelSize(float sdf) { return length(gradient); } +float distanceFromSymmetricRoundedSuperellipse( + vec2 p, + vec4 superellipse_degrees_top, + vec4 superellipse_degrees_right, + vec4 superellipse_semi_axes_top, + vec4 superellipse_semi_axes_right, + vec4 angle_spans_top, + vec4 angle_spans_right, + vec4 octant_offsets_c, + vec4 radii_width, + vec4 radii_height, + vec4 circle_centers_top_x, + vec4 circle_centers_top_y, + vec4 circle_centers_right_x, + vec4 circle_centers_right_y, + vec4 superellipse_scales_x, + vec4 superellipse_scales_y, + vec4 quadrant_centers_x, + vec4 quadrant_centers_y) { + // Fold the pixel into the Top-Right quadrant (x > 0, y < 0) + vec2 p_folded = vec2(abs(p.x), -abs(p.y)); + + // The Top-Right quadrant is always index 0 + int quadrant_index = 0; + + float se_degree_top = superellipse_degrees_top[quadrant_index]; + float se_degree_right = superellipse_degrees_right[quadrant_index]; + float se_a_top = superellipse_semi_axes_top[quadrant_index]; + float se_a_right = superellipse_semi_axes_right[quadrant_index]; + float angle_span_top = angle_spans_top[quadrant_index]; + float angle_span_right = angle_spans_right[quadrant_index]; + float c = octant_offsets_c[quadrant_index]; + float radius_top = radii_width[quadrant_index]; + float radius_right = radii_height[quadrant_index]; + + vec2 circle_center_top = vec2(circle_centers_top_x[quadrant_index], + circle_centers_top_y[quadrant_index]); + vec2 circle_center_right = vec2(circle_centers_right_x[quadrant_index], + circle_centers_right_y[quadrant_index]); + + vec2 scale = vec2(superellipse_scales_x[quadrant_index], + superellipse_scales_y[quadrant_index]); + + vec2 q_center = vec2(quadrant_centers_x[quadrant_index], + quadrant_centers_y[quadrant_index]); + + return getQuadrantDistance( + p_folded, se_degree_top, se_degree_right, se_a_top, se_a_right, + angle_span_top, angle_span_right, c, radius_top, radius_right, + circle_center_top, circle_center_right, scale, q_center, quadrant_index); +} + // Computes the SDF value and pixel size for a filled shape. // // `p` is position relative to the center of the shape. @@ -481,7 +533,19 @@ vec2 filledSDF(vec2 p) { sdf = distanceFromOval(p, frag_info.size); } else if (frag_info.type < 3.5) { // Rounded Rect sdf = distanceFromRoundedRect(p, frag_info.size, frag_info.radii_width); - } else { + } else if (frag_info.type < 4.5) { // Symmetric Rounded Superellipse + sdf = distanceFromSymmetricRoundedSuperellipse( + p, frag_info.superellipse_degrees_top, + frag_info.superellipse_degrees_right, + frag_info.superellipse_semi_axes_top, + frag_info.superellipse_semi_axes_right, frag_info.angle_spans_top, + frag_info.angle_spans_right, frag_info.octant_offsets_c, + frag_info.radii_width, frag_info.radii_height, + frag_info.circle_centers_top_x, frag_info.circle_centers_top_y, + frag_info.circle_centers_right_x, frag_info.circle_centers_right_y, + frag_info.superellipse_scales_x, frag_info.superellipse_scales_y, + frag_info.quadrant_centers_x, frag_info.quadrant_centers_y); + } else { // Asymmetric Rounded Superellipse sdf = distanceFromRoundedSuperellipse( p, frag_info.quadrant_splits, frag_info.size, frag_info.superellipse_degrees_top, From 0f4538ecdd969da3a9424eab9c8b3c492b6ebdfb Mon Sep 17 00:00:00 2001 From: Evan Walley Date: Mon, 18 May 2026 22:17:09 -0700 Subject: [PATCH 09/15] Bot comments --- .../entity/contents/uber_sdf_parameters.cc | 40 +++++++++---------- .../contents/uber_sdf_parameters_unittests.cc | 37 +++++++++++++++++ .../impeller/entity/shaders/uber_sdf.frag | 19 +++++---- 3 files changed, 68 insertions(+), 28 deletions(-) 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 0f9bb3184e108..346633abbcec9 100644 --- a/engine/src/flutter/impeller/entity/contents/uber_sdf_parameters.cc +++ b/engine/src/flutter/impeller/entity/contents/uber_sdf_parameters.cc @@ -112,59 +112,59 @@ UberSDFParameters UberSDFParameters::MakeRoundedSuperellipse( .size = size, .stroke = stroke, .superellipse_degrees_top = - Vector4(top_right.top.se_n, bottom_right.top.se_n, + Vector4(bottom_right.top.se_n, top_right.top.se_n, bottom_left.top.se_n, top_left.top.se_n), .superellipse_degrees_right = - Vector4(top_right.right.se_n, bottom_right.right.se_n, + Vector4(bottom_right.right.se_n, top_right.right.se_n, bottom_left.right.se_n, top_left.right.se_n), .superellipse_semi_axes_top = - Vector4(top_right.top.se_a, bottom_right.top.se_a, + Vector4(bottom_right.top.se_a, top_right.top.se_a, bottom_left.top.se_a, top_left.top.se_a), .superellipse_semi_axes_right = - Vector4(top_right.right.se_a, bottom_right.right.se_a, + Vector4(bottom_right.right.se_a, top_right.right.se_a, bottom_left.right.se_a, top_left.right.se_a), - .angle_spans_top = Vector4(top_right.top.circle_max_angle.radians, - bottom_right.top.circle_max_angle.radians, + .angle_spans_top = Vector4(bottom_right.top.circle_max_angle.radians, + top_right.top.circle_max_angle.radians, bottom_left.top.circle_max_angle.radians, top_left.top.circle_max_angle.radians), - .angle_spans_right = Vector4(top_right.right.circle_max_angle.radians, - bottom_right.right.circle_max_angle.radians, + .angle_spans_right = Vector4(bottom_right.right.circle_max_angle.radians, + top_right.right.circle_max_angle.radians, bottom_left.right.circle_max_angle.radians, top_left.right.circle_max_angle.radians), .octant_offsets_c = - Vector4(top_right.top.se_a - top_right.right.se_a, - bottom_right.top.se_a - bottom_right.right.se_a, + Vector4(bottom_right.top.se_a - bottom_right.right.se_a, + top_right.top.se_a - top_right.right.se_a, bottom_left.top.se_a - bottom_left.right.se_a, top_left.top.se_a - top_left.right.se_a), .radii_width = - Vector4(top_right.top.circle_radius, bottom_right.top.circle_radius, + Vector4(bottom_right.top.circle_radius, top_right.top.circle_radius, bottom_left.top.circle_radius, top_left.top.circle_radius), .radii_height = Vector4( - top_right.right.circle_radius, bottom_right.right.circle_radius, + bottom_right.right.circle_radius, top_right.right.circle_radius, bottom_left.right.circle_radius, top_left.right.circle_radius), .circle_centers_top_x = Vector4( - top_right.top.circle_center.x, bottom_right.top.circle_center.x, + bottom_right.top.circle_center.x, top_right.top.circle_center.x, bottom_left.top.circle_center.x, top_left.top.circle_center.x), .circle_centers_top_y = Vector4( - top_right.top.circle_center.y, bottom_right.top.circle_center.y, + bottom_right.top.circle_center.y, top_right.top.circle_center.y, bottom_left.top.circle_center.y, top_left.top.circle_center.y), .circle_centers_right_x = Vector4( - top_right.right.circle_center.x, bottom_right.right.circle_center.x, + bottom_right.right.circle_center.x, top_right.right.circle_center.x, bottom_left.right.circle_center.x, top_left.right.circle_center.x), .circle_centers_right_y = Vector4( - top_right.right.circle_center.y, bottom_right.right.circle_center.y, + bottom_right.right.circle_center.y, top_right.right.circle_center.y, bottom_left.right.circle_center.y, top_left.right.circle_center.y), .superellipse_scales_x = Vector4( - top_right.signed_scale.Abs().x, bottom_right.signed_scale.Abs().x, + bottom_right.signed_scale.Abs().x, top_right.signed_scale.Abs().x, bottom_left.signed_scale.Abs().x, top_left.signed_scale.Abs().x), .superellipse_scales_y = Vector4( - top_right.signed_scale.Abs().y, bottom_right.signed_scale.Abs().y, + bottom_right.signed_scale.Abs().y, top_right.signed_scale.Abs().y, bottom_left.signed_scale.Abs().y, top_left.signed_scale.Abs().y), .quadrant_centers_x = - Vector4(top_right_center_relative.x, bottom_right_center_relative.x, + Vector4(bottom_right_center_relative.x, top_right_center_relative.x, bottom_left_center_relative.x, top_left_center_relative.x), .quadrant_centers_y = - Vector4(top_right_center_relative.y, bottom_right_center_relative.y, + Vector4(bottom_right_center_relative.y, top_right_center_relative.y, bottom_left_center_relative.y, top_left_center_relative.y), .quadrant_splits = Vector4(round_superellipse_params.top_split - center.x, 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 de52948f8698c..aae0652b2cc84 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 @@ -143,6 +143,43 @@ TEST(UberSDFParametersTest, MakeRoundedSuperellipse) { EXPECT_EQ(params.center, Point(60, 70)); EXPECT_EQ(params.size, Point(50, 50)); EXPECT_FALSE(params.stroke.has_value()); + + // Test the [BR, TR, BL, TL] packing order + EXPECT_EQ(params.radii_width.x, + round_superellipse_params.bottom_right.top.circle_radius); + EXPECT_EQ(params.radii_width.y, + round_superellipse_params.top_right.top.circle_radius); + EXPECT_EQ(params.radii_width.z, + round_superellipse_params.bottom_left.top.circle_radius); + EXPECT_EQ(params.radii_width.w, + round_superellipse_params.top_left.top.circle_radius); + + EXPECT_EQ(params.radii_height.x, + round_superellipse_params.bottom_right.right.circle_radius); + EXPECT_EQ(params.radii_height.y, + round_superellipse_params.top_right.right.circle_radius); + EXPECT_EQ(params.radii_height.z, + round_superellipse_params.bottom_left.right.circle_radius); + EXPECT_EQ(params.radii_height.w, + round_superellipse_params.top_left.right.circle_radius); + + EXPECT_EQ(params.superellipse_degrees_top.x, + round_superellipse_params.bottom_right.top.se_n); + EXPECT_EQ(params.superellipse_degrees_top.y, + round_superellipse_params.top_right.top.se_n); + EXPECT_EQ(params.superellipse_degrees_top.z, + round_superellipse_params.bottom_left.top.se_n); + EXPECT_EQ(params.superellipse_degrees_top.w, + round_superellipse_params.top_left.top.se_n); + + EXPECT_EQ(params.superellipse_scales_x.x, + round_superellipse_params.bottom_right.signed_scale.Abs().x); + EXPECT_EQ(params.superellipse_scales_x.y, + round_superellipse_params.top_right.signed_scale.Abs().x); + EXPECT_EQ(params.superellipse_scales_x.z, + round_superellipse_params.bottom_left.signed_scale.Abs().x); + EXPECT_EQ(params.superellipse_scales_x.w, + round_superellipse_params.top_left.signed_scale.Abs().x); } } // namespace testing diff --git a/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag b/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag index b0dc72cc8ce23..07acf6b60e83e 100644 --- a/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag +++ b/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag @@ -128,9 +128,9 @@ float getQuadrantDistance(vec2 p, int quadrant_index) { vec2 q_sign = vec2(1.0); if (quadrant_index == 0) - q_sign = vec2(1.0, -1.0); - else if (quadrant_index == 1) q_sign = vec2(1.0, 1.0); + else if (quadrant_index == 1) + q_sign = vec2(1.0, -1.0); else if (quadrant_index == 2) q_sign = vec2(-1.0, 1.0); else @@ -270,10 +270,13 @@ float distanceFromRoundedSuperellipse(vec2 p, // p must lie in the TL quadrant. // If cT = p x T == 0, p is parallel to T, which can misidentify points in the // BR quadrant. - if (cR <= 0.0 && (cT > 0.0 || cT == 0.0 && p.x > 0.0)) { - quadrant_index = 0; // TR - } else if (cB <= 0.0 && (cR > 0.0 || cR == 0.0 && p.x > 0.0)) { - quadrant_index = 1; // BR + + if ((cR < 0.0 || cR == 0.0 && p.x > 0.0) && + (cT > 0.0 || cT == 0.0 && p.x > 0.0)) { + quadrant_index = 1; // TR + } else if ((cB < 0.0 || cB == 0.0 && p.x > 0.0) && + (cR > 0.0 || cR == 0.0 && p.x > 0.0)) { + quadrant_index = 0; // BR } else if (cB >= 0.0 && cL <= 0.0) { quadrant_index = 2; // BL } else { @@ -486,8 +489,8 @@ float distanceFromSymmetricRoundedSuperellipse( // Fold the pixel into the Top-Right quadrant (x > 0, y < 0) vec2 p_folded = vec2(abs(p.x), -abs(p.y)); - // The Top-Right quadrant is always index 0 - int quadrant_index = 0; + // The Top-Right quadrant is index 1 + int quadrant_index = 1; float se_degree_top = superellipse_degrees_top[quadrant_index]; float se_degree_right = superellipse_degrees_right[quadrant_index]; From 8bded1a404c92d9ce675682b86e3e91f6352b41e Mon Sep 17 00:00:00 2001 From: Evan Walley Date: Thu, 21 May 2026 22:33:00 +0000 Subject: [PATCH 10/15] malioc --- engine/src/flutter/impeller/tools/malioc.json | 58 +++++++++---------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/engine/src/flutter/impeller/tools/malioc.json b/engine/src/flutter/impeller/tools/malioc.json index 60ba99c740049..9ca567ccb988c 100644 --- a/engine/src/flutter/impeller/tools/malioc.json +++ b/engine/src/flutter/impeller/tools/malioc.json @@ -8680,7 +8680,7 @@ "uses_late_zs_update": false, "variants": { "Main": { - "fp16_arithmetic": 34, + "fp16_arithmetic": 31, "has_stack_spilling": false, "performance": { "longest_path_bound_pipelines": [ @@ -8688,10 +8688,10 @@ "arith_sfu" ], "longest_path_cycles": [ - 3.875, - 3.78125, - 1.7000000476837158, - 3.875, + 4.5625, + 4.375, + 3.762500047683716, + 4.5625, 0.0, 0.25, 0.0 @@ -8723,10 +8723,10 @@ "arith_fma" ], "total_cycles": [ - 12.0, - 12.0, - 4.050000190734863, - 10.3125, + 20.5, + 20.5, + 12.4375, + 19.4375, 0.0, 0.25, 0.0 @@ -8734,7 +8734,7 @@ }, "stack_spill_bytes": 0, "thread_occupancy": 100, - "uniform_registers_used": 44, + "uniform_registers_used": 80, "work_registers_used": 32 } } @@ -8746,14 +8746,14 @@ "type": "Fragment", "variants": { "Main": { - "has_stack_spilling": true, + "has_stack_spilling": false, "performance": { "longest_path_bound_pipelines": [ "arithmetic" ], "longest_path_cycles": [ - 44.880001068115234, - 3.0, + 63.029998779296875, + 1.0, 4.0 ], "pipelines": [ @@ -8773,14 +8773,14 @@ "arithmetic" ], "total_cycles": [ - 88.66666412353516, - 3.0, + 179.3333282470703, + 1.0, 8.0 ] }, - "thread_occupancy": 100, - "uniform_registers_used": 5, - "work_registers_used": 4 + "thread_occupancy": 50, + "uniform_registers_used": 12, + "work_registers_used": 7 } } } @@ -12088,7 +12088,7 @@ "uses_late_zs_update": false, "variants": { "Main": { - "fp16_arithmetic": 38, + "fp16_arithmetic": 34, "has_stack_spilling": false, "performance": { "longest_path_bound_pipelines": [ @@ -12096,10 +12096,10 @@ "arith_sfu" ], "longest_path_cycles": [ - 3.875, - 3.299999952316284, - 1.96875, - 3.875, + 4.5625, + 3.887500047683716, + 3.8125, + 4.5625, 0.0, 0.25, 0.0 @@ -12128,13 +12128,13 @@ ], "total_bound_pipelines": [ "arith_total", - "arith_fma" + "arith_sfu" ], "total_cycles": [ - 11.0, - 11.0, - 4.612500190734863, - 10.3125, + 19.4375, + 18.875, + 12.5625, + 19.4375, 0.0, 0.25, 0.0 @@ -12142,7 +12142,7 @@ }, "stack_spill_bytes": 0, "thread_occupancy": 100, - "uniform_registers_used": 48, + "uniform_registers_used": 128, "work_registers_used": 32 } } From 60c2f917dfc363504b4bae79780f8bc4b382a59c Mon Sep 17 00:00:00 2001 From: Evan Walley Date: Tue, 26 May 2026 13:29:08 -0700 Subject: [PATCH 11/15] Break out complex RSEs into a separate pipeline --- .../flutter/impeller/display_list/canvas.cc | 48 +- engine/src/flutter/impeller/entity/BUILD.gn | 3 + .../entity/contents/complex_rse_contents.cc | 172 ++++++ .../entity/contents/complex_rse_contents.h | 59 ++ .../entity/contents/content_context.cc | 7 + .../entity/contents/content_context.h | 1 + .../impeller/entity/contents/pipelines.h | 2 + .../entity/contents/uber_sdf_contents.cc | 30 +- .../entity/contents/uber_sdf_parameters.cc | 96 +-- .../entity/contents/uber_sdf_parameters.h | 79 +-- .../contents/uber_sdf_parameters_unittests.cc | 59 +- .../impeller/entity/shaders/complex_rse.frag | 280 +++++++++ .../entity/shaders/sdf_functions.glsl | 69 +++ .../impeller/entity/shaders/sdf_utils.glsl | 36 ++ .../impeller/entity/shaders/uber_sdf.frag | 578 +++--------------- 15 files changed, 824 insertions(+), 695 deletions(-) create mode 100644 engine/src/flutter/impeller/entity/contents/complex_rse_contents.cc create mode 100644 engine/src/flutter/impeller/entity/contents/complex_rse_contents.h create mode 100644 engine/src/flutter/impeller/entity/shaders/complex_rse.frag create mode 100644 engine/src/flutter/impeller/entity/shaders/sdf_functions.glsl create mode 100644 engine/src/flutter/impeller/entity/shaders/sdf_utils.glsl diff --git a/engine/src/flutter/impeller/display_list/canvas.cc b/engine/src/flutter/impeller/display_list/canvas.cc index fe13328c0c350..2d613360856f9 100644 --- a/engine/src/flutter/impeller/display_list/canvas.cc +++ b/engine/src/flutter/impeller/display_list/canvas.cc @@ -30,6 +30,7 @@ #include "impeller/entity/contents/circle_contents.h" #include "impeller/entity/contents/clip_contents.h" #include "impeller/entity/contents/color_source_contents.h" +#include "impeller/entity/contents/complex_rse_contents.h" #include "impeller/entity/contents/content_context.h" #include "impeller/entity/contents/filters/filter_contents.h" #include "impeller/entity/contents/framebuffer_blend_contents.h" @@ -1040,15 +1041,46 @@ void Canvas::DrawRoundSuperellipse(const RoundSuperellipse& round_superellipse, auto round_superellipse_params = RoundSuperellipseParam::MakeBoundsRadii( round_superellipse.GetBounds(), round_superellipse.GetRadii()); - auto params = UberSDFParameters::MakeRoundedSuperellipse( - /*color=*/paint.color, - /*bounds=*/round_superellipse.GetBounds(), - /*round_superellipse_params=*/round_superellipse_params, - /*stroke=*/paint.GetStroke()); - - AddRenderSDFEntityToCurrentPass(paint, params); + if (round_superellipse_params.all_corners_same) { + auto params = UberSDFParameters::MakeRoundedSuperellipse( + /*color=*/paint.color, + /*bounds=*/round_superellipse.GetBounds(), + /*round_superellipse_params=*/round_superellipse_params, + /*stroke=*/paint.GetStroke()); - return; + AddRenderSDFEntityToCurrentPass(paint, params); + return; + } else { + auto contents = ComplexRoundedSuperellipseContents::Make( + /*color=*/paint.color_source ? Color::White() : paint.color, + /*bounds=*/round_superellipse.GetBounds(), + /*round_superellipse_params=*/round_superellipse_params, + /*stroke=*/paint.GetStroke()); + + const Geometry* geom = contents->GetGeometry(); + + if (paint.color_source) { + std::shared_ptr color_source_contents = + paint.CreateContents(renderer_, geom); + std::shared_ptr final_contents = + ColorFilterContents::MakeBlend( + BlendMode::kSrcIn, {FilterInput::Make(std::move(contents)), + FilterInput::Make(color_source_contents)}); + + Paint new_paint = paint; + new_paint.color_source = nullptr; + AddRenderEntityWithFiltersToCurrentPass(entity, geom, new_paint, + /*reuse_depth=*/false, + /*override_contents=*/ + std::move(final_contents)); + } else { + AddRenderEntityWithFiltersToCurrentPass(entity, geom, paint, + /*reuse_depth=*/false, + /*override_contents=*/ + std::move(contents)); + } + return; + } } if (paint.style == Paint::Style::kFill) { diff --git a/engine/src/flutter/impeller/entity/BUILD.gn b/engine/src/flutter/impeller/entity/BUILD.gn index 030f207ec34df..79ed38211b4a7 100644 --- a/engine/src/flutter/impeller/entity/BUILD.gn +++ b/engine/src/flutter/impeller/entity/BUILD.gn @@ -18,6 +18,7 @@ impeller_shaders("entity_shaders") { "shaders/blending/advanced_blend.frag", "shaders/circle.frag", "shaders/circle.vert", + "shaders/complex_rse.frag", "shaders/uber_sdf.frag", "shaders/clip.frag", "shaders/clip.vert", @@ -126,6 +127,8 @@ impeller_component("entity") { "contents/clip_contents.h", "contents/color_source_contents.cc", "contents/color_source_contents.h", + "contents/complex_rse_contents.cc", + "contents/complex_rse_contents.h", "contents/conical_gradient_contents.cc", "contents/conical_gradient_contents.h", "contents/content_context.cc", diff --git a/engine/src/flutter/impeller/entity/contents/complex_rse_contents.cc b/engine/src/flutter/impeller/entity/contents/complex_rse_contents.cc new file mode 100644 index 0000000000000..3db5e3e1b546f --- /dev/null +++ b/engine/src/flutter/impeller/entity/contents/complex_rse_contents.cc @@ -0,0 +1,172 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "impeller/entity/contents/complex_rse_contents.h" + +#include "impeller/entity/contents/color_source_contents.h" +#include "impeller/entity/contents/content_context.h" +#include "impeller/entity/contents/pipelines.h" +#include "impeller/entity/geometry/geometry.h" + +namespace impeller { + +namespace { + +using PipelineBuilderCallback = + std::function; + +using VS = ComplexRSEPipeline::VertexShader; +using FS = ComplexRSEPipeline::FragmentShader; + +} // namespace + +std::unique_ptr +ComplexRoundedSuperellipseContents::Make( + Color color, + const Rect& bounds, + const RoundSuperellipseParam& round_superellipse_params, + std::optional stroke) { + return std::unique_ptr( + new ComplexRoundedSuperellipseContents( + color, bounds, round_superellipse_params, stroke)); +} + +ComplexRoundedSuperellipseContents::ComplexRoundedSuperellipseContents( + Color color, + const Rect& bounds, + const RoundSuperellipseParam& round_superellipse_params, + std::optional stroke) + : color_(color), + bounds_(bounds), + round_superellipse_params_(round_superellipse_params), + stroke_(stroke) { + if (stroke) { + geometry_ = Geometry::MakeRect(bounds_.Expand(stroke->width / 2.0f)); + } else { + geometry_ = Geometry::MakeRect(bounds_); + } +} + +bool ComplexRoundedSuperellipseContents::Render(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const { + auto& data_host_buffer = renderer.GetTransientsDataBuffer(); + + Point center = bounds_.GetCenter(); + + RoundSuperellipseParam::Quadrant top_right = + round_superellipse_params_.top_right; + RoundSuperellipseParam::Quadrant bottom_right = + round_superellipse_params_.bottom_right; + RoundSuperellipseParam::Quadrant bottom_left = + round_superellipse_params_.bottom_left; + RoundSuperellipseParam::Quadrant top_left = + round_superellipse_params_.top_left; + + Point top_right_center_relative = top_right.offset - center; + Point bottom_right_center_relative = bottom_right.offset - center; + Point bottom_left_center_relative = bottom_left.offset - center; + Point top_left_center_relative = top_left.offset - center; + + Point size = Point(bounds_.GetSize() * 0.5f); + + VS::FrameInfo frame_info; + FS::FragInfo frag_info; + + frag_info.color = color_.WithAlpha(color_.alpha * GetOpacityFactor()); + frag_info.center = center; + frag_info.size = size; + frag_info.stroked = stroke_ ? 1.0f : 0.0f; + frag_info.stroke_width = stroke_ ? stroke_->width : 0.0f; + + frag_info.superellipse_degrees_top = + Vector4(bottom_right.top.se_n, top_right.top.se_n, bottom_left.top.se_n, + top_left.top.se_n); + frag_info.superellipse_degrees_right = + Vector4(bottom_right.right.se_n, top_right.right.se_n, + bottom_left.right.se_n, top_left.right.se_n); + frag_info.superellipse_semi_axes_top = + Vector4(bottom_right.top.se_a, top_right.top.se_a, bottom_left.top.se_a, + top_left.top.se_a); + frag_info.superellipse_semi_axes_right = + Vector4(bottom_right.right.se_a, top_right.right.se_a, + bottom_left.right.se_a, top_left.right.se_a); + frag_info.angle_spans_top = Vector4(bottom_right.top.circle_max_angle.radians, + top_right.top.circle_max_angle.radians, + bottom_left.top.circle_max_angle.radians, + top_left.top.circle_max_angle.radians); + frag_info.angle_spans_right = + Vector4(bottom_right.right.circle_max_angle.radians, + top_right.right.circle_max_angle.radians, + bottom_left.right.circle_max_angle.radians, + top_left.right.circle_max_angle.radians); + frag_info.octant_offsets_c = + Vector4(bottom_right.top.se_a - bottom_right.right.se_a, + top_right.top.se_a - top_right.right.se_a, + bottom_left.top.se_a - bottom_left.right.se_a, + top_left.top.se_a - top_left.right.se_a); + frag_info.radii_width = + Vector4(bottom_right.top.circle_radius, top_right.top.circle_radius, + bottom_left.top.circle_radius, top_left.top.circle_radius); + frag_info.radii_height = + Vector4(bottom_right.right.circle_radius, top_right.right.circle_radius, + bottom_left.right.circle_radius, top_left.right.circle_radius); + frag_info.circle_centers_top_x = + Vector4(bottom_right.top.circle_center.x, top_right.top.circle_center.x, + bottom_left.top.circle_center.x, top_left.top.circle_center.x); + frag_info.circle_centers_top_y = + Vector4(bottom_right.top.circle_center.y, top_right.top.circle_center.y, + bottom_left.top.circle_center.y, top_left.top.circle_center.y); + frag_info.circle_centers_right_x = Vector4( + bottom_right.right.circle_center.x, top_right.right.circle_center.x, + bottom_left.right.circle_center.x, top_left.right.circle_center.x); + frag_info.circle_centers_right_y = Vector4( + bottom_right.right.circle_center.y, top_right.right.circle_center.y, + bottom_left.right.circle_center.y, top_left.right.circle_center.y); + frag_info.superellipse_scales_x = + Vector4(bottom_right.signed_scale.Abs().x, top_right.signed_scale.Abs().x, + bottom_left.signed_scale.Abs().x, top_left.signed_scale.Abs().x); + frag_info.superellipse_scales_y = + Vector4(bottom_right.signed_scale.Abs().y, top_right.signed_scale.Abs().y, + bottom_left.signed_scale.Abs().y, top_left.signed_scale.Abs().y); + frag_info.quadrant_centers_x = + Vector4(bottom_right_center_relative.x, top_right_center_relative.x, + bottom_left_center_relative.x, top_left_center_relative.x); + frag_info.quadrant_centers_y = + Vector4(bottom_right_center_relative.y, top_right_center_relative.y, + bottom_left_center_relative.y, top_left_center_relative.y); + frag_info.quadrant_splits = + Vector4(round_superellipse_params_.top_split - center.x, + round_superellipse_params_.bottom_split - center.x, + round_superellipse_params_.left_split - center.y, + round_superellipse_params_.right_split - center.y); + + auto geometry_result = + GetGeometry()->GetPositionBuffer(renderer, entity, pass); + + PipelineBuilderCallback pipeline_callback = + [&renderer](ContentContextOptions options) { + return renderer.GetComplexRSEPipeline(options); + }; + + return ColorSourceContents::DrawGeometry( + this, GetGeometry(), renderer, entity, pass, pipeline_callback, + frame_info, + /*bind_fragment_callback=*/ + [&frag_info, &data_host_buffer](RenderPass& pass) { + FS::BindFragInfo(pass, data_host_buffer.EmplaceUniform(frag_info)); + return true; + }); +} + +std::optional ComplexRoundedSuperellipseContents::GetCoverage( + const Entity& entity) const { + return GetGeometry()->GetCoverage(entity.GetTransform()); +} + +const Geometry* ComplexRoundedSuperellipseContents::GetGeometry() const { + return geometry_.get(); +} + +} // namespace impeller \ No newline at end of file diff --git a/engine/src/flutter/impeller/entity/contents/complex_rse_contents.h b/engine/src/flutter/impeller/entity/contents/complex_rse_contents.h new file mode 100644 index 0000000000000..735fb97aa5a13 --- /dev/null +++ b/engine/src/flutter/impeller/entity/contents/complex_rse_contents.h @@ -0,0 +1,59 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef FLUTTER_IMPELLER_ENTITY_CONTENTS_COMPLEX_RSE_CONTENTS_H_ +#define FLUTTER_IMPELLER_ENTITY_CONTENTS_COMPLEX_RSE_CONTENTS_H_ + +#include +#include + +#include "flutter/impeller/entity/contents/color_source_contents.h" +#include "flutter/impeller/entity/contents/contents.h" +#include "impeller/entity/geometry/geometry.h" +#include "impeller/geometry/color.h" +#include "impeller/geometry/rect.h" +#include "impeller/geometry/round_superellipse_param.h" +#include "impeller/geometry/stroke_parameters.h" + +namespace impeller { + +class ComplexRoundedSuperellipseContents : public ColorSourceContents { + public: + static std::unique_ptr Make( + Color color, + const Rect& bounds, + const RoundSuperellipseParam& round_superellipse_params, + std::optional stroke); + + bool Render(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const override; + + std::optional GetCoverage(const Entity& entity) const override; + + const Geometry* GetGeometry() const override; + + private: + explicit ComplexRoundedSuperellipseContents( + Color color, + const Rect& bounds, + const RoundSuperellipseParam& round_superellipse_params, + std::optional stroke); + + Color color_; + Rect bounds_; + RoundSuperellipseParam round_superellipse_params_; + std::optional stroke_; + std::unique_ptr geometry_; + + ComplexRoundedSuperellipseContents( + const ComplexRoundedSuperellipseContents&) = delete; + + ComplexRoundedSuperellipseContents& operator=( + const ComplexRoundedSuperellipseContents&) = delete; +}; + +} // namespace impeller + +#endif // FLUTTER_IMPELLER_ENTITY_CONTENTS_COMPLEX_RSE_CONTENTS_H_ diff --git a/engine/src/flutter/impeller/entity/contents/content_context.cc b/engine/src/flutter/impeller/entity/contents/content_context.cc index 07b118e0d0e80..d9f51c018d503 100644 --- a/engine/src/flutter/impeller/entity/contents/content_context.cc +++ b/engine/src/flutter/impeller/entity/contents/content_context.cc @@ -305,6 +305,7 @@ struct ContentContext::Pipelines { Variants vertices_uber_1_; Variants vertices_uber_2_; Variants uber_sdf; + Variants complex_rse; Variants yuv_to_rgb_filter; // Web doesn't support external texture OpenGL extensions @@ -638,6 +639,7 @@ ContentContext::ContentContext( pipelines_->circle.CreateDefault(*context_, options); if (context_->GetFlags().use_sdfs) { pipelines_->uber_sdf.CreateDefault(*context_, options); + pipelines_->complex_rse.CreateDefault(*context_, options); } if (context_->GetCapabilities()->SupportsSSBO()) { @@ -1207,6 +1209,11 @@ PipelineRef ContentContext::GetUberSDFPipeline( return GetPipeline(this, pipelines_->uber_sdf, opts); } +PipelineRef ContentContext::GetComplexRSEPipeline( + ContentContextOptions opts) const { + return GetPipeline(this, pipelines_->complex_rse, opts); +} + PipelineRef ContentContext::GetPorterDuffPipeline( BlendMode mode, ContentContextOptions opts) const { diff --git a/engine/src/flutter/impeller/entity/contents/content_context.h b/engine/src/flutter/impeller/entity/contents/content_context.h index 1aa0f194ec844..fe675077c2311 100644 --- a/engine/src/flutter/impeller/entity/contents/content_context.h +++ b/engine/src/flutter/impeller/entity/contents/content_context.h @@ -215,6 +215,7 @@ class ContentContext { PipelineRef GetXorBlendPipeline(ContentContextOptions opts) const; PipelineRef GetYUVToRGBFilterPipeline(ContentContextOptions opts) const; PipelineRef GetUberSDFPipeline(ContentContextOptions opts) const; + PipelineRef GetComplexRSEPipeline(ContentContextOptions opts) const; #ifdef IMPELLER_ENABLE_OPENGLES #if !defined(FML_OS_EMSCRIPTEN) PipelineRef GetTiledTextureExternalPipeline(ContentContextOptions opts) const; diff --git a/engine/src/flutter/impeller/entity/contents/pipelines.h b/engine/src/flutter/impeller/entity/contents/pipelines.h index 2e47ea8b33d44..72b66bf7c1d60 100644 --- a/engine/src/flutter/impeller/entity/contents/pipelines.h +++ b/engine/src/flutter/impeller/entity/contents/pipelines.h @@ -14,6 +14,7 @@ #include "impeller/entity/clip.frag.h" #include "impeller/entity/clip.vert.h" #include "impeller/entity/color_matrix_color_filter.frag.h" +#include "impeller/entity/complex_rse.frag.h" #include "impeller/entity/conical_gradient_fill_conical.frag.h" #include "impeller/entity/conical_gradient_fill_radial.frag.h" #include "impeller/entity/conical_gradient_fill_strip.frag.h" @@ -162,6 +163,7 @@ using TiledTexturePipeline = RenderPipelineHandle; using VerticesUber2Shader = RenderPipelineHandle; using UberSDFPipeline = RenderPipelineHandle; +using ComplexRSEPipeline = RenderPipelineHandle; using YUVToRGBFilterPipeline = RenderPipelineHandle; // clang-format on 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 eca59b31cc5c7..9c86c8fdcbbbe 100644 --- a/engine/src/flutter/impeller/entity/contents/uber_sdf_contents.cc +++ b/engine/src/flutter/impeller/entity/contents/uber_sdf_contents.cc @@ -32,8 +32,6 @@ Scalar ToShaderType(UberSDFParameters::Type type) { return 3.0f; case UberSDFParameters::Type::kRoundedSuperellipseSymmetric: return 4.0f; - case UberSDFParameters::Type::kRoundedSuperellipse: - return 5.0f; } } @@ -80,24 +78,16 @@ bool UberSDFContents::Render(const ContentContext& renderer, frag_info.stroke_join = params_.stroke ? ToShaderStrokeJoin(params_.stroke->join) : 0.0f; frag_info.aa_pixels = UberSDFParameters::kAntialiasPixels; - frag_info.superellipse_degrees_top = params_.superellipse_degrees_top; - frag_info.superellipse_degrees_right = params_.superellipse_degrees_right; - frag_info.superellipse_semi_axes_top = params_.superellipse_semi_axes_top; - frag_info.superellipse_semi_axes_right = params_.superellipse_semi_axes_right; - frag_info.angle_spans_top = params_.angle_spans_top; - frag_info.angle_spans_right = params_.angle_spans_right; - frag_info.octant_offsets_c = params_.octant_offsets_c; - frag_info.radii_width = params_.radii_width; - frag_info.radii_height = params_.radii_height; - frag_info.circle_centers_top_x = params_.circle_centers_top_x; - frag_info.circle_centers_top_y = params_.circle_centers_top_y; - frag_info.circle_centers_right_x = params_.circle_centers_right_x; - frag_info.circle_centers_right_y = params_.circle_centers_right_y; - frag_info.superellipse_scales_x = params_.superellipse_scales_x; - frag_info.superellipse_scales_y = params_.superellipse_scales_y; - frag_info.quadrant_centers_x = params_.quadrant_centers_x; - frag_info.quadrant_centers_y = params_.quadrant_centers_y; - frag_info.quadrant_splits = params_.quadrant_splits; + frag_info.superellipse_degree = params_.superellipse_degree; + frag_info.superellipse_semi_axis = params_.superellipse_semi_axis; + frag_info.angle_span = params_.angle_span; + frag_info.octant_offset_c = params_.octant_offset_c; + frag_info.radius = params_.radius; + frag_info.circle_center_top = params_.circle_center_top; + frag_info.circle_center_right = params_.circle_center_right; + frag_info.superellipse_scale = params_.superellipse_scale; + frag_info.quadrant_center = params_.quadrant_center; + frag_info.radii = params_.radii; auto geometry_result = GetGeometry()->GetPositionBuffer(renderer, entity, pass); 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 346633abbcec9..17736fb456c57 100644 --- a/engine/src/flutter/impeller/entity/contents/uber_sdf_parameters.cc +++ b/engine/src/flutter/impeller/entity/contents/uber_sdf_parameters.cc @@ -69,8 +69,8 @@ UberSDFParameters UberSDFParameters::MakeRoundedRect( .center = rect.GetCenter(), .size = size, .stroke = stroke, - .radii_width = Vector4(radii.bottom_right.width, radii.top_right.width, - radii.bottom_left.width, radii.top_left.width)}; + .radii = Vector4(radii.bottom_right.width, radii.top_right.width, + radii.bottom_left.width, radii.top_left.width)}; } UberSDFParameters UberSDFParameters::MakeRoundedSuperellipse( @@ -78,99 +78,33 @@ UberSDFParameters UberSDFParameters::MakeRoundedSuperellipse( const Rect& bounds, const RoundSuperellipseParam& round_superellipse_params, std::optional stroke) { + FML_DCHECK(round_superellipse_params.all_corners_same); Point center = bounds.GetCenter(); RoundSuperellipseParam::Quadrant top_right = round_superellipse_params.top_right; - RoundSuperellipseParam::Quadrant bottom_right = - round_superellipse_params.all_corners_same - ? top_right - : round_superellipse_params.bottom_right; - RoundSuperellipseParam::Quadrant bottom_left = - round_superellipse_params.all_corners_same - ? top_right - : round_superellipse_params.bottom_left; - RoundSuperellipseParam::Quadrant top_left = - round_superellipse_params.all_corners_same - ? top_right - : round_superellipse_params.top_left; Point top_right_center_relative = top_right.offset - center; - Point bottom_right_center_relative = bottom_right.offset - center; - Point bottom_left_center_relative = bottom_left.offset - center; - Point top_left_center_relative = top_left.offset - center; Point size = Point(bounds.GetSize() * 0.5f); - Type type = round_superellipse_params.all_corners_same - ? Type::kRoundedSuperellipseSymmetric - : Type::kRoundedSuperellipse; return UberSDFParameters{ - .type = type, + .type = Type::kRoundedSuperellipseSymmetric, .color = color, .center = center, .size = size, .stroke = stroke, - .superellipse_degrees_top = - Vector4(bottom_right.top.se_n, top_right.top.se_n, - bottom_left.top.se_n, top_left.top.se_n), - .superellipse_degrees_right = - Vector4(bottom_right.right.se_n, top_right.right.se_n, - bottom_left.right.se_n, top_left.right.se_n), - .superellipse_semi_axes_top = - Vector4(bottom_right.top.se_a, top_right.top.se_a, - bottom_left.top.se_a, top_left.top.se_a), - .superellipse_semi_axes_right = - Vector4(bottom_right.right.se_a, top_right.right.se_a, - bottom_left.right.se_a, top_left.right.se_a), - .angle_spans_top = Vector4(bottom_right.top.circle_max_angle.radians, - top_right.top.circle_max_angle.radians, - bottom_left.top.circle_max_angle.radians, - top_left.top.circle_max_angle.radians), - .angle_spans_right = Vector4(bottom_right.right.circle_max_angle.radians, - top_right.right.circle_max_angle.radians, - bottom_left.right.circle_max_angle.radians, - top_left.right.circle_max_angle.radians), - .octant_offsets_c = - Vector4(bottom_right.top.se_a - bottom_right.right.se_a, - top_right.top.se_a - top_right.right.se_a, - bottom_left.top.se_a - bottom_left.right.se_a, - top_left.top.se_a - top_left.right.se_a), - .radii_width = - Vector4(bottom_right.top.circle_radius, top_right.top.circle_radius, - bottom_left.top.circle_radius, top_left.top.circle_radius), - .radii_height = Vector4( - bottom_right.right.circle_radius, top_right.right.circle_radius, - bottom_left.right.circle_radius, top_left.right.circle_radius), - .circle_centers_top_x = Vector4( - bottom_right.top.circle_center.x, top_right.top.circle_center.x, - bottom_left.top.circle_center.x, top_left.top.circle_center.x), - .circle_centers_top_y = Vector4( - bottom_right.top.circle_center.y, top_right.top.circle_center.y, - bottom_left.top.circle_center.y, top_left.top.circle_center.y), - .circle_centers_right_x = Vector4( - bottom_right.right.circle_center.x, top_right.right.circle_center.x, - bottom_left.right.circle_center.x, top_left.right.circle_center.x), - .circle_centers_right_y = Vector4( - bottom_right.right.circle_center.y, top_right.right.circle_center.y, - bottom_left.right.circle_center.y, top_left.right.circle_center.y), - .superellipse_scales_x = Vector4( - bottom_right.signed_scale.Abs().x, top_right.signed_scale.Abs().x, - bottom_left.signed_scale.Abs().x, top_left.signed_scale.Abs().x), - .superellipse_scales_y = Vector4( - bottom_right.signed_scale.Abs().y, top_right.signed_scale.Abs().y, - bottom_left.signed_scale.Abs().y, top_left.signed_scale.Abs().y), - .quadrant_centers_x = - Vector4(bottom_right_center_relative.x, top_right_center_relative.x, - bottom_left_center_relative.x, top_left_center_relative.x), - .quadrant_centers_y = - Vector4(bottom_right_center_relative.y, top_right_center_relative.y, - bottom_left_center_relative.y, top_left_center_relative.y), - .quadrant_splits = - Vector4(round_superellipse_params.top_split - center.x, - round_superellipse_params.bottom_split - center.x, - round_superellipse_params.left_split - center.y, - round_superellipse_params.right_split - center.y)}; + .superellipse_degree = Point(top_right.top.se_n, top_right.right.se_n), + .superellipse_semi_axis = Point(top_right.top.se_a, top_right.right.se_a), + .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, + .radius = + Point(top_right.top.circle_radius, top_right.right.circle_radius), + .circle_center_top = top_right.top.circle_center, + .circle_center_right = top_right.right.circle_center, + .superellipse_scale = top_right.signed_scale.Abs(), + .quadrant_center = top_right_center_relative}; } } // 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 ae248643f97bf..015511288b6f6 100644 --- a/engine/src/flutter/impeller/entity/contents/uber_sdf_parameters.h +++ b/engine/src/flutter/impeller/entity/contents/uber_sdf_parameters.h @@ -30,7 +30,6 @@ struct UberSDFParameters { kOval, kRoundedRect, kRoundedSuperellipseSymmetric, - kRoundedSuperellipse, }; /// Creates UberSDFParameters for a rectangle. @@ -80,75 +79,41 @@ struct UberSDFParameters { /// The stroke parameters. If std::nullopt, the shape is filled. std::optional stroke; - /// The degree (n) of the superellipse curve for the top octant of each - /// quadrant. - Vector4 superellipse_degrees_top; - - /// The degree (n) of the superellipse curve for the right octant of each - /// quadrant. - Vector4 superellipse_degrees_right; - - /// The semi-axis length of the superellipse curve for the top octant of each - /// quadrant. - Vector4 superellipse_semi_axes_top; - - /// The semi-axis length of the superellipse curve for the right octant of - /// each quadrant. - Vector4 superellipse_semi_axes_right; + /// The degree (n) of the superellipse curve for the top and right octants. + Point superellipse_degree; - /// The angular span of the circular cap for the top octant of each quadrant. - Vector4 angle_spans_top; + /// The semi-axis length of the superellipse curve for the top and right + /// octants. + Point superellipse_semi_axis; - /// The angular span of the circular cap for the right octant of each - /// quadrant. - Vector4 angle_spans_right; + /// 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. - Vector4 octant_offsets_c; - - /// The horizontal corner radii for rounded shapes and circular caps of - /// superellipses. - Vector4 radii_width; + float octant_offset_c; - /// The vertical corner radii for rounded shapes and circular caps of - /// superellipses. - Vector4 radii_height; + /// The corner radii for rounded shapes and circular caps of superellipses for + /// top and right octants. + Point radius; - /// The X coordinates of the circular cap centers for the top octant of each + /// The circular cap center for the top octant of each /// quadrant. - Vector4 circle_centers_top_x; + Point circle_center_top; - /// The Y coordinates of the circular cap centers for the top octant of each + /// The circular cap center for the right octant of each /// quadrant. - Vector4 circle_centers_top_y; + Point circle_center_right; - /// The X coordinates of the circular cap centers for the right octant of each - /// quadrant. - Vector4 circle_centers_right_x; - - /// The Y coordinates of the circular cap centers for the right octant of each - /// quadrant. - Vector4 circle_centers_right_y; - - /// The X scaling factors used to transform normalized superellipses to their + /// The scaling factors used to transform normalized superellipses to their /// true size. - Vector4 superellipse_scales_x; + Point superellipse_scale; - /// The Y scaling factors used to transform normalized superellipses to their - /// true size. - Vector4 superellipse_scales_y; - - /// The X coordinates of the geometric centers for each of the four corner - /// quadrants. - Vector4 quadrant_centers_x; - - /// The Y coordinates of the geometric centers for each of the four corner - /// quadrants. - Vector4 quadrant_centers_y; + /// The geometric center for the corner + /// quadrant. + Point quadrant_center; - /// The local coordinate split points (top, bottom, left, right) dividing the - /// quadrants. - Vector4 quadrant_splits; + /// Rounding radii for standard rounded rects. + Vector4 radii; }; } // namespace impeller 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 aae0652b2cc84..4af511b9e5fa7 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 @@ -116,10 +116,10 @@ TEST(UberSDFParametersTest, MakeRoundedRect) { EXPECT_EQ(params.color, Color::Red()); EXPECT_EQ(params.center, Point(60, 70)); EXPECT_EQ(params.size, Point(50, 50)); - EXPECT_EQ(params.radii_width.w, 1.0f); - EXPECT_EQ(params.radii_width.y, 2.0f); - EXPECT_EQ(params.radii_width.z, 3.0f); - EXPECT_EQ(params.radii_width.x, 4.0f); + EXPECT_EQ(params.radii.w, 1.0f); + EXPECT_EQ(params.radii.y, 2.0f); + EXPECT_EQ(params.radii.z, 3.0f); + EXPECT_EQ(params.radii.x, 4.0f); EXPECT_FALSE(params.stroke.has_value()); } @@ -127,9 +127,9 @@ TEST(UberSDFParametersTest, MakeRoundedSuperellipse) { Rect rect = Rect::MakeXYWH(10, 20, 100, 100); RoundingRadii radii = { .top_left = Size(10.0f, 10.0f), - .top_right = Size(20.0f, 20.0f), - .bottom_left = Size(40.0f, 40.0f), - .bottom_right = Size(30.0f, 30.0f), + .top_right = Size(10.0f, 10.0f), + .bottom_left = Size(10.0f, 10.0f), + .bottom_right = Size(10.0f, 10.0f), }; auto round_superellipse_params = RoundSuperellipseParam::MakeBoundsRadii(rect, radii); @@ -138,48 +138,27 @@ TEST(UberSDFParametersTest, MakeRoundedSuperellipse) { /*round_superellipse_params=*/round_superellipse_params, /*stroke=*/std::nullopt); - EXPECT_EQ(params.type, UberSDFParameters::Type::kRoundedSuperellipse); + EXPECT_EQ(params.type, + UberSDFParameters::Type::kRoundedSuperellipseSymmetric); EXPECT_EQ(params.color, Color::Red()); EXPECT_EQ(params.center, Point(60, 70)); EXPECT_EQ(params.size, Point(50, 50)); EXPECT_FALSE(params.stroke.has_value()); - // Test the [BR, TR, BL, TL] packing order - EXPECT_EQ(params.radii_width.x, - round_superellipse_params.bottom_right.top.circle_radius); - EXPECT_EQ(params.radii_width.y, + EXPECT_EQ(params.radius.x, round_superellipse_params.top_right.top.circle_radius); - EXPECT_EQ(params.radii_width.z, - round_superellipse_params.bottom_left.top.circle_radius); - EXPECT_EQ(params.radii_width.w, - round_superellipse_params.top_left.top.circle_radius); - - EXPECT_EQ(params.radii_height.x, - round_superellipse_params.bottom_right.right.circle_radius); - EXPECT_EQ(params.radii_height.y, + EXPECT_EQ(params.radius.y, round_superellipse_params.top_right.right.circle_radius); - EXPECT_EQ(params.radii_height.z, - round_superellipse_params.bottom_left.right.circle_radius); - EXPECT_EQ(params.radii_height.w, - round_superellipse_params.top_left.right.circle_radius); - - EXPECT_EQ(params.superellipse_degrees_top.x, - round_superellipse_params.bottom_right.top.se_n); - EXPECT_EQ(params.superellipse_degrees_top.y, + + EXPECT_EQ(params.superellipse_degree.x, round_superellipse_params.top_right.top.se_n); - EXPECT_EQ(params.superellipse_degrees_top.z, - round_superellipse_params.bottom_left.top.se_n); - EXPECT_EQ(params.superellipse_degrees_top.w, - round_superellipse_params.top_left.top.se_n); - - EXPECT_EQ(params.superellipse_scales_x.x, - round_superellipse_params.bottom_right.signed_scale.Abs().x); - EXPECT_EQ(params.superellipse_scales_x.y, + EXPECT_EQ(params.superellipse_degree.y, + round_superellipse_params.top_right.right.se_n); + + EXPECT_EQ(params.superellipse_scale.x, round_superellipse_params.top_right.signed_scale.Abs().x); - EXPECT_EQ(params.superellipse_scales_x.z, - round_superellipse_params.bottom_left.signed_scale.Abs().x); - EXPECT_EQ(params.superellipse_scales_x.w, - round_superellipse_params.top_left.signed_scale.Abs().x); + EXPECT_EQ(params.superellipse_scale.y, + round_superellipse_params.top_right.signed_scale.Abs().y); } } // namespace testing diff --git a/engine/src/flutter/impeller/entity/shaders/complex_rse.frag b/engine/src/flutter/impeller/entity/shaders/complex_rse.frag new file mode 100644 index 0000000000000..91c4426f95e5f --- /dev/null +++ b/engine/src/flutter/impeller/entity/shaders/complex_rse.frag @@ -0,0 +1,280 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +precision mediump float; + +#include +#include + +#include "sdf_functions.glsl" +#include "sdf_utils.glsl" + +uniform FragInfo { + vec4 color; + vec2 center; + vec2 size; + float stroke_width; + float stroked; + vec4 superellipse_degrees_top; + vec4 superellipse_degrees_right; + vec4 superellipse_semi_axes_top; + vec4 superellipse_semi_axes_right; + vec4 angle_spans_top; + vec4 angle_spans_right; + vec4 octant_offsets_c; + vec4 radii_width; + vec4 radii_height; + vec4 circle_centers_top_x; + vec4 circle_centers_top_y; + vec4 circle_centers_right_x; + vec4 circle_centers_right_y; + vec4 superellipse_scales_x; + vec4 superellipse_scales_y; + vec4 quadrant_centers_x; + vec4 quadrant_centers_y; + vec4 quadrant_splits; +} +frag_info; + +out vec4 frag_color; + +highp in vec2 v_position; + +float distanceFromCircle(vec2 p, float radius) { + return length(p) - radius; +} + +float getQuadrantDistance(vec2 p, + float se_degree_top, + float se_degree_right, + float se_a_top, + float se_a_right, + float angle_span_top, + float angle_span_right, + float c, + float radius_top, + float radius_right, + vec2 circle_center_top, + vec2 circle_center_right, + vec2 scale, + vec2 q_center, + int quadrant_index) { + vec2 q_sign = vec2(1.0); + if (quadrant_index == 0) + q_sign = vec2(1.0, 1.0); + else if (quadrant_index == 1) + q_sign = vec2(1.0, -1.0); + else if (quadrant_index == 2) + q_sign = vec2(-1.0, 1.0); + else + q_sign = vec2(-1.0, -1.0); + + // Transform the point into the quadrant's local space + vec2 p_local = (p - q_center) * q_sign; + + // Clamp the point to positive values - this avoids issues with the sdf + // calculations below. This also means that interior distances for this + // function are not totally accurate. + vec2 p_clamped = max(p_local, 0.0); + + // For points that we clamped, return an approximate interior distance + vec2 extents = vec2(scale.x * se_a_top, scale.y * se_a_right); + vec2 d_rect = p_local - extents; + float dist_rect_local = + length(max(d_rect, 0.0)) + min(max(d_rect.x, d_rect.y), 0.0); + + if (p_local.x <= 0.0 || p_local.y <= 0.0) { + return dist_rect_local; + } + + // Map p in to a square. + vec2 p_norm = p_clamped / scale; + + // Declare all RSE params for a single octant. + float se_degree; + float span; + float radius; + vec2 circle_center; + float axis_length; + + // 'p_norm' in the coordinate system of the octant. + vec2 p_oct; + + // We split the quadrant along the diagonal of the transition (p_norm.y + c == + // p_norm.x). This allows us to grab the correct set of parameters for the + // "top" and "right" halves of the corner. + if (p_norm.y + c > p_norm.x) { + p_oct = p_norm + vec2(0.0, c); + se_degree = se_degree_top; + span = angle_span_top; + radius = radius_top; + circle_center = circle_center_top; + axis_length = se_a_top; + } else { + p_oct = p_norm.yx - vec2(0.0, c); + se_degree = se_degree_right; + span = angle_span_right; + radius = radius_right; + circle_center = circle_center_right; + axis_length = se_a_right; + } + + // Move the point to the corner circle's coordinate system. + vec2 p_rel = p_oct - circle_center; + // Grab the angle offset of the point. + float theta = atan(p_rel.y, p_rel.x); + + // The angular distance between the point and the 45 degree midline. + float d_theta = theta - PI_OVER_FOUR; + d_theta = mod(d_theta + PI, TWO_PI) - PI; + + float dist_raw; + vec2 grad_oct; + + // If the point is within the span of the corner circle's arc, + // use a circle SDF. + // This works because the normals of the circular and superelliptical sections + // agree at the transition angle, the total RSE curve is continuous and + // the closest point on a continuous curve to a point lies along the normal. + + // We also compute the gradient of the distance function for normalization. + if (abs(d_theta) < abs(span)) { + dist_raw = distanceFromCircle(p_rel, radius); + grad_oct = normalize(p_rel); + } else { + dist_raw = sdSuperellipse(p_oct / axis_length, se_degree) * axis_length; + // Clamp the coordinate to avoid division by zero + vec2 p_oct_clamped = max(p_oct, vec2(0.001)); + float max_p = max(p_oct_clamped.x, p_oct_clamped.y); + vec2 p_safe = p_oct_clamped / max_p; + // Approximation of the gradient + grad_oct = normalize(pow(p_safe, vec2(se_degree - 1.0))); + } + + if (p_norm.y + c <= p_norm.x) { + grad_oct = grad_oct.yx; + } + + // Divide the distance by the length of the gradient. + // This ensures that the resulting distance has a gradient magnitude of 1 + // everywhere, allowing to be mixed cleanly with other SDFs. + float corner_dist = dist_raw / length(grad_oct / scale); + + return max(dist_rect_local, corner_dist); +} + +float distanceFromRoundedSuperellipse(vec2 p, + vec4 quadrant_splits, + vec2 size, + vec4 superellipse_degrees_top, + vec4 superellipse_degrees_right, + vec4 superellipse_semi_axes_top, + vec4 superellipse_semi_axes_right, + vec4 angle_spans_top, + vec4 angle_spans_right, + vec4 octant_offsets_c, + vec4 radii_width, + vec4 radii_height, + vec4 circle_centers_top_x, + vec4 circle_centers_top_y, + vec4 circle_centers_right_x, + vec4 circle_centers_right_y, + vec4 superellipse_scales_x, + vec4 superellipse_scales_y, + vec4 quadrant_centers_x, + vec4 quadrant_centers_y) { + vec2 T = vec2(quadrant_splits.x, -size.y); + vec2 R = vec2(size.x, quadrant_splits.w); + vec2 B = vec2(quadrant_splits.y, size.y); + vec2 L = vec2(-size.x, quadrant_splits.z); + + // Grab the 2d cross products between p and the split points. + // Imagine drawing a line L from the center of the shape to each split point, + // p x L tells us whether p is clockwise or counterclockwise relative to L. + float cT = T.x * p.y - T.y * p.x; + float cR = R.x * p.y - R.y * p.x; + float cB = B.x * p.y - B.y * p.x; + float cL = L.x * p.y - L.y * p.x; + + int quadrant_index = 0; + // cR = p x R <= 0 -> p is counterclockwise relative to R. + // cT = p x T > 0 -> p is clockwise relative to T. + // If p is clockwise relative to T and counterclockwise relative to R, + // p must lie in the TR quadrant. + // If cT = p x T == 0, p is parallel to T, which can misidentify points in the + // BR quadrant. + if ((cR < 0.0 || cR == 0.0 && p.x > 0.0) && + (cT > 0.0 || cT == 0.0 && p.x > 0.0)) { + quadrant_index = 1; // TR + } else if ((cB < 0.0 || cB == 0.0 && p.x > 0.0) && + (cR > 0.0 || cR == 0.0 && p.x > 0.0)) { + quadrant_index = 0; // BR + } else if (cB >= 0.0 && cL <= 0.0) { + quadrant_index = 2; // BL + } else { + quadrant_index = 3; // TL + } + + float se_degree_top = superellipse_degrees_top[quadrant_index]; + float se_degree_right = superellipse_degrees_right[quadrant_index]; + float se_a_top = superellipse_semi_axes_top[quadrant_index]; + float se_a_right = superellipse_semi_axes_right[quadrant_index]; + float angle_span_top = angle_spans_top[quadrant_index]; + float angle_span_right = angle_spans_right[quadrant_index]; + float c = octant_offsets_c[quadrant_index]; + float radius_top = radii_width[quadrant_index]; + float radius_right = radii_height[quadrant_index]; + + vec2 circle_center_top = vec2(circle_centers_top_x[quadrant_index], + circle_centers_top_y[quadrant_index]); + vec2 circle_center_right = vec2(circle_centers_right_x[quadrant_index], + circle_centers_right_y[quadrant_index]); + + vec2 scale = vec2(superellipse_scales_x[quadrant_index], + superellipse_scales_y[quadrant_index]); + + vec2 q_center = vec2(quadrant_centers_x[quadrant_index], + quadrant_centers_y[quadrant_index]); + + return getQuadrantDistance( + p, se_degree_top, se_degree_right, se_a_top, se_a_right, angle_span_top, + angle_span_right, c, radius_top, radius_right, circle_center_top, + circle_center_right, scale, q_center, quadrant_index); +} + +float pixelSize(float sdf) { + vec2 gradient = vec2(dFdx(sdf), dFdy(sdf)); + return length(gradient); +} + +void main() { + vec2 p = v_position - frag_info.center; + + float base_sdf = distanceFromRoundedSuperellipse( + p, frag_info.quadrant_splits, frag_info.size, + frag_info.superellipse_degrees_top, frag_info.superellipse_degrees_right, + frag_info.superellipse_semi_axes_top, + frag_info.superellipse_semi_axes_right, frag_info.angle_spans_top, + frag_info.angle_spans_right, frag_info.octant_offsets_c, + frag_info.radii_width, frag_info.radii_height, + frag_info.circle_centers_top_x, frag_info.circle_centers_top_y, + frag_info.circle_centers_right_x, frag_info.circle_centers_right_y, + frag_info.superellipse_scales_x, frag_info.superellipse_scales_y, + frag_info.quadrant_centers_x, frag_info.quadrant_centers_y); + + float base_pixel_size = pixelSize(base_sdf); + + vec2 sdf_and_pixel_size = + (frag_info.stroked < 0.5) + ? vec2(base_sdf, base_pixel_size) + : SDFStroke(base_sdf, base_pixel_size, frag_info.stroke_width); + + float sdf = sdf_and_pixel_size.x; + float pixel_size = sdf_and_pixel_size.y; + + float alpha = SDFAlpha(sdf, pixel_size, 1.0); + + frag_color = vec4(frag_info.color.rgb, frag_info.color.a * alpha); + frag_color = IPPremultiply(frag_color); +} \ No newline at end of file diff --git a/engine/src/flutter/impeller/entity/shaders/sdf_functions.glsl b/engine/src/flutter/impeller/entity/shaders/sdf_functions.glsl new file mode 100644 index 0000000000000..de52e3133aa49 --- /dev/null +++ b/engine/src/flutter/impeller/entity/shaders/sdf_functions.glsl @@ -0,0 +1,69 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SDF_FUNCTIONS_GLSL_ +#define SDF_FUNCTIONS_GLSL_ + +const float PI = 3.14159265; +const float TWO_PI = 6.28318531; +const float PI_OVER_FOUR = 0.78539816; + +// SDF for a superellipse defined by (x/a)^n + (y/b)^n = 1 +// +// `p` is the coordinate of the point relative to the center of the superellipse +// normalized by the length of the ellipse semi-axes (a, b) +// `n` is the exponent of the superellipse +// +// https://iquilezles.org/articles/ellipsedist/ +// +// The MIT License +// Copyright © 2015 Inigo Quilez +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: The above copyright +// notice and this permission notice shall be included in all copies or +// substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", +// WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE +// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR +// THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// https://www.youtube.com/c/InigoQuilez +// https://iquilezles.org + +float sdSuperellipse(vec2 p, float n) { + // symmetries + p = abs(p); + if (p.y > p.x) + p = p.yx; + + n = 2.0 / n; // note the remapping in order to match the implicit versions + + float xa = 0.0, xb = TWO_PI / 8.0; + for (int i = 0; i < 6; i++) { + float x = 0.5 * (xa + xb); + float c = cos(x); + float s = sin(x); + float cn = pow(c, n); + float sn = pow(s, n); + float y = (p.x - cn) * cn * s * s - (p.y - sn) * sn * c * c; + + if (y < 0.0) + xa = x; + else + xb = x; + } + // compute distance + vec2 qa = pow(vec2(cos(xa), sin(xa)), vec2(n)); + vec2 qb = pow(vec2(cos(xb), sin(xb)), vec2(n)); + vec2 pa = p - qa, ba = qb - qa; + float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0); + return length(pa - ba * h) * sign(pa.x * ba.y - pa.y * ba.x); +} + +#endif \ No newline at end of file diff --git a/engine/src/flutter/impeller/entity/shaders/sdf_utils.glsl b/engine/src/flutter/impeller/entity/shaders/sdf_utils.glsl new file mode 100644 index 0000000000000..631757c3b824e --- /dev/null +++ b/engine/src/flutter/impeller/entity/shaders/sdf_utils.glsl @@ -0,0 +1,36 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SDF_UTILS_GLSL_ +#define SDF_UTILS_GLSL_ + +// Applies anti-aliasing to an SDF value. +// +// Fade from alpha 1 to 0 across the edge of the SDF (where it +// goes from negative to positive). Fade through distance of half +// (pixel_size * aa_pixels) in each direction. +float SDFAlpha(float sdf, float pixel_size, float aa_pixels) { + float fade_size = pixel_size * aa_pixels * 0.5; + return 1.0 - smoothstep(-fade_size, fade_size, sdf); +} + +// Converts a filled SDF into a stroked SDF. +// +// This is the generic annular conversion for most shapes. +// Some shapes (like Rect with Miter/Bevel joins) require special handling. +vec2 SDFStroke(float base_sdf, float base_pixel_size, float stroke_width) { + // Stroke width is clamped to be at least the base sdf's pixel size. + float half_stroke = max(stroke_width, base_pixel_size) * 0.5; + + // The stroked SDF is defined by the +/- half_stroke isolines of the base SDF. + float sdf = abs(base_sdf) - half_stroke; + + // For these shapes, the stroked pixel size is the same as the base pixel + // size. This is because the stroked SDF's gradient has the same magnitudes as + // the base SDF's gradient (except for a discontinuity at the center of the + // stroke, which does not affect the final render). + return vec2(sdf, base_pixel_size); +} + +#endif \ No newline at end of file diff --git a/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag b/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag index 07acf6b60e83e..86620fefd4083 100644 --- a/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag +++ b/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag @@ -7,6 +7,9 @@ precision mediump float; #include #include +#include "sdf_functions.glsl" +#include "sdf_utils.glsl" + uniform FragInfo { vec4 color; vec2 center; @@ -16,24 +19,16 @@ uniform FragInfo { float aa_pixels; float stroked; float type; - vec4 superellipse_degrees_top; - vec4 superellipse_degrees_right; - vec4 superellipse_semi_axes_top; - vec4 superellipse_semi_axes_right; - vec4 angle_spans_top; - vec4 angle_spans_right; - vec4 octant_offsets_c; - vec4 radii_width; - vec4 radii_height; - vec4 circle_centers_top_x; - vec4 circle_centers_top_y; - vec4 circle_centers_right_x; - vec4 circle_centers_right_y; - vec4 superellipse_scales_x; - vec4 superellipse_scales_y; - vec4 quadrant_centers_x; - vec4 quadrant_centers_y; - vec4 quadrant_splits; + vec2 superellipse_degree; + vec2 superellipse_semi_axis; + vec2 angle_span; + float octant_offset_c; + vec2 radius; + vec2 circle_center_top; + vec2 circle_center_right; + vec2 superellipse_scale; + vec2 quadrant_center; + vec4 radii; } frag_info; @@ -41,10 +36,6 @@ out vec4 frag_color; highp in vec2 v_position; -const float PI = 3.14159265; -const float TWO_PI = 6.28318531; -const float PI_OVER_FOUR = 0.78539816; - float distanceFromCircle(vec2 p, float radius) { return length(p) - radius; } @@ -54,98 +45,49 @@ float distanceFromRect(vec2 p, vec2 b) { return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0); } -// SDF for a superellipse defined by (x/a)^n + (y/b)^n = 1 -// -// `p` is the coordinate of the point relative to the center of the superellipse -// normalized by the length of the ellipse semi-axes (a, b) -// `n` is the exponent of the superellipse -// -// https://iquilezles.org/articles/ellipsedist/ -// -// The MIT License -// Copyright © 2015 Inigo Quilez -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: The above copyright -// notice and this permission notice shall be included in all copies or -// substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", -// WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE -// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR -// THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// https://www.youtube.com/c/InigoQuilez -// https://iquilezles.org - -float sdSuperellipse(vec2 p, float n) { - // symmetries +float distanceFromOval(vec2 p, vec2 ab) { p = abs(p); - if (p.y > p.x) - p = p.yx; - - n = 2.0 / n; // note the remapping in order to match the implicit versions - - float xa = 0.0, xb = TWO_PI / 8.0; - for (int i = 0; i < 6; i++) { - float x = 0.5 * (xa + xb); - float c = cos(x); - float s = sin(x); - float cn = pow(c, n); - float sn = pow(s, n); - float y = (p.x - cn) * cn * s * s - (p.y - sn) * sn * c * c; - - if (y < 0.0) - xa = x; - else - xb = x; + vec2 q = ab * (p - ab); + float w = (q.x < q.y) ? 1.570796327 : 0.0; + for (int i = 0; i < 5; i++) { + vec2 cs = vec2(cos(w), sin(w)); + vec2 u = ab * vec2(cs.x, cs.y); + vec2 v = ab * vec2(-cs.y, cs.x); + w = w + dot(p - u, v) / (dot(p - u, u) + dot(v, v)); } - // compute distance - vec2 qa = pow(vec2(cos(xa), sin(xa)), vec2(n)); - vec2 qb = pow(vec2(cos(xb), sin(xb)), vec2(n)); - vec2 pa = p - qa, ba = qb - qa; - float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0); - return length(pa - ba * h) * sign(pa.x * ba.y - pa.y * ba.x); + float d = length(p - ab * vec2(cos(w), sin(w))); + return (dot(p / ab, p / ab) > 1.0) ? d : -d; } -float getQuadrantDistance(vec2 p, - float se_degree_top, - float se_degree_right, - float se_a_top, - float se_a_right, - float angle_span_top, - float angle_span_right, - float c, - float radius_top, - float radius_right, - vec2 circle_center_top, - vec2 circle_center_right, - vec2 scale, - vec2 q_center, - int quadrant_index) { - vec2 q_sign = vec2(1.0); - if (quadrant_index == 0) - q_sign = vec2(1.0, 1.0); - else if (quadrant_index == 1) - q_sign = vec2(1.0, -1.0); - else if (quadrant_index == 2) - q_sign = vec2(-1.0, 1.0); - else - q_sign = vec2(-1.0, -1.0); - - // Transform the point into the quadrant's local space - vec2 p_local = (p - q_center) * q_sign; - - // Clamp the point to positive values - this avoids issues with the sdf - // calculations below. This also means that interior distances for this - // function are not totally accurate. +float distanceFromRoundedRect(in vec2 p, in vec2 b, in vec4 r) { + r.xy = (p.x > 0.0) ? r.xy : r.zw; + r.x = (p.y > 0.0) ? r.x : r.y; + vec2 q = abs(p) - b + r.x; + return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r.x; +} + +float distanceFromChamferRect(vec2 p, vec2 half_size, float chamfer_size) { + p = abs(p); + float d1 = max(p.x - half_size.x, p.y - half_size.y); + float d2 = + (p.x + p.y - half_size.x - half_size.y + chamfer_size) * 0.70710678; + return max(d1, d2); +} + +float getQuadrantDistanceSymmetric(vec2 p, + vec2 se_degree, + vec2 se_a, + vec2 angle_span, + float c, + vec2 radius, + vec2 circle_center_top, + vec2 circle_center_right, + vec2 scale, + vec2 q_center) { + vec2 p_local = p - q_center; vec2 p_clamped = max(p_local, 0.0); - // For points that we clamped, return an approximate interior distance - vec2 extents = vec2(scale.x * se_a_top, scale.y * se_a_right); + vec2 extents = vec2(scale.x * se_a.x, scale.y * se_a.y); vec2 d_rect = p_local - extents; float dist_rect_local = length(max(d_rect, 0.0)) + min(max(d_rect.x, d_rect.y), 0.0); @@ -154,378 +96,85 @@ float getQuadrantDistance(vec2 p, return dist_rect_local; } - // Map p in to a square. vec2 p_norm = p_clamped / scale; - // Declare all RSE params for a single octant. - float se_degree; + float se_d; float span; - float radius; + float r; vec2 circle_center; float axis_length; - // 'p_norm' in the coordinate system of the octant. vec2 p_oct; - // We split the quadrant along the diagonal of the transition (p_norm.y + c == - // p_norm.x). This allows us to grab the correct set of parameters for the - // "top" and "right" halves of the corner. if (p_norm.y + c > p_norm.x) { p_oct = p_norm + vec2(0.0, c); - se_degree = se_degree_top; - span = angle_span_top; - radius = radius_top; + se_d = se_degree.x; + span = angle_span.x; + r = radius.x; circle_center = circle_center_top; - axis_length = se_a_top; + axis_length = se_a.x; } else { p_oct = p_norm.yx - vec2(0.0, c); - se_degree = se_degree_right; - span = angle_span_right; - radius = radius_right; + se_d = se_degree.y; + span = angle_span.y; + r = radius.y; circle_center = circle_center_right; - axis_length = se_a_right; + axis_length = se_a.y; } - // Move the point to the corner circle's coordinate system. vec2 p_rel = p_oct - circle_center; - // Grab the angle offset of the point. float theta = atan(p_rel.y, p_rel.x); - // The angular distance between the point and the 45 degree midline. float d_theta = theta - PI_OVER_FOUR; d_theta = mod(d_theta + PI, TWO_PI) - PI; float dist_raw; vec2 grad_oct; - // If the point is within the span of the corner circle's arc, - // use a circle SDF. - // This works because the normals of the circular and superelliptical sections - // agree at the transition angle, the total RSE curve is continuous and - // the closest point on a continuous curve to a point lies along the normal. - - // We also compute the gradient of the distance function for normalization. if (abs(d_theta) < abs(span)) { - dist_raw = distanceFromCircle(p_rel, radius); + dist_raw = distanceFromCircle(p_rel, r); grad_oct = normalize(p_rel); } else { - dist_raw = sdSuperellipse(p_oct / axis_length, se_degree) * axis_length; - // Clamp the coordinate to avoid division by zero + dist_raw = sdSuperellipse(p_oct / axis_length, se_d) * axis_length; vec2 p_oct_clamped = max(p_oct, vec2(0.001)); float max_p = max(p_oct_clamped.x, p_oct_clamped.y); vec2 p_safe = p_oct_clamped / max_p; - // Approximation of the gradient - grad_oct = normalize(pow(p_safe, vec2(se_degree - 1.0))); + grad_oct = normalize(pow(p_safe, vec2(se_d - 1.0))); } if (p_norm.y + c <= p_norm.x) { grad_oct = grad_oct.yx; } - // Divide the distance by the length of the gradient. - // This ensures that the resulting distance has a gradient magnitude of 1 - // everywhere, allowing to be mixed cleanly with other SDFs. float corner_dist = dist_raw / length(grad_oct / scale); - return max(corner_dist, dist_rect_local); -} - -float distanceFromRoundedSuperellipse(vec2 p, - vec4 quadrant_splits, - vec2 size, - vec4 superellipse_degrees_top, - vec4 superellipse_degrees_right, - vec4 superellipse_semi_axes_top, - vec4 superellipse_semi_axes_right, - vec4 angle_spans_top, - vec4 angle_spans_right, - vec4 octant_offsets_c, - vec4 radii_width, - vec4 radii_height, - vec4 circle_centers_top_x, - vec4 circle_centers_top_y, - vec4 circle_centers_right_x, - vec4 circle_centers_right_y, - vec4 superellipse_scales_x, - vec4 superellipse_scales_y, - vec4 quadrant_centers_x, - vec4 quadrant_centers_y) { - vec2 T = vec2(quadrant_splits.x, -size.y); - vec2 R = vec2(size.x, quadrant_splits.w); - vec2 B = vec2(quadrant_splits.y, size.y); - vec2 L = vec2(-size.x, quadrant_splits.z); - - // Grab the 2d cross products between p and the split points. - // Imagine drawing a line L from the center of the shape to each split point, - // p x L tells us whether p is clockwise or counterclockwise relative to L. - float cT = T.x * p.y - T.y * p.x; - float cR = R.x * p.y - R.y * p.x; - float cB = B.x * p.y - B.y * p.x; - float cL = L.x * p.y - L.y * p.x; - - int quadrant_index = 0; - // cR = p x R <= 0 -> p is counterclockwise relative to R. - // cT = p x T > 0 -> p is clockwise relative to T. - // If p is clockwise relative to T and counterclockwise relative to R, - // p must lie in the TL quadrant. - // If cT = p x T == 0, p is parallel to T, which can misidentify points in the - // BR quadrant. - - if ((cR < 0.0 || cR == 0.0 && p.x > 0.0) && - (cT > 0.0 || cT == 0.0 && p.x > 0.0)) { - quadrant_index = 1; // TR - } else if ((cB < 0.0 || cB == 0.0 && p.x > 0.0) && - (cR > 0.0 || cR == 0.0 && p.x > 0.0)) { - quadrant_index = 0; // BR - } else if (cB >= 0.0 && cL <= 0.0) { - quadrant_index = 2; // BL - } else { - quadrant_index = 3; // TL - } - - float se_degree_top = superellipse_degrees_top[quadrant_index]; - float se_degree_right = superellipse_degrees_right[quadrant_index]; - float se_a_top = superellipse_semi_axes_top[quadrant_index]; - float se_a_right = superellipse_semi_axes_right[quadrant_index]; - float angle_span_top = angle_spans_top[quadrant_index]; - float angle_span_right = angle_spans_right[quadrant_index]; - float c = octant_offsets_c[quadrant_index]; - float radius_top = radii_width[quadrant_index]; - float radius_right = radii_height[quadrant_index]; - - vec2 circle_center_top = vec2(circle_centers_top_x[quadrant_index], - circle_centers_top_y[quadrant_index]); - vec2 circle_center_right = vec2(circle_centers_right_x[quadrant_index], - circle_centers_right_y[quadrant_index]); - - vec2 scale = vec2(superellipse_scales_x[quadrant_index], - superellipse_scales_y[quadrant_index]); - - vec2 q_center = vec2(quadrant_centers_x[quadrant_index], - quadrant_centers_y[quadrant_index]); - - return getQuadrantDistance( - p, se_degree_top, se_degree_right, se_a_top, se_a_right, angle_span_top, - angle_span_right, c, radius_top, radius_right, circle_center_top, - circle_center_right, scale, q_center, quadrant_index); -} -// Define an ellipse as q(w) = (a*cos(w), b*sin(w)), and p = (x, y) on the -// plane. Let q(w0) be the closest point on q to p, then q(w0) - p is tangent to -// q(w0), and (q(w0) - p) dot q'(w0) = 0. This function uses the Newton-Raphson -// method to find q(w0). -// -// `p` is the coordinate of the point relative to the center of the oval -// `ab` is the extent of the oval from the center to the x and y axis -// -// https://iquilezles.org/articles/ellipsedist/ -// -// The MIT License -// Copyright © 2015 Inigo Quilez -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: The above copyright -// notice and this permission notice shall be included in all copies or -// substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", -// WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE -// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR -// THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// https://www.youtube.com/c/InigoQuilez -// https://iquilezles.org - -float distanceFromOval(vec2 p, vec2 ab) { - // The ellipse is symmetric along both axes, do the calculation in the upper - // right quadrant. - p = abs(p); - - // Initial guess for w0. Determine whether q is closer to the top of the - // ellipse or closer to the righthand side. Use the top (0) or righthand side - // (pi/2) as the initial guess for w0. - vec2 q = ab * (p - ab); - float w = (q.x < q.y) ? 1.570796327 : 0.0; - for (int i = 0; i < 5; i++) { - vec2 cs = vec2(cos(w), sin(w)); - - // u = q(w) = (a*cos(w), b*sin(w)) - vec2 u = ab * vec2(cs.x, cs.y); - - // v = q'(w) = (a*-sin(w), b*cos(w)) - vec2 v = ab * vec2(-cs.y, cs.x); - - // Newton-Raphson update step, w_n = w_n-1 + f(w_n-1)/f'(w_n-1) - // In this case f(w) = (p - q(w)) dot q'(w) = (p - u) dot v - w = w + dot(p - u, v) / (dot(p - u, u) + dot(v, v)); - } - - // Compute final point and distance - float d = length(p - ab * vec2(cos(w), sin(w))); - - // Return signed distance. - // p is outside the ellipse if (p.x/a)^2 + (p.y/b)^2 > 0 - return (dot(p / ab, p / ab) > 1.0) ? d : -d; + return max(dist_rect_local, corner_dist); } -float distanceFromChamferRect(vec2 p, vec2 b, float chamfer) { - vec2 d = abs(p) - b; - - d = (d.y > d.x) ? d.yx : d.xy; - d.y += chamfer; - - const float k = 1.0 - sqrt(2.0); - if (d.y < 0.0 && d.y + d.x * k < 0.0) { - return d.x; - } - - if (d.x < d.y) { - return (d.x + d.y) * sqrt(0.5); - } +float distanceFromSymmetricRoundedSuperellipse(vec2 p, + vec2 superellipse_degree, + vec2 superellipse_semi_axis, + vec2 angle_span, + float octant_offset_c, + vec2 radius, + vec2 circle_center_top, + vec2 circle_center_right, + vec2 superellipse_scale, + vec2 quadrant_center) { + // Fold the pixel into the Top-Right quadrant (x > 0, y < 0) + vec2 p_folded = vec2(abs(p.x), -abs(p.y)); - return length(d); + return getQuadrantDistanceSymmetric( + p_folded, superellipse_degree, superellipse_semi_axis, angle_span, + octant_offset_c, radius, circle_center_top, circle_center_right, + superellipse_scale, quadrant_center); } -// Exact math for rounded rect. -// -// `p` is position relative to the center of the shape. -// `b` is the size of box, .x is the distance between center and left/right, .y -// is the distance between center and top/bottom. `r` is radii for each corner -// in order [bottom_right, top_right, bottom_left, top_left]. -// -// See https://iquilezles.org/articles/distfunctions2d/ -// -// The MIT License -// Copyright © 2015 Inigo Quilez -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: The above copyright -// notice and this permission notice shall be included in all copies or -// substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", -// WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED -// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE -// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -// TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR -// THE USE OR OTHER DEALINGS IN THE SOFTWARE. -// https://www.youtube.com/c/InigoQuilez -// https://iquilezles.org - -float distanceFromRoundedRect(in vec2 p, in vec2 b, in vec4 r) { - r.xy = (p.x > 0.0) ? r.xy : r.zw; - r.x = (p.y > 0.0) ? r.x : r.y; - vec2 q = abs(p) - b + r.x; - return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r.x; -} - -// Returns the pixel size for the given SDF value. -// -// This is the size of a pixel at the current fragment, measured in the -// direction perpendicular to the SDF's shape. float pixelSize(float sdf) { - // Gradient vector of the SDF at point p. Points in the direction of steepest - // increase away from SDF's shape. At the edges of the shape, this is - // perpendicular to the edge. - // - // The x and y magnitudes of the gradient are determined by the dFdx and dFdy - // of the SDF value. dFdx and dFdy return the change of a value in the x and y - // direction per screen-space unit (physical pixel). So this gradient - // is the change in the SDF, at point p, in local space units per pixel. vec2 gradient = vec2(dFdx(sdf), dFdy(sdf)); - - // The length of the gradient vector is how fast the SDF changes per - // screen-space pixel distance. In other words, it is the size of a pixel - // measured in the units of the SDF calculation. - // - // In local space, the SDF always increases by 1 in the gradient's direction - // per unit distance. That's the definition of an SDF: it is the distance to - // the closest point of the shape. But in terms of screen-space, the SDF may - // increase by a different amount than 1 per unit distance (in screen-space - // units, i.e. physical pixels), due to scales/skews/rotations. - // - // As an example, consider the SDF of an unscaled/unskewed circle centered at - // the origin. The gradient is vec2(1.0, 0.0) for points along the positive x - // axis[^1]: for every one pixel we move along the positive x axis, - // the SDF value increases by 1.0. Now consider the same circle with a - // transformation that scales it by 2 along the x axis. With a transformation, - // the local space size of the circle remains the same, but the way it maps - // onto screen-space pixels is changed. In screen-space the circle is - // stretched to be twice as wide as the original circle in the postive and - // negative x directions. The gradient for this will be vec2(0.5, 0.0) along - // the positive x axis: for every physical pixel we move along the positive x - // axis, we move only 0.5 units in the SDF's local space. - // - // [^1]: In the real world, there would not be a pixel where the gradient - // vector for a circle is exactly (1.0, 0.0) due to the way dFdx and dFdy are - // approximated from pixel samples. This does not affect the applicability - // of this example. return length(gradient); } -float distanceFromSymmetricRoundedSuperellipse( - vec2 p, - vec4 superellipse_degrees_top, - vec4 superellipse_degrees_right, - vec4 superellipse_semi_axes_top, - vec4 superellipse_semi_axes_right, - vec4 angle_spans_top, - vec4 angle_spans_right, - vec4 octant_offsets_c, - vec4 radii_width, - vec4 radii_height, - vec4 circle_centers_top_x, - vec4 circle_centers_top_y, - vec4 circle_centers_right_x, - vec4 circle_centers_right_y, - vec4 superellipse_scales_x, - vec4 superellipse_scales_y, - vec4 quadrant_centers_x, - vec4 quadrant_centers_y) { - // Fold the pixel into the Top-Right quadrant (x > 0, y < 0) - vec2 p_folded = vec2(abs(p.x), -abs(p.y)); - - // The Top-Right quadrant is index 1 - int quadrant_index = 1; - - float se_degree_top = superellipse_degrees_top[quadrant_index]; - float se_degree_right = superellipse_degrees_right[quadrant_index]; - float se_a_top = superellipse_semi_axes_top[quadrant_index]; - float se_a_right = superellipse_semi_axes_right[quadrant_index]; - float angle_span_top = angle_spans_top[quadrant_index]; - float angle_span_right = angle_spans_right[quadrant_index]; - float c = octant_offsets_c[quadrant_index]; - float radius_top = radii_width[quadrant_index]; - float radius_right = radii_height[quadrant_index]; - - vec2 circle_center_top = vec2(circle_centers_top_x[quadrant_index], - circle_centers_top_y[quadrant_index]); - vec2 circle_center_right = vec2(circle_centers_right_x[quadrant_index], - circle_centers_right_y[quadrant_index]); - - vec2 scale = vec2(superellipse_scales_x[quadrant_index], - superellipse_scales_y[quadrant_index]); - - vec2 q_center = vec2(quadrant_centers_x[quadrant_index], - quadrant_centers_y[quadrant_index]); - - return getQuadrantDistance( - p_folded, se_degree_top, se_degree_right, se_a_top, se_a_right, - angle_span_top, angle_span_right, c, radius_top, radius_right, - circle_center_top, circle_center_right, scale, q_center, quadrant_index); -} - -// Computes the SDF value and pixel size for a filled shape. -// -// `p` is position relative to the center of the shape. -// -// Returns a vec2 with: -// x: The SDF value at `p`. -// y: The pixel size at `p`. vec2 filledSDF(vec2 p) { float sdf; if (frag_info.type < 0.5) { // Circle @@ -535,86 +184,41 @@ vec2 filledSDF(vec2 p) { } else if (frag_info.type < 2.5) { // Oval sdf = distanceFromOval(p, frag_info.size); } else if (frag_info.type < 3.5) { // Rounded Rect - sdf = distanceFromRoundedRect(p, frag_info.size, frag_info.radii_width); - } else if (frag_info.type < 4.5) { // Symmetric Rounded Superellipse + sdf = distanceFromRoundedRect(p, frag_info.size, frag_info.radii); + } else { // Symmetric Rounded Superellipse sdf = distanceFromSymmetricRoundedSuperellipse( - p, frag_info.superellipse_degrees_top, - frag_info.superellipse_degrees_right, - frag_info.superellipse_semi_axes_top, - frag_info.superellipse_semi_axes_right, frag_info.angle_spans_top, - frag_info.angle_spans_right, frag_info.octant_offsets_c, - frag_info.radii_width, frag_info.radii_height, - frag_info.circle_centers_top_x, frag_info.circle_centers_top_y, - frag_info.circle_centers_right_x, frag_info.circle_centers_right_y, - frag_info.superellipse_scales_x, frag_info.superellipse_scales_y, - frag_info.quadrant_centers_x, frag_info.quadrant_centers_y); - } else { // Asymmetric Rounded Superellipse - sdf = distanceFromRoundedSuperellipse( - p, frag_info.quadrant_splits, frag_info.size, - frag_info.superellipse_degrees_top, - frag_info.superellipse_degrees_right, - frag_info.superellipse_semi_axes_top, - frag_info.superellipse_semi_axes_right, frag_info.angle_spans_top, - frag_info.angle_spans_right, frag_info.octant_offsets_c, - frag_info.radii_width, frag_info.radii_height, - frag_info.circle_centers_top_x, frag_info.circle_centers_top_y, - frag_info.circle_centers_right_x, frag_info.circle_centers_right_y, - frag_info.superellipse_scales_x, frag_info.superellipse_scales_y, - frag_info.quadrant_centers_x, frag_info.quadrant_centers_y); + p, frag_info.superellipse_degree, frag_info.superellipse_semi_axis, + frag_info.angle_span, frag_info.octant_offset_c, frag_info.radius, + frag_info.circle_center_top, frag_info.circle_center_right, + frag_info.superellipse_scale, frag_info.quadrant_center); } return vec2(sdf, pixelSize(sdf)); } -// Computes the SDF value and pixel size for a stroked shape. -// -// `p` is position relative to the center of the shape. -// -// Returns a vec2 with: -// x: The SDF value at `p`. -// y: The pixel size at `p`. vec2 strokedSDF(vec2 p) { - // Get the base (filled) SDF for this shape. The filled SDF pixel size is used - // to calculate a minimum stroke width, and the filled SDF value is used to - // calculate the stroked SDF value for many shapes. vec2 base_sdf_and_pixel_size = filledSDF(p); float base_sdf = base_sdf_and_pixel_size.x; float base_pixel_size = base_sdf_and_pixel_size.y; - // Stroke width is clamped to be at least the base sdf's pixel size. float half_stroke = max(frag_info.stroke_width, base_pixel_size) * 0.5; - // Some cases need special handling because their stroked SDFs have a - // different shape from their base SDFs. if (frag_info.type >= 0.5 && frag_info.type < 1.5) { // Rect if (frag_info.stroke_join < 0.5) { // Miter - // Outer edge is the SDF for a rect with size expanded by half_stroke. float outer = distanceFromRect(p, frag_info.size + half_stroke); - // Inner edge is base_sdf's -half_stroke isoline. float inner = base_sdf + half_stroke; float sdf = max(outer, -inner); return vec2(sdf, pixelSize(sdf)); } else if (frag_info.stroke_join < 1.5) { // Bevel - // Outer edge is the SDF for a rect with size expanded by half_stroke, - // with a half_stroke chamfer. float outer = distanceFromChamferRect(p, frag_info.size + half_stroke, half_stroke); - // Inner edge is base_sdf's -half_stroke isoline. float inner = base_sdf + half_stroke; float sdf = max(outer, -inner); return vec2(sdf, pixelSize(sdf)); - } // else stroke_join is Round. Fall through to the common case. + } } - // For most shapes, the stroked SDF is defined by the +/- half_stroke - // isolines of the base SDF. See the "Making shapes annular" section in - // https://iquilezles.org/articles/distfunctions2d/. - float sdf = abs(base_sdf) - half_stroke; - // For these shapes, the stroked pixel size is the same as the base pixel - // size. This is because the stroked SDF's gradient has the same magnitudes as - // the base SDF's gradient (except for a discontinuity at the center of the - // stroke, which does not affect the final render). - return vec2(sdf, base_pixel_size); + return SDFStroke(base_sdf, base_pixel_size, frag_info.stroke_width); } void main() { @@ -625,11 +229,7 @@ void main() { float sdf = sdf_and_pixel_size.x; float pixel_size = sdf_and_pixel_size.y; - // Anti-aliasing. Fade from alpha 1 to 0 across the edge of the SDF (where it - // goes from negative to positive). Fade through distance of half - // (pixel_size * aa_pixels) in each direction. - float fade_size = pixel_size * frag_info.aa_pixels * 0.5; - float alpha = 1.0 - smoothstep(-fade_size, fade_size, sdf); + float alpha = SDFAlpha(sdf, pixel_size, frag_info.aa_pixels); frag_color = vec4(frag_info.color.rgb, frag_info.color.a * alpha); frag_color = IPPremultiply(frag_color); From f380feddc06e660cf90ce7399bb505228b4da029 Mon Sep 17 00:00:00 2001 From: Evan Walley Date: Tue, 26 May 2026 20:35:40 +0000 Subject: [PATCH 12/15] malioc --- engine/src/flutter/impeller/tools/malioc.json | 243 ++++++++++++++++-- 1 file changed, 217 insertions(+), 26 deletions(-) diff --git a/engine/src/flutter/impeller/tools/malioc.json b/engine/src/flutter/impeller/tools/malioc.json index 9ca567ccb988c..49cfce1f7163b 100644 --- a/engine/src/flutter/impeller/tools/malioc.json +++ b/engine/src/flutter/impeller/tools/malioc.json @@ -649,6 +649,79 @@ } } }, + "flutter/impeller/entity/complex_rse.frag.vkspv": { + "Mali-G78": { + "core": "Mali-G78", + "filename": "flutter/impeller/entity/complex_rse.frag.vkspv", + "has_side_effects": false, + "has_uniform_computation": true, + "modifies_coverage": false, + "reads_color_buffer": false, + "type": "Fragment", + "uses_late_zs_test": false, + "uses_late_zs_update": false, + "variants": { + "Main": { + "fp16_arithmetic": 31, + "has_stack_spilling": false, + "performance": { + "longest_path_bound_pipelines": [ + "arith_total", + "arith_sfu" + ], + "longest_path_cycles": [ + 4.25, + 3.637500047683716, + 3.487499952316284, + 4.25, + 0.0, + 0.25, + 0.0 + ], + "pipelines": [ + "arith_total", + "arith_fma", + "arith_cvt", + "arith_sfu", + "load_store", + "varying", + "texture" + ], + "shortest_path_bound_pipelines": [ + "arith_total", + "arith_cvt" + ], + "shortest_path_cycles": [ + 0.65625, + 0.515625, + 0.65625, + 0.375, + 0.0, + 0.25, + 0.0 + ], + "total_bound_pipelines": [ + "arith_total", + "arith_sfu" + ], + "total_cycles": [ + 4.3125, + 3.737499952316284, + 3.75, + 4.3125, + 0.0, + 0.25, + 0.0 + ] + }, + "stack_spill_bytes": 0, + "thread_occupancy": 100, + "uniform_registers_used": 128, + "work_registers_used": 32 + } + } + } + }, "flutter/impeller/entity/conical_gradient_fill_conical.frag.vkspv": { "Mali-G78": { "core": "Mali-G78", @@ -2786,6 +2859,124 @@ } } }, + "flutter/impeller/entity/gles/complex_rse.frag.gles": { + "Mali-G78": { + "core": "Mali-G78", + "filename": "flutter/impeller/entity/gles/complex_rse.frag.gles", + "has_side_effects": false, + "has_uniform_computation": true, + "modifies_coverage": false, + "reads_color_buffer": false, + "type": "Fragment", + "uses_late_zs_test": false, + "uses_late_zs_update": false, + "variants": { + "Main": { + "fp16_arithmetic": 31, + "has_stack_spilling": false, + "performance": { + "longest_path_bound_pipelines": [ + "arith_total", + "arith_sfu" + ], + "longest_path_cycles": [ + 4.25, + 4.074999809265137, + 3.46875, + 4.25, + 0.0, + 0.25, + 0.0 + ], + "pipelines": [ + "arith_total", + "arith_fma", + "arith_cvt", + "arith_sfu", + "load_store", + "varying", + "texture" + ], + "shortest_path_bound_pipelines": [ + "arith_total", + "arith_cvt" + ], + "shortest_path_cycles": [ + 0.824999988079071, + 0.59375, + 0.824999988079071, + 0.375, + 0.0, + 0.25, + 0.0 + ], + "total_bound_pipelines": [ + "arith_total", + "arith_sfu" + ], + "total_cycles": [ + 4.3125, + 4.1875, + 3.924999952316284, + 4.3125, + 0.0, + 0.25, + 0.0 + ] + }, + "stack_spill_bytes": 0, + "thread_occupancy": 100, + "uniform_registers_used": 64, + "work_registers_used": 32 + } + } + }, + "Mali-T880": { + "core": "Mali-T880", + "filename": "flutter/impeller/entity/gles/complex_rse.frag.gles", + "has_uniform_computation": false, + "type": "Fragment", + "variants": { + "Main": { + "has_stack_spilling": true, + "performance": { + "longest_path_bound_pipelines": [ + "arithmetic" + ], + "longest_path_cycles": [ + 54.119998931884766, + 5.0, + 2.0 + ], + "pipelines": [ + "arithmetic", + "load_store", + "texture" + ], + "shortest_path_bound_pipelines": [ + "arithmetic" + ], + "shortest_path_cycles": [ + 9.899999618530273, + 1.0, + 2.0 + ], + "total_bound_pipelines": [ + "arithmetic" + ], + "total_cycles": [ + 56.33333206176758, + 5.0, + 2.0 + ] + }, + "thread_occupancy": 100, + "uniform_registers_used": 11, + "work_registers_used": 4 + } + } + } + }, "flutter/impeller/entity/gles/conical_gradient_fill_conical.frag.gles": { "Mali-G78": { "core": "Mali-G78", @@ -8680,7 +8871,7 @@ "uses_late_zs_update": false, "variants": { "Main": { - "fp16_arithmetic": 31, + "fp16_arithmetic": 34, "has_stack_spilling": false, "performance": { "longest_path_bound_pipelines": [ @@ -8688,10 +8879,10 @@ "arith_sfu" ], "longest_path_cycles": [ - 4.5625, - 4.375, - 3.762500047683716, - 4.5625, + 4.4375, + 4.21875, + 1.90625, + 4.4375, 0.0, 0.25, 0.0 @@ -8712,7 +8903,7 @@ "shortest_path_cycles": [ 0.4375, 0.4375, - 0.109375, + 0.09375, 0.375, 0.0, 0.25, @@ -8723,10 +8914,10 @@ "arith_fma" ], "total_cycles": [ - 20.5, - 20.5, - 12.4375, - 19.4375, + 12.75, + 12.75, + 4.53125, + 11.375, 0.0, 0.25, 0.0 @@ -8734,7 +8925,7 @@ }, "stack_spill_bytes": 0, "thread_occupancy": 100, - "uniform_registers_used": 80, + "uniform_registers_used": 48, "work_registers_used": 32 } } @@ -8752,7 +8943,7 @@ "arithmetic" ], "longest_path_cycles": [ - 63.029998779296875, + 48.5099983215332, 1.0, 4.0 ], @@ -8773,14 +8964,14 @@ "arithmetic" ], "total_cycles": [ - 179.3333282470703, + 98.0, 1.0, 8.0 ] }, "thread_occupancy": 50, - "uniform_registers_used": 12, - "work_registers_used": 7 + "uniform_registers_used": 5, + "work_registers_used": 6 } } } @@ -12088,7 +12279,7 @@ "uses_late_zs_update": false, "variants": { "Main": { - "fp16_arithmetic": 34, + "fp16_arithmetic": 38, "has_stack_spilling": false, "performance": { "longest_path_bound_pipelines": [ @@ -12096,10 +12287,10 @@ "arith_sfu" ], "longest_path_cycles": [ - 4.5625, - 3.887500047683716, - 3.8125, - 4.5625, + 4.4375, + 3.762500047683716, + 2.125, + 4.4375, 0.0, 0.25, 0.0 @@ -12128,13 +12319,13 @@ ], "total_bound_pipelines": [ "arith_total", - "arith_sfu" + "arith_fma" ], "total_cycles": [ - 19.4375, - 18.875, - 12.5625, - 19.4375, + 11.9375, + 11.9375, + 5.03125, + 11.375, 0.0, 0.25, 0.0 @@ -12142,7 +12333,7 @@ }, "stack_spill_bytes": 0, "thread_occupancy": 100, - "uniform_registers_used": 128, + "uniform_registers_used": 50, "work_registers_used": 32 } } From 5c4fa9ff66dbaefd70e88e6d5dc4f5d76dcec021 Mon Sep 17 00:00:00 2001 From: Evan Walley Date: Wed, 27 May 2026 19:12:14 -0700 Subject: [PATCH 13/15] comments, opmitize symmetric RSE code, clean up --- .../entity/contents/complex_rse_contents.cc | 2 +- .../entity/contents/complex_rse_contents.h | 3 + .../entity/contents/uber_sdf_contents.cc | 2 - .../entity/contents/uber_sdf_parameters.cc | 7 +- .../entity/contents/uber_sdf_parameters.h | 11 +- .../contents/uber_sdf_parameters_unittests.cc | 4 +- .../impeller/entity/shaders/complex_rse.frag | 2 +- .../entity/shaders/sdf_functions.glsl | 2 +- .../impeller/entity/shaders/sdf_utils.glsl | 2 +- .../impeller/entity/shaders/uber_sdf.frag | 115 ++++++------------ 10 files changed, 53 insertions(+), 97 deletions(-) diff --git a/engine/src/flutter/impeller/entity/contents/complex_rse_contents.cc b/engine/src/flutter/impeller/entity/contents/complex_rse_contents.cc index 3db5e3e1b546f..5f9ccb18065a1 100644 --- a/engine/src/flutter/impeller/entity/contents/complex_rse_contents.cc +++ b/engine/src/flutter/impeller/entity/contents/complex_rse_contents.cc @@ -169,4 +169,4 @@ const Geometry* ComplexRoundedSuperellipseContents::GetGeometry() const { return geometry_.get(); } -} // namespace impeller \ No newline at end of file +} // namespace impeller diff --git a/engine/src/flutter/impeller/entity/contents/complex_rse_contents.h b/engine/src/flutter/impeller/entity/contents/complex_rse_contents.h index 735fb97aa5a13..1b2eb09a0cf15 100644 --- a/engine/src/flutter/impeller/entity/contents/complex_rse_contents.h +++ b/engine/src/flutter/impeller/entity/contents/complex_rse_contents.h @@ -18,6 +18,9 @@ namespace impeller { +/// A contents class that renders asymmetric rounded superellipses using SDFs. +/// +/// Separated from 'UberSDFContents' to reduce uniform bloat class ComplexRoundedSuperellipseContents : public ColorSourceContents { public: static std::unique_ptr Make( 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 9c86c8fdcbbbe..5bec7103c92b5 100644 --- a/engine/src/flutter/impeller/entity/contents/uber_sdf_contents.cc +++ b/engine/src/flutter/impeller/entity/contents/uber_sdf_contents.cc @@ -82,11 +82,9 @@ bool UberSDFContents::Render(const ContentContext& renderer, frag_info.superellipse_semi_axis = params_.superellipse_semi_axis; frag_info.angle_span = params_.angle_span; frag_info.octant_offset_c = params_.octant_offset_c; - frag_info.radius = params_.radius; frag_info.circle_center_top = params_.circle_center_top; frag_info.circle_center_right = params_.circle_center_right; frag_info.superellipse_scale = params_.superellipse_scale; - frag_info.quadrant_center = params_.quadrant_center; frag_info.radii = params_.radii; auto geometry_result = 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 17736fb456c57..e804a0cc7469c 100644 --- a/engine/src/flutter/impeller/entity/contents/uber_sdf_parameters.cc +++ b/engine/src/flutter/impeller/entity/contents/uber_sdf_parameters.cc @@ -84,8 +84,6 @@ UberSDFParameters UberSDFParameters::MakeRoundedSuperellipse( RoundSuperellipseParam::Quadrant top_right = round_superellipse_params.top_right; - Point top_right_center_relative = top_right.offset - center; - Point size = Point(bounds.GetSize() * 0.5f); return UberSDFParameters{ @@ -99,12 +97,11 @@ UberSDFParameters UberSDFParameters::MakeRoundedSuperellipse( .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, - .radius = - Point(top_right.top.circle_radius, top_right.right.circle_radius), .circle_center_top = top_right.top.circle_center, .circle_center_right = top_right.right.circle_center, .superellipse_scale = top_right.signed_scale.Abs(), - .quadrant_center = top_right_center_relative}; + .radii = Vector4(top_right.top.circle_radius, + top_right.right.circle_radius, 0.0f, 0.0f)}; } } // 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 015511288b6f6..942d6ccf5db3e 100644 --- a/engine/src/flutter/impeller/entity/contents/uber_sdf_parameters.h +++ b/engine/src/flutter/impeller/entity/contents/uber_sdf_parameters.h @@ -92,10 +92,6 @@ struct UberSDFParameters { /// The geometric offset 'c' used to connect the two octants of each quadrant. float octant_offset_c; - /// The corner radii for rounded shapes and circular caps of superellipses for - /// top and right octants. - Point radius; - /// The circular cap center for the top octant of each /// quadrant. Point circle_center_top; @@ -108,11 +104,8 @@ struct UberSDFParameters { /// true size. Point superellipse_scale; - /// The geometric center for the corner - /// quadrant. - Point quadrant_center; - - /// Rounding radii for standard rounded rects. + /// Rounding radii for standard rounded rects and corner radii for circular + /// caps of superellipses for top and right octants. Vector4 radii; }; 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 4af511b9e5fa7..2c5858925e1bb 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 @@ -145,9 +145,9 @@ TEST(UberSDFParametersTest, MakeRoundedSuperellipse) { EXPECT_EQ(params.size, Point(50, 50)); EXPECT_FALSE(params.stroke.has_value()); - EXPECT_EQ(params.radius.x, + EXPECT_EQ(params.radii.x, round_superellipse_params.top_right.top.circle_radius); - EXPECT_EQ(params.radius.y, + EXPECT_EQ(params.radii.y, round_superellipse_params.top_right.right.circle_radius); EXPECT_EQ(params.superellipse_degree.x, diff --git a/engine/src/flutter/impeller/entity/shaders/complex_rse.frag b/engine/src/flutter/impeller/entity/shaders/complex_rse.frag index 91c4426f95e5f..fe48c767958e6 100644 --- a/engine/src/flutter/impeller/entity/shaders/complex_rse.frag +++ b/engine/src/flutter/impeller/entity/shaders/complex_rse.frag @@ -277,4 +277,4 @@ void main() { frag_color = vec4(frag_info.color.rgb, frag_info.color.a * alpha); frag_color = IPPremultiply(frag_color); -} \ No newline at end of file +} diff --git a/engine/src/flutter/impeller/entity/shaders/sdf_functions.glsl b/engine/src/flutter/impeller/entity/shaders/sdf_functions.glsl index de52e3133aa49..576d39a93a69f 100644 --- a/engine/src/flutter/impeller/entity/shaders/sdf_functions.glsl +++ b/engine/src/flutter/impeller/entity/shaders/sdf_functions.glsl @@ -66,4 +66,4 @@ float sdSuperellipse(vec2 p, float n) { return length(pa - ba * h) * sign(pa.x * ba.y - pa.y * ba.x); } -#endif \ No newline at end of file +#endif diff --git a/engine/src/flutter/impeller/entity/shaders/sdf_utils.glsl b/engine/src/flutter/impeller/entity/shaders/sdf_utils.glsl index 631757c3b824e..407830a30f664 100644 --- a/engine/src/flutter/impeller/entity/shaders/sdf_utils.glsl +++ b/engine/src/flutter/impeller/entity/shaders/sdf_utils.glsl @@ -33,4 +33,4 @@ vec2 SDFStroke(float base_sdf, float base_pixel_size, float stroke_width) { return vec2(sdf, base_pixel_size); } -#endif \ No newline at end of file +#endif diff --git a/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag b/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag index 86620fefd4083..d15ea608803e8 100644 --- a/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag +++ b/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag @@ -23,11 +23,9 @@ uniform FragInfo { vec2 superellipse_semi_axis; vec2 angle_span; float octant_offset_c; - vec2 radius; vec2 circle_center_top; vec2 circle_center_right; vec2 superellipse_scale; - vec2 quadrant_center; vec4 radii; } frag_info; @@ -74,100 +72,67 @@ float distanceFromChamferRect(vec2 p, vec2 half_size, float chamfer_size) { return max(d1, d2); } -float getQuadrantDistanceSymmetric(vec2 p, - vec2 se_degree, - vec2 se_a, - vec2 angle_span, - float c, - vec2 radius, - vec2 circle_center_top, - vec2 circle_center_right, - vec2 scale, - vec2 q_center) { - vec2 p_local = p - q_center; - vec2 p_clamped = max(p_local, 0.0); - - vec2 extents = vec2(scale.x * se_a.x, scale.y * se_a.y); - vec2 d_rect = p_local - extents; - float dist_rect_local = - length(max(d_rect, 0.0)) + min(max(d_rect.x, d_rect.y), 0.0); - - if (p_local.x <= 0.0 || p_local.y <= 0.0) { - return dist_rect_local; - } - - vec2 p_norm = p_clamped / scale; +float distanceFromRoundedSuperellipse(vec2 p, + vec2 degree, + vec2 se_a, + vec2 radii, + vec2 angle_span, + vec2 circle_center_top, + vec2 circle_center_right, + float c, + vec2 scale) { + // Do work in the first quadrant to simply things. + p = abs(p); + // Map p in to a square. + vec2 p_norm = p / scale; - float se_d; - float span; - float r; + // Declare all RSE params for a single octant. + float se_degree, span, radius, axis_length; vec2 circle_center; - float axis_length; + // 'p' in the coordinate system of the octant. vec2 p_oct; + // We split the quadrant along the diagonal of the transition (p_norm.y + c == + // p_norm.x). This allows us to grab the correct set of parameters for the + // "top" and "right" halves of the corner. if (p_norm.y + c > p_norm.x) { p_oct = p_norm + vec2(0.0, c); - se_d = se_degree.x; + se_degree = degree.x; span = angle_span.x; - r = radius.x; + radius = radii.x; circle_center = circle_center_top; axis_length = se_a.x; } else { + // For the 'right' octant, we flip the point and shift it according to + // the CPU's OctantContains/Flip logic. p_oct = p_norm.yx - vec2(0.0, c); - se_d = se_degree.y; + se_degree = degree.y; span = angle_span.y; - r = radius.y; + radius = radii.y; circle_center = circle_center_right; axis_length = se_a.y; } + // Move the point to the corner circle's coordinate system. vec2 p_rel = p_oct - circle_center; + + // Grab the angle offset of the point. float theta = atan(p_rel.y, p_rel.x); + // The angular distance between the point and the 45 degree midline. float d_theta = theta - PI_OVER_FOUR; d_theta = mod(d_theta + PI, TWO_PI) - PI; - float dist_raw; - vec2 grad_oct; - + // If the point is within the span of the corner circle's arc, + // use a circle SDF. + // This works because the normals of the circular and superelliptical sections + // agree at the transition angle, the total RSE curve is continuous and + // the closest point on a continuous curve to a point lies along the normal. if (abs(d_theta) < abs(span)) { - dist_raw = distanceFromCircle(p_rel, r); - grad_oct = normalize(p_rel); - } else { - dist_raw = sdSuperellipse(p_oct / axis_length, se_d) * axis_length; - vec2 p_oct_clamped = max(p_oct, vec2(0.001)); - float max_p = max(p_oct_clamped.x, p_oct_clamped.y); - vec2 p_safe = p_oct_clamped / max_p; - grad_oct = normalize(pow(p_safe, vec2(se_d - 1.0))); + return distanceFromCircle(p_rel, radius); } - - if (p_norm.y + c <= p_norm.x) { - grad_oct = grad_oct.yx; - } - - float corner_dist = dist_raw / length(grad_oct / scale); - - return max(dist_rect_local, corner_dist); -} - -float distanceFromSymmetricRoundedSuperellipse(vec2 p, - vec2 superellipse_degree, - vec2 superellipse_semi_axis, - vec2 angle_span, - float octant_offset_c, - vec2 radius, - vec2 circle_center_top, - vec2 circle_center_right, - vec2 superellipse_scale, - vec2 quadrant_center) { - // Fold the pixel into the Top-Right quadrant (x > 0, y < 0) - vec2 p_folded = vec2(abs(p.x), -abs(p.y)); - - return getQuadrantDistanceSymmetric( - p_folded, superellipse_degree, superellipse_semi_axis, angle_span, - octant_offset_c, radius, circle_center_top, circle_center_right, - superellipse_scale, quadrant_center); + return sdSuperellipse(p_oct / axis_length, se_degree) * axis_length; } float pixelSize(float sdf) { @@ -186,11 +151,11 @@ vec2 filledSDF(vec2 p) { } else if (frag_info.type < 3.5) { // Rounded Rect sdf = distanceFromRoundedRect(p, frag_info.size, frag_info.radii); } else { // Symmetric Rounded Superellipse - sdf = distanceFromSymmetricRoundedSuperellipse( + sdf = distanceFromRoundedSuperellipse( p, frag_info.superellipse_degree, frag_info.superellipse_semi_axis, - frag_info.angle_span, frag_info.octant_offset_c, frag_info.radius, - frag_info.circle_center_top, frag_info.circle_center_right, - frag_info.superellipse_scale, frag_info.quadrant_center); + 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.superellipse_scale); } return vec2(sdf, pixelSize(sdf)); } From 669d16896efe0eae0647ac2c929b4d584d985b5f Mon Sep 17 00:00:00 2001 From: Evan Walley Date: Thu, 28 May 2026 02:18:06 +0000 Subject: [PATCH 14/15] malioc --- engine/src/flutter/impeller/tools/malioc.json | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/engine/src/flutter/impeller/tools/malioc.json b/engine/src/flutter/impeller/tools/malioc.json index 49cfce1f7163b..1c17bc82e88b1 100644 --- a/engine/src/flutter/impeller/tools/malioc.json +++ b/engine/src/flutter/impeller/tools/malioc.json @@ -8879,10 +8879,10 @@ "arith_sfu" ], "longest_path_cycles": [ - 4.4375, - 4.21875, - 1.90625, - 4.4375, + 3.875, + 3.78125, + 1.59375, + 3.875, 0.0, 0.25, 0.0 @@ -8914,10 +8914,10 @@ "arith_fma" ], "total_cycles": [ - 12.75, - 12.75, - 4.53125, - 11.375, + 11.8125, + 11.8125, + 3.9375, + 10.25, 0.0, 0.25, 0.0 @@ -8925,7 +8925,7 @@ }, "stack_spill_bytes": 0, "thread_occupancy": 100, - "uniform_registers_used": 48, + "uniform_registers_used": 42, "work_registers_used": 32 } } @@ -8937,14 +8937,14 @@ "type": "Fragment", "variants": { "Main": { - "has_stack_spilling": false, + "has_stack_spilling": true, "performance": { "longest_path_bound_pipelines": [ "arithmetic" ], "longest_path_cycles": [ - 48.5099983215332, - 1.0, + 43.560001373291016, + 3.0, 4.0 ], "pipelines": [ @@ -8964,14 +8964,14 @@ "arithmetic" ], "total_cycles": [ - 98.0, - 1.0, + 87.33333587646484, + 3.0, 8.0 ] }, - "thread_occupancy": 50, + "thread_occupancy": 100, "uniform_registers_used": 5, - "work_registers_used": 6 + "work_registers_used": 4 } } } @@ -12287,10 +12287,10 @@ "arith_sfu" ], "longest_path_cycles": [ - 4.4375, - 3.762500047683716, - 2.125, - 4.4375, + 3.875, + 3.299999952316284, + 1.8624999523162842, + 3.875, 0.0, 0.25, 0.0 @@ -12322,10 +12322,10 @@ "arith_fma" ], "total_cycles": [ - 11.9375, - 11.9375, - 5.03125, - 11.375, + 10.9375, + 10.9375, + 4.5, + 10.25, 0.0, 0.25, 0.0 @@ -12333,7 +12333,7 @@ }, "stack_spill_bytes": 0, "thread_occupancy": 100, - "uniform_registers_used": 50, + "uniform_registers_used": 46, "work_registers_used": 32 } } From 718b6074d24c08357b521f1bc7de233cc112bbaa Mon Sep 17 00:00:00 2001 From: Evan Walley Date: Thu, 28 May 2026 10:35:36 -0700 Subject: [PATCH 15/15] c->C --- .../src/flutter/impeller/entity/contents/complex_rse_contents.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/src/flutter/impeller/entity/contents/complex_rse_contents.h b/engine/src/flutter/impeller/entity/contents/complex_rse_contents.h index 1b2eb09a0cf15..055f5d2c03608 100644 --- a/engine/src/flutter/impeller/entity/contents/complex_rse_contents.h +++ b/engine/src/flutter/impeller/entity/contents/complex_rse_contents.h @@ -18,7 +18,7 @@ namespace impeller { -/// A contents class that renders asymmetric rounded superellipses using SDFs. +/// A Contents class that renders asymmetric rounded superellipses using SDFs. /// /// Separated from 'UberSDFContents' to reduce uniform bloat class ComplexRoundedSuperellipseContents : public ColorSourceContents {