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 e9382d1e365cb..d653ff0c9aadb 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, 440, 440), /*radii=*/ + { + .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(550, 50, 440, 440), /*radii=*/ + { + .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, 550, 400, 400), /*radii=*/ + { + .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(550, 550, 400, 400), /*radii=*/ + { + .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); + + 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 cfce13632d86c..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" @@ -1036,38 +1037,50 @@ void Canvas::DrawRoundSuperellipse(const RoundSuperellipse& round_superellipse, entity.SetBlendMode(paint.blend_mode); if (renderer_.GetContext()->GetFlags().use_sdfs && - IsCompatibleWithSDFRendering(paint) && - round_superellipse.GetRadii().AreAllCornersSame()) { + IsCompatibleWithSDFRendering(paint)) { 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)); - - 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()), - /*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..5f9ccb18065a1 --- /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 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..055f5d2c03608 --- /dev/null +++ b/engine/src/flutter/impeller/entity/contents/complex_rse_contents.h @@ -0,0 +1,62 @@ +// 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 { + +/// 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( + 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 d4f56cccb6a29..5bec7103c92b5 100644 --- a/engine/src/flutter/impeller/entity/contents/uber_sdf_contents.cc +++ b/engine/src/flutter/impeller/entity/contents/uber_sdf_contents.cc @@ -30,7 +30,7 @@ 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; } } @@ -73,24 +73,19 @@ 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 = 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_semi_axis = params_.superellipse_semi_axis; + frag_info.angle_span = params_.angle_span; + frag_info.octant_offset_c = params_.octant_offset_c; + frag_info.circle_center_top = params_.circle_center_top; + frag_info.circle_center_right = params_.circle_center_right; frag_info.superellipse_scale = params_.superellipse_scale; + 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 6108ae3547441..e804a0cc7469c 100644 --- a/engine/src/flutter/impeller/entity/contents/uber_sdf_parameters.cc +++ b/engine/src/flutter/impeller/entity/contents/uber_sdf_parameters.cc @@ -63,41 +63,45 @@ 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 = Vector4(radii.bottom_right.width, radii.top_right.width, + radii.bottom_left.width, radii.top_left.width)}; } 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, + const Rect& bounds, + const RoundSuperellipseParam& round_superellipse_params, std::optional stroke) { - Point size = Point(rect.GetSize() * 0.5f); + FML_DCHECK(round_superellipse_params.all_corners_same); + Point center = bounds.GetCenter(); + + RoundSuperellipseParam::Quadrant top_right = + round_superellipse_params.top_right; + + Point size = Point(bounds.GetSize() * 0.5f); + return UberSDFParameters{ - .type = Type::kRoundedSuperellipse, + .type = Type::kRoundedSuperellipseSymmetric, .color = color, - .center = rect.GetCenter(), + .center = center, .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_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, + .circle_center_top = top_right.top.circle_center, + .circle_center_right = top_right.right.circle_center, + .superellipse_scale = top_right.signed_scale.Abs(), + .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 e5c36973936d8..942d6ccf5db3e 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" @@ -28,7 +29,7 @@ struct UberSDFParameters { kRect, kOval, kRoundedRect, - kRoundedSuperellipse, + kRoundedSuperellipseSymmetric, }; /// Creates UberSDFParameters for a rectangle. @@ -54,18 +55,11 @@ 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, + const Rect& bounds, + const RoundSuperellipseParam& round_superellipse_params, std::optional stroke); /// The type of shape to render. @@ -85,32 +79,34 @@ struct UberSDFParameters { /// The stroke parameters. If std::nullopt, the shape is filled. 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. + /// The degree (n) of the superellipse curve for the top and right octants. Point superellipse_degree; - /// The semi-axes of the top (.x) and right (.y) superellipse segments. - Point superellipse_a; + /// The semi-axis length of the superellipse curve for the top and right + /// octants. + Point superellipse_semi_axis; - /// The spans of the top (.x) and right (.y) circular arcs. - Point corner_angle_span; + /// The angular span of the circular cap for the top and right octants. + Point angle_span; - /// The center of the top circular arc in a RoundSuperellipse. - Point corner_circle_center_top; + /// The geometric offset 'c' used to connect the two octants of each quadrant. + float octant_offset_c; - /// The center of the right circular arc in a RoundSuperellipse. - Point corner_circle_center_right; + /// The circular cap center for the top octant of each + /// quadrant. + Point circle_center_top; - /// The offset of the octants in a RoundSuperellipse. - Scalar superellipse_c; + /// The circular cap center for the right octant of each + /// quadrant. + Point circle_center_right; - /// The scale of the superellipse. + /// The scaling factors used to transform normalized superellipses to their + /// true size. Point superellipse_scale; + + /// Rounding radii for standard rounded rects and corner radii for circular + /// caps of superellipses for top and right octants. + 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 56cafc90d0720..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 @@ -116,12 +116,50 @@ 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.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()); } +TEST(UberSDFParametersTest, MakeRoundedSuperellipse) { + Rect rect = Rect::MakeXYWH(10, 20, 100, 100); + RoundingRadii radii = { + .top_left = Size(10.0f, 10.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); + auto params = UberSDFParameters::MakeRoundedSuperellipse( + /*color=*/Color::Red(), /*bounds=*/rect, + /*round_superellipse_params=*/round_superellipse_params, + /*stroke=*/std::nullopt); + + 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()); + + EXPECT_EQ(params.radii.x, + round_superellipse_params.top_right.top.circle_radius); + EXPECT_EQ(params.radii.y, + round_superellipse_params.top_right.right.circle_radius); + + EXPECT_EQ(params.superellipse_degree.x, + round_superellipse_params.top_right.top.se_n); + 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_scale.y, + round_superellipse_params.top_right.signed_scale.Abs().y); +} + } // namespace testing } // namespace impeller 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..fe48c767958e6 --- /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); +} 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..576d39a93a69f --- /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 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..407830a30f664 --- /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 diff --git a/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag b/engine/src/flutter/impeller/entity/shaders/uber_sdf.frag index 3c001e63099db..d15ea608803e8 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,15 +19,14 @@ uniform FragInfo { float aa_pixels; float stroked; 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_semi_axis; + vec2 angle_span; + float octant_offset_c; + vec2 circle_center_top; + vec2 circle_center_right; vec2 superellipse_scale; + vec4 radii; } frag_info; @@ -32,10 +34,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; } @@ -45,70 +43,41 @@ 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 + 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)); + } + float d = length(p - ab * vec2(cos(w), sin(w))); + return (dot(p / ab, p / ab) > 1.0) ? d : -d; +} - 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; +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; +} - 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); +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 distanceFromRoundedSuperellipse(vec2 p, vec2 degree, vec2 se_a, - vec4 radii_top, + vec2 radii, vec2 angle_span, vec2 circle_center_top, - vec4 radii_right, vec2 circle_center_right, float c, vec2 scale) { @@ -131,7 +100,7 @@ float distanceFromRoundedSuperellipse(vec2 p, p_oct = p_norm + vec2(0.0, c); se_degree = degree.x; span = angle_span.x; - radius = radii_top.x; + radius = radii.x; circle_center = circle_center_top; axis_length = se_a.x; } else { @@ -140,7 +109,7 @@ float distanceFromRoundedSuperellipse(vec2 p, p_oct = p_norm.yx - vec2(0.0, c); se_degree = degree.y; span = angle_span.y; - radius = radii_right.x; + radius = radii.y; circle_center = circle_center_right; axis_length = se_a.y; } @@ -166,171 +135,11 @@ float distanceFromRoundedSuperellipse(vec2 p, return sdSuperellipse(p_oct / axis_length, se_degree) * axis_length; } -// 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; -} - -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); - } - - return length(d); -} - -// 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); } -// 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 @@ -341,67 +150,40 @@ 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); - } else { + } else { // Symmetric Rounded Superellipse 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, + p, frag_info.superellipse_degree, frag_info.superellipse_semi_axis, + 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)); } -// 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() { @@ -412,11 +194,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); 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( diff --git a/engine/src/flutter/impeller/tools/malioc.json b/engine/src/flutter/impeller/tools/malioc.json index b6ba728769fe9..2c2fef321d909 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", @@ -8688,7 +8879,7 @@ "longest_path_cycles": [ 3.875, 3.78125, - 1.7000000476837158, + 1.59375, 3.875, 0.0, 0.25, @@ -8710,7 +8901,7 @@ "shortest_path_cycles": [ 0.4375, 0.4375, - 0.109375, + 0.09375, 0.375, 0.0, 0.25, @@ -8721,10 +8912,10 @@ "arith_fma" ], "total_cycles": [ - 12.0, - 12.0, - 4.050000190734863, - 10.3125, + 11.8125, + 11.8125, + 3.9375, + 10.25, 0.0, 0.25, 0.0 @@ -8732,7 +8923,7 @@ }, "stack_spill_bytes": 0, "thread_occupancy": 100, - "uniform_registers_used": 44, + "uniform_registers_used": 42, "work_registers_used": 32 } } @@ -8750,7 +8941,7 @@ "arithmetic" ], "longest_path_cycles": [ - 44.880001068115234, + 43.560001373291016, 3.0, 4.0 ], @@ -8771,7 +8962,7 @@ "arithmetic" ], "total_cycles": [ - 88.66666412353516, + 87.33333587646484, 3.0, 8.0 ] @@ -12096,7 +12287,7 @@ "longest_path_cycles": [ 3.875, 3.299999952316284, - 1.96875, + 1.8624999523162842, 3.875, 0.0, 0.25, @@ -12129,10 +12320,10 @@ "arith_fma" ], "total_cycles": [ - 11.0, - 11.0, - 4.612500190734863, - 10.3125, + 10.9375, + 10.9375, + 4.5, + 10.25, 0.0, 0.25, 0.0 @@ -12140,7 +12331,7 @@ }, "stack_spill_bytes": 0, "thread_occupancy": 100, - "uniform_registers_used": 48, + "uniform_registers_used": 46, "work_registers_used": 32 } }