diff --git a/engine/src/flutter/impeller/aiks/aiks_unittests.cc b/engine/src/flutter/impeller/aiks/aiks_unittests.cc index 138076f72b..cc1b17fac6 100644 --- a/engine/src/flutter/impeller/aiks/aiks_unittests.cc +++ b/engine/src/flutter/impeller/aiks/aiks_unittests.cc @@ -1481,13 +1481,9 @@ TEST_P(AiksTest, SolidStrokesRenderCorrectly) { canvas.ClipPath(PathBuilder{}.AddCircle(middle, radius).TakePath()); } - for (auto join : - {SolidStrokeContents::Join::kBevel, SolidStrokeContents::Join::kRound, - SolidStrokeContents::Join::kMiter}) { + for (auto join : {Join::kBevel, Join::kRound, Join::kMiter}) { paint.stroke_join = join; - for (auto cap : - {SolidStrokeContents::Cap::kButt, SolidStrokeContents::Cap::kSquare, - SolidStrokeContents::Cap::kRound}) { + for (auto cap : {Cap::kButt, Cap::kSquare, Cap::kRound}) { paint.stroke_cap = cap; canvas.DrawPath(path, paint); canvas.Translate({80, 0}); diff --git a/engine/src/flutter/impeller/aiks/canvas.cc b/engine/src/flutter/impeller/aiks/canvas.cc index 50ecd4b1be..d5982a521c 100644 --- a/engine/src/flutter/impeller/aiks/canvas.cc +++ b/engine/src/flutter/impeller/aiks/canvas.cc @@ -209,7 +209,7 @@ void Canvas::DrawCircle(Point center, Scalar radius, Paint paint) { void Canvas::ClipPath(Path path, Entity::ClipOperation clip_op) { auto contents = std::make_shared(); - contents->SetGeometry(Geometry::MakePath(std::move(path))); + contents->SetGeometry(Geometry::MakeFillPath(std::move(path))); contents->SetClipOperation(clip_op); Entity entity; diff --git a/engine/src/flutter/impeller/aiks/paint.cc b/engine/src/flutter/impeller/aiks/paint.cc index 76853dc6ec..7f7788f3d7 100644 --- a/engine/src/flutter/impeller/aiks/paint.cc +++ b/engine/src/flutter/impeller/aiks/paint.cc @@ -15,7 +15,7 @@ std::shared_ptr Paint::CreateContentsForEntity(Path path, auto& source = color_source.value(); auto contents = source(); contents->SetGeometry(cover ? Geometry::MakeCover() - : Geometry::MakePath(std::move(path))); + : Geometry::MakeFillPath(std::move(path))); contents->SetAlpha(color.alpha); return contents; } @@ -24,18 +24,16 @@ std::shared_ptr Paint::CreateContentsForEntity(Path path, case Style::kFill: { auto solid_color = std::make_shared(); solid_color->SetGeometry(cover ? Geometry::MakeCover() - : Geometry::MakePath(std::move(path))); + : Geometry::MakeFillPath(std::move(path))); solid_color->SetColor(color); return solid_color; } case Style::kStroke: { auto solid_stroke = std::make_shared(); - solid_stroke->SetPath(std::move(path)); + solid_stroke->SetGeometry( + Geometry::MakeStrokePath(std::move(path), stroke_width, stroke_miter, + stroke_cap, stroke_join)); solid_stroke->SetColor(color); - solid_stroke->SetStrokeSize(stroke_width); - solid_stroke->SetStrokeMiter(stroke_miter); - solid_stroke->SetStrokeCap(stroke_cap); - solid_stroke->SetStrokeJoin(stroke_join); return solid_stroke; } } diff --git a/engine/src/flutter/impeller/aiks/paint.h b/engine/src/flutter/impeller/aiks/paint.h index 9fa8505701..025eb9a721 100644 --- a/engine/src/flutter/impeller/aiks/paint.h +++ b/engine/src/flutter/impeller/aiks/paint.h @@ -15,6 +15,7 @@ #include "impeller/entity/contents/solid_stroke_contents.h" #include "impeller/entity/contents/sweep_gradient_contents.h" #include "impeller/entity/entity.h" +#include "impeller/entity/geometry.h" #include "impeller/geometry/color.h" namespace impeller { @@ -50,8 +51,8 @@ struct Paint { std::optional color_source; Scalar stroke_width = 0.0; - SolidStrokeContents::Cap stroke_cap = SolidStrokeContents::Cap::kButt; - SolidStrokeContents::Join stroke_join = SolidStrokeContents::Join::kMiter; + Cap stroke_cap = Cap::kButt; + Join stroke_join = Join::kMiter; Scalar stroke_miter = 4.0; Style style = Style::kFill; BlendMode blend_mode = BlendMode::kSourceOver; diff --git a/engine/src/flutter/impeller/display_list/display_list_dispatcher.cc b/engine/src/flutter/impeller/display_list/display_list_dispatcher.cc index 3d914a50ba..83b26c381b 100644 --- a/engine/src/flutter/impeller/display_list/display_list_dispatcher.cc +++ b/engine/src/flutter/impeller/display_list/display_list_dispatcher.cc @@ -29,6 +29,7 @@ #include "impeller/entity/contents/sweep_gradient_contents.h" #include "impeller/entity/contents/tiled_texture_contents.h" #include "impeller/entity/entity.h" +#include "impeller/entity/geometry.h" #include "impeller/geometry/path.h" #include "impeller/geometry/path_builder.h" #include "impeller/geometry/scalar.h" @@ -227,13 +228,13 @@ void DisplayListDispatcher::setStrokeMiter(SkScalar limit) { void DisplayListDispatcher::setStrokeCap(flutter::DlStrokeCap cap) { switch (cap) { case flutter::DlStrokeCap::kButt: - paint_.stroke_cap = SolidStrokeContents::Cap::kButt; + paint_.stroke_cap = Cap::kButt; break; case flutter::DlStrokeCap::kRound: - paint_.stroke_cap = SolidStrokeContents::Cap::kRound; + paint_.stroke_cap = Cap::kRound; break; case flutter::DlStrokeCap::kSquare: - paint_.stroke_cap = SolidStrokeContents::Cap::kSquare; + paint_.stroke_cap = Cap::kSquare; break; } } @@ -242,13 +243,13 @@ void DisplayListDispatcher::setStrokeCap(flutter::DlStrokeCap cap) { void DisplayListDispatcher::setStrokeJoin(flutter::DlStrokeJoin join) { switch (join) { case flutter::DlStrokeJoin::kMiter: - paint_.stroke_join = SolidStrokeContents::Join::kMiter; + paint_.stroke_join = Join::kMiter; break; case flutter::DlStrokeJoin::kRound: - paint_.stroke_join = SolidStrokeContents::Join::kRound; + paint_.stroke_join = Join::kRound; break; case flutter::DlStrokeJoin::kBevel: - paint_.stroke_join = SolidStrokeContents::Join::kBevel; + paint_.stroke_join = Join::kBevel; break; } } @@ -1010,8 +1011,8 @@ void DisplayListDispatcher::drawPoints(SkCanvas::PointMode mode, paint.style = Paint::Style::kStroke; switch (mode) { case SkCanvas::kPoints_PointMode: - if (paint.stroke_cap == SolidStrokeContents::Cap::kButt) { - paint.stroke_cap = SolidStrokeContents::Cap::kSquare; + if (paint.stroke_cap == Cap::kButt) { + paint.stroke_cap = Cap::kSquare; } for (uint32_t i = 0; i < count; i++) { Point p0 = ToPoint(points[i]); diff --git a/engine/src/flutter/impeller/entity/contents/clip_contents.cc b/engine/src/flutter/impeller/entity/contents/clip_contents.cc index a7ba8e6ec7..cb8b56213b 100644 --- a/engine/src/flutter/impeller/entity/contents/clip_contents.cc +++ b/engine/src/flutter/impeller/entity/contents/clip_contents.cc @@ -117,12 +117,8 @@ bool ClipContents::Render(const ContentContext& renderer, cmd.pipeline = renderer.GetClipPipeline(options); - auto& host_buffer = pass.GetTransientsBuffer(); auto allocator = renderer.GetContext()->GetResourceAllocator(); - auto geometry_result = geometry_->GetPositionBuffer( - allocator, host_buffer, renderer.GetTessellator(), - pass.GetRenderTargetSize(), - entity.GetTransformation().GetMaxBasisLength()); + auto geometry_result = geometry_->GetPositionBuffer(renderer, entity, pass); cmd.BindVertices(geometry_result.vertex_buffer); cmd.primitive_type = geometry_result.type; info.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * diff --git a/engine/src/flutter/impeller/entity/contents/filters/blend_filter_contents.cc b/engine/src/flutter/impeller/entity/contents/filters/blend_filter_contents.cc index 754c716913..687d19f401 100644 --- a/engine/src/flutter/impeller/entity/contents/filters/blend_filter_contents.cc +++ b/engine/src/flutter/impeller/entity/contents/filters/blend_filter_contents.cc @@ -236,7 +236,7 @@ static std::optional PipelineBlend( if (foreground_color.has_value()) { auto contents = std::make_shared(); - contents->SetGeometry(Geometry::MakePath( + contents->SetGeometry(Geometry::MakeFillPath( PathBuilder{} .AddRect(Rect::MakeSize(pass.GetRenderTargetSize())) .TakePath())); diff --git a/engine/src/flutter/impeller/entity/contents/linear_gradient_contents.cc b/engine/src/flutter/impeller/entity/contents/linear_gradient_contents.cc index 1b4d3a77e4..59d52b02dd 100644 --- a/engine/src/flutter/impeller/entity/contents/linear_gradient_contents.cc +++ b/engine/src/flutter/impeller/entity/contents/linear_gradient_contents.cc @@ -76,12 +76,9 @@ bool LinearGradientContents::Render(const ContentContext& renderer, OptionsFromPassAndEntity(pass, entity)); cmd.stencil_reference = entity.GetStencilDepth(); - auto& host_buffer = pass.GetTransientsBuffer(); auto allocator = renderer.GetContext()->GetResourceAllocator(); - auto geometry_result = GetGeometry()->GetPositionBuffer( - allocator, host_buffer, renderer.GetTessellator(), - pass.GetRenderTargetSize(), - entity.GetTransformation().GetMaxBasisLength()); + auto geometry_result = + GetGeometry()->GetPositionBuffer(renderer, entity, pass); cmd.BindVertices(geometry_result.vertex_buffer); cmd.primitive_type = geometry_result.type; FS::BindGradientInfo( diff --git a/engine/src/flutter/impeller/entity/contents/radial_gradient_contents.cc b/engine/src/flutter/impeller/entity/contents/radial_gradient_contents.cc index 65352a56ce..84f809e301 100644 --- a/engine/src/flutter/impeller/entity/contents/radial_gradient_contents.cc +++ b/engine/src/flutter/impeller/entity/contents/radial_gradient_contents.cc @@ -75,12 +75,10 @@ bool RadialGradientContents::Render(const ContentContext& renderer, cmd.pipeline = renderer.GetRadialGradientFillPipeline( OptionsFromPassAndEntity(pass, entity)); cmd.stencil_reference = entity.GetStencilDepth(); - auto& host_buffer = pass.GetTransientsBuffer(); + auto allocator = renderer.GetContext()->GetResourceAllocator(); - auto geometry_result = GetGeometry()->GetPositionBuffer( - allocator, host_buffer, renderer.GetTessellator(), - pass.GetRenderTargetSize(), - entity.GetTransformation().GetMaxBasisLength()); + auto geometry_result = + GetGeometry()->GetPositionBuffer(renderer, entity, pass); cmd.BindVertices(geometry_result.vertex_buffer); cmd.primitive_type = geometry_result.type; FS::BindGradientInfo( diff --git a/engine/src/flutter/impeller/entity/contents/runtime_effect_contents.cc b/engine/src/flutter/impeller/entity/contents/runtime_effect_contents.cc index b0c06a00e9..d659c608e8 100644 --- a/engine/src/flutter/impeller/entity/contents/runtime_effect_contents.cc +++ b/engine/src/flutter/impeller/entity/contents/runtime_effect_contents.cc @@ -78,10 +78,8 @@ bool RuntimeEffectContents::Render(const ContentContext& renderer, /// Resolve geometry. /// - auto geometry_result = GetGeometry()->GetPositionBuffer( - context->GetResourceAllocator(), pass.GetTransientsBuffer(), - renderer.GetTessellator(), pass.GetRenderTargetSize(), - entity.GetTransformation().GetMaxBasisLength()); + auto geometry_result = + GetGeometry()->GetPositionBuffer(renderer, entity, pass); //-------------------------------------------------------------------------- /// Get or create runtime stage pipeline. diff --git a/engine/src/flutter/impeller/entity/contents/solid_color_contents.cc b/engine/src/flutter/impeller/entity/contents/solid_color_contents.cc index e16dc54b8d..4a01fe1456 100644 --- a/engine/src/flutter/impeller/entity/contents/solid_color_contents.cc +++ b/engine/src/flutter/impeller/entity/contents/solid_color_contents.cc @@ -59,12 +59,7 @@ bool SolidColorContents::Render(const ContentContext& renderer, renderer.GetSolidFillPipeline(OptionsFromPassAndEntity(pass, entity)); cmd.stencil_reference = entity.GetStencilDepth(); - auto& host_buffer = pass.GetTransientsBuffer(); - auto allocator = renderer.GetContext()->GetResourceAllocator(); - auto geometry_result = geometry_->GetPositionBuffer( - allocator, host_buffer, renderer.GetTessellator(), - pass.GetRenderTargetSize(), - entity.GetTransformation().GetMaxBasisLength()); + auto geometry_result = geometry_->GetPositionBuffer(renderer, entity, pass); cmd.BindVertices(geometry_result.vertex_buffer); cmd.primitive_type = geometry_result.type; @@ -87,7 +82,7 @@ bool SolidColorContents::Render(const ContentContext& renderer, std::unique_ptr SolidColorContents::Make(Path path, Color color) { auto contents = std::make_unique(); - contents->SetGeometry(Geometry::MakePath(std::move(path))); + contents->SetGeometry(Geometry::MakeFillPath(std::move(path))); contents->SetColor(color); return contents; } diff --git a/engine/src/flutter/impeller/entity/contents/solid_stroke_contents.cc b/engine/src/flutter/impeller/entity/contents/solid_stroke_contents.cc index bf66c5e2a5..1707c07bee 100644 --- a/engine/src/flutter/impeller/entity/contents/solid_stroke_contents.cc +++ b/engine/src/flutter/impeller/entity/contents/solid_stroke_contents.cc @@ -9,15 +9,14 @@ #include "impeller/entity/contents/clip_contents.h" #include "impeller/entity/contents/content_context.h" #include "impeller/entity/entity.h" +#include "impeller/entity/solid_fill.frag.h" +#include "impeller/entity/solid_fill.vert.h" #include "impeller/geometry/path_builder.h" #include "impeller/renderer/render_pass.h" namespace impeller { -SolidStrokeContents::SolidStrokeContents() { - SetStrokeCap(Cap::kButt); - SetStrokeJoin(Join::kMiter); -} +SolidStrokeContents::SolidStrokeContents(){}; SolidStrokeContents::~SolidStrokeContents() = default; @@ -29,8 +28,8 @@ const Color& SolidStrokeContents::GetColor() const { return color_; } -void SolidStrokeContents::SetPath(Path path) { - path_ = std::move(path); +void SolidStrokeContents::SetGeometry(std::unique_ptr geometry) { + geometry_ = std::move(geometry); } std::optional SolidStrokeContents::GetCoverage( @@ -38,386 +37,46 @@ std::optional SolidStrokeContents::GetCoverage( if (color_.IsTransparent()) { return std::nullopt; } - - auto path_bounds = path_.GetBoundingBox(); - if (!path_bounds.has_value()) { - return std::nullopt; - } - auto path_coverage = path_bounds->TransformBounds(entity.GetTransformation()); - - Scalar max_radius = 0.5; - if (cap_ == Cap::kSquare) { - max_radius = max_radius * kSqrt2; - } - if (join_ == Join::kMiter) { - max_radius = std::max(max_radius, miter_limit_ * 0.5f); - } - Scalar determinant = entity.GetTransformation().GetDeterminant(); - if (determinant == 0) { - return std::nullopt; - } - Scalar min_size = 1.0f / sqrt(std::abs(determinant)); - Vector2 max_radius_xy = entity.GetTransformation().TransformDirection( - Vector2(max_radius, max_radius) * std::max(stroke_size_, min_size)); - - return Rect(path_coverage.origin - max_radius_xy, - Size(path_coverage.size.width + max_radius_xy.x * 2, - path_coverage.size.height + max_radius_xy.y * 2)); -} - -static VertexBuffer CreateSolidStrokeVertices( - const Path& path, - HostBuffer& buffer, - Scalar stroke_width, - const SolidStrokeContents::CapProc& cap_proc, - const SolidStrokeContents::JoinProc& join_proc, - Scalar miter_limit, - Scalar tolerance) { - using VS = SolidFillVertexShader; - - VertexBufferBuilder vtx_builder; - auto polyline = path.CreatePolyline(); - - VS::PerVertexData vtx; - - // Offset state. - Point offset; - Point previous_offset; // Used for computing joins. - - auto compute_offset = [&polyline, &offset, &previous_offset, - &stroke_width](size_t point_i) { - previous_offset = offset; - Point direction = - (polyline.points[point_i] - polyline.points[point_i - 1]).Normalize(); - offset = Vector2{-direction.y, direction.x} * stroke_width * 0.5; - }; - - for (size_t contour_i = 0; contour_i < polyline.contours.size(); - contour_i++) { - size_t contour_start_point_i, contour_end_point_i; - std::tie(contour_start_point_i, contour_end_point_i) = - polyline.GetContourPointBounds(contour_i); - - switch (contour_end_point_i - contour_start_point_i) { - case 1: { - Point p = polyline.points[contour_start_point_i]; - cap_proc(vtx_builder, p, {-stroke_width * 0.5f, 0}, tolerance); - cap_proc(vtx_builder, p, {stroke_width * 0.5f, 0}, tolerance); - continue; - } - case 0: - continue; // This contour has no renderable content. - default: - break; - } - - // The first point's offset is always the same as the second point. - compute_offset(contour_start_point_i + 1); - const Point contour_first_offset = offset; - - if (contour_i > 0) { - // This branch only executes when we've just finished drawing a contour - // and are switching to a new one. - // We're drawing a triangle strip, so we need to "pick up the pen" by - // appending two vertices at the end of the previous contour and two - // vertices at the start of the new contour (thus connecting the two - // contours with two zero volume triangles, which will be discarded by the - // rasterizer). - vtx.position = polyline.points[contour_start_point_i - 1]; - // Append two vertices when "picking up" the pen so that the triangle - // drawn when moving to the beginning of the new contour will have zero - // volume. - vtx_builder.AppendVertex(vtx); - vtx_builder.AppendVertex(vtx); - - vtx.position = polyline.points[contour_start_point_i]; - // Append two vertices at the beginning of the new contour, which appends - // two triangles of zero area. - vtx_builder.AppendVertex(vtx); - vtx_builder.AppendVertex(vtx); - } - - // Generate start cap. - if (!polyline.contours[contour_i].is_closed) { - cap_proc(vtx_builder, polyline.points[contour_start_point_i], -offset, - tolerance); - } - - // Generate contour geometry. - for (size_t point_i = contour_start_point_i; point_i < contour_end_point_i; - point_i++) { - if (point_i > contour_start_point_i) { - // Generate line rect. - vtx.position = polyline.points[point_i - 1] + offset; - vtx_builder.AppendVertex(vtx); - vtx.position = polyline.points[point_i - 1] - offset; - vtx_builder.AppendVertex(vtx); - vtx.position = polyline.points[point_i] + offset; - vtx_builder.AppendVertex(vtx); - vtx.position = polyline.points[point_i] - offset; - vtx_builder.AppendVertex(vtx); - - if (point_i < contour_end_point_i - 1) { - compute_offset(point_i + 1); - - // Generate join from the current line to the next line. - join_proc(vtx_builder, polyline.points[point_i], previous_offset, - offset, miter_limit, tolerance); - } - } - } - - // Generate end cap or join. - if (!polyline.contours[contour_i].is_closed) { - cap_proc(vtx_builder, polyline.points[contour_end_point_i - 1], offset, - tolerance); - } else { - join_proc(vtx_builder, polyline.points[contour_start_point_i], offset, - contour_first_offset, miter_limit, tolerance); - } - } - - return vtx_builder.CreateVertexBuffer(buffer); + return geometry_->GetCoverage(entity.GetTransformation()); } bool SolidStrokeContents::Render(const ContentContext& renderer, const Entity& entity, RenderPass& pass) const { - if (stroke_size_ < 0.0) { - return true; - } - + using VS = SolidFillVertexShader; + using FS = SolidFillFragmentShader; VS::VertInfo vert_info; vert_info.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * entity.GetTransformation(); - Scalar determinant = entity.GetTransformation().GetDeterminant(); - if (determinant == 0) { - return true; - } FS::FragInfo frag_info; frag_info.color = color_.Premultiply(); Command cmd; - cmd.primitive_type = PrimitiveType::kTriangleStrip; cmd.label = "Solid Stroke"; + cmd.stencil_reference = entity.GetStencilDepth(); + + auto geometry_result = geometry_->GetPositionBuffer(renderer, entity, pass); + auto options = OptionsFromPassAndEntity(pass, entity); - if (!color_.IsOpaque()) { + if (geometry_result.prevent_overdraw) { options.stencil_compare = CompareFunction::kEqual; options.stencil_operation = StencilOperation::kIncrementClamp; } cmd.pipeline = renderer.GetSolidFillPipeline(options); - cmd.stencil_reference = entity.GetStencilDepth(); - auto tolerance = - kDefaultCurveTolerance / - (stroke_size_ * entity.GetTransformation().GetMaxBasisLength()); - - Scalar min_size = 1.0f / sqrt(std::abs(determinant)); - Scalar stroke_width = std::max(stroke_size_, min_size); - cmd.BindVertices(CreateSolidStrokeVertices( - path_, pass.GetTransientsBuffer(), stroke_width, cap_proc_, join_proc_, - miter_limit_ * stroke_width * 0.5, tolerance)); + cmd.BindVertices(geometry_result.vertex_buffer); + cmd.primitive_type = geometry_result.type; VS::BindVertInfo(cmd, pass.GetTransientsBuffer().EmplaceUniform(vert_info)); FS::BindFragInfo(cmd, pass.GetTransientsBuffer().EmplaceUniform(frag_info)); pass.AddCommand(cmd); - if (!color_.IsOpaque()) { + if (geometry_result.prevent_overdraw) { return ClipRestoreContents().Render(renderer, entity, pass); } return true; } -void SolidStrokeContents::SetStrokeSize(Scalar size) { - stroke_size_ = size; -} - -Scalar SolidStrokeContents::GetStrokeSize() const { - return stroke_size_; -} - -void SolidStrokeContents::SetStrokeMiter(Scalar miter_limit) { - if (miter_limit < 0) { - return; // Skia behaves like this. - } - miter_limit_ = miter_limit; -} - -Scalar SolidStrokeContents::GetStrokeMiter() { - return miter_limit_; -} - -void SolidStrokeContents::SetStrokeCap(Cap cap) { - cap_ = cap; - - switch (cap) { - case Cap::kButt: - cap_proc_ = [](VertexBufferBuilder& vtx_builder, - const Point& position, const Point& offset, - Scalar tolerance) {}; - break; - case Cap::kRound: - cap_proc_ = [](VertexBufferBuilder& vtx_builder, - const Point& position, const Point& offset, - Scalar tolerance) { - VS::PerVertexData vtx; - - Point forward(offset.y, -offset.x); - Point forward_normal = forward.Normalize(); - - auto arc_points = - CubicPathComponent( - offset, offset + forward * PathBuilder::kArcApproximationMagic, - forward + offset * PathBuilder::kArcApproximationMagic, forward) - .CreatePolyline(tolerance); - - vtx.position = position + offset; - vtx_builder.AppendVertex(vtx); - vtx.position = position - offset; - vtx_builder.AppendVertex(vtx); - for (const auto& point : arc_points) { - vtx.position = position + point; - vtx_builder.AppendVertex(vtx); - vtx.position = position + (-point).Reflect(forward_normal); - vtx_builder.AppendVertex(vtx); - } - }; - break; - case Cap::kSquare: - cap_proc_ = [](VertexBufferBuilder& vtx_builder, - const Point& position, const Point& offset, - Scalar tolerance) { - VS::PerVertexData vtx; - vtx.position = position; - - Point forward(offset.y, -offset.x); - - vtx.position = position + offset; - vtx_builder.AppendVertex(vtx); - vtx.position = position - offset; - vtx_builder.AppendVertex(vtx); - vtx.position = position + offset + forward; - vtx_builder.AppendVertex(vtx); - vtx.position = position - offset + forward; - vtx_builder.AppendVertex(vtx); - }; - break; - } -} - -SolidStrokeContents::Cap SolidStrokeContents::GetStrokeCap() { - return cap_; -} - -static Scalar CreateBevelAndGetDirection( - VertexBufferBuilder& vtx_builder, - const Point& position, - const Point& start_offset, - const Point& end_offset) { - SolidFillVertexShader::PerVertexData vtx; - vtx.position = position; - vtx_builder.AppendVertex(vtx); - - Scalar dir = start_offset.Cross(end_offset) > 0 ? -1 : 1; - vtx.position = position + start_offset * dir; - vtx_builder.AppendVertex(vtx); - vtx.position = position + end_offset * dir; - vtx_builder.AppendVertex(vtx); - - return dir; -} - -void SolidStrokeContents::SetStrokeJoin(Join join) { - join_ = join; - - switch (join) { - case Join::kBevel: - join_proc_ = [](VertexBufferBuilder& vtx_builder, - const Point& position, const Point& start_offset, - const Point& end_offset, Scalar miter_limit, - Scalar tolerance) { - CreateBevelAndGetDirection(vtx_builder, position, start_offset, - end_offset); - }; - break; - case Join::kMiter: - join_proc_ = [](VertexBufferBuilder& vtx_builder, - const Point& position, const Point& start_offset, - const Point& end_offset, Scalar miter_limit, - Scalar tolerance) { - Point start_normal = start_offset.Normalize(); - Point end_normal = end_offset.Normalize(); - - // 1 for no joint (straight line), 0 for max joint (180 degrees). - Scalar alignment = (start_normal.Dot(end_normal) + 1) / 2; - if (ScalarNearlyEqual(alignment, 1)) { - return; - } - - Scalar dir = CreateBevelAndGetDirection(vtx_builder, position, - start_offset, end_offset); - - Point miter_point = (start_offset + end_offset) / 2 / alignment; - if (miter_point.GetDistanceSquared({0, 0}) > - miter_limit * miter_limit) { - return; // Convert to bevel when we exceed the miter limit. - } - - // Outer miter point. - VS::PerVertexData vtx; - vtx.position = position + miter_point * dir; - vtx_builder.AppendVertex(vtx); - }; - break; - case Join::kRound: - join_proc_ = [](VertexBufferBuilder& vtx_builder, - const Point& position, const Point& start_offset, - const Point& end_offset, Scalar miter_limit, - Scalar tolerance) { - Point start_normal = start_offset.Normalize(); - Point end_normal = end_offset.Normalize(); - - // 0 for no joint (straight line), 1 for max joint (180 degrees). - Scalar alignment = 1 - (start_normal.Dot(end_normal) + 1) / 2; - if (ScalarNearlyEqual(alignment, 0)) { - return; - } - - Scalar dir = CreateBevelAndGetDirection(vtx_builder, position, - start_offset, end_offset); - - Point middle = - (start_offset + end_offset).Normalize() * start_offset.GetLength(); - Point middle_normal = middle.Normalize(); - - Point middle_handle = middle + Point(-middle.y, middle.x) * - PathBuilder::kArcApproximationMagic * - alignment * dir; - Point start_handle = - start_offset + Point(start_offset.y, -start_offset.x) * - PathBuilder::kArcApproximationMagic * alignment * - dir; - - auto arc_points = CubicPathComponent(start_offset, start_handle, - middle_handle, middle) - .CreatePolyline(tolerance); - - VS::PerVertexData vtx; - for (const auto& point : arc_points) { - vtx.position = position + point * dir; - vtx_builder.AppendVertex(vtx); - vtx.position = position + (-point * dir).Reflect(middle_normal); - vtx_builder.AppendVertex(vtx); - } - }; - break; - } -} - -SolidStrokeContents::Join SolidStrokeContents::GetStrokeJoin() { - return join_; -} - } // namespace impeller diff --git a/engine/src/flutter/impeller/entity/contents/solid_stroke_contents.h b/engine/src/flutter/impeller/entity/contents/solid_stroke_contents.h index 101daf616e..ac341965c3 100644 --- a/engine/src/flutter/impeller/entity/contents/solid_stroke_contents.h +++ b/engine/src/flutter/impeller/entity/contents/solid_stroke_contents.h @@ -11,8 +11,7 @@ #include "flutter/fml/macros.h" #include "impeller/entity/contents/content_context.h" #include "impeller/entity/contents/contents.h" -#include "impeller/entity/solid_fill.frag.h" -#include "impeller/entity/solid_fill.vert.h" +#include "impeller/entity/geometry.h" #include "impeller/geometry/color.h" #include "impeller/geometry/path_component.h" #include "impeller/geometry/point.h" @@ -20,61 +19,17 @@ namespace impeller { class SolidStrokeContents final : public Contents { - using VS = SolidFillVertexShader; - using FS = SolidFillFragmentShader; - public: - enum class Cap { - kButt, - kRound, - kSquare, - }; - - enum class Join { - kMiter, - kRound, - kBevel, - }; - - using CapProc = - std::function& vtx_builder, - const Point& position, - const Point& offset, - Scalar tolerance)>; - using JoinProc = - std::function& vtx_builder, - const Point& position, - const Point& start_offset, - const Point& end_offset, - Scalar miter_limit, - Scalar tolerance)>; - SolidStrokeContents(); ~SolidStrokeContents() override; - void SetPath(Path path); + void SetGeometry(std::unique_ptr geometry); void SetColor(Color color); const Color& GetColor() const; - void SetStrokeSize(Scalar size); - - Scalar GetStrokeSize() const; - - void SetStrokeMiter(Scalar miter_limit); - - Scalar GetStrokeMiter(); - - void SetStrokeCap(Cap cap); - - Cap GetStrokeCap(); - - void SetStrokeJoin(Join join); - - Join GetStrokeJoin(); - // |Contents| std::optional GetCoverage(const Entity& entity) const override; @@ -84,17 +39,8 @@ class SolidStrokeContents final : public Contents { RenderPass& pass) const override; private: - Path path_; - + std::unique_ptr geometry_; Color color_; - Scalar stroke_size_ = 0.0; - Scalar miter_limit_ = 4.0; - - Cap cap_; - CapProc cap_proc_; - - Join join_; - JoinProc join_proc_; FML_DISALLOW_COPY_AND_ASSIGN(SolidStrokeContents); }; diff --git a/engine/src/flutter/impeller/entity/contents/sweep_gradient_contents.cc b/engine/src/flutter/impeller/entity/contents/sweep_gradient_contents.cc index 921c8fc967..e5f2c75626 100644 --- a/engine/src/flutter/impeller/entity/contents/sweep_gradient_contents.cc +++ b/engine/src/flutter/impeller/entity/contents/sweep_gradient_contents.cc @@ -81,12 +81,10 @@ bool SweepGradientContents::Render(const ContentContext& renderer, cmd.pipeline = renderer.GetSweepGradientFillPipeline( OptionsFromPassAndEntity(pass, entity)); cmd.stencil_reference = entity.GetStencilDepth(); - auto& host_buffer = pass.GetTransientsBuffer(); + auto allocator = renderer.GetContext()->GetResourceAllocator(); - auto geometry_result = GetGeometry()->GetPositionBuffer( - allocator, host_buffer, renderer.GetTessellator(), - pass.GetRenderTargetSize(), - entity.GetTransformation().GetMaxBasisLength()); + auto geometry_result = + GetGeometry()->GetPositionBuffer(renderer, entity, pass); cmd.BindVertices(geometry_result.vertex_buffer); cmd.primitive_type = geometry_result.type; FS::BindGradientInfo( diff --git a/engine/src/flutter/impeller/entity/contents/tiled_texture_contents.cc b/engine/src/flutter/impeller/entity/contents/tiled_texture_contents.cc index 18c4691dac..abc7b2cb60 100644 --- a/engine/src/flutter/impeller/entity/contents/tiled_texture_contents.cc +++ b/engine/src/flutter/impeller/entity/contents/tiled_texture_contents.cc @@ -67,11 +67,9 @@ bool TiledTextureContents::Render(const ContentContext& renderer, cmd.pipeline = renderer.GetTiledTexturePipeline(OptionsFromPassAndEntity(pass, entity)); cmd.stencil_reference = entity.GetStencilDepth(); - auto allocator = renderer.GetContext()->GetResourceAllocator(); - auto geometry_result = GetGeometry()->GetPositionBuffer( - allocator, host_buffer, renderer.GetTessellator(), - pass.GetRenderTargetSize(), - entity.GetTransformation().GetMaxBasisLength()); + + auto geometry_result = + GetGeometry()->GetPositionBuffer(renderer, entity, pass); cmd.BindVertices(geometry_result.vertex_buffer); cmd.primitive_type = geometry_result.type; VS::BindVertInfo(cmd, host_buffer.EmplaceUniform(vert_info)); diff --git a/engine/src/flutter/impeller/entity/contents/vertices_contents.cc b/engine/src/flutter/impeller/entity/contents/vertices_contents.cc index e1a2f50fe9..f789228972 100644 --- a/engine/src/flutter/impeller/entity/contents/vertices_contents.cc +++ b/engine/src/flutter/impeller/entity/contents/vertices_contents.cc @@ -41,7 +41,6 @@ bool VerticesContents::Render(const ContentContext& renderer, const Entity& entity, RenderPass& pass) const { auto& host_buffer = pass.GetTransientsBuffer(); - auto allocator = renderer.GetContext()->GetResourceAllocator(); auto vertex_type = geometry_->GetVertexType(); Command cmd; @@ -53,8 +52,7 @@ bool VerticesContents::Render(const ContentContext& renderer, using VS = GeometryColorPipeline::VertexShader; auto geometry_result = geometry_->GetPositionColorBuffer( - allocator, host_buffer, renderer.GetTessellator(), color_, - blend_mode_); + renderer, entity, pass, color_, blend_mode_); cmd.pipeline = renderer.GetGeometryColorPipeline( OptionsFromPassAndEntity(pass, entity)); cmd.primitive_type = geometry_result.type; @@ -70,10 +68,8 @@ bool VerticesContents::Render(const ContentContext& renderer, case GeometryVertexType::kPosition: { using VS = GeometryPositionPipeline::VertexShader; - auto geometry_result = geometry_->GetPositionBuffer( - allocator, host_buffer, renderer.GetTessellator(), - pass.GetRenderTargetSize(), - entity.GetTransformation().GetMaxBasisLength()); + auto geometry_result = + geometry_->GetPositionBuffer(renderer, entity, pass); cmd.pipeline = renderer.GetGeometryPositionPipeline( OptionsFromPassAndEntity(pass, entity)); cmd.primitive_type = geometry_result.type; diff --git a/engine/src/flutter/impeller/entity/entity_unittests.cc b/engine/src/flutter/impeller/entity/entity_unittests.cc index e80cc19c2e..debae6cbce 100644 --- a/engine/src/flutter/impeller/entity/entity_unittests.cc +++ b/engine/src/flutter/impeller/entity/entity_unittests.cc @@ -200,9 +200,8 @@ TEST_P(EntityTest, ThreeStrokesInOnePath) { Entity entity; entity.SetTransformation(Matrix::MakeScale(GetContentScale())); auto contents = std::make_unique(); - contents->SetPath(std::move(path)); + contents->SetGeometry(Geometry::MakeStrokePath(std::move(path), 5.0)); contents->SetColor(Color::Red()); - contents->SetStrokeSize(5.0); entity.SetContents(std::move(contents)); ASSERT_TRUE(OpenPlaygroundHere(entity)); } @@ -240,9 +239,8 @@ TEST_P(EntityTest, TriangleInsideASquare) { Entity entity; entity.SetTransformation(Matrix::MakeScale(GetContentScale())); auto contents = std::make_unique(); - contents->SetPath(std::move(path)); + contents->SetGeometry(Geometry::MakeStrokePath(std::move(path), 20.0)); contents->SetColor(Color::Red()); - contents->SetStrokeSize(20.0); entity.SetContents(std::move(contents)); return entity.Render(context, pass); @@ -282,15 +280,11 @@ TEST_P(EntityTest, StrokeCapAndJoinTest) { auto world_matrix = Matrix::MakeScale(GetContentScale()); auto render_path = [width = width, &context, &pass, &world_matrix]( - Path path, SolidStrokeContents::Cap cap, - SolidStrokeContents::Join join) { + Path path, Cap cap, Join join) { auto contents = std::make_unique(); - contents->SetPath(path); + contents->SetGeometry( + Geometry::MakeStrokePath(path, width, miter_limit, cap, join)); contents->SetColor(Color::Red()); - contents->SetStrokeSize(width); - contents->SetStrokeCap(cap); - contents->SetStrokeJoin(join); - contents->SetStrokeMiter(miter_limit); Entity entity; entity.SetTransformation(world_matrix); @@ -299,7 +293,7 @@ TEST_P(EntityTest, StrokeCapAndJoinTest) { auto coverage = entity.GetCoverage(); if (coverage.has_value()) { auto bounds_contents = std::make_unique(); - bounds_contents->SetGeometry(Geometry::MakePath( + bounds_contents->SetGeometry(Geometry::MakeFillPath( PathBuilder{}.AddRect(entity.GetCoverage().value()).TakePath())); bounds_contents->SetColor(Color::Green().WithAlpha(0.5)); Entity bounds_entity; @@ -321,8 +315,7 @@ TEST_P(EntityTest, StrokeCapAndJoinTest) { auto [c, d] = IMPELLER_PLAYGROUND_LINE(off + c_def, off + d_def, r, Color::Black(), Color::White()); render_path(PathBuilder{}.AddCubicCurve(a, b, d, c).TakePath(), - SolidStrokeContents::Cap::kButt, - SolidStrokeContents::Join::kBevel); + Cap::kButt, Join::kBevel); } // Cap::kSquare demo. @@ -333,8 +326,7 @@ TEST_P(EntityTest, StrokeCapAndJoinTest) { auto [c, d] = IMPELLER_PLAYGROUND_LINE(off + c_def, off + d_def, r, Color::Black(), Color::White()); render_path(PathBuilder{}.AddCubicCurve(a, b, d, c).TakePath(), - SolidStrokeContents::Cap::kSquare, - SolidStrokeContents::Join::kBevel); + Cap::kSquare, Join::kBevel); } // Cap::kRound demo. @@ -345,8 +337,7 @@ TEST_P(EntityTest, StrokeCapAndJoinTest) { auto [c, d] = IMPELLER_PLAYGROUND_LINE(off + c_def, off + d_def, r, Color::Black(), Color::White()); render_path(PathBuilder{}.AddCubicCurve(a, b, d, c).TakePath(), - SolidStrokeContents::Cap::kRound, - SolidStrokeContents::Join::kBevel); + Cap::kRound, Join::kBevel); } // Join::kBevel demo. @@ -357,7 +348,7 @@ TEST_P(EntityTest, StrokeCapAndJoinTest) { Point c = IMPELLER_PLAYGROUND_POINT(off + c_def, r, Color::White()); render_path( PathBuilder{}.MoveTo(a).LineTo(b).LineTo(c).Close().TakePath(), - SolidStrokeContents::Cap::kButt, SolidStrokeContents::Join::kBevel); + Cap::kButt, Join::kBevel); } // Join::kMiter demo. @@ -368,7 +359,7 @@ TEST_P(EntityTest, StrokeCapAndJoinTest) { Point c = IMPELLER_PLAYGROUND_POINT(off + c_def, r, Color::White()); render_path( PathBuilder{}.MoveTo(a).LineTo(b).LineTo(c).Close().TakePath(), - SolidStrokeContents::Cap::kButt, SolidStrokeContents::Join::kMiter); + Cap::kButt, Join::kMiter); } // Join::kRound demo. @@ -379,7 +370,7 @@ TEST_P(EntityTest, StrokeCapAndJoinTest) { Point c = IMPELLER_PLAYGROUND_POINT(off + c_def, r, Color::White()); render_path( PathBuilder{}.MoveTo(a).LineTo(b).LineTo(c).Close().TakePath(), - SolidStrokeContents::Cap::kButt, SolidStrokeContents::Join::kRound); + Cap::kButt, Join::kRound); } return true; @@ -639,34 +630,44 @@ TEST_P(EntityTest, CubicCurveAndOverlapTest) { TEST_P(EntityTest, SolidStrokeContentsSetStrokeCapsAndJoins) { { - SolidStrokeContents stroke; + auto geometry = Geometry::MakeStrokePath(Path{}); + auto path_geometry = static_cast(geometry.get()); // Defaults. - ASSERT_EQ(stroke.GetStrokeCap(), SolidStrokeContents::Cap::kButt); - ASSERT_EQ(stroke.GetStrokeJoin(), SolidStrokeContents::Join::kMiter); + ASSERT_EQ(path_geometry->GetStrokeCap(), Cap::kButt); + ASSERT_EQ(path_geometry->GetStrokeJoin(), Join::kMiter); } { - SolidStrokeContents stroke; - stroke.SetStrokeCap(SolidStrokeContents::Cap::kSquare); - ASSERT_EQ(stroke.GetStrokeCap(), SolidStrokeContents::Cap::kSquare); + auto geometry = Geometry::MakeStrokePath(Path{}, 1.0, 4.0, Cap::kSquare); + auto path_geometry = static_cast(geometry.get()); + ASSERT_EQ(path_geometry->GetStrokeCap(), Cap::kSquare); } { - SolidStrokeContents stroke; - stroke.SetStrokeCap(SolidStrokeContents::Cap::kRound); - ASSERT_EQ(stroke.GetStrokeCap(), SolidStrokeContents::Cap::kRound); + auto geometry = Geometry::MakeStrokePath(Path{}, 1.0, 4.0, Cap::kRound); + auto path_geometry = static_cast(geometry.get()); + ASSERT_EQ(path_geometry->GetStrokeCap(), Cap::kRound); } } -TEST_P(EntityTest, SolidStrokeContentsSetMiter) { - SolidStrokeContents contents; - ASSERT_FLOAT_EQ(contents.GetStrokeMiter(), 4); +TEST_P(EntityTest, SolidStrokeContentsSetMiterLimit) { + { + auto geometry = Geometry::MakeStrokePath(Path{}); + auto path_geometry = static_cast(geometry.get()); + ASSERT_FLOAT_EQ(path_geometry->GetMiterLimit(), 4); + } - contents.SetStrokeMiter(8); - ASSERT_FLOAT_EQ(contents.GetStrokeMiter(), 8); + { + auto geometry = Geometry::MakeStrokePath(Path{}, 1.0, /*miter_limit=*/8.0); + auto path_geometry = static_cast(geometry.get()); + ASSERT_FLOAT_EQ(path_geometry->GetMiterLimit(), 8); + } - contents.SetStrokeMiter(-1); - ASSERT_FLOAT_EQ(contents.GetStrokeMiter(), 8); + { + auto geometry = Geometry::MakeStrokePath(Path{}, 1.0, /*miter_limit=*/-1.0); + auto path_geometry = static_cast(geometry.get()); + ASSERT_FLOAT_EQ(path_geometry->GetMiterLimit(), 4); + } } TEST_P(EntityTest, BlendingModeOptions) { @@ -955,7 +956,7 @@ TEST_P(EntityTest, GaussianBlurFilter) { auto fill = std::make_shared(); fill->SetColor(input_color); fill->SetGeometry( - Geometry::MakePath(PathBuilder{}.AddRect(input_rect).TakePath())); + Geometry::MakeFillPath(PathBuilder{}.AddRect(input_rect).TakePath())); input = fill; input_size = input_rect.size; @@ -1140,12 +1141,13 @@ TEST_P(EntityTest, ContentsGetBoundsForEmptyPathReturnsNullopt) { TEST_P(EntityTest, SolidStrokeCoverageIsCorrect) { { + auto geometry = Geometry::MakeStrokePath( + PathBuilder{}.AddLine({0, 0}, {10, 10}).TakePath(), 4.0, 4.0, + Cap::kButt, Join::kBevel); + Entity entity; auto contents = std::make_unique(); - contents->SetPath(PathBuilder{}.AddLine({0, 0}, {10, 10}).TakePath()); - contents->SetStrokeCap(SolidStrokeContents::Cap::kButt); - contents->SetStrokeJoin(SolidStrokeContents::Join::kBevel); - contents->SetStrokeSize(4); + contents->SetGeometry(std::move(geometry)); contents->SetColor(Color::Black()); entity.SetContents(std::move(contents)); auto actual = entity.GetCoverage(); @@ -1156,12 +1158,13 @@ TEST_P(EntityTest, SolidStrokeCoverageIsCorrect) { // Cover the Cap::kSquare case. { + auto geometry = Geometry::MakeStrokePath( + PathBuilder{}.AddLine({0, 0}, {10, 10}).TakePath(), 4.0, 4.0, + Cap::kSquare, Join::kBevel); + Entity entity; auto contents = std::make_unique(); - contents->SetPath(PathBuilder{}.AddLine({0, 0}, {10, 10}).TakePath()); - contents->SetStrokeCap(SolidStrokeContents::Cap::kSquare); - contents->SetStrokeJoin(SolidStrokeContents::Join::kBevel); - contents->SetStrokeSize(4); + contents->SetGeometry(std::move(geometry)); contents->SetColor(Color::Black()); entity.SetContents(std::move(contents)); auto actual = entity.GetCoverage(); @@ -1173,13 +1176,13 @@ TEST_P(EntityTest, SolidStrokeCoverageIsCorrect) { // Cover the Join::kMiter case. { + auto geometry = Geometry::MakeStrokePath( + PathBuilder{}.AddLine({0, 0}, {10, 10}).TakePath(), 4.0, 2.0, + Cap::kSquare, Join::kMiter); + Entity entity; auto contents = std::make_unique(); - contents->SetPath(PathBuilder{}.AddLine({0, 0}, {10, 10}).TakePath()); - contents->SetStrokeCap(SolidStrokeContents::Cap::kSquare); - contents->SetStrokeJoin(SolidStrokeContents::Join::kMiter); - contents->SetStrokeSize(4); - contents->SetStrokeMiter(2); + contents->SetGeometry(std::move(geometry)); contents->SetColor(Color::Black()); entity.SetContents(std::move(contents)); auto actual = entity.GetCoverage(); @@ -1191,7 +1194,7 @@ TEST_P(EntityTest, SolidStrokeCoverageIsCorrect) { TEST_P(EntityTest, BorderMaskBlurCoverageIsCorrect) { auto fill = std::make_shared(); - fill->SetGeometry(Geometry::MakePath( + fill->SetGeometry(Geometry::MakeFillPath( PathBuilder{}.AddRect(Rect::MakeXYWH(0, 0, 300, 400)).TakePath())); fill->SetColor(Color::CornflowerBlue()); auto border_mask_blur = FilterContents::MakeBorderMaskBlur( @@ -1424,7 +1427,7 @@ TEST_P(EntityTest, SolidFillCoverageIsCorrect) { fill->SetColor(Color::CornflowerBlue()); auto expected = Rect::MakeLTRB(100, 110, 200, 220); fill->SetGeometry( - Geometry::MakePath(PathBuilder{}.AddRect(expected).TakePath())); + Geometry::MakeFillPath(PathBuilder{}.AddRect(expected).TakePath())); auto coverage = fill->GetCoverage({}); ASSERT_TRUE(coverage.has_value()); @@ -1435,7 +1438,7 @@ TEST_P(EntityTest, SolidFillCoverageIsCorrect) { { auto fill = std::make_shared(); fill->SetColor(Color::CornflowerBlue()); - fill->SetGeometry(Geometry::MakePath( + fill->SetGeometry(Geometry::MakeFillPath( PathBuilder{}.AddRect(Rect::MakeLTRB(100, 110, 200, 220)).TakePath())); Entity entity; @@ -1452,7 +1455,7 @@ TEST_P(EntityTest, SolidFillCoverageIsCorrect) { { auto fill = std::make_shared(); fill->SetColor(Color::WhiteTransparent()); - fill->SetGeometry(Geometry::MakePath( + fill->SetGeometry(Geometry::MakeFillPath( PathBuilder{}.AddRect(Rect::MakeLTRB(100, 110, 200, 220)).TakePath())); auto coverage = fill->GetCoverage({}); @@ -1474,7 +1477,7 @@ TEST_P(EntityTest, SolidFillShouldRenderIsCorrect) { { auto fill = std::make_shared(); fill->SetColor(Color::CornflowerBlue()); - fill->SetGeometry(Geometry::MakePath( + fill->SetGeometry(Geometry::MakeFillPath( PathBuilder{}.AddRect(Rect::MakeLTRB(0, 0, 100, 100)).TakePath())); ASSERT_TRUE(fill->ShouldRender(Entity{}, Rect::MakeSize(Size{100, 100}))); ASSERT_FALSE( @@ -1499,7 +1502,7 @@ TEST_P(EntityTest, ClipContentsShouldRenderIsCorrect) { { auto clip = std::make_shared(); ASSERT_TRUE(clip->ShouldRender(Entity{}, Rect::MakeSize(Size{100, 100}))); - clip->SetGeometry(Geometry::MakePath( + clip->SetGeometry(Geometry::MakeFillPath( PathBuilder{}.AddRect(Rect::MakeLTRB(0, 0, 100, 100)).TakePath())); ASSERT_TRUE(clip->ShouldRender(Entity{}, Rect::MakeSize(Size{100, 100}))); ASSERT_TRUE( @@ -1559,7 +1562,7 @@ TEST_P(EntityTest, RRectShadowTest) { auto coverage = entity.GetCoverage(); if (show_coverage && coverage.has_value()) { auto bounds_contents = std::make_unique(); - bounds_contents->SetGeometry(Geometry::MakePath( + bounds_contents->SetGeometry(Geometry::MakeFillPath( PathBuilder{}.AddRect(entity.GetCoverage().value()).TakePath())); bounds_contents->SetColor(coverage_color.Premultiply()); Entity bounds_entity; @@ -1575,7 +1578,7 @@ TEST_P(EntityTest, RRectShadowTest) { TEST_P(EntityTest, ColorMatrixFilterCoverageIsCorrect) { // Set up a simple color background. auto fill = std::make_shared(); - fill->SetGeometry(Geometry::MakePath( + fill->SetGeometry(Geometry::MakeFillPath( PathBuilder{}.AddRect(Rect::MakeXYWH(0, 0, 300, 400)).TakePath())); fill->SetColor(Color::Coral()); @@ -1669,7 +1672,7 @@ TEST_P(EntityTest, ColorMatrixFilterEditable) { TEST_P(EntityTest, LinearToSrgbFilterCoverageIsCorrect) { // Set up a simple color background. auto fill = std::make_shared(); - fill->SetGeometry(Geometry::MakePath( + fill->SetGeometry(Geometry::MakeFillPath( PathBuilder{}.AddRect(Rect::MakeXYWH(0, 0, 300, 400)).TakePath())); fill->SetColor(Color::MintCream()); @@ -1721,7 +1724,7 @@ TEST_P(EntityTest, LinearToSrgbFilter) { TEST_P(EntityTest, SrgbToLinearFilterCoverageIsCorrect) { // Set up a simple color background. auto fill = std::make_shared(); - fill->SetGeometry(Geometry::MakePath( + fill->SetGeometry(Geometry::MakeFillPath( PathBuilder{}.AddRect(Rect::MakeXYWH(0, 0, 300, 400)).TakePath())); fill->SetColor(Color::DeepPink()); diff --git a/engine/src/flutter/impeller/entity/geometry.cc b/engine/src/flutter/impeller/entity/geometry.cc index 35c3d9f1bb..aad8042d4c 100644 --- a/engine/src/flutter/impeller/entity/geometry.cc +++ b/engine/src/flutter/impeller/entity/geometry.cc @@ -7,6 +7,7 @@ #include "impeller/entity/position_color.vert.h" #include "impeller/geometry/path_builder.h" #include "impeller/renderer/device_buffer.h" +#include "impeller/renderer/render_pass.h" #include "impeller/tessellator/tessellator.h" namespace impeller { @@ -20,8 +21,21 @@ std::unique_ptr Geometry::MakeVertices(Vertices vertices) { return std::make_unique(std::move(vertices)); } -std::unique_ptr Geometry::MakePath(Path path) { - return std::make_unique(std::move(path)); +std::unique_ptr Geometry::MakeFillPath(Path path) { + return std::make_unique(std::move(path)); +} + +std::unique_ptr Geometry::MakeStrokePath(Path path, + Scalar stroke_width, + Scalar miter_limit, + Cap stroke_cap, + Join stroke_join) { + // Skia behaves like this. + if (miter_limit < 0) { + miter_limit = 4.0; + } + return std::make_unique(path, stroke_width, miter_limit, + stroke_cap, stroke_join); } std::unique_ptr Geometry::MakeCover() { @@ -45,11 +59,9 @@ static PrimitiveType GetPrimitiveType(const Vertices& vertices) { } GeometryResult VerticesGeometry::GetPositionBuffer( - std::shared_ptr device_allocator, - HostBuffer& host_buffer, - std::shared_ptr tessellator, - ISize render_target_size, - Scalar max_basis_length) { + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) { if (!vertices_.IsValid()) { return {}; } @@ -62,7 +74,8 @@ GeometryResult VerticesGeometry::GetPositionBuffer( buffer_desc.size = total_vtx_bytes + total_idx_bytes; buffer_desc.storage_mode = StorageMode::kHostVisible; - auto buffer = device_allocator->CreateBuffer(buffer_desc); + auto buffer = + renderer.GetContext()->GetResourceAllocator()->CreateBuffer(buffer_desc); const auto& positions = vertices_.GetPositions(); if (!buffer->CopyHostBuffer( @@ -93,9 +106,9 @@ GeometryResult VerticesGeometry::GetPositionBuffer( } GeometryResult VerticesGeometry::GetPositionColorBuffer( - std::shared_ptr device_allocator, - HostBuffer& host_buffer, - std::shared_ptr tessellator, + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass, Color paint_color, BlendMode blend_mode) { using VS = GeometryColorPipeline::VertexShader; @@ -125,7 +138,8 @@ GeometryResult VerticesGeometry::GetPositionColorBuffer( buffer_desc.size = total_vtx_bytes + total_idx_bytes; buffer_desc.storage_mode = StorageMode::kHostVisible; - auto buffer = device_allocator->CreateBuffer(buffer_desc); + auto buffer = + renderer.GetContext()->GetResourceAllocator()->CreateBuffer(buffer_desc); if (!buffer->CopyHostBuffer(reinterpret_cast(vertex_data.data()), Range{0, total_vtx_bytes}, 0)) { @@ -154,41 +168,39 @@ GeometryResult VerticesGeometry::GetPositionColorBuffer( } GeometryResult VerticesGeometry::GetPositionUVBuffer( - std::shared_ptr device_allocator, - HostBuffer& host_buffer, - std::shared_ptr tessellator, - ISize render_target_size) { + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) { // TODO(jonahwilliams): support texture coordinates in vertices. return {}; } -GeometryVertexType VerticesGeometry::GetVertexType() { +GeometryVertexType VerticesGeometry::GetVertexType() const { if (vertices_.GetColors().size()) { return GeometryVertexType::kColor; } return GeometryVertexType::kPosition; } -std::optional VerticesGeometry::GetCoverage(Matrix transform) { +std::optional VerticesGeometry::GetCoverage( + const Matrix& transform) const { return vertices_.GetTransformedBoundingBox(transform); } /////// Path Geometry /////// -PathGeometry::PathGeometry(Path path) : path_(std::move(path)) {} +FillPathGeometry::FillPathGeometry(Path path) : path_(std::move(path)) {} -PathGeometry::~PathGeometry() = default; +FillPathGeometry::~FillPathGeometry() = default; -GeometryResult PathGeometry::GetPositionBuffer( - std::shared_ptr device_allocator, - HostBuffer& host_buffer, - std::shared_ptr tessellator, - ISize render_target_size, - Scalar max_basis_length) { +GeometryResult FillPathGeometry::GetPositionBuffer( + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) { VertexBuffer vertex_buffer; - auto tesselation_result = tessellator->Tessellate( - path_.GetFillType(), - path_.CreatePolyline(kDefaultCurveTolerance / max_basis_length), + auto& host_buffer = pass.GetTransientsBuffer(); + auto tesselation_result = renderer.GetTessellator()->Tessellate( + path_.GetFillType(), path_.CreatePolyline(), [&vertex_buffer, &host_buffer]( const float* vertices, size_t vertices_count, const uint16_t* indices, size_t indices_count) { @@ -210,47 +222,439 @@ GeometryResult PathGeometry::GetPositionBuffer( }; } -GeometryResult PathGeometry::GetPositionColorBuffer( - std::shared_ptr device_allocator, - HostBuffer& host_buffer, - std::shared_ptr tessellator, +GeometryResult FillPathGeometry::GetPositionColorBuffer( + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass, Color paint_color, BlendMode blend_mode) { // TODO(jonahwilliams): support per-color vertex in path geometry. return {}; } -GeometryResult PathGeometry::GetPositionUVBuffer( - std::shared_ptr device_allocator, - HostBuffer& host_buffer, - std::shared_ptr tessellator, - ISize render_target_size) { +GeometryResult FillPathGeometry::GetPositionUVBuffer( + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) { // TODO(jonahwilliams): support texture coordinates in path geometry. return {}; } -GeometryVertexType PathGeometry::GetVertexType() { +GeometryVertexType FillPathGeometry::GetVertexType() const { return GeometryVertexType::kPosition; } -std::optional PathGeometry::GetCoverage(Matrix transform) { +std::optional FillPathGeometry::GetCoverage( + const Matrix& transform) const { return path_.GetTransformedBoundingBox(transform); } +///// Stroke Geometry ////// + +StrokePathGeometry::StrokePathGeometry(Path path, + Scalar stroke_width, + Scalar miter_limit, + Cap stroke_cap, + Join stroke_join) + : path_(std::move(path)), + stroke_width_(stroke_width), + miter_limit_(miter_limit), + stroke_cap_(stroke_cap), + stroke_join_(stroke_join) {} + +StrokePathGeometry::~StrokePathGeometry() = default; + +Scalar StrokePathGeometry::GetStrokeWidth() const { + return stroke_width_; +} + +Scalar StrokePathGeometry::GetMiterLimit() const { + return miter_limit_; +} + +Cap StrokePathGeometry::GetStrokeCap() const { + return stroke_cap_; +} + +Join StrokePathGeometry::GetStrokeJoin() const { + return stroke_join_; +} + +// static +Scalar StrokePathGeometry::CreateBevelAndGetDirection( + VertexBufferBuilder& vtx_builder, + const Point& position, + const Point& start_offset, + const Point& end_offset) { + SolidFillVertexShader::PerVertexData vtx; + vtx.position = position; + vtx_builder.AppendVertex(vtx); + + Scalar dir = start_offset.Cross(end_offset) > 0 ? -1 : 1; + vtx.position = position + start_offset * dir; + vtx_builder.AppendVertex(vtx); + vtx.position = position + end_offset * dir; + vtx_builder.AppendVertex(vtx); + + return dir; +} + +// static +StrokePathGeometry::JoinProc StrokePathGeometry::GetJoinProc(Join stroke_join) { + using VS = SolidFillVertexShader; + StrokePathGeometry::JoinProc join_proc; + switch (stroke_join) { + case Join::kBevel: + join_proc = [](VertexBufferBuilder& vtx_builder, + const Point& position, const Point& start_offset, + const Point& end_offset, Scalar miter_limit, + Scalar tolerance) { + CreateBevelAndGetDirection(vtx_builder, position, start_offset, + end_offset); + }; + break; + case Join::kMiter: + join_proc = [](VertexBufferBuilder& vtx_builder, + const Point& position, const Point& start_offset, + const Point& end_offset, Scalar miter_limit, + Scalar tolerance) { + Point start_normal = start_offset.Normalize(); + Point end_normal = end_offset.Normalize(); + + // 1 for no joint (straight line), 0 for max joint (180 degrees). + Scalar alignment = (start_normal.Dot(end_normal) + 1) / 2; + if (ScalarNearlyEqual(alignment, 1)) { + return; + } + + Scalar dir = CreateBevelAndGetDirection(vtx_builder, position, + start_offset, end_offset); + + Point miter_point = (start_offset + end_offset) / 2 / alignment; + if (miter_point.GetDistanceSquared({0, 0}) > + miter_limit * miter_limit) { + return; // Convert to bevel when we exceed the miter limit. + } + + // Outer miter point. + VS::PerVertexData vtx; + vtx.position = position + miter_point * dir; + vtx_builder.AppendVertex(vtx); + }; + break; + case Join::kRound: + join_proc = [](VertexBufferBuilder& vtx_builder, + const Point& position, const Point& start_offset, + const Point& end_offset, Scalar miter_limit, + Scalar tolerance) { + Point start_normal = start_offset.Normalize(); + Point end_normal = end_offset.Normalize(); + + // 0 for no joint (straight line), 1 for max joint (180 degrees). + Scalar alignment = 1 - (start_normal.Dot(end_normal) + 1) / 2; + if (ScalarNearlyEqual(alignment, 0)) { + return; + } + + Scalar dir = CreateBevelAndGetDirection(vtx_builder, position, + start_offset, end_offset); + + Point middle = + (start_offset + end_offset).Normalize() * start_offset.GetLength(); + Point middle_normal = middle.Normalize(); + + Point middle_handle = middle + Point(-middle.y, middle.x) * + PathBuilder::kArcApproximationMagic * + alignment * dir; + Point start_handle = + start_offset + Point(start_offset.y, -start_offset.x) * + PathBuilder::kArcApproximationMagic * alignment * + dir; + + auto arc_points = CubicPathComponent(start_offset, start_handle, + middle_handle, middle) + .CreatePolyline(tolerance); + + VS::PerVertexData vtx; + for (const auto& point : arc_points) { + vtx.position = position + point * dir; + vtx_builder.AppendVertex(vtx); + vtx.position = position + (-point * dir).Reflect(middle_normal); + vtx_builder.AppendVertex(vtx); + } + }; + break; + } + return join_proc; +} + +// static +StrokePathGeometry::CapProc StrokePathGeometry::GetCapProc(Cap stroke_cap) { + using VS = SolidFillVertexShader; + StrokePathGeometry::CapProc cap_proc; + switch (stroke_cap) { + case Cap::kButt: + cap_proc = [](VertexBufferBuilder& vtx_builder, + const Point& position, const Point& offset, + Scalar tolerance) {}; + break; + case Cap::kRound: + cap_proc = [](VertexBufferBuilder& vtx_builder, + const Point& position, const Point& offset, + Scalar tolerance) { + VS::PerVertexData vtx; + + Point forward(offset.y, -offset.x); + Point forward_normal = forward.Normalize(); + + auto arc_points = + CubicPathComponent( + offset, offset + forward * PathBuilder::kArcApproximationMagic, + forward + offset * PathBuilder::kArcApproximationMagic, forward) + .CreatePolyline(tolerance); + + vtx.position = position + offset; + vtx_builder.AppendVertex(vtx); + vtx.position = position - offset; + vtx_builder.AppendVertex(vtx); + for (const auto& point : arc_points) { + vtx.position = position + point; + vtx_builder.AppendVertex(vtx); + vtx.position = position + (-point).Reflect(forward_normal); + vtx_builder.AppendVertex(vtx); + } + }; + break; + case Cap::kSquare: + cap_proc = [](VertexBufferBuilder& vtx_builder, + const Point& position, const Point& offset, + Scalar tolerance) { + VS::PerVertexData vtx; + vtx.position = position; + + Point forward(offset.y, -offset.x); + + vtx.position = position + offset; + vtx_builder.AppendVertex(vtx); + vtx.position = position - offset; + vtx_builder.AppendVertex(vtx); + vtx.position = position + offset + forward; + vtx_builder.AppendVertex(vtx); + vtx.position = position - offset + forward; + vtx_builder.AppendVertex(vtx); + }; + break; + } + return cap_proc; +} + +// static +VertexBuffer StrokePathGeometry::CreateSolidStrokeVertices( + const Path& path, + HostBuffer& buffer, + Scalar stroke_width, + Scalar scaled_miter_limit, + const StrokePathGeometry::JoinProc& join_proc, + const StrokePathGeometry::CapProc& cap_proc, + Scalar tolerance) { + VertexBufferBuilder vtx_builder; + auto polyline = path.CreatePolyline(); + + VS::PerVertexData vtx; + + // Offset state. + Point offset; + Point previous_offset; // Used for computing joins. + + auto compute_offset = [&polyline, &offset, &previous_offset, + &stroke_width](size_t point_i) { + previous_offset = offset; + Point direction = + (polyline.points[point_i] - polyline.points[point_i - 1]).Normalize(); + offset = Vector2{-direction.y, direction.x} * stroke_width * 0.5; + }; + + for (size_t contour_i = 0; contour_i < polyline.contours.size(); + contour_i++) { + size_t contour_start_point_i, contour_end_point_i; + std::tie(contour_start_point_i, contour_end_point_i) = + polyline.GetContourPointBounds(contour_i); + + switch (contour_end_point_i - contour_start_point_i) { + case 1: { + Point p = polyline.points[contour_start_point_i]; + cap_proc(vtx_builder, p, {-stroke_width * 0.5f, 0}, tolerance); + cap_proc(vtx_builder, p, {stroke_width * 0.5f, 0}, tolerance); + continue; + } + case 0: + continue; // This contour has no renderable content. + default: + break; + } + + // The first point's offset is always the same as the second point. + compute_offset(contour_start_point_i + 1); + const Point contour_first_offset = offset; + + if (contour_i > 0) { + // This branch only executes when we've just finished drawing a contour + // and are switching to a new one. + // We're drawing a triangle strip, so we need to "pick up the pen" by + // appending two vertices at the end of the previous contour and two + // vertices at the start of the new contour (thus connecting the two + // contours with two zero volume triangles, which will be discarded by + // the rasterizer). + vtx.position = polyline.points[contour_start_point_i - 1]; + // Append two vertices when "picking up" the pen so that the triangle + // drawn when moving to the beginning of the new contour will have zero + // volume. + vtx_builder.AppendVertex(vtx); + vtx_builder.AppendVertex(vtx); + + vtx.position = polyline.points[contour_start_point_i]; + // Append two vertices at the beginning of the new contour, which + // appends two triangles of zero area. + vtx_builder.AppendVertex(vtx); + vtx_builder.AppendVertex(vtx); + } + + // Generate start cap. + if (!polyline.contours[contour_i].is_closed) { + cap_proc(vtx_builder, polyline.points[contour_start_point_i], -offset, + tolerance); + } + + // Generate contour geometry. + for (size_t point_i = contour_start_point_i; point_i < contour_end_point_i; + point_i++) { + if (point_i > contour_start_point_i) { + // Generate line rect. + vtx.position = polyline.points[point_i - 1] + offset; + vtx_builder.AppendVertex(vtx); + vtx.position = polyline.points[point_i - 1] - offset; + vtx_builder.AppendVertex(vtx); + vtx.position = polyline.points[point_i] + offset; + vtx_builder.AppendVertex(vtx); + vtx.position = polyline.points[point_i] - offset; + vtx_builder.AppendVertex(vtx); + + if (point_i < contour_end_point_i - 1) { + compute_offset(point_i + 1); + + // Generate join from the current line to the next line. + join_proc(vtx_builder, polyline.points[point_i], previous_offset, + offset, scaled_miter_limit, tolerance); + } + } + } + + // Generate end cap or join. + if (!polyline.contours[contour_i].is_closed) { + cap_proc(vtx_builder, polyline.points[contour_end_point_i - 1], offset, + tolerance); + } else { + join_proc(vtx_builder, polyline.points[contour_start_point_i], offset, + contour_first_offset, scaled_miter_limit, tolerance); + } + } + + return vtx_builder.CreateVertexBuffer(buffer); +} + +GeometryResult StrokePathGeometry::GetPositionBuffer( + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) { + if (stroke_width_ < 0.0) { + return {}; + } + auto determinant = entity.GetTransformation().GetDeterminant(); + if (determinant == 0) { + return {}; + } + + Scalar min_size = 1.0f / sqrt(std::abs(determinant)); + Scalar stroke_width = std::max(stroke_width_, min_size); + + auto tolerance = + kDefaultCurveTolerance / + (stroke_width_ * entity.GetTransformation().GetMaxBasisLength()); + + auto& host_buffer = pass.GetTransientsBuffer(); + auto vertex_buffer = CreateSolidStrokeVertices( + path_, host_buffer, stroke_width, miter_limit_ * stroke_width_ * 0.5, + GetJoinProc(stroke_join_), GetCapProc(stroke_cap_), tolerance); + + return GeometryResult{ + .type = PrimitiveType::kTriangleStrip, + .vertex_buffer = vertex_buffer, + .prevent_overdraw = true, + }; +} + +GeometryResult StrokePathGeometry::GetPositionColorBuffer( + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass, + Color paint_color, + BlendMode blend_mode) { + // TODO(jonahwilliams): support per-color vertex in path geometry. + return {}; +} + +GeometryResult StrokePathGeometry::GetPositionUVBuffer( + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) { + // TODO(jonahwilliams): support texture coordinates in path geometry. + return {}; +} + +GeometryVertexType StrokePathGeometry::GetVertexType() const { + return GeometryVertexType::kPosition; +} + +std::optional StrokePathGeometry::GetCoverage( + const Matrix& transform) const { + auto path_bounds = path_.GetBoundingBox(); + if (!path_bounds.has_value()) { + return std::nullopt; + } + auto path_coverage = path_bounds->TransformBounds(transform); + + Scalar max_radius = 0.5; + if (stroke_cap_ == Cap::kSquare) { + max_radius = max_radius * kSqrt2; + } + if (stroke_join_ == Join::kMiter) { + max_radius = std::max(max_radius, miter_limit_ * 0.5f); + } + Scalar determinant = transform.GetDeterminant(); + if (determinant == 0) { + return std::nullopt; + } + Scalar min_size = 1.0f / sqrt(std::abs(determinant)); + Vector2 max_radius_xy = transform.TransformDirection( + Vector2(max_radius, max_radius) * std::max(stroke_width_, min_size)); + + return Rect(path_coverage.origin - max_radius_xy, + Size(path_coverage.size.width + max_radius_xy.x * 2, + path_coverage.size.height + max_radius_xy.y * 2)); +} + /////// Cover Geometry /////// CoverGeometry::CoverGeometry() = default; CoverGeometry::~CoverGeometry() = default; -GeometryResult CoverGeometry::GetPositionBuffer( - std::shared_ptr device_allocator, - HostBuffer& host_buffer, - std::shared_ptr tessellator, - ISize render_target_size, - Scalar max_basis_length) { - auto rect = Rect(Size(render_target_size)); +GeometryResult CoverGeometry::GetPositionBuffer(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) { + auto rect = Rect(Size(pass.GetRenderTargetSize())); constexpr uint16_t kRectIndicies[4] = {0, 1, 2, 3}; + auto& host_buffer = pass.GetTransientsBuffer(); return GeometryResult{ .type = PrimitiveType::kTriangleStrip, .vertex_buffer = {.vertex_buffer = host_buffer.Emplace( @@ -266,9 +670,9 @@ GeometryResult CoverGeometry::GetPositionBuffer( } GeometryResult CoverGeometry::GetPositionColorBuffer( - std::shared_ptr device_allocator, - HostBuffer& host_buffer, - std::shared_ptr tessellator, + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass, Color paint_color, BlendMode blend_mode) { // TODO(jonahwilliams): support per-color vertex in cover geometry. @@ -276,19 +680,18 @@ GeometryResult CoverGeometry::GetPositionColorBuffer( } GeometryResult CoverGeometry::GetPositionUVBuffer( - std::shared_ptr device_allocator, - HostBuffer& host_buffer, - std::shared_ptr tessellator, - ISize render_target_size) { + const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) { // TODO(jonahwilliams): support texture coordinates in cover geometry. return {}; } -GeometryVertexType CoverGeometry::GetVertexType() { +GeometryVertexType CoverGeometry::GetVertexType() const { return GeometryVertexType::kPosition; } -std::optional CoverGeometry::GetCoverage(Matrix transform) { +std::optional CoverGeometry::GetCoverage(const Matrix& transform) const { return Rect::MakeMaximum(); } diff --git a/engine/src/flutter/impeller/entity/geometry.h b/engine/src/flutter/impeller/entity/geometry.h index 666da614f6..e0b69042b9 100644 --- a/engine/src/flutter/impeller/entity/geometry.h +++ b/engine/src/flutter/impeller/entity/geometry.h @@ -4,6 +4,9 @@ #pragma once +#include "impeller/entity/contents/contents.h" +#include "impeller/entity/entity.h" +#include "impeller/entity/solid_fill.vert.h" #include "impeller/geometry/color.h" #include "impeller/geometry/path.h" #include "impeller/geometry/vertices.h" @@ -27,6 +30,18 @@ enum GeometryVertexType { kUV, }; +enum class Cap { + kButt, + kRound, + kSquare, +}; + +enum class Join { + kMiter, + kRound, + kBevel, +}; + class Geometry { public: Geometry(); @@ -35,115 +50,191 @@ class Geometry { static std::unique_ptr MakeVertices(Vertices vertices); - static std::unique_ptr MakePath(Path path); + static std::unique_ptr MakeFillPath(Path path); + + static std::unique_ptr MakeStrokePath( + Path path, + Scalar stroke_width = 0.0, + Scalar miter_limit = 4.0, + Cap stroke_cap = Cap::kButt, + Join stroke_join = Join::kMiter); static std::unique_ptr MakeCover(); - virtual GeometryResult GetPositionBuffer( - std::shared_ptr device_allocator, - HostBuffer& host_buffer, - std::shared_ptr tessellator, - ISize render_target_size, - Scalar max_basis_length) = 0; + virtual GeometryResult GetPositionBuffer(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) = 0; - virtual GeometryResult GetPositionColorBuffer( - std::shared_ptr device_allocator, - HostBuffer& host_buffer, - std::shared_ptr tessellator, - Color paint_color, - BlendMode blend_mode) = 0; + virtual GeometryResult GetPositionColorBuffer(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass, + Color paint_color, + BlendMode blend_mode) = 0; - virtual GeometryResult GetPositionUVBuffer( - std::shared_ptr device_allocator, - HostBuffer& host_buffer, - std::shared_ptr tessellator, - ISize render_target_size) = 0; + virtual GeometryResult GetPositionUVBuffer(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) = 0; - virtual GeometryVertexType GetVertexType() = 0; + virtual GeometryVertexType GetVertexType() const = 0; - virtual std::optional GetCoverage(Matrix transform) = 0; + virtual std::optional GetCoverage(const Matrix& transform) const = 0; }; /// @brief A geometry that is created from a vertices object. class VerticesGeometry : public Geometry { public: - VerticesGeometry(Vertices vertices); + explicit VerticesGeometry(Vertices vertices); ~VerticesGeometry(); private: // |Geometry| - GeometryResult GetPositionBuffer(std::shared_ptr device_allocator, - HostBuffer& host_buffer, - std::shared_ptr tessellator, - ISize render_target_size, - Scalar max_basis_length) override; + GeometryResult GetPositionBuffer(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) override; // |Geometry| - GeometryResult GetPositionColorBuffer( - std::shared_ptr device_allocator, - HostBuffer& host_buffer, - std::shared_ptr tessellator, - Color paint_color, - BlendMode blend_mode) override; + GeometryResult GetPositionColorBuffer(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass, + Color paint_color, + BlendMode blend_mode) override; // |Geometry| - GeometryResult GetPositionUVBuffer( - std::shared_ptr device_allocator, - HostBuffer& host_buffer, - std::shared_ptr tessellator, - ISize render_target_size) override; + GeometryResult GetPositionUVBuffer(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) override; // |Geometry| - GeometryVertexType GetVertexType() override; + GeometryVertexType GetVertexType() const override; // |Geometry| - std::optional GetCoverage(Matrix transform) override; + std::optional GetCoverage(const Matrix& transform) const override; Vertices vertices_; FML_DISALLOW_COPY_AND_ASSIGN(VerticesGeometry); }; -/// @brief A geometry that is created from a path object. -class PathGeometry : public Geometry { +/// @brief A geometry that is created from a filled path object. +class FillPathGeometry : public Geometry { public: - PathGeometry(Path path); + explicit FillPathGeometry(Path path); - ~PathGeometry(); + ~FillPathGeometry(); private: // |Geometry| - GeometryResult GetPositionBuffer(std::shared_ptr device_allocator, - HostBuffer& host_buffer, - std::shared_ptr tessellator, - ISize render_target_size, - Scalar max_basis_length) override; + GeometryResult GetPositionBuffer(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) override; // |Geometry| - GeometryResult GetPositionColorBuffer( - std::shared_ptr device_allocator, - HostBuffer& host_buffer, - std::shared_ptr tessellator, - Color paint_color, - BlendMode blend_mode) override; + GeometryResult GetPositionColorBuffer(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass, + Color paint_color, + BlendMode blend_mode) override; // |Geometry| - GeometryResult GetPositionUVBuffer( - std::shared_ptr device_allocator, - HostBuffer& host_buffer, - std::shared_ptr tessellator, - ISize render_target_size) override; + GeometryResult GetPositionUVBuffer(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) override; // |Geometry| - GeometryVertexType GetVertexType() override; + GeometryVertexType GetVertexType() const override; // |Geometry| - std::optional GetCoverage(Matrix transform) override; + std::optional GetCoverage(const Matrix& transform) const override; Path path_; - FML_DISALLOW_COPY_AND_ASSIGN(PathGeometry); + FML_DISALLOW_COPY_AND_ASSIGN(FillPathGeometry); +}; + +/// @brief A geometry that is created from a stroked path object. +class StrokePathGeometry : public Geometry { + public: + StrokePathGeometry(Path path, + Scalar stroke_width, + Scalar miter_limit, + Cap stroke_cap, + Join stroke_join); + + ~StrokePathGeometry(); + + Scalar GetStrokeWidth() const; + + Scalar GetMiterLimit() const; + + Cap GetStrokeCap() const; + + Join GetStrokeJoin() const; + + private: + using VS = SolidFillVertexShader; + + using CapProc = + std::function& vtx_builder, + const Point& position, + const Point& offset, + Scalar tolerance)>; + using JoinProc = + std::function& vtx_builder, + const Point& position, + const Point& start_offset, + const Point& end_offset, + Scalar miter_limit, + Scalar tolerance)>; + + // |Geometry| + GeometryResult GetPositionBuffer(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) override; + + // |Geometry| + GeometryResult GetPositionColorBuffer(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass, + Color paint_color, + BlendMode blend_mode) override; + + // |Geometry| + GeometryResult GetPositionUVBuffer(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) override; + + // |Geometry| + GeometryVertexType GetVertexType() const override; + + // |Geometry| + std::optional GetCoverage(const Matrix& transform) const override; + + static Scalar CreateBevelAndGetDirection( + VertexBufferBuilder& vtx_builder, + const Point& position, + const Point& start_offset, + const Point& end_offset); + + static VertexBuffer CreateSolidStrokeVertices(const Path& path, + HostBuffer& buffer, + Scalar stroke_width, + Scalar scaled_miter_limit, + const JoinProc& join_proc, + const CapProc& cap_proc, + Scalar tolerance); + + static StrokePathGeometry::JoinProc GetJoinProc(Join stroke_join); + + static StrokePathGeometry::CapProc GetCapProc(Cap stroke_cap); + + Path path_; + Scalar stroke_width_; + Scalar miter_limit_; + Cap stroke_cap_; + Join stroke_join_; + + FML_DISALLOW_COPY_AND_ASSIGN(StrokePathGeometry); }; /// @brief A geometry that implements "drawPaint" like behavior by covering @@ -156,32 +247,27 @@ class CoverGeometry : public Geometry { private: // |Geometry| - GeometryResult GetPositionBuffer(std::shared_ptr device_allocator, - HostBuffer& host_buffer, - std::shared_ptr tessellator, - ISize render_target_size, - Scalar max_basis_length) override; + GeometryResult GetPositionBuffer(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) override; // |Geometry| - GeometryResult GetPositionColorBuffer( - std::shared_ptr device_allocator, - HostBuffer& host_buffer, - std::shared_ptr tessellator, - Color paint_color, - BlendMode blend_mode) override; + GeometryResult GetPositionColorBuffer(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass, + Color paint_color, + BlendMode blend_mode) override; // |Geometry| - GeometryResult GetPositionUVBuffer( - std::shared_ptr device_allocator, - HostBuffer& host_buffer, - std::shared_ptr tessellator, - ISize render_target_size) override; + GeometryResult GetPositionUVBuffer(const ContentContext& renderer, + const Entity& entity, + RenderPass& pass) override; // |Geometry| - GeometryVertexType GetVertexType() override; + GeometryVertexType GetVertexType() const override; // |Geometry| - std::optional GetCoverage(Matrix transform) override; + std::optional GetCoverage(const Matrix& transform) const override; FML_DISALLOW_COPY_AND_ASSIGN(CoverGeometry); };