diff --git a/ci/builders/mac_host_engine.json b/ci/builders/mac_host_engine.json index bb7be58956c39..f72aabce9809f 100644 --- a/ci/builders/mac_host_engine.json +++ b/ci/builders/mac_host_engine.json @@ -401,6 +401,12 @@ "realm": "production" } ], + "dependencies": [ + { + "dependency": "goldctl", + "version": "git_revision:720a542f6fe4f92922c3b8f0fdcc4d2ac6bb83cd" + } + ], "drone_dimensions": [ "device_type=none", "os=Mac-13", diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index c93f8899a759d..b52c469b2f816 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -40395,6 +40395,7 @@ ORIGIN: ../../../flutter/impeller/entity/shaders/gaussian_blur/kernel.vert + ../ ORIGIN: ../../../flutter/impeller/entity/shaders/gaussian_blur/kernel_decal.frag + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/shaders/gaussian_blur/kernel_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 @@ -40416,7 +40417,6 @@ ORIGIN: ../../../flutter/impeller/entity/shaders/solid_fill.vert + ../../../flut ORIGIN: ../../../flutter/impeller/entity/shaders/texture_fill.frag + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/shaders/texture_fill.vert + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/shaders/texture_fill_strict_src.frag + ../../../flutter/LICENSE -ORIGIN: ../../../flutter/impeller/entity/shaders/texture_uv_fill.vert + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/shaders/tiled_texture_fill.frag + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/shaders/tiled_texture_fill_external.frag + ../../../flutter/LICENSE ORIGIN: ../../../flutter/impeller/entity/shaders/vertices.frag + ../../../flutter/LICENSE @@ -43275,6 +43275,7 @@ FILE: ../../../flutter/impeller/entity/shaders/gaussian_blur/kernel.vert FILE: ../../../flutter/impeller/entity/shaders/gaussian_blur/kernel_decal.frag FILE: ../../../flutter/impeller/entity/shaders/gaussian_blur/kernel_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 @@ -43296,7 +43297,6 @@ FILE: ../../../flutter/impeller/entity/shaders/solid_fill.vert FILE: ../../../flutter/impeller/entity/shaders/texture_fill.frag FILE: ../../../flutter/impeller/entity/shaders/texture_fill.vert FILE: ../../../flutter/impeller/entity/shaders/texture_fill_strict_src.frag -FILE: ../../../flutter/impeller/entity/shaders/texture_uv_fill.vert FILE: ../../../flutter/impeller/entity/shaders/tiled_texture_fill.frag FILE: ../../../flutter/impeller/entity/shaders/tiled_texture_fill_external.frag FILE: ../../../flutter/impeller/entity/shaders/vertices.frag diff --git a/impeller/aiks/aiks_unittests.cc b/impeller/aiks/aiks_unittests.cc index fc0863fa9ba0c..0499459fde694 100644 --- a/impeller/aiks/aiks_unittests.cc +++ b/impeller/aiks/aiks_unittests.cc @@ -14,6 +14,7 @@ #include #include "flutter/testing/testing.h" +#include "gtest/gtest.h" #include "impeller/aiks/canvas.h" #include "impeller/aiks/color_filter.h" #include "impeller/aiks/image.h" @@ -2348,6 +2349,10 @@ TEST_P(AiksTest, DrawPaintTransformsBounds) { } TEST_P(AiksTest, CanDrawPoints) { + if (GetBackend() == PlaygroundBackend::kMetal) { + // https://github.com/flutter/flutter/issues/147184 + GTEST_SKIP() << "Draw Points is currently broken on the metal m1 backend."; + } std::vector points = { {0, 0}, // {100, 100}, // @@ -2442,6 +2447,10 @@ TEST_P(AiksTest, DrawAtlasAdvancedAndTransform) { } TEST_P(AiksTest, CanDrawPointsWithTextureMap) { + if (GetBackend() == PlaygroundBackend::kMetal) { + // https://github.com/flutter/flutter/issues/147184 + GTEST_SKIP() << "Draw Points is currently broken on the metal m1 backend."; + } auto texture = CreateTextureForFixture("table_mountain_nx.png", /*enable_mipmapping=*/true); diff --git a/impeller/core/range.h b/impeller/core/range.h index 94c61baf3bb3a..d0971725c6928 100644 --- a/impeller/core/range.h +++ b/impeller/core/range.h @@ -7,6 +7,8 @@ #include +#include "flutter/fml/macros.h" + namespace impeller { struct Range { diff --git a/impeller/entity/BUILD.gn b/impeller/entity/BUILD.gn index 6a268d224117f..42d0a32a3f244 100644 --- a/impeller/entity/BUILD.gn +++ b/impeller/entity/BUILD.gn @@ -39,7 +39,6 @@ impeller_shaders("entity_shaders") { "shaders/gradients/sweep_gradient_fill.frag", "shaders/texture_fill.frag", "shaders/texture_fill.vert", - "shaders/texture_uv_fill.vert", "shaders/tiled_texture_fill.frag", "shaders/tiled_texture_fill_external.frag", "shaders/texture_fill_strict_src.frag", @@ -80,6 +79,7 @@ impeller_shaders("modern_entity_shaders") { "shaders/gradients/radial_gradient_ssbo_fill.frag", "shaders/gradients/sweep_gradient_ssbo_fill.frag", "shaders/geometry/points.comp", + "shaders/geometry/uv.comp", ] } diff --git a/impeller/entity/contents/clip_contents.h b/impeller/entity/contents/clip_contents.h index 5481298a2a8b2..6044692c6962a 100644 --- a/impeller/entity/contents/clip_contents.h +++ b/impeller/entity/contents/clip_contents.h @@ -9,6 +9,7 @@ #include #include +#include "flutter/fml/macros.h" #include "impeller/entity/contents/contents.h" #include "impeller/entity/entity.h" #include "impeller/entity/geometry/geometry.h" diff --git a/impeller/entity/contents/color_source_contents.h b/impeller/entity/contents/color_source_contents.h index 84a7e9a65a437..3c5b932100444 100644 --- a/impeller/entity/contents/color_source_contents.h +++ b/impeller/entity/contents/color_source_contents.h @@ -118,7 +118,10 @@ class ColorSourceContents : public Contents { RenderPass& pass, const PipelineBuilderCallback& pipeline_callback, typename VertexShaderT::FrameInfo frame_info, - const BindFragmentCallback& bind_fragment_callback) const { + const BindFragmentCallback& bind_fragment_callback, + bool enable_uvs = false, + Rect texture_coverage = {}, + const Matrix& effect_transform = {}) const { auto options = OptionsFromPassAndEntity(pass, entity); GeometryResult::Mode geometry_mode = GetGeometry()->GetResultMode(); @@ -178,7 +181,10 @@ class ColorSourceContents : public Contents { } GeometryResult geometry_result = - geometry.GetPositionBuffer(renderer, entity, pass); + enable_uvs + ? geometry.GetPositionUVBuffer(texture_coverage, effect_transform, + renderer, entity, pass) + : geometry.GetPositionBuffer(renderer, entity, pass); if (geometry_result.vertex_buffer.vertex_count == 0u) { return true; } diff --git a/impeller/entity/contents/content_context.cc b/impeller/entity/contents/content_context.cc index 34da4ffee1ad0..bd3e05c135f6a 100644 --- a/impeller/entity/contents/content_context.cc +++ b/impeller/entity/contents/content_context.cc @@ -260,7 +260,8 @@ ContentContext::ContentContext( ? std::make_shared( context_->GetResourceAllocator()) : std::move(render_target_allocator)), - host_buffer_(HostBuffer::Create(context_->GetResourceAllocator())) { + host_buffer_(HostBuffer::Create(context_->GetResourceAllocator())), + pending_command_buffers_(std::make_unique()) { if (!context_ || !context_->IsValid()) { return; } @@ -421,7 +422,8 @@ ContentContext::ContentContext( rrect_blur_pipelines_.CreateDefault(*context_, options_trianglestrip); texture_strict_src_pipelines_.CreateDefault(*context_, options); - tiled_texture_pipelines_.CreateDefault(*context_, options, {supports_decal}); + position_uv_pipelines_.CreateDefault(*context_, options); + tiled_texture_pipelines_.CreateDefault(*context_, options); kernel_decal_pipelines_.CreateDefault(*context_, options_trianglestrip); kernel_nodecal_pipelines_.CreateDefault(*context_, options_trianglestrip); border_mask_blur_pipelines_.CreateDefault(*context_, options_trianglestrip); @@ -454,6 +456,11 @@ ContentContext::ContentContext( 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(); } is_valid_ = true; diff --git a/impeller/entity/contents/content_context.h b/impeller/entity/contents/content_context.h index 062649f77bd30..361cd6650dc2e 100644 --- a/impeller/entity/contents/content_context.h +++ b/impeller/entity/contents/content_context.h @@ -57,8 +57,8 @@ #include "impeller/entity/texture_fill.frag.h" #include "impeller/entity/texture_fill.vert.h" #include "impeller/entity/texture_fill_strict_src.frag.h" -#include "impeller/entity/texture_uv_fill.vert.h" #include "impeller/entity/tiled_texture_fill.frag.h" +#include "impeller/entity/uv.comp.h" #include "impeller/entity/vertices.frag.h" #include "impeller/entity/yuv_to_rgb_filter.frag.h" @@ -129,8 +129,10 @@ using TexturePipeline = using TextureStrictSrcPipeline = RenderPipelineHandle; +using PositionUVPipeline = RenderPipelineHandle; using TiledTexturePipeline = - RenderPipelineHandle; using KernelDecalPipeline = RenderPipelineHandle; @@ -251,13 +253,20 @@ using FramebufferBlendSoftLightPipeline = /// Geometry Pipelines using PointsComputeShaderPipeline = ComputePipelineBuilder; +using UvComputeShaderPipeline = ComputePipelineBuilder; #ifdef IMPELLER_ENABLE_OPENGLES using TiledTextureExternalPipeline = - RenderPipelineHandle; #endif // IMPELLER_ENABLE_OPENGLES +// A struct used to isolate command buffer storage from the content +// context options to preserve const-ness. +struct PendingCommandBuffers { + std::vector> command_buffers; +}; + /// Pipeline state configuration. /// /// Each unique combination of these options requires a different pipeline state @@ -469,6 +478,11 @@ class ContentContext { } #endif // IMPELLER_ENABLE_OPENGLES + std::shared_ptr> GetPositionUVPipeline( + ContentContextOptions opts) const { + return GetPipeline(position_uv_pipelines_, opts); + } + std::shared_ptr> GetTiledTexturePipeline( ContentContextOptions opts) const { return GetPipeline(tiled_texture_pipelines_, opts); @@ -713,6 +727,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; const Capabilities& GetDeviceCapabilities() const; @@ -912,6 +932,7 @@ class ContentContext { mutable Variants tiled_texture_external_pipelines_; #endif // IMPELLER_ENABLE_OPENGLES + mutable Variants position_uv_pipelines_; mutable Variants tiled_texture_pipelines_; mutable Variants kernel_decal_pipelines_; mutable Variants kernel_nodecal_pipelines_; @@ -976,6 +997,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( @@ -1035,6 +1058,7 @@ class ContentContext { #endif // IMPELLER_ENABLE_3D std::shared_ptr render_target_cache_; std::shared_ptr host_buffer_; + std::unique_ptr pending_command_buffers_; bool wireframe_ = false; ContentContext(const ContentContext&) = delete; diff --git a/impeller/entity/contents/filters/blend_filter_contents.cc b/impeller/entity/contents/filters/blend_filter_contents.cc index 3497c5bdd8e80..2e353dbafa691 100644 --- a/impeller/entity/contents/filters/blend_filter_contents.cc +++ b/impeller/entity/contents/filters/blend_filter_contents.cc @@ -728,7 +728,8 @@ std::optional BlendFilterContents::RenderFilter( } if (blend_mode_ <= Entity::kLastPipelineBlendMode) { - if (inputs.size() == 1 && foreground_color_.has_value()) { + if (inputs.size() == 1 && foreground_color_.has_value() && + GetAbsorbOpacity() == ColorFilterContents::AbsorbOpacity::kYes) { return CreateForegroundPorterDuffBlend( inputs[0], renderer, entity, coverage, foreground_color_.value(), blend_mode_, GetAlpha(), GetAbsorbOpacity()); @@ -738,7 +739,8 @@ std::optional BlendFilterContents::RenderFilter( } if (blend_mode_ <= Entity::kLastAdvancedBlendMode) { - if (inputs.size() == 1 && foreground_color_.has_value()) { + if (inputs.size() == 1 && foreground_color_.has_value() && + GetAbsorbOpacity() == ColorFilterContents::AbsorbOpacity::kYes) { return CreateForegroundAdvancedBlend( inputs[0], renderer, entity, coverage, foreground_color_.value(), blend_mode_, GetAlpha(), GetAbsorbOpacity()); diff --git a/impeller/entity/contents/texture_contents.cc b/impeller/entity/contents/texture_contents.cc index 72fce8d0ff211..d48b4b6eaace2 100644 --- a/impeller/entity/contents/texture_contents.cc +++ b/impeller/entity/contents/texture_contents.cc @@ -14,6 +14,7 @@ #include "impeller/entity/texture_fill.frag.h" #include "impeller/entity/texture_fill.vert.h" #include "impeller/entity/texture_fill_strict_src.frag.h" +#include "impeller/entity/tiled_texture_fill_external.frag.h" #include "impeller/geometry/constants.h" #include "impeller/renderer/render_pass.h" #include "impeller/renderer/vertex_buffer_builder.h" @@ -113,6 +114,7 @@ bool TextureContents::Render(const ContentContext& renderer, using VS = TextureFillVertexShader; using FS = TextureFillFragmentShader; + using FSExternal = TiledTextureFillExternalFragmentShader; using FSStrict = TextureFillStrictSrcFragmentShader; if (destination_rect_.IsEmpty() || source_rect_.IsEmpty() || @@ -120,9 +122,8 @@ bool TextureContents::Render(const ContentContext& renderer, return true; // Nothing to render. } - [[maybe_unused]] bool is_external_texture = + bool is_external_texture = texture_->GetTextureDescriptor().type == TextureType::kTextureExternalOES; - FML_DCHECK(!is_external_texture); auto source_rect = capture.AddRect("Source rect", source_rect_); auto texture_coords = @@ -158,14 +159,46 @@ bool TextureContents::Render(const ContentContext& renderer, } pipeline_options.primitive_type = PrimitiveType::kTriangleStrip; - pass.SetPipeline(strict_source_rect_enabled_ - ? renderer.GetTextureStrictSrcPipeline(pipeline_options) - : renderer.GetTexturePipeline(pipeline_options)); + std::shared_ptr> pipeline; +#ifdef IMPELLER_ENABLE_OPENGLES + if (is_external_texture) { + pipeline = renderer.GetTiledTextureExternalPipeline(pipeline_options); + } +#endif // IMPELLER_ENABLE_OPENGLES + + if (!pipeline) { + if (strict_source_rect_enabled_) { + pipeline = renderer.GetTextureStrictSrcPipeline(pipeline_options); + } else { + pipeline = renderer.GetTexturePipeline(pipeline_options); + } + } + pass.SetPipeline(pipeline); pass.SetVertexBuffer(vertex_builder.CreateVertexBuffer(host_buffer)); VS::BindFrameInfo(pass, host_buffer.EmplaceUniform(frame_info)); - if (strict_source_rect_enabled_) { + if (is_external_texture) { + FSExternal::FragInfo frag_info; + frag_info.x_tile_mode = + static_cast(sampler_descriptor_.width_address_mode); + frag_info.y_tile_mode = + static_cast(sampler_descriptor_.height_address_mode); + frag_info.alpha = capture.AddScalar("Alpha", GetOpacity()); + + auto sampler_descriptor = sampler_descriptor_; + // OES_EGL_image_external states that only CLAMP_TO_EDGE is valid, so + // we emulate all other tile modes here by remapping the texture + // coordinates. + sampler_descriptor.width_address_mode = SamplerAddressMode::kClampToEdge; + sampler_descriptor.height_address_mode = SamplerAddressMode::kClampToEdge; + + FSExternal::BindFragInfo(pass, host_buffer.EmplaceUniform((frag_info))); + FSExternal::BindSAMPLEREXTERNALOESTextureSampler( + pass, texture_, + renderer.GetContext()->GetSamplerLibrary()->GetSampler( + sampler_descriptor)); + } else if (strict_source_rect_enabled_) { // For a strict source rect, shrink the texture coordinate range by half a // texel to ensure that linear filtering does not sample anything outside // the source rect bounds. diff --git a/impeller/entity/contents/tiled_texture_contents.cc b/impeller/entity/contents/tiled_texture_contents.cc index 3f6d4a3715203..de6497033a307 100644 --- a/impeller/entity/contents/tiled_texture_contents.cc +++ b/impeller/entity/contents/tiled_texture_contents.cc @@ -6,6 +6,8 @@ #include "fml/logging.h" #include "impeller/entity/contents/content_context.h" +#include "impeller/entity/texture_fill.frag.h" +#include "impeller/entity/texture_fill.vert.h" #include "impeller/entity/tiled_texture_fill.frag.h" #include "impeller/entity/tiled_texture_fill_external.frag.h" #include "impeller/renderer/render_pass.h" @@ -114,7 +116,7 @@ bool TiledTextureContents::Render(const ContentContext& renderer, return true; } - using VS = TextureUvFillVertexShader; + using VS = TextureFillVertexShader; using FS = TiledTextureFillFragmentShader; using FSExternal = TiledTextureFillExternalFragmentShader; @@ -126,11 +128,11 @@ bool TiledTextureContents::Render(const ContentContext& renderer, bool is_external_texture = texture_->GetTextureDescriptor().type == TextureType::kTextureExternalOES; + bool uses_emulated_tile_mode = + UsesEmulatedTileMode(renderer.GetDeviceCapabilities()); + VS::FrameInfo frame_info; frame_info.texture_sampler_y_coord_scale = texture_->GetYCoordScale(); - frame_info.uv_transform = - Rect::MakeSize(texture_size).GetNormalizingTransform() * - GetInverseEffectTransform(); PipelineBuilderMethod pipeline_method; @@ -138,10 +140,14 @@ bool TiledTextureContents::Render(const ContentContext& renderer, if (is_external_texture) { pipeline_method = &ContentContext::GetTiledTextureExternalPipeline; } else { - pipeline_method = &ContentContext::GetTiledTexturePipeline; + pipeline_method = uses_emulated_tile_mode + ? &ContentContext::GetTiledTexturePipeline + : &ContentContext::GetTexturePipeline; } #else - pipeline_method = &ContentContext::GetTiledTexturePipeline; + pipeline_method = uses_emulated_tile_mode + ? &ContentContext::GetTiledTexturePipeline + : &ContentContext::GetTexturePipeline; #endif // IMPELLER_ENABLE_OPENGLES PipelineBuilderCallback pipeline_callback = @@ -150,10 +156,15 @@ bool TiledTextureContents::Render(const ContentContext& renderer, }; return ColorSourceContents::DrawGeometry( renderer, entity, pass, pipeline_callback, frame_info, - [this, &renderer, &is_external_texture](RenderPass& pass) { + [this, &renderer, &is_external_texture, + &uses_emulated_tile_mode](RenderPass& pass) { auto& host_buffer = renderer.GetTransientsBuffer(); - pass.SetCommandLabel("TextureFill"); + if (uses_emulated_tile_mode) { + pass.SetCommandLabel("TiledTextureFill"); + } else { + pass.SetCommandLabel("TextureFill"); + } if (is_external_texture) { FSExternal::FragInfo frag_info; @@ -161,12 +172,17 @@ bool TiledTextureContents::Render(const ContentContext& renderer, frag_info.y_tile_mode = static_cast(y_tile_mode_); frag_info.alpha = GetOpacityFactor(); FSExternal::BindFragInfo(pass, host_buffer.EmplaceUniform(frag_info)); - } else { + } else if (uses_emulated_tile_mode) { FS::FragInfo frag_info; frag_info.x_tile_mode = static_cast(x_tile_mode_); frag_info.y_tile_mode = static_cast(y_tile_mode_); frag_info.alpha = GetOpacityFactor(); FS::BindFragInfo(pass, host_buffer.EmplaceUniform(frag_info)); + } else { + TextureFillFragmentShader::FragInfo frag_info; + frag_info.alpha = GetOpacityFactor(); + TextureFillFragmentShader::BindFragInfo( + pass, host_buffer.EmplaceUniform(frag_info)); } if (is_external_texture) { @@ -205,7 +221,10 @@ bool TiledTextureContents::Render(const ContentContext& renderer, } return true; - }); + }, + /*enable_uvs=*/true, + /*texture_coverage=*/Rect::MakeSize(texture_size), + /*effect_transform=*/GetInverseEffectTransform()); } std::optional TiledTextureContents::RenderToSnapshot( diff --git a/impeller/entity/contents/vertices_contents.cc b/impeller/entity/contents/vertices_contents.cc index b67aee75d192b..9aeaa83023b83 100644 --- a/impeller/entity/contents/vertices_contents.cc +++ b/impeller/entity/contents/vertices_contents.cc @@ -133,8 +133,8 @@ void VerticesUVContents::SetAlpha(Scalar alpha) { bool VerticesUVContents::Render(const ContentContext& renderer, const Entity& entity, RenderPass& pass) const { - using VS = TiledTexturePipeline::VertexShader; - using FS = TiledTexturePipeline::FragmentShader; + using VS = TexturePipeline::VertexShader; + using FS = TexturePipeline::FragmentShader; auto src_contents = parent_.GetSourceContents(); @@ -158,23 +158,21 @@ bool VerticesUVContents::Render(const ContentContext& renderer, if (!coverage.has_value()) { return false; } - auto geometry_result = geometry->GetPositionBuffer(renderer, entity, pass); + auto geometry_result = geometry->GetPositionUVBuffer( + coverage.value(), Matrix(), renderer, entity, pass); auto opts = OptionsFromPassAndEntity(pass, entity); opts.primitive_type = geometry_result.type; - pass.SetPipeline(renderer.GetTiledTexturePipeline(opts)); + pass.SetPipeline(renderer.GetTexturePipeline(opts)); pass.SetVertexBuffer(std::move(geometry_result.vertex_buffer)); VS::FrameInfo frame_info; frame_info.mvp = geometry_result.transform; frame_info.texture_sampler_y_coord_scale = snapshot->texture->GetYCoordScale(); - frame_info.uv_transform = coverage.value().GetNormalizingTransform(); VS::BindFrameInfo(pass, host_buffer.EmplaceUniform(frame_info)); FS::FragInfo frag_info; frag_info.alpha = alpha_ * snapshot->opacity; - frag_info.x_tile_mode = 0; - frag_info.y_tile_mode = 0; FS::BindFragInfo(pass, host_buffer.EmplaceUniform(frag_info)); FS::BindTextureSampler(pass, snapshot->texture, renderer.GetContext()->GetSamplerLibrary()->GetSampler( diff --git a/impeller/entity/entity_unittests.cc b/impeller/entity/entity_unittests.cc index 2df4baaad979a..61594bd4d2e64 100644 --- a/impeller/entity/entity_unittests.cc +++ b/impeller/entity/entity_unittests.cc @@ -2830,7 +2830,12 @@ TEST_P(EntityTest, CanComputeGeometryForEmptyPathsWithoutCrashing) { auto position_result = geom->GetPositionBuffer(*GetContentContext(), entity, render_pass); + auto uv_result = + geom->GetPositionUVBuffer(Rect::MakeLTRB(0, 0, 100, 100), Matrix(), + *GetContentContext(), entity, render_pass); + EXPECT_EQ(position_result.vertex_buffer.vertex_count, 0u); + EXPECT_EQ(uv_result.vertex_buffer.vertex_count, 0u); EXPECT_EQ(geom->GetResultMode(), GeometryResult::Mode::kNormal); } diff --git a/impeller/entity/geometry/circle_geometry.cc b/impeller/entity/geometry/circle_geometry.cc index 4f782feeb1441..bd9aa8c796378 100644 --- a/impeller/entity/geometry/circle_geometry.cc +++ b/impeller/entity/geometry/circle_geometry.cc @@ -44,6 +44,31 @@ GeometryResult CircleGeometry::GetPositionBuffer(const ContentContext& renderer, return ComputePositionGeometry(renderer, generator, entity, pass); } +// |Geometry| +GeometryResult CircleGeometry::GetPositionUVBuffer( + Rect texture_coverage, + Matrix effect_transform, + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const { + auto& transform = entity.GetTransform(); + auto uv_transform = + texture_coverage.GetNormalizingTransform() * effect_transform; + + Scalar half_width = stroke_width_ < 0 ? 0.0 + : LineGeometry::ComputePixelHalfWidth( + transform, stroke_width_); + std::shared_ptr tessellator = renderer.GetTessellator(); + + // We call the StrokedCircle method which will simplify to a + // FilledCircleGenerator if the inner_radius is <= 0. + auto generator = + tessellator->StrokedCircle(transform, center_, radius_, half_width); + + return ComputePositionUVGeometry(renderer, generator, uv_transform, entity, + pass); +} + GeometryVertexType CircleGeometry::GetVertexType() const { return GeometryVertexType::kPosition; } diff --git a/impeller/entity/geometry/circle_geometry.h b/impeller/entity/geometry/circle_geometry.h index d715a2fc02e2e..be75f891317d1 100644 --- a/impeller/entity/geometry/circle_geometry.h +++ b/impeller/entity/geometry/circle_geometry.h @@ -39,6 +39,13 @@ class CircleGeometry final : public Geometry { // |Geometry| std::optional GetCoverage(const Matrix& transform) const override; + // |Geometry| + GeometryResult GetPositionUVBuffer(Rect texture_coverage, + Matrix effect_transform, + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const override; + Point center_; Scalar radius_; Scalar stroke_width_; diff --git a/impeller/entity/geometry/cover_geometry.cc b/impeller/entity/geometry/cover_geometry.cc index cdf64d70c7fb4..1f80687d14752 100644 --- a/impeller/entity/geometry/cover_geometry.cc +++ b/impeller/entity/geometry/cover_geometry.cc @@ -33,6 +33,18 @@ GeometryResult CoverGeometry::GetPositionBuffer(const ContentContext& renderer, }; } +// |Geometry| +GeometryResult CoverGeometry::GetPositionUVBuffer( + Rect texture_coverage, + Matrix effect_transform, + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const { + auto rect = Rect::MakeSize(pass.GetRenderTargetSize()); + return ComputeUVGeometryForRect(rect, texture_coverage, effect_transform, + renderer, entity, pass); +} + GeometryVertexType CoverGeometry::GetVertexType() const { return GeometryVertexType::kPosition; } diff --git a/impeller/entity/geometry/cover_geometry.h b/impeller/entity/geometry/cover_geometry.h index 5dfe6a76e8a3b..e9f56530cded2 100644 --- a/impeller/entity/geometry/cover_geometry.h +++ b/impeller/entity/geometry/cover_geometry.h @@ -34,6 +34,13 @@ class CoverGeometry final : public Geometry { // |Geometry| std::optional GetCoverage(const Matrix& transform) const override; + // |Geometry| + GeometryResult GetPositionUVBuffer(Rect texture_coverage, + Matrix effect_transform, + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const override; + CoverGeometry(const CoverGeometry&) = delete; CoverGeometry& operator=(const CoverGeometry&) = delete; diff --git a/impeller/entity/geometry/ellipse_geometry.cc b/impeller/entity/geometry/ellipse_geometry.cc index 623c9a8abf8a9..b0d21324b437e 100644 --- a/impeller/entity/geometry/ellipse_geometry.cc +++ b/impeller/entity/geometry/ellipse_geometry.cc @@ -22,6 +22,20 @@ GeometryResult EllipseGeometry::GetPositionBuffer( entity, pass); } +// |Geometry| +GeometryResult EllipseGeometry::GetPositionUVBuffer( + Rect texture_coverage, + Matrix effect_transform, + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const { + return ComputePositionUVGeometry( + renderer, + renderer.GetTessellator()->FilledEllipse(entity.GetTransform(), bounds_), + texture_coverage.GetNormalizingTransform() * effect_transform, entity, + pass); +} + GeometryVertexType EllipseGeometry::GetVertexType() const { return GeometryVertexType::kPosition; } diff --git a/impeller/entity/geometry/ellipse_geometry.h b/impeller/entity/geometry/ellipse_geometry.h index 734899bb28619..892d03568e6de 100644 --- a/impeller/entity/geometry/ellipse_geometry.h +++ b/impeller/entity/geometry/ellipse_geometry.h @@ -37,6 +37,13 @@ class EllipseGeometry final : public Geometry { // |Geometry| std::optional GetCoverage(const Matrix& transform) const override; + // |Geometry| + GeometryResult GetPositionUVBuffer(Rect texture_coverage, + Matrix effect_transform, + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const override; + Rect bounds_; EllipseGeometry(const EllipseGeometry&) = delete; diff --git a/impeller/entity/geometry/fill_path_geometry.cc b/impeller/entity/geometry/fill_path_geometry.cc index 77286c0dad6ed..1b3dc51813266 100644 --- a/impeller/entity/geometry/fill_path_geometry.cc +++ b/impeller/entity/geometry/fill_path_geometry.cc @@ -36,8 +36,15 @@ GeometryResult FillPathGeometry::GetPositionBuffer( }; } - VertexBuffer vertex_buffer = renderer.GetTessellator()->TessellateConvex( - path_, host_buffer, entity.GetTransform().GetMaxBasisLength()); + VertexBuffer vertex_buffer; + + auto points = renderer.GetTessellator()->TessellateConvex( + path_, entity.GetTransform().GetMaxBasisLength()); + + vertex_buffer.vertex_buffer = host_buffer.Emplace( + points.data(), points.size() * sizeof(Point), alignof(Point)); + vertex_buffer.index_buffer = {}, vertex_buffer.vertex_count = points.size(); + vertex_buffer.index_type = IndexType::kNone; return GeometryResult{ .type = PrimitiveType::kTriangleStrip, @@ -47,6 +54,53 @@ GeometryResult FillPathGeometry::GetPositionBuffer( }; } +// |Geometry| +GeometryResult FillPathGeometry::GetPositionUVBuffer( + Rect texture_coverage, + Matrix effect_transform, + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const { + using VS = TextureFillVertexShader; + + const auto& bounding_box = path_.GetBoundingBox(); + if (bounding_box.has_value() && bounding_box->IsEmpty()) { + return GeometryResult{ + .type = PrimitiveType::kTriangle, + .vertex_buffer = + VertexBuffer{ + .vertex_buffer = {}, + .vertex_count = 0, + .index_type = IndexType::k16bit, + }, + .transform = pass.GetOrthographicTransform() * entity.GetTransform(), + }; + } + + auto uv_transform = + texture_coverage.GetNormalizingTransform() * effect_transform; + + auto points = renderer.GetTessellator()->TessellateConvex( + path_, entity.GetTransform().GetMaxBasisLength()); + + VertexBufferBuilder vertex_builder; + vertex_builder.Reserve(points.size()); + for (auto i = 0u; i < points.size(); i++) { + VS::PerVertexData data; + data.position = points[i]; + data.texture_coords = uv_transform * points[i]; + vertex_builder.AppendVertex(data); + } + + return GeometryResult{ + .type = PrimitiveType::kTriangleStrip, + .vertex_buffer = + vertex_builder.CreateVertexBuffer(renderer.GetTransientsBuffer()), + .transform = entity.GetShaderTransform(pass), + .mode = GetResultMode(), + }; +} + GeometryResult::Mode FillPathGeometry::GetResultMode() const { const auto& bounding_box = path_.GetBoundingBox(); if (path_.IsConvex() || diff --git a/impeller/entity/geometry/fill_path_geometry.h b/impeller/entity/geometry/fill_path_geometry.h index 9127084a3384f..722f7cc661c40 100644 --- a/impeller/entity/geometry/fill_path_geometry.h +++ b/impeller/entity/geometry/fill_path_geometry.h @@ -35,6 +35,13 @@ class FillPathGeometry final : public Geometry { // |Geometry| std::optional GetCoverage(const Matrix& transform) const override; + // |Geometry| + GeometryResult GetPositionUVBuffer(Rect texture_coverage, + Matrix effect_transform, + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const override; + // |Geometry| GeometryResult::Mode GetResultMode() const override; diff --git a/impeller/entity/geometry/geometry.cc b/impeller/entity/geometry/geometry.cc index d595317d3656a..bc774a2a80e70 100644 --- a/impeller/entity/geometry/geometry.cc +++ b/impeller/entity/geometry/geometry.cc @@ -7,6 +7,7 @@ #include #include +#include "fml/status.h" #include "impeller/entity/contents/content_context.h" #include "impeller/entity/geometry/circle_geometry.h" #include "impeller/entity/geometry/cover_geometry.h" @@ -53,6 +54,120 @@ GeometryResult Geometry::ComputePositionGeometry( }; } +GeometryResult Geometry::ComputePositionUVGeometry( + const ContentContext& renderer, + const Tessellator::VertexGenerator& generator, + const Matrix& uv_transform, + const Entity& entity, + RenderPass& pass) { + using VT = TextureFillVertexShader::PerVertexData; + + size_t count = generator.GetVertexCount(); + + return GeometryResult{ + .type = generator.GetTriangleType(), + .vertex_buffer = + { + .vertex_buffer = renderer.GetTransientsBuffer().Emplace( + count * sizeof(VT), alignof(VT), + [&generator, &uv_transform](uint8_t* buffer) { + auto vertices = reinterpret_cast(buffer); + generator.GenerateVertices( + [&vertices, &uv_transform](const Point& p) { // + *vertices++ = { + .position = p, + .texture_coords = uv_transform * p, + }; + }); + FML_DCHECK(vertices == reinterpret_cast(buffer) + + generator.GetVertexCount()); + }), + .vertex_count = count, + .index_type = IndexType::kNone, + }, + .transform = entity.GetShaderTransform(pass), + }; +} + +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; + data.texture_coords = effect_transform * + (old_vtx.position - texture_origin) / + texture_coverage; + vertex_builder.AppendVertex(data); + }); + return vertex_builder; +} + +GeometryResult ComputeUVGeometryForRect(Rect source_rect, + Rect texture_bounds, + Matrix effect_transform, + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) { + auto& host_buffer = renderer.GetTransientsBuffer(); + + // Calculate UV-specific transform based on texture coverage and effect. + // For example, if the texture is 100x100 and the effect transform is + // scaling by 2.0, texture_bounds.GetNormalizingTransform() will result in a + // Matrix that scales by 0.01, and then if the effect_transform is + // Matrix::MakeScale(Vector2{2, 2}), the resulting uv_transform will have x + // and y basis vectors with scale 0.02. + auto uv_transform = texture_bounds.GetNormalizingTransform() * // + effect_transform; + + // Allocate space for vertex and UV data (4 vertices) + // 0: position + // 1: UV + // 2: position + // 3: UV + // etc. + Point data[8]; + + // Get the raw points from the rect and transform them into UV space. + auto points = source_rect.GetPoints(); + for (auto i = 0u, j = 0u; i < 8; i += 2, j++) { + // Store original coordinates. + data[i] = points[j]; + + // Store transformed UV coordinates. + data[i + 1] = uv_transform * points[j]; + } + + return GeometryResult{ + .type = PrimitiveType::kTriangleStrip, + .vertex_buffer = + { + .vertex_buffer = host_buffer.Emplace( + /*buffer=*/data, + /*length=*/16 * sizeof(float), + /*align=*/alignof(float)), + .vertex_count = 4, + .index_type = IndexType::kNone, + }, + .transform = entity.GetShaderTransform(pass), + }; +} + +GeometryResult Geometry::GetPositionUVBuffer(Rect texture_coverage, + Matrix effect_transform, + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const { + return {}; +} + GeometryResult::Mode Geometry::GetResultMode() const { return GeometryResult::Mode::kNormal; } diff --git a/impeller/entity/geometry/geometry.h b/impeller/entity/geometry/geometry.h index 70939b8f0dc63..5144ead7c433d 100644 --- a/impeller/entity/geometry/geometry.h +++ b/impeller/entity/geometry/geometry.h @@ -9,6 +9,7 @@ #include "impeller/core/vertex_buffer.h" #include "impeller/entity/contents/content_context.h" #include "impeller/entity/entity.h" +#include "impeller/entity/texture_fill.vert.h" #include "impeller/renderer/render_pass.h" #include "impeller/renderer/vertex_buffer_builder.h" @@ -51,6 +52,34 @@ enum GeometryVertexType { kUV, }; +/// @brief 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. +VertexBufferBuilder +ComputeUVGeometryCPU( + VertexBufferBuilder& input, + Point texture_origin, + Size texture_coverage, + Matrix effect_transform); + +/// @brief Computes geometry and UV coordinates for a rectangle to be rendered. +/// +/// UV is the horizontal and vertical coordinates within the texture. +/// +/// @param source_rect The rectangle to be rendered. +/// @param texture_bounds The local space bounding box of the geometry. +/// @param effect_transform The transform to apply to the UV coordinates. +/// @param renderer The content context to use for allocating buffers. +/// @param entity The entity to use for the transform. +/// @param pass The render pass to use for the transform. +GeometryResult ComputeUVGeometryForRect(Rect source_rect, + Rect texture_bounds, + Matrix effect_transform, + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass); + class Geometry { public: static std::shared_ptr MakeFillPath( @@ -93,6 +122,12 @@ class Geometry { const Entity& entity, RenderPass& pass) const = 0; + virtual GeometryResult GetPositionUVBuffer(Rect texture_coverage, + Matrix effect_transform, + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const = 0; + virtual GeometryResult::Mode GetResultMode() const; virtual GeometryVertexType GetVertexType() const = 0; @@ -121,6 +156,13 @@ class Geometry { const Tessellator::VertexGenerator& generator, const Entity& entity, RenderPass& pass); + + static GeometryResult ComputePositionUVGeometry( + const ContentContext& renderer, + const Tessellator::VertexGenerator& generator, + const Matrix& uv_transform, + const Entity& entity, + RenderPass& pass); }; } // namespace impeller diff --git a/impeller/entity/geometry/geometry_unittests.cc b/impeller/entity/geometry/geometry_unittests.cc index 09061eb2628ae..990b1419659ed 100644 --- a/impeller/entity/geometry/geometry_unittests.cc +++ b/impeller/entity/geometry/geometry_unittests.cc @@ -61,6 +61,21 @@ class ImpellerEntityUnitTestAccessor { return StrokePathGeometry::GenerateSolidStrokeVertices( polyline, stroke_width, miter_limit, stroke_join, stroke_cap, scale); } + + static std::vector + GenerateSolidStrokeVerticesUV(const Path::Polyline& polyline, + Scalar stroke_width, + Scalar miter_limit, + Join stroke_join, + Cap stroke_cap, + Scalar scale, + Point texture_origin, + Size texture_size, + const Matrix& effect_transform) { + return StrokePathGeometry::GenerateSolidStrokeVerticesUV( + polyline, stroke_width, miter_limit, stroke_join, stroke_cap, scale, + texture_origin, texture_size, effect_transform); + } }; namespace testing { @@ -127,6 +142,95 @@ TEST(EntityGeometryTest, RoundRectGeometryCoversArea) { EXPECT_TRUE(geometry->CoversArea({}, Rect::MakeLTRB(1, 30, 99, 70))); } +TEST(EntityGeometryTest, StrokePathGeometryTransformOfLine) { + auto path = + PathBuilder().AddLine(Point(100, 100), Point(200, 100)).TakePath(); + auto points = std::make_unique>(); + auto polyline = + path.CreatePolyline(1.0f, std::move(points), + [&points](Path::Polyline::PointBufferPtr reclaimed) { + points = std::move(reclaimed); + }); + + auto vertices = ImpellerEntityUnitTestAccessor::GenerateSolidStrokeVertices( + polyline, 10.0f, 10.0f, Join::kBevel, Cap::kButt, 1.0); + + std::vector expected = { + {.position = Point(100.0f, 105.0f)}, // + {.position = Point(100.0f, 95.0f)}, // + {.position = Point(100.0f, 105.0f)}, // + {.position = Point(100.0f, 95.0f)}, // + {.position = Point(200.0f, 105.0f)}, // + {.position = Point(200.0f, 95.0f)}, // + {.position = Point(200.0f, 105.0f)}, // + {.position = Point(200.0f, 95.0f)}, // + }; + + EXPECT_SOLID_VERTICES_NEAR(vertices, expected); + + { + auto uv_vertices = + ImpellerEntityUnitTestAccessor::GenerateSolidStrokeVerticesUV( + polyline, 10.0f, 10.0f, Join::kBevel, Cap::kButt, 1.0, // + Point(50.0f, 40.0f), Size(20.0f, 40.0f), Matrix()); + // uvx = (x - 50) / 20 + // uvy = (y - 40) / 40 + auto uv = [](const Point& p) { + return Point((p.x - 50.0f) / 20.0f, // + (p.y - 40.0f) / 40.0f); + }; + std::vector uv_expected; + for (size_t i = 0; i < expected.size(); i++) { + auto p = expected[i].position; + uv_expected.push_back({.position = p, .texture_coords = uv(p)}); + } + + EXPECT_TEXTURE_VERTICES_NEAR(uv_vertices, uv_expected); + } + + { + auto uv_vertices = + ImpellerEntityUnitTestAccessor::GenerateSolidStrokeVerticesUV( + polyline, 10.0f, 10.0f, Join::kBevel, Cap::kButt, 1.0, // + Point(50.0f, 40.0f), Size(20.0f, 40.0f), + Matrix::MakeScale({8.0f, 4.0f, 1.0f})); + // uvx = ((x * 8) - 50) / 20 + // uvy = ((y * 4) - 40) / 40 + auto uv = [](const Point& p) { + return Point(((p.x * 8.0f) - 50.0f) / 20.0f, + ((p.y * 4.0f) - 40.0f) / 40.0f); + }; + std::vector uv_expected; + for (size_t i = 0; i < expected.size(); i++) { + auto p = expected[i].position; + uv_expected.push_back({.position = p, .texture_coords = uv(p)}); + } + + EXPECT_TEXTURE_VERTICES_NEAR(uv_vertices, uv_expected); + } + + { + auto uv_vertices = + ImpellerEntityUnitTestAccessor::GenerateSolidStrokeVerticesUV( + polyline, 10.0f, 10.0f, Join::kBevel, Cap::kButt, 1.0, // + Point(50.0f, 40.0f), Size(20.0f, 40.0f), + Matrix::MakeTranslation({8.0f, 4.0f})); + // uvx = ((x + 8) - 50) / 20 + // uvy = ((y + 4) - 40) / 40 + auto uv = [](const Point& p) { + return Point(((p.x + 8.0f) - 50.0f) / 20.0f, + ((p.y + 4.0f) - 40.0f) / 40.0f); + }; + std::vector uv_expected; + for (size_t i = 0; i < expected.size(); i++) { + auto p = expected[i].position; + uv_expected.push_back({.position = p, .texture_coords = uv(p)}); + } + + EXPECT_TEXTURE_VERTICES_NEAR(uv_vertices, uv_expected); + } +} + TEST(EntityGeometryTest, GeometryResultHasReasonableDefaults) { GeometryResult result; EXPECT_EQ(result.type, PrimitiveType::kTriangleStrip); diff --git a/impeller/entity/geometry/line_geometry.cc b/impeller/entity/geometry/line_geometry.cc index cb4c658fca7a2..ea5f087fb5ad7 100644 --- a/impeller/entity/geometry/line_geometry.cc +++ b/impeller/entity/geometry/line_geometry.cc @@ -108,6 +108,58 @@ GeometryResult LineGeometry::GetPositionBuffer(const ContentContext& renderer, }; } +// |Geometry| +GeometryResult LineGeometry::GetPositionUVBuffer(Rect texture_coverage, + Matrix effect_transform, + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const { + auto& host_buffer = renderer.GetTransientsBuffer(); + using VT = TextureFillVertexShader::PerVertexData; + + auto& transform = entity.GetTransform(); + auto radius = ComputePixelHalfWidth(transform, width_); + + auto uv_transform = + texture_coverage.GetNormalizingTransform() * effect_transform; + + if (cap_ == Cap::kRound) { + std::shared_ptr tessellator = renderer.GetTessellator(); + auto generator = tessellator->RoundCapLine(transform, p0_, p1_, radius); + return ComputePositionUVGeometry(renderer, generator, uv_transform, entity, + pass); + } + + Point corners[4]; + if (!ComputeCorners(corners, transform, cap_ == Cap::kSquare)) { + return kEmptyResult; + } + + size_t count = 4; + BufferView vertex_buffer = + host_buffer.Emplace(count * sizeof(VT), alignof(VT), + [&uv_transform, &corners](uint8_t* buffer) { + auto vertices = reinterpret_cast(buffer); + for (auto& corner : corners) { + *vertices++ = { + .position = corner, + .texture_coords = uv_transform * corner, + }; + } + }); + + return GeometryResult{ + .type = PrimitiveType::kTriangleStrip, + .vertex_buffer = + { + .vertex_buffer = vertex_buffer, + .vertex_count = count, + .index_type = IndexType::kNone, + }, + .transform = entity.GetShaderTransform(pass), + }; +} + GeometryVertexType LineGeometry::GetVertexType() const { return GeometryVertexType::kPosition; } diff --git a/impeller/entity/geometry/line_geometry.h b/impeller/entity/geometry/line_geometry.h index 072dc7056b70b..58032074ed5d9 100644 --- a/impeller/entity/geometry/line_geometry.h +++ b/impeller/entity/geometry/line_geometry.h @@ -57,6 +57,13 @@ class LineGeometry final : public Geometry { // |Geometry| std::optional GetCoverage(const Matrix& transform) const override; + // |Geometry| + GeometryResult GetPositionUVBuffer(Rect texture_coverage, + Matrix effect_transform, + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const override; + Point p0_; Point p1_; Scalar width_; diff --git a/impeller/entity/geometry/point_field_geometry.cc b/impeller/entity/geometry/point_field_geometry.cc index dd169b1823fd6..3aabb90176569 100644 --- a/impeller/entity/geometry/point_field_geometry.cc +++ b/impeller/entity/geometry/point_field_geometry.cc @@ -34,6 +34,33 @@ GeometryResult PointFieldGeometry::GetPositionBuffer( }; } +GeometryResult PointFieldGeometry::GetPositionUVBuffer( + Rect texture_coverage, + Matrix effect_transform, + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const { + if (renderer.GetDeviceCapabilities().SupportsCompute()) { + return GetPositionBufferGPU(renderer, entity, pass, texture_coverage, + effect_transform); + } + + 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.GetSize(), effect_transform); + + auto& host_buffer = renderer.GetTransientsBuffer(); + return { + .type = PrimitiveType::kTriangleStrip, + .vertex_buffer = uv_vtx_builder.CreateVertexBuffer(host_buffer), + .transform = entity.GetShaderTransform(pass), + }; +} + std::optional> PointFieldGeometry::GetPositionBufferCPU(const ContentContext& renderer, const Entity& entity, @@ -100,7 +127,9 @@ PointFieldGeometry::GetPositionBufferCPU(const ContentContext& renderer, GeometryResult PointFieldGeometry::GetPositionBufferGPU( const ContentContext& renderer, const Entity& entity, - RenderPass& pass) const { + RenderPass& pass, + std::optional texture_coverage, + std::optional effect_transform) const { FML_DCHECK(renderer.GetDeviceCapabilities().SupportsCompute()); if (radius_ < 0.0) { return {}; @@ -157,6 +186,33 @@ GeometryResult PointFieldGeometry::GetPositionBufferGPU( output = geometry_buffer; } + if (texture_coverage.has_value() && effect_transform.has_value()) { + BufferView geometry_uv_buffer = host_buffer.Emplace( + nullptr, total * sizeof(Vector4), + std::max(DefaultUniformAlignment(), alignof(Vector4))); + + using UV = UvComputeShader; + + compute_pass->AddBufferMemoryBarrier(); + compute_pass->SetCommandLabel("UV Geometry"); + compute_pass->SetPipeline(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().GetSize()); + + UV::BindFrameInfo(*compute_pass, host_buffer.EmplaceUniform(frame_info)); + UV::BindGeometryData(*compute_pass, geometry_buffer); + UV::BindGeometryUVData(*compute_pass, geometry_uv_buffer); + + if (!compute_pass->Compute(ISize(total, 1)).ok()) { + return {}; + } + output = geometry_uv_buffer; + } + if (!compute_pass->EncodeCommands()) { return {}; } diff --git a/impeller/entity/geometry/point_field_geometry.h b/impeller/entity/geometry/point_field_geometry.h index 1c5fa1fc0212c..9d43f07cb9fbe 100644 --- a/impeller/entity/geometry/point_field_geometry.h +++ b/impeller/entity/geometry/point_field_geometry.h @@ -23,15 +23,25 @@ class PointFieldGeometry final : public Geometry { const Entity& entity, RenderPass& pass) const override; + // |Geometry| + GeometryResult GetPositionUVBuffer(Rect texture_coverage, + Matrix effect_transform, + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const override; + // |Geometry| GeometryVertexType GetVertexType() const override; // |Geometry| std::optional GetCoverage(const Matrix& transform) const override; - GeometryResult GetPositionBufferGPU(const ContentContext& renderer, - const Entity& entity, - RenderPass& pass) const; + GeometryResult GetPositionBufferGPU( + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass, + std::optional texture_coverage = std::nullopt, + std::optional effect_transform = std::nullopt) const; std::optional> GetPositionBufferCPU(const ContentContext& renderer, diff --git a/impeller/entity/geometry/rect_geometry.cc b/impeller/entity/geometry/rect_geometry.cc index f4b1dff2fde04..6d72efb2f235a 100644 --- a/impeller/entity/geometry/rect_geometry.cc +++ b/impeller/entity/geometry/rect_geometry.cc @@ -25,6 +25,16 @@ GeometryResult RectGeometry::GetPositionBuffer(const ContentContext& renderer, }; } +// |Geometry| +GeometryResult RectGeometry::GetPositionUVBuffer(Rect texture_coverage, + Matrix effect_transform, + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const { + return ComputeUVGeometryForRect(rect_, texture_coverage, effect_transform, + renderer, entity, pass); +} + GeometryVertexType RectGeometry::GetVertexType() const { return GeometryVertexType::kPosition; } diff --git a/impeller/entity/geometry/rect_geometry.h b/impeller/entity/geometry/rect_geometry.h index 57ff2fd0152a6..bed8c23d6c419 100644 --- a/impeller/entity/geometry/rect_geometry.h +++ b/impeller/entity/geometry/rect_geometry.h @@ -32,6 +32,13 @@ class RectGeometry final : public Geometry { // |Geometry| std::optional GetCoverage(const Matrix& transform) const override; + // |Geometry| + GeometryResult GetPositionUVBuffer(Rect texture_coverage, + Matrix effect_transform, + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const override; + private: Rect rect_; diff --git a/impeller/entity/geometry/round_rect_geometry.cc b/impeller/entity/geometry/round_rect_geometry.cc index ae95d728d8b90..edcf12074afe6 100644 --- a/impeller/entity/geometry/round_rect_geometry.cc +++ b/impeller/entity/geometry/round_rect_geometry.cc @@ -19,6 +19,21 @@ GeometryResult RoundRectGeometry::GetPositionBuffer( entity, pass); } +// |Geometry| +GeometryResult RoundRectGeometry::GetPositionUVBuffer( + Rect texture_coverage, + Matrix effect_transform, + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const { + return ComputePositionUVGeometry( + renderer, + renderer.GetTessellator()->FilledRoundRect(entity.GetTransform(), bounds_, + radii_), + texture_coverage.GetNormalizingTransform() * effect_transform, entity, + pass); +} + GeometryVertexType RoundRectGeometry::GetVertexType() const { return GeometryVertexType::kPosition; } diff --git a/impeller/entity/geometry/round_rect_geometry.h b/impeller/entity/geometry/round_rect_geometry.h index ee55bdc258ea0..78f0523a20f1f 100644 --- a/impeller/entity/geometry/round_rect_geometry.h +++ b/impeller/entity/geometry/round_rect_geometry.h @@ -37,6 +37,13 @@ class RoundRectGeometry final : public Geometry { // |Geometry| std::optional GetCoverage(const Matrix& transform) const override; + // |Geometry| + GeometryResult GetPositionUVBuffer(Rect texture_coverage, + Matrix effect_transform, + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const override; + const Rect bounds_; const Size radii_; diff --git a/impeller/entity/geometry/stroke_path_geometry.cc b/impeller/entity/geometry/stroke_path_geometry.cc index f6eb60fe2b100..49f8ee92d0a07 100644 --- a/impeller/entity/geometry/stroke_path_geometry.cc +++ b/impeller/entity/geometry/stroke_path_geometry.cc @@ -7,6 +7,7 @@ #include "impeller/core/buffer_view.h" #include "impeller/core/formats.h" #include "impeller/entity/geometry/geometry.h" +#include "impeller/entity/texture_fill.vert.h" #include "impeller/geometry/path_builder.h" #include "impeller/geometry/path_component.h" @@ -44,6 +45,49 @@ class PositionWriter { std::vector data_ = {}; }; +class PositionUVWriter { + public: + PositionUVWriter(const Point& texture_origin, + const Size& texture_size, + const Matrix& effect_transform) + : texture_origin_(texture_origin), + texture_size_(texture_size), + effect_transform_(effect_transform) {} + + const std::vector& GetData() { + if (effect_transform_.IsIdentity()) { + auto origin = texture_origin_; + auto scale = 1.0 / texture_size_; + + for (auto& pvd : data_) { + pvd.texture_coords = (pvd.position - origin) * scale; + } + } else { + auto texture_rect = Rect::MakeOriginSize(texture_origin_, texture_size_); + Matrix uv_transform = + texture_rect.GetNormalizingTransform() * effect_transform_; + + for (auto& pvd : data_) { + pvd.texture_coords = uv_transform * pvd.position; + } + } + return data_; + } + + void AppendVertex(const Point& point) { + data_.emplace_back(TextureFillVertexShader::PerVertexData{ + .position = point, + // .texture_coords = default, will be filled in during |GetData()| + }); + } + + private: + std::vector data_ = {}; + const Point texture_origin_; + const Size texture_size_; + const Matrix effect_transform_; +}; + template class StrokeGenerator { public: @@ -481,6 +525,27 @@ StrokePathGeometry::GenerateSolidStrokeVertices(const Path::Polyline& polyline, return vtx_builder.GetData(); } +std::vector +StrokePathGeometry::GenerateSolidStrokeVerticesUV( + const Path::Polyline& polyline, + Scalar stroke_width, + Scalar miter_limit, + Join stroke_join, + Cap stroke_cap, + Scalar scale, + Point texture_origin, + Size texture_size, + const Matrix& effect_transform) { + auto scaled_miter_limit = stroke_width * miter_limit * 0.5f; + auto join_proc = GetJoinProc(stroke_join); + auto cap_proc = GetCapProc(stroke_cap); + StrokeGenerator stroke_generator(polyline, stroke_width, scaled_miter_limit, + join_proc, cap_proc, scale); + PositionUVWriter vtx_builder(texture_origin, texture_size, effect_transform); + stroke_generator.Generate(vtx_builder); + return vtx_builder.GetData(); +} + StrokePathGeometry::StrokePathGeometry(const Path& path, Scalar stroke_width, Scalar miter_limit, @@ -554,6 +619,52 @@ GeometryResult StrokePathGeometry::GetPositionBuffer( }; } +GeometryResult StrokePathGeometry::GetPositionUVBuffer( + Rect texture_coverage, + Matrix effect_transform, + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const { + if (stroke_width_ < 0.0) { + return {}; + } + auto determinant = entity.GetTransform().GetDeterminant(); + if (determinant == 0) { + return {}; + } + + Scalar min_size = 1.0f / sqrt(std::abs(determinant)); + Scalar stroke_width = std::max(stroke_width_, min_size); + + auto& host_buffer = renderer.GetTransientsBuffer(); + auto scale = entity.GetTransform().GetMaxBasisLength(); + auto polyline = renderer.GetTessellator()->CreateTempPolyline(path_, scale); + + PositionUVWriter writer(Point{0, 0}, texture_coverage.GetSize(), + effect_transform); + CreateSolidStrokeVertices(writer, polyline, stroke_width, + miter_limit_ * stroke_width_ * 0.5f, + GetJoinProc(stroke_join_), + GetCapProc(stroke_cap_), scale); + + BufferView buffer_view = host_buffer.Emplace( + writer.GetData().data(), + writer.GetData().size() * sizeof(TextureFillVertexShader::PerVertexData), + alignof(TextureFillVertexShader::PerVertexData)); + + return GeometryResult{ + .type = PrimitiveType::kTriangleStrip, + .vertex_buffer = + { + .vertex_buffer = buffer_view, + .vertex_count = writer.GetData().size(), + .index_type = IndexType::kNone, + }, + .transform = entity.GetShaderTransform(pass), + .mode = GeometryResult::Mode::kPreventOverdraw, + }; +} + GeometryResult::Mode StrokePathGeometry::GetResultMode() const { return GeometryResult::Mode::kPreventOverdraw; } diff --git a/impeller/entity/geometry/stroke_path_geometry.h b/impeller/entity/geometry/stroke_path_geometry.h index 1772e8ada685d..254bcc0a03e0e 100644 --- a/impeller/entity/geometry/stroke_path_geometry.h +++ b/impeller/entity/geometry/stroke_path_geometry.h @@ -34,6 +34,13 @@ class StrokePathGeometry final : public Geometry { const Entity& entity, RenderPass& pass) const override; + // |Geometry| + GeometryResult GetPositionUVBuffer(Rect texture_coverage, + Matrix effect_transform, + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const override; + // |Geometry| GeometryResult::Mode GetResultMode() const override; @@ -52,6 +59,17 @@ class StrokePathGeometry final : public Geometry { Cap stroke_cap, Scalar scale); + static std::vector + GenerateSolidStrokeVerticesUV(const Path::Polyline& polyline, + Scalar stroke_width, + Scalar miter_limit, + Join stroke_join, + Cap stroke_cap, + Scalar scale, + Point texture_origin, + Size texture_size, + const Matrix& effect_transform); + friend class ImpellerBenchmarkAccessor; friend class ImpellerEntityUnitTestAccessor; diff --git a/impeller/entity/geometry/vertices_geometry.cc b/impeller/entity/geometry/vertices_geometry.cc index 5e6d14a6d44cd..a352a212c59d5 100644 --- a/impeller/entity/geometry/vertices_geometry.cc +++ b/impeller/entity/geometry/vertices_geometry.cc @@ -187,6 +187,63 @@ GeometryResult VerticesGeometry::GetPositionColorBuffer( }; } +GeometryResult VerticesGeometry::GetPositionUVBuffer( + Rect texture_coverage, + Matrix effect_transform, + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const { + using VS = TexturePipeline::VertexShader; + + auto index_count = indices_.size(); + auto vertex_count = vertices_.size(); + auto uv_transform = + texture_coverage.GetNormalizingTransform() * effect_transform; + auto has_texture_coordinates = HasTextureCoordinates(); + + size_t total_vtx_bytes = vertices_.size() * sizeof(VS::PerVertexData); + size_t total_idx_bytes = index_count * sizeof(uint16_t); + auto vertex_buffer = renderer.GetTransientsBuffer().Emplace( + total_vtx_bytes, alignof(VS::PerVertexData), [&](uint8_t* data) { + VS::PerVertexData* vtx_contents = + reinterpret_cast(data); + for (auto i = 0u; i < vertices_.size(); i++) { + auto vertex = vertices_[i]; + auto texture_coord = + has_texture_coordinates ? texture_coordinates_[i] : vertices_[i]; + auto uv = uv_transform * texture_coord; + // From experimentation we need to clamp these values to < 1.0 or else + // there can be flickering. + VS::PerVertexData vertex_data = { + .position = vertex, + .texture_coords = + Point(std::clamp(uv.x, 0.0f, 1.0f - kEhCloseEnough), + std::clamp(uv.y, 0.0f, 1.0f - kEhCloseEnough)), + }; + std::memcpy(vtx_contents++, &vertex_data, sizeof(VS::PerVertexData)); + } + }); + + BufferView index_buffer = {}; + if (index_count > 0) { + index_buffer = renderer.GetTransientsBuffer().Emplace( + indices_.data(), total_idx_bytes, alignof(uint16_t)); + } + + return GeometryResult{ + .type = GetPrimitiveType(), + .vertex_buffer = + { + .vertex_buffer = vertex_buffer, + .index_buffer = index_buffer, + .vertex_count = index_count > 0 ? index_count : vertex_count, + .index_type = + index_count > 0 ? IndexType::k16bit : IndexType::kNone, + }, + .transform = entity.GetShaderTransform(pass), + }; +} + GeometryResult VerticesGeometry::GetPositionUVColorBuffer( Rect texture_coverage, Matrix effect_transform, diff --git a/impeller/entity/geometry/vertices_geometry.h b/impeller/entity/geometry/vertices_geometry.h index b3a11223455e2..c28b4288a1b8b 100644 --- a/impeller/entity/geometry/vertices_geometry.h +++ b/impeller/entity/geometry/vertices_geometry.h @@ -37,6 +37,13 @@ class VerticesGeometry final : public Geometry { const Entity& entity, RenderPass& pass) const; + // |Geometry| + GeometryResult GetPositionUVBuffer(Rect texture_coverage, + Matrix effect_transform, + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) const override; + // |Geometry| GeometryResult GetPositionBuffer(const ContentContext& renderer, const Entity& entity, 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/entity/shaders/texture_uv_fill.vert b/impeller/entity/shaders/texture_uv_fill.vert deleted file mode 100644 index 11e91d833c7bf..0000000000000 --- a/impeller/entity/shaders/texture_uv_fill.vert +++ /dev/null @@ -1,26 +0,0 @@ -// 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 - -// A shader that computes texture UVs from a normalizing transform. -uniform FrameInfo { - mat4 mvp; - // A normlizing transform created from the texture bounds and effect transform - mat4 uv_transform; - float texture_sampler_y_coord_scale; -} -frame_info; - -in vec2 position; - -out vec2 v_texture_coords; - -void main() { - gl_Position = frame_info.mvp * vec4(position, 0.0, 1.0); - vec2 texture_coords = (frame_info.uv_transform * vec4(position, 0.0, 1.0)).xy; - v_texture_coords = - IPRemapCoords(texture_coords, frame_info.texture_sampler_y_coord_scale); -} diff --git a/impeller/entity/shaders/tiled_texture_fill.frag b/impeller/entity/shaders/tiled_texture_fill.frag index 25b2af3526ecf..747bb646dac3d 100644 --- a/impeller/entity/shaders/tiled_texture_fill.frag +++ b/impeller/entity/shaders/tiled_texture_fill.frag @@ -7,8 +7,6 @@ precision mediump float; #include #include -layout(constant_id = 0) const float supports_decal = 1.0; - uniform f16sampler2D texture_sampler; uniform FragInfo { @@ -23,18 +21,11 @@ in highp vec2 v_texture_coords; out f16vec4 frag_color; void main() { - if (supports_decal == 1.0) { - frag_color = texture(texture_sampler, // sampler - v_texture_coords, // texture coordinates - float16_t(kDefaultMipBias)) * - float16_t(frag_info.alpha); - } else { - frag_color = IPHalfSampleWithTileMode( - texture_sampler, // sampler - v_texture_coords, // texture coordinates - float16_t(frag_info.x_tile_mode), // x tile mode - float16_t(frag_info.y_tile_mode) // y tile mode - ) * - float16_t(frag_info.alpha); - } + frag_color = + IPHalfSampleWithTileMode(texture_sampler, // sampler + v_texture_coords, // texture coordinates + float16_t(frag_info.x_tile_mode), // x tile mode + float16_t(frag_info.y_tile_mode) // y tile mode + ) * + float16_t(frag_info.alpha); } diff --git a/impeller/geometry/geometry_benchmarks.cc b/impeller/geometry/geometry_benchmarks.cc index e119a01b4296e..98d17cf7d8c15 100644 --- a/impeller/geometry/geometry_benchmarks.cc +++ b/impeller/geometry/geometry_benchmarks.cc @@ -5,6 +5,7 @@ #include "flutter/benchmarking/benchmarking.h" #include "flutter/impeller/entity/solid_fill.vert.h" +#include "flutter/impeller/entity/texture_fill.vert.h" #include "impeller/entity/geometry/stroke_path_geometry.h" #include "impeller/geometry/path.h" @@ -25,6 +26,21 @@ class ImpellerBenchmarkAccessor { return StrokePathGeometry::GenerateSolidStrokeVertices( polyline, stroke_width, miter_limit, stroke_join, stroke_cap, scale); } + + static std::vector + GenerateSolidStrokeVerticesUV(const Path::Polyline& polyline, + Scalar stroke_width, + Scalar miter_limit, + Join stroke_join, + Cap stroke_cap, + Scalar scale, + Point texture_origin, + Size texture_size, + const Matrix& effect_transform) { + return StrokePathGeometry::GenerateSolidStrokeVerticesUV( + polyline, stroke_width, miter_limit, stroke_join, stroke_cap, scale, + texture_origin, texture_size, effect_transform); + } }; namespace { @@ -43,37 +59,66 @@ template static void BM_Polyline(benchmark::State& state, Args&&... args) { auto args_tuple = std::make_tuple(std::move(args)...); auto path = std::get(args_tuple); + bool tessellate = std::get(args_tuple); size_t point_count = 0u; size_t single_point_count = 0u; auto points = std::make_unique>(); points->reserve(2048); while (state.KeepRunning()) { - auto polyline = path.CreatePolyline( - // Clang-tidy doesn't know that the points get moved back before - // getting moved again in this loop. - // NOLINTNEXTLINE(clang-analyzer-cplusplus.Move) - 1.0f, std::move(points), - [&points](Path::Polyline::PointBufferPtr reclaimed) { - points = std::move(reclaimed); - }); - single_point_count = polyline.points->size(); - point_count += single_point_count; + if (tessellate) { + tess.Tessellate(path, 1.0f, + [&point_count, &single_point_count]( + const float* vertices, size_t vertices_count, + const uint16_t* indices, size_t indices_count) { + if (indices_count > 0) { + single_point_count = indices_count; + point_count += indices_count; + } else { + single_point_count = vertices_count; + point_count += vertices_count; + } + return true; + }); + } else { + auto polyline = path.CreatePolyline( + // Clang-tidy doesn't know that the points get moved back before + // getting moved again in this loop. + // NOLINTNEXTLINE(clang-analyzer-cplusplus.Move) + 1.0f, std::move(points), + [&points](Path::Polyline::PointBufferPtr reclaimed) { + points = std::move(reclaimed); + }); + single_point_count = polyline.points->size(); + point_count += single_point_count; + } } state.counters["SinglePointCount"] = single_point_count; state.counters["TotalPointCount"] = point_count; } +enum class UVMode { + kNoUV, + kUVRect, + kUVRectTx, +}; + template static void BM_StrokePolyline(benchmark::State& state, Args&&... args) { auto args_tuple = std::make_tuple(std::move(args)...); auto path = std::get(args_tuple); auto cap = std::get(args_tuple); auto join = std::get(args_tuple); + auto generate_uv = std::get(args_tuple); const Scalar stroke_width = 5.0f; const Scalar miter_limit = 10.0f; const Scalar scale = 1.0f; + const Point texture_origin = Point(0, 0); + const Size texture_size = Size(100, 100); + const Matrix effect_transform = (generate_uv == UVMode::kUVRectTx) + ? Matrix::MakeScale({2.0f, 2.0f, 1.0f}) + : Matrix(); auto points = std::make_unique>(); points->reserve(2048); @@ -86,9 +131,16 @@ static void BM_StrokePolyline(benchmark::State& state, Args&&... args) { size_t point_count = 0u; size_t single_point_count = 0u; while (state.KeepRunning()) { - auto vertices = ImpellerBenchmarkAccessor::GenerateSolidStrokeVertices( - polyline, stroke_width, miter_limit, join, cap, scale); - single_point_count = vertices.size(); + if (generate_uv == UVMode::kNoUV) { + auto vertices = ImpellerBenchmarkAccessor::GenerateSolidStrokeVertices( + polyline, stroke_width, miter_limit, join, cap, scale); + single_point_count = vertices.size(); + } else { + auto vertices = ImpellerBenchmarkAccessor::GenerateSolidStrokeVerticesUV( + polyline, stroke_width, miter_limit, join, cap, scale, // + texture_origin, texture_size, effect_transform); + single_point_count = vertices.size(); + } point_count += single_point_count; } state.counters["SinglePointCount"] = single_point_count; @@ -103,13 +155,11 @@ static void BM_Convex(benchmark::State& state, Args&&... args) { size_t point_count = 0u; size_t single_point_count = 0u; auto points = std::make_unique>(); - auto indices = std::make_unique>(); points->reserve(2048); - indices->reserve(2048); while (state.KeepRunning()) { - tess.TessellateConvexInternal(path, *points, *indices, 1.0f); - single_point_count = indices->size(); - point_count += indices->size(); + auto points = tess.TessellateConvex(path, 1.0f); + single_point_count = points.size(); + point_count += points.size(); } state.counters["SinglePointCount"] = single_point_count; state.counters["TotalPointCount"] = point_count; @@ -117,7 +167,7 @@ static void BM_Convex(benchmark::State& state, Args&&... args) { #define MAKE_STROKE_BENCHMARK_CAPTURE(path, cap, join, closed, uvname, uvtype) \ BENCHMARK_CAPTURE(BM_StrokePolyline, stroke_##path##_##cap##_##join##uvname, \ - Create##path(closed), Cap::k##cap, Join::k##join) + Create##path(closed), Cap::k##cap, Join::k##join, uvtype) #define MAKE_STROKE_BENCHMARK_CAPTURE_CAPS_JOINS(path, uvname, uvtype) \ MAKE_STROKE_BENCHMARK_CAPTURE(path, Butt, Bevel, false, uvname, uvtype); \ @@ -126,20 +176,39 @@ static void BM_Convex(benchmark::State& state, Args&&... args) { MAKE_STROKE_BENCHMARK_CAPTURE(path, Square, Bevel, false, uvname, uvtype); \ MAKE_STROKE_BENCHMARK_CAPTURE(path, Round, Bevel, false, uvname, uvtype) +#define MAKE_STROKE_BENCHMARK_CAPTURE_UVS(path) \ + MAKE_STROKE_BENCHMARK_CAPTURE_CAPS_JOINS(path, , UVMode::kNoUV); \ + MAKE_STROKE_BENCHMARK_CAPTURE_CAPS_JOINS(path, _uv, UVMode::kUVRectTx); \ + MAKE_STROKE_BENCHMARK_CAPTURE_CAPS_JOINS(path, _uvNoTx, UVMode::kUVRect) + BENCHMARK_CAPTURE(BM_Polyline, cubic_polyline, CreateCubic(true), false); +BENCHMARK_CAPTURE(BM_Polyline, cubic_polyline_tess, CreateCubic(true), true); BENCHMARK_CAPTURE(BM_Polyline, unclosed_cubic_polyline, CreateCubic(false), false); +BENCHMARK_CAPTURE(BM_Polyline, + unclosed_cubic_polyline_tess, + CreateCubic(false), + true); +MAKE_STROKE_BENCHMARK_CAPTURE_UVS(Cubic); BENCHMARK_CAPTURE(BM_Polyline, quad_polyline, CreateQuadratic(true), false); +BENCHMARK_CAPTURE(BM_Polyline, quad_polyline_tess, CreateQuadratic(true), true); BENCHMARK_CAPTURE(BM_Polyline, unclosed_quad_polyline, CreateQuadratic(false), false); +BENCHMARK_CAPTURE(BM_Polyline, + unclosed_quad_polyline_tess, + CreateQuadratic(false), + true); +MAKE_STROKE_BENCHMARK_CAPTURE_UVS(Quadratic); BENCHMARK_CAPTURE(BM_Convex, rrect_convex, CreateRRect(), true); -MAKE_STROKE_BENCHMARK_CAPTURE(RRect, Butt, Bevel, , , ); +MAKE_STROKE_BENCHMARK_CAPTURE(RRect, Butt, Bevel, , , UVMode::kNoUV); +MAKE_STROKE_BENCHMARK_CAPTURE(RRect, Butt, Bevel, , _uv, UVMode::kUVRectTx); +MAKE_STROKE_BENCHMARK_CAPTURE(RRect, Butt, Bevel, , _uvNoTx, UVMode::kUVRect); namespace { diff --git a/impeller/geometry/path.cc b/impeller/geometry/path.cc index ab5ed9be7f65a..2eb682f5ff722 100644 --- a/impeller/geometry/path.cc +++ b/impeller/geometry/path.cc @@ -349,54 +349,4 @@ std::optional Path::GetTransformedBoundingBox( return bounds->TransformBounds(transform); } -void Path::WritePolyline(Scalar scale, VertexWriter& writer) const { - auto& path_components = data_->components; - auto& path_points = data_->points; - - for (size_t component_i = 0; component_i < path_components.size(); - component_i++) { - const auto& path_component = path_components[component_i]; - switch (path_component.type) { - case ComponentType::kLinear: { - const LinearPathComponent* linear = - reinterpret_cast( - &path_points[path_component.index]); - writer.Write(linear->p2); - break; - } - case ComponentType::kQuadratic: { - const QuadraticPathComponent* quad = - reinterpret_cast( - &path_points[path_component.index]); - quad->ToLinearPathComponents(scale, writer); - break; - } - case ComponentType::kCubic: { - const CubicPathComponent* cubic = - reinterpret_cast( - &path_points[path_component.index]); - cubic->ToLinearPathComponents(scale, writer); - break; - } - case ComponentType::kContour: - if (component_i == path_components.size() - 1) { - // If the last component is a contour, that means it's an empty - // contour, so skip it. - continue; - } - writer.EndContour(); - - // Insert contour start. - const auto& next_component = path_components[component_i + 1]; - // It doesn't matter what type this is as all structs are laid out - // with p1 as the first member. - const LinearPathComponent* linear = - reinterpret_cast( - &path_points[next_component.index]); - writer.Write(linear->p1); - break; - } - } -} - } // namespace impeller diff --git a/impeller/geometry/path.h b/impeller/geometry/path.h index 3ba9ff98d1cd8..ba56a4101c1bd 100644 --- a/impeller/geometry/path.h +++ b/impeller/geometry/path.h @@ -11,7 +11,6 @@ #include #include "impeller/geometry/path_component.h" -#include "impeller/geometry/rect.h" namespace impeller { @@ -169,13 +168,6 @@ class Path { std::make_unique>(), Polyline::ReclaimPointBufferCallback reclaim = nullptr) const; - /// Generate a polyline into the temporary storage held by the [writer]. - /// - /// It is suitable to use the max basis length of the matrix used to transform - /// the path. If the provided scale is 0, curves will revert to straight - /// lines. - void WritePolyline(Scalar scale, VertexWriter& writer) const; - std::optional GetBoundingBox() const; std::optional GetTransformedBoundingBox(const Matrix& transform) const; diff --git a/impeller/geometry/path_component.cc b/impeller/geometry/path_component.cc index 11f7da4ed736e..3a00414dfa804 100644 --- a/impeller/geometry/path_component.cc +++ b/impeller/geometry/path_component.cc @@ -10,63 +10,6 @@ namespace impeller { -VertexWriter::VertexWriter(std::vector& points, - std::vector& indices) - : points_(points), indices_(indices) {} - -void VertexWriter::EndContour() { - if (points_.size() == 0u || contour_start_ == points_.size() - 1) { - // Empty or first contour. - return; - } - - auto start = contour_start_; - auto end = points_.size() - 1; - // All filled paths are drawn as if they are closed, but if - // there is an explicit close then a lineTo to the origin - // is inserted. This point isn't strictly necesary to - // correctly render the shape and can be dropped. - if (points_[end] == points_[start]) { - end--; - } - - if (contour_start_ > 0) { - // Triangle strip break. - indices_.emplace_back(indices_.back()); - indices_.emplace_back(start); - indices_.emplace_back(start); - - // If the contour has an odd number of points, insert an extra point when - // bridging to the next contour to preserve the correct triangle winding - // order. - if (previous_contour_odd_points_) { - indices_.emplace_back(start); - } - } else { - indices_.emplace_back(start); - } - - size_t a = start + 1; - size_t b = end; - while (a < b) { - indices_.emplace_back(a); - indices_.emplace_back(b); - a++; - b--; - } - if (a == b) { - indices_.emplace_back(a); - previous_contour_odd_points_ = false; - } else { - previous_contour_odd_points_ = true; - } - contour_start_ = points_.size(); -} - -void VertexWriter::Write(Point point) { - points_.emplace_back(point); -} - /* * Based on: https://en.wikipedia.org/wiki/B%C3%A9zier_curve#Specific_cases */ @@ -176,16 +119,6 @@ void QuadraticPathComponent::ToLinearPathComponents( proc(p2); } -void QuadraticPathComponent::ToLinearPathComponents( - Scalar scale, - VertexWriter& writer) const { - Scalar line_count = std::ceilf(ComputeQuadradicSubdivisions(scale, *this)); - for (size_t i = 1; i < line_count; i += 1) { - writer.Write(Solve(i / line_count)); - } - writer.Write(p2); -} - std::vector QuadraticPathComponent::Extrema() const { CubicPathComponent elevated(*this); return elevated.Extrema(); @@ -256,15 +189,6 @@ void CubicPathComponent::ToLinearPathComponents(Scalar scale, proc(p2); } -void CubicPathComponent::ToLinearPathComponents(Scalar scale, - VertexWriter& writer) const { - Scalar line_count = std::ceilf(ComputeCubicSubdivisions(scale, *this)); - for (size_t i = 1; i < line_count; i++) { - writer.Write(Solve(i / line_count)); - } - writer.Write(p2); -} - static inline bool NearEqual(Scalar a, Scalar b, Scalar epsilon) { return (a > (b - epsilon)) && (a < (b + epsilon)); } diff --git a/impeller/geometry/path_component.h b/impeller/geometry/path_component.h index ff7f15ae121fa..c310052a893dc 100644 --- a/impeller/geometry/path_component.h +++ b/impeller/geometry/path_component.h @@ -10,32 +10,12 @@ #include #include -#include "impeller/geometry/matrix.h" #include "impeller/geometry/point.h" +#include "impeller/geometry/rect.h" #include "impeller/geometry/scalar.h" namespace impeller { -/// @brief An interface for generating a multi contour polyline as a triangle -/// strip. -class VertexWriter { - public: - explicit VertexWriter(std::vector& points, - std::vector& indices); - - ~VertexWriter() = default; - - void EndContour(); - - void Write(Point point); - - private: - bool previous_contour_odd_points_ = false; - size_t contour_start_ = 0u; - std::vector& points_; - std::vector& indices_; -}; - struct LinearPathComponent { Point p1; Point p2; @@ -84,8 +64,6 @@ struct QuadraticPathComponent { void ToLinearPathComponents(Scalar scale_factor, const PointProc& proc) const; - void ToLinearPathComponents(Scalar scale, VertexWriter& writer) const; - std::vector Extrema() const; bool operator==(const QuadraticPathComponent& other) const { @@ -131,8 +109,6 @@ struct CubicPathComponent { void ToLinearPathComponents(Scalar scale, const PointProc& proc) const; - void ToLinearPathComponents(Scalar scale, VertexWriter& writer) const; - CubicPathComponent Subsegment(Scalar t0, Scalar t1) const; bool operator==(const CubicPathComponent& other) const { diff --git a/impeller/renderer/renderer_unittests.cc b/impeller/renderer/renderer_unittests.cc index 5ff7bdd781b05..0024e42c9d861 100644 --- a/impeller/renderer/renderer_unittests.cc +++ b/impeller/renderer/renderer_unittests.cc @@ -38,6 +38,7 @@ #include "impeller/renderer/render_target.h" #include "impeller/renderer/renderer.h" #include "impeller/renderer/vertex_buffer_builder.h" +#include "impeller/tessellator/tessellator.h" #include "third_party/imgui/imgui.h" // TODO(zanderso): https://github.com/flutter/flutter/issues/127701 @@ -391,15 +392,25 @@ TEST_P(RendererTest, CanRenderInstanced) { using FS = InstancedDrawFragmentShader; VertexBufferBuilder builder; - builder.AddVertices({ - VS::PerVertexData{.vtx = Point{10, 10}}, - VS::PerVertexData{.vtx = Point{110, 10}}, - VS::PerVertexData{.vtx = Point{10, 110}}, - - VS::PerVertexData{.vtx = Point{110, 10}}, - VS::PerVertexData{.vtx = Point{10, 110}}, - VS::PerVertexData{.vtx = Point{110, 100}}, - }); + + ASSERT_EQ(Tessellator::Result::kSuccess, + Tessellator{}.Tessellate( + PathBuilder{} + .AddRect(Rect::MakeXYWH(10, 10, 100, 100)) + .TakePath(FillType::kOdd), + 1.0f, + [&builder](const float* vertices, size_t vertices_count, + const uint16_t* indices, size_t indices_count) { + for (auto i = 0u; i < vertices_count * 2; i += 2) { + VS::PerVertexData data; + data.vtx = {vertices[i], vertices[i + 1]}; + builder.AppendVertex(data); + } + for (auto i = 0u; i < indices_count; i++) { + builder.AppendIndex(indices[i]); + } + return true; + })); ASSERT_NE(GetContext(), nullptr); auto pipeline = diff --git a/impeller/tessellator/BUILD.gn b/impeller/tessellator/BUILD.gn index 9bd2f853c75c7..bc56fb770f069 100644 --- a/impeller/tessellator/BUILD.gn +++ b/impeller/tessellator/BUILD.gn @@ -15,6 +15,7 @@ impeller_component("tessellator") { deps = [ "../core", "//flutter/fml", + "//third_party/libtess2", ] } @@ -29,6 +30,8 @@ impeller_component("tessellator_shared") { sources = [ "c/tessellator.cc", "c/tessellator.h", + "tessellator.cc", + "tessellator.h", ] deps = [ diff --git a/impeller/tessellator/c/tessellator.cc b/impeller/tessellator/c/tessellator.cc index 0f2c9734fc3b6..f5300dcba1dee 100644 --- a/impeller/tessellator/c/tessellator.cc +++ b/impeller/tessellator/c/tessellator.cc @@ -4,205 +4,9 @@ #include "tessellator.h" -#include #include -#include "third_party/libtess2/Include/tesselator.h" - namespace impeller { - -void DestroyTessellator(TESStesselator* tessellator) { - if (tessellator != nullptr) { - ::tessDeleteTess(tessellator); - } -} - -using CTessellator = - std::unique_ptr; - -static int ToTessWindingRule(FillType fill_type) { - switch (fill_type) { - case FillType::kOdd: - return TESS_WINDING_ODD; - case FillType::kNonZero: - return TESS_WINDING_NONZERO; - } - return TESS_WINDING_ODD; -} - -static void* HeapAlloc(void* userData, unsigned int size) { - return malloc(size); -} - -static void* HeapRealloc(void* userData, void* ptr, unsigned int size) { - return realloc(ptr, size); -} - -static void HeapFree(void* userData, void* ptr) { - free(ptr); -} - -// Note: these units are "number of entities" for bucket size and not in KB. -static const TESSalloc kAlloc = { - HeapAlloc, HeapRealloc, HeapFree, 0, /* =userData */ - 16, /* =meshEdgeBucketSize */ - 16, /* =meshVertexBucketSize */ - 16, /* =meshFaceBucketSize */ - 16, /* =dictNodeBucketSize */ - 16, /* =regionBucketSize */ - 0 /* =extraVertices */ -}; - -class LibtessTessellator { - public: - LibtessTessellator() : c_tessellator_(nullptr, &DestroyTessellator) { - TESSalloc alloc = kAlloc; - { - // libTess2 copies the TESSalloc despite the non-const argument. - CTessellator tessellator(::tessNewTess(&alloc), &DestroyTessellator); - c_tessellator_ = std::move(tessellator); - } - } - - ~LibtessTessellator() {} - - enum class Result { - kSuccess, - kInputError, - kTessellationError, - }; - - /// @brief A callback that returns the results of the tessellation. - /// - /// The index buffer may not be populated, in which case [indices] will - /// be nullptr and indices_count will be 0. - using BuilderCallback = std::function; - - //---------------------------------------------------------------------------- - /// @brief Generates filled triangles from the path. A callback is - /// invoked once for the entire tessellation. - /// - /// @param[in] path The path to tessellate. - /// @param[in] tolerance The tolerance value for conversion of the path to - /// a polyline. This value is often derived from the - /// Matrix::GetMaxBasisLength of the CTM applied to the - /// path for rendering. - /// @param[in] callback The callback, return false to indicate failure. - /// - /// @return The result status of the tessellation. - /// - Result Tessellate(const Path& path, - Scalar tolerance, - const BuilderCallback& callback) { - if (!callback) { - return Result::kInputError; - } - - std::unique_ptr> buffer = - std::make_unique>(); - auto polyline = path.CreatePolyline(tolerance, std::move(buffer)); - - auto fill_type = path.GetFillType(); - - if (polyline.points->empty()) { - return Result::kInputError; - } - - auto tessellator = c_tessellator_.get(); - if (!tessellator) { - return Result::kTessellationError; - } - - constexpr int kVertexSize = 2; - constexpr int kPolygonSize = 3; - - //---------------------------------------------------------------------------- - /// Feed contour information to the tessellator. - /// - static_assert(sizeof(Point) == 2 * sizeof(float)); - for (size_t contour_i = 0; contour_i < polyline.contours.size(); - contour_i++) { - size_t start_point_index, end_point_index; - std::tie(start_point_index, end_point_index) = - polyline.GetContourPointBounds(contour_i); - - ::tessAddContour(tessellator, // the C tessellator - kVertexSize, // - polyline.points->data() + start_point_index, // - sizeof(Point), // - end_point_index - start_point_index // - ); - } - - //---------------------------------------------------------------------------- - /// Let's tessellate. - /// - auto result = ::tessTesselate(tessellator, // tessellator - ToTessWindingRule(fill_type), // winding - TESS_POLYGONS, // element type - kPolygonSize, // polygon size - kVertexSize, // vertex size - nullptr // normal (null is automatic) - ); - - if (result != 1) { - return Result::kTessellationError; - } - - int element_item_count = tessGetElementCount(tessellator) * kPolygonSize; - - // We default to using a 16bit index buffer, but in cases where we generate - // more tessellated data than this can contain we need to fall back to - // dropping the index buffer entirely. Instead code could instead switch to - // a uint32 index buffer, but this is done for simplicity with the other - // fast path above. - if (element_item_count < USHRT_MAX) { - int vertex_item_count = tessGetVertexCount(tessellator); - auto vertices = tessGetVertices(tessellator); - auto elements = tessGetElements(tessellator); - - // libtess uses an int index internally due to usage of -1 as a sentinel - // value. - std::vector indices(element_item_count); - for (int i = 0; i < element_item_count; i++) { - indices[i] = static_cast(elements[i]); - } - if (!callback(vertices, vertex_item_count, indices.data(), - element_item_count)) { - return Result::kInputError; - } - } else { - std::vector points; - std::vector data; - - int vertex_item_count = tessGetVertexCount(tessellator) * kVertexSize; - auto vertices = tessGetVertices(tessellator); - points.reserve(vertex_item_count); - for (int i = 0; i < vertex_item_count; i += 2) { - points.emplace_back(vertices[i], vertices[i + 1]); - } - - int element_item_count = tessGetElementCount(tessellator) * kPolygonSize; - auto elements = tessGetElements(tessellator); - data.reserve(element_item_count); - for (int i = 0; i < element_item_count; i++) { - data.emplace_back(points[elements[i]].x); - data.emplace_back(points[elements[i]].y); - } - if (!callback(data.data(), element_item_count, nullptr, 0u)) { - return Result::kInputError; - } - } - - return Result::kSuccess; - } - - CTessellator c_tessellator_; -}; - PathBuilder* CreatePathBuilder() { return new PathBuilder(); } @@ -238,7 +42,7 @@ struct Vertices* Tessellate(PathBuilder* builder, Scalar tolerance) { auto path = builder->CopyPath(static_cast(fill_type)); std::vector points; - if (LibtessTessellator{}.Tessellate( + if (Tessellator{}.Tessellate( path, tolerance, [&points](const float* vertices, size_t vertices_count, const uint16_t* indices, size_t indices_count) { @@ -253,7 +57,7 @@ struct Vertices* Tessellate(PathBuilder* builder, points.push_back(point.y); } return true; - }) != LibtessTessellator::Result::kSuccess) { + }) != Tessellator::Result::kSuccess) { return nullptr; } diff --git a/impeller/tessellator/c/tessellator.h b/impeller/tessellator/c/tessellator.h index c8e19b0e4f248..ef5bfc950f107 100644 --- a/impeller/tessellator/c/tessellator.h +++ b/impeller/tessellator/c/tessellator.h @@ -8,6 +8,7 @@ #include #include "impeller/geometry/path_builder.h" +#include "impeller/tessellator/tessellator.h" #ifdef _WIN32 #define IMPELLER_API __declspec(dllexport) diff --git a/impeller/tessellator/tessellator.cc b/impeller/tessellator/tessellator.cc index 4fba4a71f9d38..afbf01da90f83 100644 --- a/impeller/tessellator/tessellator.cc +++ b/impeller/tessellator/tessellator.cc @@ -4,21 +4,166 @@ #include "impeller/tessellator/tessellator.h" -#include "impeller/core/buffer_view.h" -#include "impeller/core/formats.h" -#include "impeller/core/vertex_buffer.h" +#include "third_party/libtess2/Include/tesselator.h" namespace impeller { +static void* HeapAlloc(void* userData, unsigned int size) { + return malloc(size); +} + +static void* HeapRealloc(void* userData, void* ptr, unsigned int size) { + return realloc(ptr, size); +} + +static void HeapFree(void* userData, void* ptr) { + free(ptr); +} + +// Note: these units are "number of entities" for bucket size and not in KB. +static const TESSalloc kAlloc = { + HeapAlloc, HeapRealloc, HeapFree, 0, /* =userData */ + 16, /* =meshEdgeBucketSize */ + 16, /* =meshVertexBucketSize */ + 16, /* =meshFaceBucketSize */ + 16, /* =dictNodeBucketSize */ + 16, /* =regionBucketSize */ + 0 /* =extraVertices */ +}; + Tessellator::Tessellator() : point_buffer_(std::make_unique>()), - index_buffer_(std::make_unique>()) { + c_tessellator_(nullptr, &DestroyTessellator) { point_buffer_->reserve(2048); - index_buffer_->reserve(2048); + TESSalloc alloc = kAlloc; + { + // libTess2 copies the TESSalloc despite the non-const argument. + CTessellator tessellator(::tessNewTess(&alloc), &DestroyTessellator); + c_tessellator_ = std::move(tessellator); + } } Tessellator::~Tessellator() = default; +static int ToTessWindingRule(FillType fill_type) { + switch (fill_type) { + case FillType::kOdd: + return TESS_WINDING_ODD; + case FillType::kNonZero: + return TESS_WINDING_NONZERO; + } + return TESS_WINDING_ODD; +} + +Tessellator::Result Tessellator::Tessellate(const Path& path, + Scalar tolerance, + const BuilderCallback& callback) { + if (!callback) { + return Result::kInputError; + } + + point_buffer_->clear(); + auto polyline = + path.CreatePolyline(tolerance, std::move(point_buffer_), + [this](Path::Polyline::PointBufferPtr point_buffer) { + point_buffer_ = std::move(point_buffer); + }); + + auto fill_type = path.GetFillType(); + + if (polyline.points->empty()) { + return Result::kInputError; + } + + auto tessellator = c_tessellator_.get(); + if (!tessellator) { + return Result::kTessellationError; + } + + constexpr int kVertexSize = 2; + constexpr int kPolygonSize = 3; + + //---------------------------------------------------------------------------- + /// Feed contour information to the tessellator. + /// + static_assert(sizeof(Point) == 2 * sizeof(float)); + for (size_t contour_i = 0; contour_i < polyline.contours.size(); + contour_i++) { + size_t start_point_index, end_point_index; + std::tie(start_point_index, end_point_index) = + polyline.GetContourPointBounds(contour_i); + + ::tessAddContour(tessellator, // the C tessellator + kVertexSize, // + polyline.points->data() + start_point_index, // + sizeof(Point), // + end_point_index - start_point_index // + ); + } + + //---------------------------------------------------------------------------- + /// Let's tessellate. + /// + auto result = ::tessTesselate(tessellator, // tessellator + ToTessWindingRule(fill_type), // winding + TESS_POLYGONS, // element type + kPolygonSize, // polygon size + kVertexSize, // vertex size + nullptr // normal (null is automatic) + ); + + if (result != 1) { + return Result::kTessellationError; + } + + int element_item_count = tessGetElementCount(tessellator) * kPolygonSize; + + // We default to using a 16bit index buffer, but in cases where we generate + // more tessellated data than this can contain we need to fall back to + // dropping the index buffer entirely. Instead code could instead switch to + // a uint32 index buffer, but this is done for simplicity with the other + // fast path above. + if (element_item_count < USHRT_MAX) { + int vertex_item_count = tessGetVertexCount(tessellator); + auto vertices = tessGetVertices(tessellator); + auto elements = tessGetElements(tessellator); + + // libtess uses an int index internally due to usage of -1 as a sentinel + // value. + std::vector indices(element_item_count); + for (int i = 0; i < element_item_count; i++) { + indices[i] = static_cast(elements[i]); + } + if (!callback(vertices, vertex_item_count, indices.data(), + element_item_count)) { + return Result::kInputError; + } + } else { + std::vector points; + std::vector data; + + int vertex_item_count = tessGetVertexCount(tessellator) * kVertexSize; + auto vertices = tessGetVertices(tessellator); + points.reserve(vertex_item_count); + for (int i = 0; i < vertex_item_count; i += 2) { + points.emplace_back(vertices[i], vertices[i + 1]); + } + + int element_item_count = tessGetElementCount(tessellator) * kPolygonSize; + auto elements = tessGetElements(tessellator); + data.reserve(element_item_count); + for (int i = 0; i < element_item_count; i++) { + data.emplace_back(points[elements[i]].x); + data.emplace_back(points[elements[i]].y); + } + if (!callback(data.data(), element_item_count, nullptr, 0u)) { + return Result::kInputError; + } + } + + return Result::kSuccess; +} + Path::Polyline Tessellator::CreateTempPolyline(const Path& path, Scalar tolerance) { FML_DCHECK(point_buffer_); @@ -31,49 +176,73 @@ Path::Polyline Tessellator::CreateTempPolyline(const Path& path, return polyline; } -VertexBuffer Tessellator::TessellateConvex(const Path& path, - HostBuffer& host_buffer, - Scalar tolerance) { +std::vector Tessellator::TessellateConvex(const Path& path, + Scalar tolerance) { FML_DCHECK(point_buffer_); - FML_DCHECK(index_buffer_); - TessellateConvexInternal(path, *point_buffer_, *index_buffer_, tolerance); - - if (point_buffer_->empty()) { - return VertexBuffer{ - .vertex_buffer = {}, - .index_buffer = {}, - .vertex_count = 0u, - .index_type = IndexType::k16bit, - }; - } - - BufferView vertex_buffer = host_buffer.Emplace( - point_buffer_->data(), sizeof(Point) * point_buffer_->size(), - alignof(Point)); - BufferView index_buffer = host_buffer.Emplace( - index_buffer_->data(), sizeof(uint16_t) * index_buffer_->size(), - alignof(uint16_t)); - - return VertexBuffer{ - .vertex_buffer = std::move(vertex_buffer), - .index_buffer = std::move(index_buffer), - .vertex_count = index_buffer_->size(), - .index_type = IndexType::k16bit, - }; -} - -void Tessellator::TessellateConvexInternal(const Path& path, - std::vector& point_buffer, - std::vector& index_buffer, - Scalar tolerance) { - index_buffer_->clear(); + std::vector output; point_buffer_->clear(); + auto polyline = + path.CreatePolyline(tolerance, std::move(point_buffer_), + [this](Path::Polyline::PointBufferPtr point_buffer) { + point_buffer_ = std::move(point_buffer); + }); + if (polyline.points->size() == 0) { + return output; + } - VertexWriter writer(point_buffer, index_buffer); + output.reserve(polyline.points->size() + + (4 * (polyline.contours.size() - 1))); + bool previous_contour_odd_points = false; + for (auto j = 0u; j < polyline.contours.size(); j++) { + auto [start, end] = polyline.GetContourPointBounds(j); + auto first_point = polyline.GetPoint(start); + + // Some polygons will not self close and an additional triangle + // must be inserted, others will self close and we need to avoid + // inserting an extra triangle. + if (polyline.GetPoint(end - 1) == first_point) { + end--; + } + + if (j > 0) { + // Triangle strip break. + output.emplace_back(output.back()); + output.emplace_back(first_point); + output.emplace_back(first_point); + + // If the contour has an odd number of points, insert an extra point when + // bridging to the next contour to preserve the correct triangle winding + // order. + if (previous_contour_odd_points) { + output.emplace_back(first_point); + } + } else { + output.emplace_back(first_point); + } + + size_t a = start + 1; + size_t b = end - 1; + while (a < b) { + output.emplace_back(polyline.GetPoint(a)); + output.emplace_back(polyline.GetPoint(b)); + a++; + b--; + } + if (a == b) { + previous_contour_odd_points = false; + output.emplace_back(polyline.GetPoint(a)); + } else { + previous_contour_odd_points = true; + } + } + return output; +} - path.WritePolyline(tolerance, writer); - writer.EndContour(); +void DestroyTessellator(TESStesselator* tessellator) { + if (tessellator != nullptr) { + ::tessDeleteTess(tessellator); + } } static constexpr int kPrecomputedDivisionCount = 1024; diff --git a/impeller/tessellator/tessellator.h b/impeller/tessellator/tessellator.h index 1b95336c9afc6..6251e814aa764 100644 --- a/impeller/tessellator/tessellator.h +++ b/impeller/tessellator/tessellator.h @@ -10,8 +10,6 @@ #include #include "impeller/core/formats.h" -#include "impeller/core/host_buffer.h" -#include "impeller/core/vertex_buffer.h" #include "impeller/geometry/path.h" #include "impeller/geometry/point.h" #include "impeller/geometry/trig.h" @@ -20,6 +18,11 @@ struct TESStesselator; namespace impeller { +void DestroyTessellator(TESStesselator* tessellator); + +using CTessellator = + std::unique_ptr; + //------------------------------------------------------------------------------ /// @brief A utility that generates triangles of the specified fill type /// given a polyline. This happens on the CPU. @@ -66,6 +69,12 @@ class Tessellator { }; public: + enum class Result { + kSuccess, + kInputError, + kTessellationError, + }; + /// @brief A callback function for a |VertexGenerator| to deliver /// the vertices it computes as |Point| objects. using TessellatedVertexProc = std::function; @@ -164,30 +173,44 @@ class Tessellator { ~Tessellator(); + /// @brief A callback that returns the results of the tessellation. + /// + /// The index buffer may not be populated, in which case [indices] will + /// be nullptr and indices_count will be 0. + using BuilderCallback = std::function; + //---------------------------------------------------------------------------- - /// @brief Given a convex path, create a triangle fan structure. + /// @brief Generates filled triangles from the path. A callback is + /// invoked once for the entire tessellation. /// /// @param[in] path The path to tessellate. /// @param[in] tolerance The tolerance value for conversion of the path to /// a polyline. This value is often derived from the /// Matrix::GetMaxBasisLength of the CTM applied to the /// path for rendering. - /// @param[in] host_buffer The host buffer for allocation of vertices/index - /// data. + /// @param[in] callback The callback, return false to indicate failure. /// - /// @return A vertex buffer containing all data from the provided curve. - VertexBuffer TessellateConvex(const Path& path, - HostBuffer& host_buffer, - Scalar tolerance); + /// @return The result status of the tessellation. + /// + Tessellator::Result Tessellate(const Path& path, + Scalar tolerance, + const BuilderCallback& callback); - /// Visible for testing. + //---------------------------------------------------------------------------- + /// @brief Given a convex path, create a triangle fan structure. + /// + /// @param[in] path The path to tessellate. + /// @param[in] tolerance The tolerance value for conversion of the path to + /// a polyline. This value is often derived from the + /// Matrix::GetMaxBasisLength of the CTM applied to the + /// path for rendering. + /// + /// @return A point vector containing the vertices in triangle strip format. /// - /// This method only exists for the ease of benchmarking without using the - /// real allocator needed by the [host_buffer]. - void TessellateConvexInternal(const Path& path, - std::vector& point_buffer, - std::vector& index_buffer, - Scalar tolerance); + std::vector TessellateConvex(const Path& path, Scalar tolerance); //---------------------------------------------------------------------------- /// @brief Create a temporary polyline. Only one per-process can exist at @@ -276,9 +299,9 @@ class Tessellator { private: /// Used for polyline generation. std::unique_ptr> point_buffer_; - std::unique_ptr> index_buffer_; + CTessellator c_tessellator_; - // Data for various Circle/EllipseGenerator classes, cached per + // Data for variouos Circle/EllipseGenerator classes, cached per // Tessellator instance which is usually the foreground life of an app // if not longer. static constexpr size_t kCachedTrigCount = 300; diff --git a/impeller/tessellator/tessellator_unittests.cc b/impeller/tessellator/tessellator_unittests.cc index 6a80b756bd8da..2c54d4282db01 100644 --- a/impeller/tessellator/tessellator_unittests.cc +++ b/impeller/tessellator/tessellator_unittests.cc @@ -13,61 +13,97 @@ namespace impeller { namespace testing { -TEST(TessellatorTest, TessellateConvex) { +TEST(TessellatorTest, TessellatorBuilderReturnsCorrectResultStatus) { + // Zero points. { Tessellator t; - std::vector points; - std::vector indices; - // Sanity check simple rectangle. - t.TessellateConvexInternal( - PathBuilder{}.AddRect(Rect::MakeLTRB(0, 0, 10, 10)).TakePath(), points, - indices, 1.0); - - // Note: the origin point is repeated but not referenced in the indices - // below - std::vector expected = {{0, 0}, {10, 0}, {10, 10}, {0, 10}, {0, 0}}; - std::vector expected_indices = {0, 1, 3, 2}; - EXPECT_EQ(points, expected); - EXPECT_EQ(indices, expected_indices); + auto path = PathBuilder{}.TakePath(FillType::kOdd); + Tessellator::Result result = t.Tessellate( + path, 1.0f, + [](const float* vertices, size_t vertices_count, + const uint16_t* indices, size_t indices_count) { return true; }); + + ASSERT_EQ(result, Tessellator::Result::kInputError); + } + + // One point. + { + Tessellator t; + auto path = PathBuilder{}.LineTo({0, 0}).TakePath(FillType::kOdd); + Tessellator::Result result = t.Tessellate( + path, 1.0f, + [](const float* vertices, size_t vertices_count, + const uint16_t* indices, size_t indices_count) { return true; }); + + ASSERT_EQ(result, Tessellator::Result::kSuccess); + } + + // Two points. + { + Tessellator t; + auto path = PathBuilder{}.AddLine({0, 0}, {0, 1}).TakePath(FillType::kOdd); + Tessellator::Result result = t.Tessellate( + path, 1.0f, + [](const float* vertices, size_t vertices_count, + const uint16_t* indices, size_t indices_count) { return true; }); + + ASSERT_EQ(result, Tessellator::Result::kSuccess); + } + + // Many points. + { + Tessellator t; + PathBuilder builder; + for (int i = 0; i < 1000; i++) { + auto coord = i * 1.0f; + builder.AddLine({coord, coord}, {coord + 1, coord + 1}); + } + auto path = builder.TakePath(FillType::kOdd); + Tessellator::Result result = t.Tessellate( + path, 1.0f, + [](const float* vertices, size_t vertices_count, + const uint16_t* indices, size_t indices_count) { return true; }); + + ASSERT_EQ(result, Tessellator::Result::kSuccess); } + // Closure fails. { Tessellator t; - std::vector points; - std::vector indices; - t.TessellateConvexInternal(PathBuilder{} - .AddRect(Rect::MakeLTRB(0, 0, 10, 10)) - .AddRect(Rect::MakeLTRB(20, 20, 30, 30)) - .TakePath(), - points, indices, 1.0); - - std::vector expected = {{0, 0}, {10, 0}, {10, 10}, {0, 10}, - {0, 0}, {20, 20}, {30, 20}, {30, 30}, - {20, 30}, {20, 20}}; - std::vector expected_indices = {0, 1, 3, 2, 2, 5, 5, 6, 8, 7}; - EXPECT_EQ(points, expected); - EXPECT_EQ(indices, expected_indices); + auto path = PathBuilder{}.AddLine({0, 0}, {0, 1}).TakePath(FillType::kOdd); + Tessellator::Result result = t.Tessellate( + path, 1.0f, + [](const float* vertices, size_t vertices_count, + const uint16_t* indices, size_t indices_count) { return false; }); + + ASSERT_EQ(result, Tessellator::Result::kInputError); } } -// Filled Paths without an explicit close should still be closed -TEST(TessellatorTest, TessellateConvexUnclosedPath) { - Tessellator t; - std::vector points; - std::vector indices; - - // Create a rectangle that lacks an explicit close. - Path path = PathBuilder{} - .LineTo({100, 0}) - .LineTo({100, 100}) - .LineTo({0, 100}) - .TakePath(); - t.TessellateConvexInternal(path, points, indices, 1.0); - - std::vector expected = {{0, 0}, {100, 0}, {100, 100}, {0, 100}}; - std::vector expected_indices = {0, 1, 3, 2}; - EXPECT_EQ(points, expected); - EXPECT_EQ(indices, expected_indices); +TEST(TessellatorTest, TessellateConvex) { + { + Tessellator t; + // Sanity check simple rectangle. + auto pts = t.TessellateConvex( + PathBuilder{}.AddRect(Rect::MakeLTRB(0, 0, 10, 10)).TakePath(), 1.0); + + std::vector expected = {{0, 0}, {10, 0}, {0, 10}, {10, 10}}; + EXPECT_EQ(pts, expected); + } + + { + Tessellator t; + auto pts = t.TessellateConvex(PathBuilder{} + .AddRect(Rect::MakeLTRB(0, 0, 10, 10)) + .AddRect(Rect::MakeLTRB(20, 20, 30, 30)) + .TakePath(), + 1.0); + + std::vector expected = {{0, 0}, {10, 0}, {0, 10}, {10, 10}, + {10, 10}, {20, 20}, {20, 20}, {30, 20}, + {20, 30}, {30, 30}}; + EXPECT_EQ(pts, expected); + } } TEST(TessellatorTest, CircleVertexCounts) { @@ -438,10 +474,7 @@ TEST(TessellatorTest, EarlyReturnEmptyConvexShape) { builder.MoveTo({0, 0}); builder.MoveTo({10, 10}, /*relative=*/true); - std::vector points; - std::vector indices; - tessellator->TessellateConvexInternal(builder.TakePath(), points, indices, - 3.0); + auto points = tessellator->TessellateConvex(builder.TakePath(), 3.0); EXPECT_TRUE(points.empty()); } diff --git a/impeller/tools/malioc.json b/impeller/tools/malioc.json index 3f5ff33f9a5b2..a0e3a966be0aa 100644 --- a/impeller/tools/malioc.json +++ b/impeller/tools/malioc.json @@ -5710,169 +5710,12 @@ } } }, - "flutter/impeller/entity/gles/texture_uv_fill.vert.gles": { - "Mali-G78": { - "core": "Mali-G78", - "filename": "flutter/impeller/entity/gles/texture_uv_fill.vert.gles", - "has_uniform_computation": true, - "type": "Vertex", - "variants": { - "Position": { - "fp16_arithmetic": 0, - "has_stack_spilling": false, - "performance": { - "longest_path_bound_pipelines": [ - "load_store" - ], - "longest_path_cycles": [ - 0.140625, - 0.140625, - 0.0, - 0.0, - 2.0, - 0.0 - ], - "pipelines": [ - "arith_total", - "arith_fma", - "arith_cvt", - "arith_sfu", - "load_store", - "texture" - ], - "shortest_path_bound_pipelines": [ - "load_store" - ], - "shortest_path_cycles": [ - 0.140625, - 0.140625, - 0.0, - 0.0, - 2.0, - 0.0 - ], - "total_bound_pipelines": [ - "load_store" - ], - "total_cycles": [ - 0.140625, - 0.140625, - 0.0, - 0.0, - 2.0, - 0.0 - ] - }, - "stack_spill_bytes": 0, - "thread_occupancy": 100, - "uniform_registers_used": 28, - "work_registers_used": 32 - }, - "Varying": { - "fp16_arithmetic": 0, - "has_stack_spilling": false, - "performance": { - "longest_path_bound_pipelines": [ - "load_store" - ], - "longest_path_cycles": [ - 0.078125, - 0.078125, - 0.015625, - 0.0, - 3.0, - 0.0 - ], - "pipelines": [ - "arith_total", - "arith_fma", - "arith_cvt", - "arith_sfu", - "load_store", - "texture" - ], - "shortest_path_bound_pipelines": [ - "load_store" - ], - "shortest_path_cycles": [ - 0.078125, - 0.078125, - 0.015625, - 0.0, - 3.0, - 0.0 - ], - "total_bound_pipelines": [ - "load_store" - ], - "total_cycles": [ - 0.078125, - 0.078125, - 0.015625, - 0.0, - 3.0, - 0.0 - ] - }, - "stack_spill_bytes": 0, - "thread_occupancy": 100, - "uniform_registers_used": 16, - "work_registers_used": 9 - } - } - }, - "Mali-T880": { - "core": "Mali-T880", - "filename": "flutter/impeller/entity/gles/texture_uv_fill.vert.gles", - "has_uniform_computation": false, - "type": "Vertex", - "variants": { - "Main": { - "has_stack_spilling": false, - "performance": { - "longest_path_bound_pipelines": [ - "load_store" - ], - "longest_path_cycles": [ - 3.299999952316284, - 4.0, - 0.0 - ], - "pipelines": [ - "arithmetic", - "load_store", - "texture" - ], - "shortest_path_bound_pipelines": [ - "load_store" - ], - "shortest_path_cycles": [ - 3.299999952316284, - 4.0, - 0.0 - ], - "total_bound_pipelines": [ - "load_store" - ], - "total_cycles": [ - 3.3333332538604736, - 4.0, - 0.0 - ] - }, - "thread_occupancy": 100, - "uniform_registers_used": 7, - "work_registers_used": 3 - } - } - } - }, "flutter/impeller/entity/gles/tiled_texture_fill.frag.gles": { "Mali-G78": { "core": "Mali-G78", "filename": "flutter/impeller/entity/gles/tiled_texture_fill.frag.gles", "has_side_effects": false, - "has_uniform_computation": false, + "has_uniform_computation": true, "modifies_coverage": false, "reads_color_buffer": false, "type": "Fragment", @@ -5880,17 +5723,17 @@ "uses_late_zs_update": false, "variants": { "Main": { - "fp16_arithmetic": 100, + "fp16_arithmetic": 33, "has_stack_spilling": false, "performance": { "longest_path_bound_pipelines": [ - "varying", - "texture" + "arith_total", + "arith_cvt" ], "longest_path_cycles": [ - 0.046875, + 0.265625, 0.03125, - 0.046875, + 0.265625, 0.0, 0.0, 0.25, @@ -5910,22 +5753,22 @@ "texture" ], "shortest_path_cycles": [ + 0.0625, 0.03125, - 0.03125, - 0.015625, + 0.0625, 0.0, 0.0, 0.25, 0.25 ], "total_bound_pipelines": [ - "varying", - "texture" + "arith_total", + "arith_cvt" ], "total_cycles": [ - 0.046875, + 0.265625, 0.03125, - 0.046875, + 0.265625, 0.0, 0.0, 0.25, @@ -5949,12 +5792,10 @@ "has_stack_spilling": false, "performance": { "longest_path_bound_pipelines": [ - "arithmetic", - "load_store", - "texture" + "arithmetic" ], "longest_path_cycles": [ - 1.0, + 3.299999952316284, 1.0, 1.0 ], @@ -5964,21 +5805,18 @@ "texture" ], "shortest_path_bound_pipelines": [ - "arithmetic", - "load_store", - "texture" + "arithmetic" ], "shortest_path_cycles": [ + 1.3200000524520874, 1.0, - 1.0, - 1.0 + 0.0 ], "total_bound_pipelines": [ - "load_store", - "texture" + "arithmetic" ], "total_cycles": [ - 0.6666666865348816, + 3.6666667461395264, 1.0, 1.0 ] @@ -8813,118 +8651,6 @@ } } }, - "flutter/impeller/entity/texture_uv_fill.vert.vkspv": { - "Mali-G78": { - "core": "Mali-G78", - "filename": "flutter/impeller/entity/texture_uv_fill.vert.vkspv", - "has_uniform_computation": true, - "type": "Vertex", - "variants": { - "Position": { - "fp16_arithmetic": 0, - "has_stack_spilling": false, - "performance": { - "longest_path_bound_pipelines": [ - "load_store" - ], - "longest_path_cycles": [ - 0.125, - 0.125, - 0.0, - 0.0, - 2.0, - 0.0 - ], - "pipelines": [ - "arith_total", - "arith_fma", - "arith_cvt", - "arith_sfu", - "load_store", - "texture" - ], - "shortest_path_bound_pipelines": [ - "load_store" - ], - "shortest_path_cycles": [ - 0.125, - 0.125, - 0.0, - 0.0, - 2.0, - 0.0 - ], - "total_bound_pipelines": [ - "load_store" - ], - "total_cycles": [ - 0.125, - 0.125, - 0.0, - 0.0, - 2.0, - 0.0 - ] - }, - "stack_spill_bytes": 0, - "thread_occupancy": 100, - "uniform_registers_used": 40, - "work_registers_used": 32 - }, - "Varying": { - "fp16_arithmetic": 0, - "has_stack_spilling": false, - "performance": { - "longest_path_bound_pipelines": [ - "load_store" - ], - "longest_path_cycles": [ - 0.078125, - 0.078125, - 0.015625, - 0.0, - 3.0, - 0.0 - ], - "pipelines": [ - "arith_total", - "arith_fma", - "arith_cvt", - "arith_sfu", - "load_store", - "texture" - ], - "shortest_path_bound_pipelines": [ - "load_store" - ], - "shortest_path_cycles": [ - 0.078125, - 0.078125, - 0.015625, - 0.0, - 3.0, - 0.0 - ], - "total_bound_pipelines": [ - "load_store" - ], - "total_cycles": [ - 0.078125, - 0.078125, - 0.015625, - 0.0, - 3.0, - 0.0 - ] - }, - "stack_spill_bytes": 0, - "thread_occupancy": 100, - "uniform_registers_used": 32, - "work_registers_used": 9 - } - } - } - }, "flutter/impeller/entity/tiled_texture_fill.frag.vkspv": { "Mali-G78": { "core": "Mali-G78", @@ -8938,18 +8664,20 @@ "uses_late_zs_update": false, "variants": { "Main": { - "fp16_arithmetic": 100, + "fp16_arithmetic": 33, "has_stack_spilling": false, "performance": { "longest_path_bound_pipelines": [ + "arith_total", + "arith_cvt", "varying", "texture" ], "longest_path_cycles": [ + 0.25, 0.03125, - 0.03125, - 0.015625, - 0.0, + 0.25, + 0.0625, 0.0, 0.25, 0.25 @@ -8964,27 +8692,28 @@ "texture" ], "shortest_path_bound_pipelines": [ - "varying", - "texture" + "varying" ], "shortest_path_cycles": [ + 0.140625, 0.03125, - 0.03125, - 0.015625, - 0.0, + 0.140625, + 0.0625, 0.0, 0.25, - 0.25 + 0.0 ], "total_bound_pipelines": [ + "arith_total", + "arith_cvt", "varying", "texture" ], "total_cycles": [ + 0.25, 0.03125, - 0.03125, - 0.015625, - 0.0, + 0.25, + 0.0625, 0.0, 0.25, 0.25 @@ -8993,7 +8722,7 @@ "stack_spill_bytes": 0, "thread_occupancy": 100, "uniform_registers_used": 4, - "work_registers_used": 5 + "work_registers_used": 7 } } } @@ -9070,6 +8799,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": 0, + "has_stack_spilling": false, + "performance": { + "longest_path_bound_pipelines": [ + "load_store" + ], + "longest_path_cycles": [ + 0.1875, + 0.1875, + 0.125, + 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.125, + 0.0625, + 2.0, + 0.0 + ] + }, + "shared_storage_used": 0, + "stack_spill_bytes": 0, + "thread_occupancy": 100, + "uniform_registers_used": 24, + "work_registers_used": 10 + } + } + } + }, "flutter/impeller/entity/vertices.frag.vkspv": { "Mali-G78": { "core": "Mali-G78", diff --git a/testing/impeller_golden_tests_output.txt b/testing/impeller_golden_tests_output.txt index d0796e370311f..f18c299b3ce94 100644 --- a/testing/impeller_golden_tests_output.txt +++ b/testing/impeller_golden_tests_output.txt @@ -209,10 +209,8 @@ impeller_Play_AiksTest_CanDrawPaint_Vulkan.png impeller_Play_AiksTest_CanDrawPerspectiveTransformWithClips_Metal.png impeller_Play_AiksTest_CanDrawPerspectiveTransformWithClips_OpenGLES.png impeller_Play_AiksTest_CanDrawPerspectiveTransformWithClips_Vulkan.png -impeller_Play_AiksTest_CanDrawPointsWithTextureMap_Metal.png impeller_Play_AiksTest_CanDrawPointsWithTextureMap_OpenGLES.png impeller_Play_AiksTest_CanDrawPointsWithTextureMap_Vulkan.png -impeller_Play_AiksTest_CanDrawPoints_Metal.png impeller_Play_AiksTest_CanDrawPoints_OpenGLES.png impeller_Play_AiksTest_CanDrawPoints_Vulkan.png impeller_Play_AiksTest_CanEmptyPictureConvertToImage_Metal.png