diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 5d2ae0906ab9a..a59702e59de55 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -1295,6 +1295,7 @@ ORIGIN: ../../../flutter/impeller/entity/shaders/gaussian_blur/gaussian_blur_alp ORIGIN: ../../../flutter/impeller/entity/shaders/gaussian_blur/gaussian_blur_alpha_nodecal.frag + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/shaders/gaussian_blur/gaussian_blur_noalpha_decal.frag + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/shaders/gaussian_blur/gaussian_blur_noalpha_nodecal.frag + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/impeller/entity/shaders/geometry/points.comp + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/shaders/glyph_atlas.frag + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/shaders/glyph_atlas.vert + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/shaders/glyph_atlas_color.frag + ../../../flutter/LICENSE @@ -3916,6 +3917,7 @@ FILE: ../../../flutter/impeller/entity/shaders/gaussian_blur/gaussian_blur_alpha FILE: ../../../flutter/impeller/entity/shaders/gaussian_blur/gaussian_blur_alpha_nodecal.frag FILE: ../../../flutter/impeller/entity/shaders/gaussian_blur/gaussian_blur_noalpha_decal.frag FILE: ../../../flutter/impeller/entity/shaders/gaussian_blur/gaussian_blur_noalpha_nodecal.frag +FILE: ../../../flutter/impeller/entity/shaders/geometry/points.comp FILE: ../../../flutter/impeller/entity/shaders/glyph_atlas.frag FILE: ../../../flutter/impeller/entity/shaders/glyph_atlas.vert FILE: ../../../flutter/impeller/entity/shaders/glyph_atlas_color.frag diff --git a/impeller/aiks/aiks_unittests.cc b/impeller/aiks/aiks_unittests.cc index f298e6caea2cb..b998d8fa537f3 100644 --- a/impeller/aiks/aiks_unittests.cc +++ b/impeller/aiks/aiks_unittests.cc @@ -2320,5 +2320,35 @@ TEST_P(AiksTest, CanRenderClippedRuntimeEffects) { ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); } +TEST_P(AiksTest, CanDrawPoints) { + std::vector points = { + {0, 0}, // + {100, 100}, // + {100, 0}, // + {0, 100}, // + {0, 0}, // + {48, 48}, // + {52, 52}, // + }; + std::vector caps = { + PointStyle::kRound, + PointStyle::kSquare, + }; + Paint paint; + paint.color = Color::Yellow().WithAlpha(0.5); + + Paint background; + background.color = Color::Black(); + + Canvas canvas; + canvas.DrawPaint(background); + canvas.Translate({200, 200}); + canvas.DrawPoints(points, 10, paint, PointStyle::kRound); + canvas.Translate({150, 0}); + canvas.DrawPoints(points, 10, paint, PointStyle::kSquare); + + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + } // namespace testing } // namespace impeller diff --git a/impeller/aiks/canvas.cc b/impeller/aiks/canvas.cc index 8ee384fc5aab6..6ac4df2f0d4fe 100644 --- a/impeller/aiks/canvas.cc +++ b/impeller/aiks/canvas.cc @@ -383,6 +383,25 @@ void Canvas::RestoreClip() { GetCurrentPass().AddEntity(entity); } +void Canvas::DrawPoints(std::vector points, + Scalar radius, + const Paint& paint, + PointStyle point_style) { + if (radius <= 0) { + return; + } + + Entity entity; + entity.SetTransformation(GetCurrentTransformation()); + entity.SetStencilDepth(GetStencilDepth()); + entity.SetBlendMode(paint.blend_mode); + entity.SetContents(paint.WithFilters(paint.CreateContentsForGeometry( + Geometry::MakePointField(std::move(points), radius, + /*round=*/point_style == PointStyle::kRound)))); + + GetCurrentPass().AddEntity(entity); +} + void Canvas::DrawPicture(Picture picture) { if (!picture.pass) { return; diff --git a/impeller/aiks/canvas.h b/impeller/aiks/canvas.h index 54be12491b09d..5833317c0072b 100644 --- a/impeller/aiks/canvas.h +++ b/impeller/aiks/canvas.h @@ -37,6 +37,14 @@ struct CanvasStackEntry { bool contains_clips = false; }; +enum class PointStyle { + /// @brief Points are drawn as squares. + kRound, + + /// @brief Points are drawn as circles. + kSquare, +}; + class Canvas { public: struct DebugOptions { @@ -100,6 +108,11 @@ class Canvas { void DrawCircle(Point center, Scalar radius, const Paint& paint); + void DrawPoints(std::vector, + Scalar radius, + const Paint& paint, + PointStyle point_style); + void DrawImage(const std::shared_ptr& image, Point offset, const Paint& paint, diff --git a/impeller/display_list/dl_dispatcher.cc b/impeller/display_list/dl_dispatcher.cc index c73ea1f9962c6..be3fb80af7d94 100644 --- a/impeller/display_list/dl_dispatcher.cc +++ b/impeller/display_list/dl_dispatcher.cc @@ -930,16 +930,17 @@ void DlDispatcher::drawPoints(PointMode mode, Paint paint = paint_; paint.style = Paint::Style::kStroke; switch (mode) { - case flutter::DlCanvas::PointMode::kPoints: - if (paint.stroke_cap == Cap::kButt) { - paint.stroke_cap = Cap::kSquare; + case flutter::DlCanvas::PointMode::kPoints: { + // Cap::kButt is also treated as a square. + auto point_style = paint.stroke_cap == Cap::kRound ? PointStyle::kRound + : PointStyle::kSquare; + auto radius = paint.stroke_width; + if (radius > 0) { + radius /= 2.0; } - for (uint32_t i = 0; i < count; i++) { - Point p0 = skia_conversions::ToPoint(points[i]); - auto path = PathBuilder{}.AddLine(p0, p0).TakePath(); - canvas_.DrawPath(path, paint); - } - break; + canvas_.DrawPoints(skia_conversions::ToPoints(points, count), radius, + paint, point_style); + } break; case flutter::DlCanvas::PointMode::kLines: for (uint32_t i = 1; i < count; i += 2) { Point p0 = skia_conversions::ToPoint(points[i - 1]); diff --git a/impeller/display_list/skia_conversions.cc b/impeller/display_list/skia_conversions.cc index 16c8978bbc6ce..e488b39a9d4ff 100644 --- a/impeller/display_list/skia_conversions.cc +++ b/impeller/display_list/skia_conversions.cc @@ -26,6 +26,14 @@ std::vector ToRects(const SkRect tex[], int count) { return result; } +std::vector ToPoints(const SkPoint points[], int count) { + std::vector result(count); + for (auto i = 0; i < count; i++) { + result[i] = ToPoint(points[i]); + } + return result; +} + PathBuilder::RoundingRadii ToRoundingRadii(const SkRRect& rrect) { using Corner = SkRRect::Corner; PathBuilder::RoundingRadii radii; diff --git a/impeller/display_list/skia_conversions.h b/impeller/display_list/skia_conversions.h index 933875bee7101..5f1524674240d 100644 --- a/impeller/display_list/skia_conversions.h +++ b/impeller/display_list/skia_conversions.h @@ -26,6 +26,8 @@ std::optional ToRect(const SkRect* rect); std::vector ToRects(const SkRect tex[], int count); +std::vector ToPoints(const SkPoint points[], int count); + Point ToPoint(const SkPoint& point); Color ToColor(const SkColor& color); diff --git a/impeller/entity/BUILD.gn b/impeller/entity/BUILD.gn index 0cfde08d51875..7a640d4bab301 100644 --- a/impeller/entity/BUILD.gn +++ b/impeller/entity/BUILD.gn @@ -96,6 +96,7 @@ impeller_shaders("modern_entity_shaders") { "shaders/linear_gradient_ssbo_fill.frag", "shaders/radial_gradient_ssbo_fill.frag", "shaders/sweep_gradient_ssbo_fill.frag", + "shaders/geometry/points.comp", ] } diff --git a/impeller/entity/contents/content_context.cc b/impeller/entity/contents/content_context.cc index 4503b40cdbe6f..729f2cbbc3b0d 100644 --- a/impeller/entity/contents/content_context.cc +++ b/impeller/entity/contents/content_context.cc @@ -11,6 +11,7 @@ #include "impeller/core/formats.h" #include "impeller/entity/entity.h" #include "impeller/renderer/command_buffer.h" +#include "impeller/renderer/pipeline_library.h" #include "impeller/renderer/render_pass.h" #include "impeller/renderer/render_target.h" #include "impeller/tessellator/tessellator.h" @@ -296,6 +297,13 @@ ContentContext::ContentContext(std::shared_ptr context) porter_duff_blend_pipelines_[{}] = CreateDefaultPipeline(*context_); + if (context_->GetCapabilities()->SupportsCompute()) { + auto pipeline_desc = + PointsComputeShaderPipeline::MakeDefaultPipelineDescriptor(*context_); + point_field_compute_pipelines_ = + context_->GetPipelineLibrary()->GetPipeline(pipeline_desc).Get(); + } + if (solid_fill_pipelines_[{}]->GetDescriptor().has_value()) { auto clip_pipeline_descriptor = solid_fill_pipelines_[{}]->GetDescriptor().value(); diff --git a/impeller/entity/contents/content_context.h b/impeller/entity/contents/content_context.h index 5042ff54ff187..9762b7a65f070 100644 --- a/impeller/entity/contents/content_context.h +++ b/impeller/entity/contents/content_context.h @@ -39,6 +39,7 @@ #include "impeller/entity/linear_to_srgb_filter.vert.h" #include "impeller/entity/morphology_filter.frag.h" #include "impeller/entity/morphology_filter.vert.h" +#include "impeller/entity/points.comp.h" #include "impeller/entity/porter_duff_blend.frag.h" #include "impeller/entity/radial_gradient_fill.frag.h" #include "impeller/entity/rrect_blur.frag.h" @@ -275,6 +276,9 @@ using FramebufferBlendSoftLightPipeline = RenderPipelineT; +/// Geometry Pipelines +using PointsComputeShaderPipeline = ComputePipelineBuilder; + /// Pipeline state configuration. /// /// Each unique combination of these options requires a different pipeline state @@ -660,6 +664,12 @@ class ContentContext { return GetPipeline(framebuffer_blend_softlight_pipelines_, opts); } + std::shared_ptr> GetPointComputePipeline() + const { + FML_DCHECK(GetDeviceCapabilities().SupportsCompute()); + return point_field_compute_pipelines_; + } + std::shared_ptr GetContext() const; std::shared_ptr GetGlyphAtlasContext( @@ -782,6 +792,8 @@ class ContentContext { framebuffer_blend_screen_pipelines_; mutable Variants framebuffer_blend_softlight_pipelines_; + mutable std::shared_ptr> + point_field_compute_pipelines_; template std::shared_ptr> GetPipeline( diff --git a/impeller/entity/entity_unittests.cc b/impeller/entity/entity_unittests.cc index 412080361f6d5..1222e8e6ba7a5 100644 --- a/impeller/entity/entity_unittests.cc +++ b/impeller/entity/entity_unittests.cc @@ -2616,5 +2616,23 @@ TEST_P(EntityTest, TessellateConvex) { } } +TEST_P(EntityTest, PointFieldGeometryDivisions) { + // Square always gives 4 divisions. + ASSERT_EQ(PointFieldGeometry::ComputeCircleDivisions(24.0, false), 4u); + ASSERT_EQ(PointFieldGeometry::ComputeCircleDivisions(2.0, false), 4u); + ASSERT_EQ(PointFieldGeometry::ComputeCircleDivisions(200.0, false), 4u); + + ASSERT_EQ(PointFieldGeometry::ComputeCircleDivisions(0.5, true), 4u); + ASSERT_EQ(PointFieldGeometry::ComputeCircleDivisions(1.5, true), 8u); + ASSERT_EQ(PointFieldGeometry::ComputeCircleDivisions(5.5, true), 24u); + ASSERT_EQ(PointFieldGeometry::ComputeCircleDivisions(12.5, true), 34u); + ASSERT_EQ(PointFieldGeometry::ComputeCircleDivisions(22.3, true), 22u); + ASSERT_EQ(PointFieldGeometry::ComputeCircleDivisions(40.5, true), 40u); + ASSERT_EQ(PointFieldGeometry::ComputeCircleDivisions(100.0, true), 100u); + // Caps at 140. + ASSERT_EQ(PointFieldGeometry::ComputeCircleDivisions(1000.0, true), 140u); + ASSERT_EQ(PointFieldGeometry::ComputeCircleDivisions(20000.0, true), 140u); +} + } // namespace testing } // namespace impeller diff --git a/impeller/entity/geometry.cc b/impeller/entity/geometry.cc index acbefbb4c5a3c..0fc99ecc9a32f 100644 --- a/impeller/entity/geometry.cc +++ b/impeller/entity/geometry.cc @@ -7,10 +7,12 @@ #include "impeller/core/device_buffer.h" #include "impeller/entity/contents/content_context.h" #include "impeller/entity/entity.h" +#include "impeller/entity/points.comp.h" #include "impeller/entity/position_color.vert.h" #include "impeller/entity/texture_fill.vert.h" #include "impeller/geometry/matrix.h" #include "impeller/geometry/path_builder.h" +#include "impeller/renderer/command_buffer.h" #include "impeller/renderer/render_pass.h" #include "impeller/tessellator/tessellator.h" @@ -64,6 +66,14 @@ std::unique_ptr Geometry::MakeFillPath(const Path& path) { return std::make_unique(path); } +// static +std::unique_ptr Geometry::MakePointField(std::vector points, + Scalar radius, + bool round) { + return std::make_unique(std::move(points), radius, round); +} + +// static std::unique_ptr Geometry::MakeStrokePath(const Path& path, Scalar stroke_width, Scalar miter_limit, @@ -796,4 +806,210 @@ std::optional RectGeometry::GetCoverage(const Matrix& transform) const { return rect_.TransformBounds(transform); } +/////// PointFieldGeometry Geometry /////// + +PointFieldGeometry::PointFieldGeometry(std::vector points, + Scalar radius, + bool round) + : points_(std::move(points)), radius_(radius), round_(round) {} + +PointFieldGeometry::~PointFieldGeometry() = default; + +GeometryResult PointFieldGeometry::GetPositionBuffer( + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) { + if (radius_ < 0.0) { + return {}; + } + auto determinant = entity.GetTransformation().GetDeterminant(); + if (determinant == 0) { + return {}; + } + + Scalar min_size = 1.0f / sqrt(std::abs(determinant)); + Scalar radius = std::max(radius_, min_size); + + if (!renderer.GetDeviceCapabilities().SupportsCompute()) { + return GetPositionBufferCPU(renderer, entity, pass, radius); + } + + auto vertices_per_geom = ComputeCircleDivisions( + entity.GetTransformation().GetMaxBasisLength() * radius, round_); + auto points_per_circle = 3 + (vertices_per_geom - 3) * 3; + auto total = points_per_circle * points_.size(); + auto& host_buffer = pass.GetTransientsBuffer(); + + using PS = PointsComputeShader; + + auto points_data = host_buffer.Emplace( + points_.data(), points_.size() * sizeof(Point), alignof(Point)); + + DeviceBufferDescriptor buffer_desc; + buffer_desc.size = total * sizeof(Point); + buffer_desc.storage_mode = StorageMode::kDevicePrivate; + + auto buffer = + renderer.GetContext()->GetResourceAllocator()->CreateBuffer(buffer_desc); + + ComputeCommand cmd; + cmd.label = "Points Geometry"; + cmd.pipeline = renderer.GetPointComputePipeline(); + + PS::FrameInfo frame_info; + frame_info.count = points_.size(); + frame_info.radius = radius; + frame_info.radian_start = round_ ? 0.0f : kPiOver4; + frame_info.radian_step = k2Pi / vertices_per_geom; + frame_info.points_per_circle = points_per_circle; + frame_info.divisions_per_circle = vertices_per_geom; + + PS::BindFrameInfo(cmd, host_buffer.EmplaceUniform(frame_info)); + PS::BindGeometryData( + cmd, {.buffer = buffer, .range = Range{0, total * sizeof(Point)}}); + PS::BindPointData(cmd, points_data); + + { + auto cmd_buffer = renderer.GetContext()->CreateCommandBuffer(); + auto pass = cmd_buffer->CreateComputePass(); + pass->SetGridSize(ISize(total, 1)); + pass->SetThreadGroupSize(ISize(total, 1)); + + if (!pass->AddCommand(std::move(cmd)) || !pass->EncodeCommands() || + !cmd_buffer->SubmitCommands()) { + return {}; + } + } + + return { + .type = PrimitiveType::kTriangle, + .vertex_buffer = {.vertex_buffer = {.buffer = buffer, + .range = + Range{0, total * sizeof(Point)}}, + .vertex_count = total, + .index_type = IndexType::kNone}, + .transform = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * + entity.GetTransformation(), + .prevent_overdraw = false, + }; +} + +GeometryResult PointFieldGeometry::GetPositionBufferCPU( + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass, + Scalar radius) { + auto vertices_per_geom = ComputeCircleDivisions( + entity.GetTransformation().GetMaxBasisLength() * radius, round_); + auto points_per_circle = 3 + (vertices_per_geom - 3) * 3; + auto total = points_per_circle * points_.size(); + auto& host_buffer = pass.GetTransientsBuffer(); + auto radian_start = round_ ? 0.0f : 0.785398f; + auto radian_step = k2Pi / vertices_per_geom; + + VertexBufferBuilder vtx_builder; + vtx_builder.Reserve(total); + + /// Precompute all relative points and angles for a fixed geometry size. + auto elapsed_angle = radian_start; + std::vector angle_table(vertices_per_geom); + for (auto i = 0u; i < vertices_per_geom; i++) { + angle_table[i] = Point(cos(elapsed_angle), sin(elapsed_angle)) * radius; + elapsed_angle += radian_step; + } + + for (auto i = 0u; i < points_.size(); i++) { + auto center = points_[i]; + + auto origin = center + angle_table[0]; + vtx_builder.AppendVertex({origin}); + + auto pt1 = center + angle_table[1]; + vtx_builder.AppendVertex({pt1}); + + auto pt2 = center + angle_table[2]; + vtx_builder.AppendVertex({pt2}); + + for (auto j = 0u; j < vertices_per_geom - 3; j++) { + vtx_builder.AppendVertex({origin}); + vtx_builder.AppendVertex({pt2}); + + pt2 = center + angle_table[j + 3]; + vtx_builder.AppendVertex({pt2}); + } + } + + return { + .type = PrimitiveType::kTriangle, + .vertex_buffer = vtx_builder.CreateVertexBuffer(host_buffer), + .transform = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * + entity.GetTransformation(), + .prevent_overdraw = false, + }; +} + +GeometryResult PointFieldGeometry::GetPositionUVBuffer( + Rect texture_coverage, + Matrix effect_transform, + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) { + FML_UNREACHABLE(); +} + +/// @brief Compute the number of vertices to divide each circle into. +/// +/// @return the number of vertices. +size_t PointFieldGeometry::ComputeCircleDivisions(Scalar scaled_radius, + bool round) { + if (!round) { + return 4; + } + + // Note: these values are approximated based on the values returned from + // the decomposition of 4 cubics performed by Path::CreatePolyline. + if (scaled_radius < 1.0) { + return 4; + } + if (scaled_radius < 2.0) { + return 8; + } + if (scaled_radius < 12.0) { + return 24; + } + if (scaled_radius < 22.0) { + return 34; + } + return std::min(scaled_radius, 140.0f); +} + +// |Geometry| +GeometryVertexType PointFieldGeometry::GetVertexType() const { + return GeometryVertexType::kPosition; +} + +// |Geometry| +std::optional PointFieldGeometry::GetCoverage( + const Matrix& transform) const { + if (points_.size() > 0) { + // Doesn't use MakePointBounds as this isn't resilient to points that + // all lie along the same axis. + auto first = points_.begin(); + auto last = points_.end(); + auto left = first->x; + auto top = first->y; + auto right = first->x; + auto bottom = first->y; + for (auto it = first + 1; it < last; ++it) { + left = std::min(left, it->x); + top = std::min(top, it->y); + right = std::max(right, it->x); + bottom = std::max(bottom, it->y); + } + return Rect::MakeLTRB(left - radius_, top - radius_, right + radius_, + bottom + radius_); + } + return std::nullopt; +} + } // namespace impeller diff --git a/impeller/entity/geometry.h b/impeller/entity/geometry.h index e3dd56deda339..6ffa6c5991c3b 100644 --- a/impeller/entity/geometry.h +++ b/impeller/entity/geometry.h @@ -57,6 +57,10 @@ class Geometry { static std::unique_ptr MakeRect(Rect rect); + static std::unique_ptr MakePointField(std::vector points, + Scalar radius, + bool round); + virtual GeometryResult GetPositionBuffer(const ContentContext& renderer, const Entity& entity, RenderPass& pass) = 0; @@ -260,4 +264,43 @@ class RectGeometry : public Geometry { FML_DISALLOW_COPY_AND_ASSIGN(RectGeometry); }; +class PointFieldGeometry : public Geometry { + public: + PointFieldGeometry(std::vector points, Scalar radius, bool round); + + ~PointFieldGeometry(); + + static size_t ComputeCircleDivisions(Scalar scaled_radius, bool round); + + private: + // |Geometry| + GeometryResult GetPositionBuffer(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) override; + + // |Geometry| + GeometryResult GetPositionUVBuffer(Rect texture_coverage, + Matrix effect_transform, + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) override; + + // |Geometry| + GeometryVertexType GetVertexType() const override; + + // |Geometry| + std::optional GetCoverage(const Matrix& transform) const override; + + GeometryResult GetPositionBufferCPU(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass, + Scalar radius); + + std::vector points_; + Scalar radius_; + bool round_; + + FML_DISALLOW_COPY_AND_ASSIGN(PointFieldGeometry); +}; + } // namespace impeller diff --git a/impeller/entity/shaders/geometry/points.comp b/impeller/entity/shaders/geometry/points.comp new file mode 100644 index 0000000000000..7d274ade49948 --- /dev/null +++ b/impeller/entity/shaders/geometry/points.comp @@ -0,0 +1,67 @@ +// 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 +#include + +// Unused, see See PointFieldGeometry::GetPositionBuffer +layout(local_size_x = 16) in; + +layout(std430) readonly buffer PointData { + // Size of this input data is frame_info.count; + vec2 points[]; +} +point_data; + +layout(std430) writeonly buffer GeometryData { + // Size of this output data is frame_info.count * points_per_circle; + vec2 geometry[]; +} +geometry_data; + +uniform FrameInfo { + uint count; + float16_t radius; + float16_t radian_start; + float16_t radian_step; + uint points_per_circle; + int divisions_per_circle; +} +frame_info; + +void main() { + uint ident = gl_GlobalInvocationID.x; + if (ident >= frame_info.count) { + return; + } + + vec2 center = point_data.points[ident]; + uint bufer_offset = ident * frame_info.points_per_circle; + + float16_t elapsed_angle = frame_info.radian_start; + + vec2 origin = + center + vec2(cos(elapsed_angle), sin(elapsed_angle)) * frame_info.radius; + geometry_data.geometry[bufer_offset++] = origin; + + elapsed_angle += frame_info.radian_step; + vec2 pt1 = + center + vec2(cos(elapsed_angle), sin(elapsed_angle)) * frame_info.radius; + geometry_data.geometry[bufer_offset++] = pt1; + + elapsed_angle += frame_info.radian_step; + vec2 pt2 = + center + vec2(cos(elapsed_angle), sin(elapsed_angle)) * frame_info.radius; + geometry_data.geometry[bufer_offset++] = pt2; + + for (int i = 0; i < frame_info.divisions_per_circle - 3; i++) { + geometry_data.geometry[bufer_offset++] = origin; + geometry_data.geometry[bufer_offset++] = pt2; + + elapsed_angle += frame_info.radian_step; + pt2 = center + + vec2(cos(elapsed_angle), sin(elapsed_angle)) * frame_info.radius; + geometry_data.geometry[bufer_offset++] = pt2; + } +} diff --git a/impeller/renderer/backend/metal/compute_pass_mtl.mm b/impeller/renderer/backend/metal/compute_pass_mtl.mm index 3a610acd154d4..5e461a8d0e764 100644 --- a/impeller/renderer/backend/metal/compute_pass_mtl.mm +++ b/impeller/renderer/backend/metal/compute_pass_mtl.mm @@ -211,6 +211,10 @@ static bool Bind(ComputePassBindingsCache& pass, id encoder, const ISize& grid_size, const ISize& thread_group_size) const { + if (grid_size.width == 0 || grid_size.height == 0) { + return true; + } + ComputePassBindingsCache pass_bindings(encoder); fml::closure pop_debug_marker = [encoder]() { [encoder popDebugGroup]; }; @@ -246,19 +250,28 @@ static bool Bind(ComputePassBindingsCache& pass, // TODO(dnfield): use feature detection to support non-uniform threadgroup // sizes. // https://github.com/flutter/flutter/issues/110619 - - // For now, check that the sizes are uniform. - FML_DCHECK(grid_size == thread_group_size); auto width = grid_size.width; auto height = grid_size.height; - while (width * height > - static_cast( - pass_bindings.GetPipeline().maxTotalThreadsPerThreadgroup)) { - width = std::max(1LL, width / 2); - height = std::max(1LL, height / 2); + + auto maxTotalThreadsPerThreadgroup = static_cast( + pass_bindings.GetPipeline().maxTotalThreadsPerThreadgroup); + + // Special case for linear processing. + if (height == 1) { + int64_t threadGroups = + std::max(width / maxTotalThreadsPerThreadgroup, 1LL); + [encoder dispatchThreadgroups:MTLSizeMake(threadGroups, 1, 1) + threadsPerThreadgroup:MTLSizeMake(maxTotalThreadsPerThreadgroup, + 1, 1)]; + } else { + while (width * height > maxTotalThreadsPerThreadgroup) { + width = std::max(1LL, width / 2); + height = std::max(1LL, height / 2); + } + + auto size = MTLSizeMake(width, height, 1); + [encoder dispatchThreadgroups:size threadsPerThreadgroup:size]; } - auto size = MTLSizeMake(width, height, 1); - [encoder dispatchThreadgroups:size threadsPerThreadgroup:size]; } return true; diff --git a/impeller/renderer/compute_unittests.cc b/impeller/renderer/compute_unittests.cc index 1ad1dec214924..8790396f5aefd 100644 --- a/impeller/renderer/compute_unittests.cc +++ b/impeller/renderer/compute_unittests.cc @@ -204,7 +204,7 @@ TEST_P(ComputeTest, MultiStageInputAndOutput) { latch.Wait(); } -TEST_P(ComputeTest, CanCorrectlyDownScaleLargeGridSize) { +TEST_P(ComputeTest, CanCompute1DimensionalData) { using CS = SampleComputeShader; auto context = GetContext(); ASSERT_TRUE(context); @@ -224,10 +224,7 @@ TEST_P(ComputeTest, CanCorrectlyDownScaleLargeGridSize) { static constexpr size_t kCount = 5; - // Intentionally making the grid size obscenely large. No GPU will tolerate - // this. - pass->SetGridSize(ISize(std::numeric_limits::max(), 1)); - pass->SetThreadGroupSize(ISize(std::numeric_limits::max(), 1)); + pass->SetGridSize(ISize(kCount, 1)); ComputeCommand cmd; cmd.label = "Compute"; @@ -305,8 +302,8 @@ TEST_P(ComputeTest, ReturnsEarlyWhenAnyGridDimensionIsZero) { static constexpr size_t kCount = 5; - // Intentionally making the grid size obscenely large. No GPU will tolerate - // this. + // Intentionally making the grid size zero in one dimension. No GPU will + // tolerate this. pass->SetGridSize(ISize(0, 1)); pass->SetThreadGroupSize(ISize(0, 1)); diff --git a/impeller/renderer/pipeline_builder.h b/impeller/renderer/pipeline_builder.h index b27c4bc753b55..ccc9e9b83a9f9 100644 --- a/impeller/renderer/pipeline_builder.h +++ b/impeller/renderer/pipeline_builder.h @@ -52,9 +52,8 @@ struct PipelineBuilder { PipelineDescriptor desc; if (InitializePipelineDescriptorDefaults(context, desc)) { return {std::move(desc)}; - } else { - return std::nullopt; } + return std::nullopt; } [[nodiscard]] static bool InitializePipelineDescriptorDefaults( diff --git a/impeller/tools/malioc.json b/impeller/tools/malioc.json index 38ca51a978083..0315be91636a6 100644 --- a/impeller/tools/malioc.json +++ b/impeller/tools/malioc.json @@ -12079,6 +12079,69 @@ } } }, + "flutter/impeller/entity/points.comp.vkspv": { + "Mali-G78": { + "core": "Mali-G78", + "filename": "flutter/impeller/entity/points.comp.vkspv", + "has_uniform_computation": true, + "type": "Compute", + "variants": { + "Main": { + "fp16_arithmetic": 35, + "has_stack_spilling": false, + "performance": { + "longest_path_bound_pipelines": [ + null + ], + "longest_path_cycles": [ + null, + null, + null, + null, + null, + null + ], + "pipelines": [ + "arith_total", + "arith_fma", + "arith_cvt", + "arith_sfu", + "load_store", + "texture" + ], + "shortest_path_bound_pipelines": [ + "arith_total", + "arith_cvt" + ], + "shortest_path_cycles": [ + 0.046875, + 0.0, + 0.046875, + 0.0, + 0.0, + 0.0 + ], + "total_bound_pipelines": [ + "load_store" + ], + "total_cycles": [ + 0.453125, + 0.21875, + 0.453125, + 0.1875, + 5.0, + 0.0 + ] + }, + "shared_storage_used": 0, + "stack_spill_bytes": 0, + "thread_occupancy": 100, + "uniform_registers_used": 16, + "work_registers_used": 17 + } + } + } + }, "flutter/impeller/entity/porter_duff_blend.frag.vkspv": { "Mali-G78": { "core": "Mali-G78",