diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index c673eb20437be..062a2adb77478 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -1297,6 +1297,7 @@ ORIGIN: ../../../flutter/impeller/entity/shaders/gaussian_blur/gaussian_blur_alp 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/geometry/uv.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 @@ -3924,6 +3925,7 @@ FILE: ../../../flutter/impeller/entity/shaders/gaussian_blur/gaussian_blur_alpha 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/geometry/uv.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 67ed30bd4c94b..eeecc60d3ec32 100644 --- a/impeller/aiks/aiks_unittests.cc +++ b/impeller/aiks/aiks_unittests.cc @@ -2373,5 +2373,35 @@ TEST_P(AiksTest, CanDrawPoints) { ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); } +TEST_P(AiksTest, CanDrawPointsWithTextureMap) { + auto texture = CreateTextureForFixture("table_mountain_nx.png", + /*enable_mipmapping=*/true); + + 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_source = ColorSource::MakeImage(texture, Entity::TileMode::kClamp, + Entity::TileMode::kClamp, {}, {}); + + Canvas canvas; + canvas.Translate({200, 200}); + canvas.DrawPoints(points, 100, paint, PointStyle::kRound); + canvas.Translate({150, 0}); + canvas.DrawPoints(points, 100, paint, PointStyle::kSquare); + + ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture())); +} + } // namespace testing } // namespace impeller diff --git a/impeller/entity/BUILD.gn b/impeller/entity/BUILD.gn index 7a640d4bab301..25acf631312ac 100644 --- a/impeller/entity/BUILD.gn +++ b/impeller/entity/BUILD.gn @@ -97,6 +97,7 @@ impeller_shaders("modern_entity_shaders") { "shaders/radial_gradient_ssbo_fill.frag", "shaders/sweep_gradient_ssbo_fill.frag", "shaders/geometry/points.comp", + "shaders/geometry/uv.comp", ] } diff --git a/impeller/entity/contents/content_context.cc b/impeller/entity/contents/content_context.cc index 729f2cbbc3b0d..6010452a0232f 100644 --- a/impeller/entity/contents/content_context.cc +++ b/impeller/entity/contents/content_context.cc @@ -302,6 +302,11 @@ ContentContext::ContentContext(std::shared_ptr context) PointsComputeShaderPipeline::MakeDefaultPipelineDescriptor(*context_); point_field_compute_pipelines_ = context_->GetPipelineLibrary()->GetPipeline(pipeline_desc).Get(); + + auto uv_pipeline_desc = + UvComputeShaderPipeline::MakeDefaultPipelineDescriptor(*context_); + uv_compute_pipelines_ = + context_->GetPipelineLibrary()->GetPipeline(uv_pipeline_desc).Get(); } if (solid_fill_pipelines_[{}]->GetDescriptor().has_value()) { diff --git a/impeller/entity/contents/content_context.h b/impeller/entity/contents/content_context.h index 9762b7a65f070..bbfe56916f619 100644 --- a/impeller/entity/contents/content_context.h +++ b/impeller/entity/contents/content_context.h @@ -53,6 +53,7 @@ #include "impeller/entity/texture_fill.vert.h" #include "impeller/entity/tiled_texture_fill.frag.h" #include "impeller/entity/tiled_texture_fill.vert.h" +#include "impeller/entity/uv.comp.h" #include "impeller/entity/vertices.frag.h" #include "impeller/entity/yuv_to_rgb_filter.frag.h" #include "impeller/entity/yuv_to_rgb_filter.vert.h" @@ -278,6 +279,7 @@ using FramebufferBlendSoftLightPipeline = /// Geometry Pipelines using PointsComputeShaderPipeline = ComputePipelineBuilder; +using UvComputeShaderPipeline = ComputePipelineBuilder; /// Pipeline state configuration. /// @@ -670,6 +672,12 @@ class ContentContext { return point_field_compute_pipelines_; } + std::shared_ptr> GetUvComputePipeline() + const { + FML_DCHECK(GetDeviceCapabilities().SupportsCompute()); + return uv_compute_pipelines_; + } + std::shared_ptr GetContext() const; std::shared_ptr GetGlyphAtlasContext( @@ -794,6 +802,8 @@ class ContentContext { framebuffer_blend_softlight_pipelines_; mutable std::shared_ptr> point_field_compute_pipelines_; + mutable std::shared_ptr> + uv_compute_pipelines_; template std::shared_ptr> GetPipeline( diff --git a/impeller/entity/geometry.cc b/impeller/entity/geometry.cc index cc87e2fa85de6..23467d50706dc 100644 --- a/impeller/entity/geometry.cc +++ b/impeller/entity/geometry.cc @@ -10,6 +10,7 @@ #include "impeller/entity/points.comp.h" #include "impeller/entity/position_color.vert.h" #include "impeller/entity/texture_fill.vert.h" +#include "impeller/entity/uv.comp.h" #include "impeller/geometry/matrix.h" #include "impeller/geometry/path_builder.h" #include "impeller/renderer/command_buffer.h" @@ -49,6 +50,31 @@ std::pair, std::vector> TessellateConvex( return std::make_pair(output, indices); } +/// Compute UV geometry for a VBB that contains only position geometry. +/// +/// texture_origin should be set to 0, 0 for stroke and stroke based geometry, +/// like the point field. +static VertexBufferBuilder +ComputeUVGeometryCPU( + VertexBufferBuilder& input, + Point texture_origin, + Size texture_coverage, + Matrix effect_transform) { + VertexBufferBuilder vertex_builder; + vertex_builder.Reserve(input.GetVertexCount()); + input.IterateVertices( + [&vertex_builder, &texture_coverage, &effect_transform, + &texture_origin](SolidFillVertexShader::PerVertexData old_vtx) { + TextureFillVertexShader::PerVertexData data; + data.position = old_vtx.position; + auto coverage_coords = + (old_vtx.position - texture_origin) / texture_coverage; + data.texture_coords = effect_transform * coverage_coords; + vertex_builder.AppendVertex(data); + }); + return vertex_builder; +} + Geometry::Geometry() = default; Geometry::~Geometry() = default; @@ -658,17 +684,8 @@ GeometryResult StrokePathGeometry::GetPositionUVBuffer( path_, stroke_width, miter_limit_ * stroke_width_ * 0.5, GetJoinProc(stroke_join_), GetCapProc(stroke_cap_), entity.GetTransformation().GetMaxBasisLength()); - - VertexBufferBuilder vertex_builder; - stroke_builder.IterateVertices( - [&vertex_builder, &texture_coverage, - &effect_transform](SolidFillVertexShader::PerVertexData old_vtx) { - TextureFillVertexShader::PerVertexData data; - data.position = old_vtx.position; - auto coverage_coords = old_vtx.position / texture_coverage.size; - data.texture_coords = effect_transform * coverage_coords; - vertex_builder.AppendVertex(data); - }); + auto vertex_builder = ComputeUVGeometryCPU( + stroke_builder, {0, 0}, texture_coverage.size, effect_transform); return GeometryResult{ .type = PrimitiveType::kTriangleStrip, @@ -817,91 +834,71 @@ GeometryResult PointFieldGeometry::GetPositionBuffer( const ContentContext& renderer, const Entity& entity, RenderPass& pass) { - if (radius_ < 0.0) { - return {}; + if (renderer.GetDeviceCapabilities().SupportsCompute()) { + return GetPositionBufferGPU(renderer, entity, pass); } - auto determinant = entity.GetTransformation().GetDeterminant(); - if (determinant == 0) { + auto vtx_builder = GetPositionBufferCPU(renderer, entity, pass); + if (!vtx_builder.has_value()) { 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(); + return { + .type = PrimitiveType::kTriangle, + .vertex_buffer = vtx_builder->CreateVertexBuffer(host_buffer), + .transform = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * + entity.GetTransformation(), + .prevent_overdraw = false, + }; +} - 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)); +GeometryResult PointFieldGeometry::GetPositionUVBuffer( + Rect texture_coverage, + Matrix effect_transform, + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) { + if (renderer.GetDeviceCapabilities().SupportsCompute()) { + return GetPositionBufferGPU(renderer, entity, pass, texture_coverage, + effect_transform); + } - if (!pass->AddCommand(std::move(cmd)) || !pass->EncodeCommands() || - !cmd_buffer->SubmitCommands()) { - return {}; - } + auto vtx_builder = GetPositionBufferCPU(renderer, entity, pass); + if (!vtx_builder.has_value()) { + return {}; } + auto uv_vtx_builder = ComputeUVGeometryCPU( + vtx_builder.value(), {0, 0}, texture_coverage.size, effect_transform); + auto& host_buffer = pass.GetTransientsBuffer(); return { .type = PrimitiveType::kTriangle, - .vertex_buffer = {.vertex_buffer = {.buffer = buffer, - .range = - Range{0, total * sizeof(Point)}}, - .vertex_count = total, - .index_type = IndexType::kNone}, + .vertex_buffer = uv_vtx_builder.CreateVertexBuffer(host_buffer), .transform = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * entity.GetTransformation(), .prevent_overdraw = false, }; } -GeometryResult PointFieldGeometry::GetPositionBufferCPU( - const ContentContext& renderer, - const Entity& entity, - RenderPass& pass, - Scalar radius) { +std::optional> +PointFieldGeometry::GetPositionBufferCPU(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) { + if (radius_ < 0.0) { + return std::nullopt; + } + auto determinant = entity.GetTransformation().GetDeterminant(); + if (determinant == 0) { + return std::nullopt; + } + + Scalar min_size = 1.0f / sqrt(std::abs(determinant)); + Scalar radius = std::max(radius_, min_size); + 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; @@ -936,25 +933,125 @@ GeometryResult PointFieldGeometry::GetPositionBufferCPU( vtx_builder.AppendVertex({pt2}); } } + return vtx_builder; +} + +GeometryResult PointFieldGeometry::GetPositionBufferGPU( + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass, + std::optional texture_coverage, + std::optional effect_transform) { + FML_DCHECK(renderer.GetDeviceCapabilities().SupportsCompute()); + 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); + + 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 cmd_buffer = renderer.GetContext()->CreateCommandBuffer(); + auto compute_pass = cmd_buffer->CreateComputePass(); + auto& host_buffer = compute_pass->GetTransientsBuffer(); + + auto points_data = + host_buffer.Emplace(points_.data(), points_.size() * sizeof(Point), + DefaultUniformAlignment()); + + DeviceBufferDescriptor buffer_desc; + buffer_desc.size = total * sizeof(Point); + buffer_desc.storage_mode = StorageMode::kDevicePrivate; + + auto geometry_buffer = renderer.GetContext() + ->GetResourceAllocator() + ->CreateBuffer(buffer_desc) + ->AsBufferView(); + + BufferView output; + { + using PS = PointsComputeShader; + 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, geometry_buffer); + PS::BindPointData(cmd, points_data); + + if (!compute_pass->AddCommand(std::move(cmd))) { + return {}; + } + output = geometry_buffer; + } + + if (texture_coverage.has_value() && effect_transform.has_value()) { + DeviceBufferDescriptor buffer_desc; + buffer_desc.size = total * sizeof(Vector4); + buffer_desc.storage_mode = StorageMode::kDevicePrivate; + + auto geometry_uv_buffer = renderer.GetContext() + ->GetResourceAllocator() + ->CreateBuffer(buffer_desc) + ->AsBufferView(); + + using UV = UvComputeShader; + + ComputeCommand cmd; + cmd.label = "UV Geometry"; + cmd.pipeline = renderer.GetUvComputePipeline(); + + UV::FrameInfo frame_info; + frame_info.count = total; + frame_info.effect_transform = effect_transform.value(); + frame_info.texture_origin = {0, 0}; + frame_info.texture_size = Vector2(texture_coverage.value().size); + + UV::BindFrameInfo(cmd, host_buffer.EmplaceUniform(frame_info)); + UV::BindGeometryData(cmd, geometry_buffer); + UV::BindGeometryUVData(cmd, geometry_uv_buffer); + + if (!compute_pass->AddCommand(std::move(cmd))) { + return {}; + } + output = geometry_uv_buffer; + } + + compute_pass->SetGridSize(ISize(total, 1)); + compute_pass->SetThreadGroupSize(ISize(total, 1)); + + if (!compute_pass->EncodeCommands() || !cmd_buffer->SubmitCommands()) { + return {}; + } return { .type = PrimitiveType::kTriangle, - .vertex_buffer = vtx_builder.CreateVertexBuffer(host_buffer), + .vertex_buffer = {.vertex_buffer = output, + .vertex_count = total, + .index_type = IndexType::kNone}, .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. diff --git a/impeller/entity/geometry.h b/impeller/entity/geometry.h index 6ffa6c5991c3b..a1a48899b923d 100644 --- a/impeller/entity/geometry.h +++ b/impeller/entity/geometry.h @@ -291,10 +291,17 @@ class PointFieldGeometry : public Geometry { // |Geometry| std::optional GetCoverage(const Matrix& transform) const override; - GeometryResult GetPositionBufferCPU(const ContentContext& renderer, - const Entity& entity, - RenderPass& pass, - Scalar radius); + GeometryResult GetPositionBufferGPU( + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass, + std::optional texture_coverage = std::nullopt, + std::optional effect_transform = std::nullopt); + + std::optional> + GetPositionBufferCPU(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass); std::vector points_; Scalar radius_; diff --git a/impeller/entity/shaders/geometry/uv.comp b/impeller/entity/shaders/geometry/uv.comp new file mode 100644 index 0000000000000..de4aacc18963b --- /dev/null +++ b/impeller/entity/shaders/geometry/uv.comp @@ -0,0 +1,60 @@ +// 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 GeometryData { + // Size of this input data is frame_info.count; + vec2 points[]; +} +geometry_data; + +layout(std430) writeonly buffer GeometryUVData { + // Size of this output data is frame_info.count; + // x,y is the original geometry. + // u,v is the texture UV. + vec4 points_uv[]; +} +geometry_uv_data; + +uniform FrameInfo { + uint count; + mat4 effect_transform; + vec2 texture_origin; + vec2 texture_size; +} +frame_info; + +vec2 project_point(mat4 m, vec2 v) { + float w = v.x * m[0][3] + v.y * m[1][3] + m[3][3]; + vec2 result = vec2(v.x * m[0][0] + v.y * m[1][0] + m[3][0], + v.x * m[0][1] + v.y * m[1][1] + m[3][1]); + + // This is Skia's behavior, but it may be reasonable to allow UB for the w=0 + // case. + if (w != 0) { + w = 1 / w; + } + return result * w; +} + +void main() { + uint ident = gl_GlobalInvocationID.x; + if (ident >= frame_info.count) { + return; + } + + vec2 point = geometry_data.points[ident]; + + vec2 coverage_coords = + (point - frame_info.texture_origin) / frame_info.texture_size; + vec2 texture_coords = + project_point(frame_info.effect_transform, coverage_coords); + + geometry_uv_data.points_uv[ident] = vec4(point, texture_coords); +} diff --git a/impeller/tools/malioc.json b/impeller/tools/malioc.json index c3fda3a9d4c8c..1730c381c6bb5 100644 --- a/impeller/tools/malioc.json +++ b/impeller/tools/malioc.json @@ -13710,6 +13710,69 @@ } } }, + "flutter/impeller/entity/uv.comp.vkspv": { + "Mali-G78": { + "core": "Mali-G78", + "filename": "flutter/impeller/entity/uv.comp.vkspv", + "has_uniform_computation": true, + "type": "Compute", + "variants": { + "Main": { + "fp16_arithmetic": 100, + "has_stack_spilling": false, + "performance": { + "longest_path_bound_pipelines": [ + "load_store" + ], + "longest_path_cycles": [ + 0.1875, + 0.1875, + 0.1875, + 0.0625, + 2.0, + 0.0 + ], + "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.1875, + 0.1875, + 0.1875, + 0.0625, + 2.0, + 0.0 + ] + }, + "shared_storage_used": 0, + "stack_spill_bytes": 0, + "thread_occupancy": 100, + "uniform_registers_used": 14, + "work_registers_used": 10 + } + } + } + }, "flutter/impeller/entity/vertices.frag.vkspv": { "Mali-G78": { "core": "Mali-G78",