diff --git a/impeller/aiks/aiks_unittests.cc b/impeller/aiks/aiks_unittests.cc index 1a7b009d53e00..0df8ff3ce6cae 100644 --- a/impeller/aiks/aiks_unittests.cc +++ b/impeller/aiks/aiks_unittests.cc @@ -3,6 +3,7 @@ // found in the LICENSE file. #include + #include "flutter/testing/testing.h" #include "impeller/aiks/aiks_playground.h" #include "impeller/aiks/canvas.h" @@ -483,40 +484,77 @@ TEST_F(AiksTest, TransformMultipliesCorrectly) { // clang-format on } -TEST_F(AiksTest, PathsShouldHaveUniformAlpha) { +TEST_F(AiksTest, SolidStrokesRenderCorrectly) { // Compare with https://fiddle.skia.org/c/027392122bec8ac2b5d5de00a4b9bbe2 - Canvas canvas; - Paint paint; + bool first_frame = true; + auto callback = [&](AiksContext& renderer, RenderPass& pass) { + if (first_frame) { + first_frame = false; + ImGui::SetNextWindowSize({480, 100}); + ImGui::SetNextWindowPos({100, 550}); + } - paint.color = Color::White(); - canvas.DrawPaint(paint); + static Color color = Color::Black().WithAlpha(0.5); + static float scale = 3; + static bool add_circle_clip = true; - paint.color = Color::Black().WithAlpha(0.5); - paint.style = Paint::Style::kStroke; - paint.stroke_width = 10; + ImGui::Begin("Controls"); + ImGui::ColorEdit4("Color", reinterpret_cast(&color)); + ImGui::SliderFloat("Scale", &scale, 0, 6); + ImGui::Checkbox("Circle clip", &add_circle_clip); + ImGui::End(); + + Canvas canvas; + Paint paint; + + paint.color = Color::White(); + canvas.DrawPaint(paint); + + paint.color = color; + paint.style = Paint::Style::kStroke; + paint.stroke_width = 10; + + Path path = PathBuilder{} + .MoveTo({20, 20}) + .QuadraticCurveTo({60, 20}, {60, 60}) + .Close() + .MoveTo({60, 20}) + .QuadraticCurveTo({60, 60}, {20, 60}) + .TakePath(); - Path path = PathBuilder{} - .MoveTo({20, 20}) - .QuadraticCurveTo({60, 20}, {60, 60}) - .Close() - .MoveTo({60, 20}) - .QuadraticCurveTo({60, 60}, {20, 60}) - .TakePath(); - - canvas.Scale({3, 3}); - for (auto join : - {SolidStrokeContents::Join::kBevel, SolidStrokeContents::Join::kRound, - SolidStrokeContents::Join::kMiter}) { - paint.stroke_join = join; - for (auto cap : - {SolidStrokeContents::Cap::kButt, SolidStrokeContents::Cap::kSquare, - SolidStrokeContents::Cap::kRound}) { - paint.stroke_cap = cap; - canvas.DrawPath(path, paint); - canvas.Translate({80, 0}); + canvas.Scale(Vector2(scale, scale)); + + if (add_circle_clip) { + auto [handle_a, handle_b] = IMPELLER_PLAYGROUND_LINE( + Point(60, 300), Point(600, 300), 20, Color::Red(), Color::Red()); + + auto screen_to_canvas = canvas.GetCurrentTransformation().Invert(); + Point point_a = screen_to_canvas * handle_a; + Point point_b = screen_to_canvas * handle_b; + + Point middle = (point_a + point_b) / 2; + auto radius = point_a.GetDistance(middle); + canvas.ClipPath(PathBuilder{}.AddCircle(middle, radius).TakePath()); } - canvas.Translate({-240, 60}); - } + + for (auto join : + {SolidStrokeContents::Join::kBevel, SolidStrokeContents::Join::kRound, + SolidStrokeContents::Join::kMiter}) { + paint.stroke_join = join; + for (auto cap : + {SolidStrokeContents::Cap::kButt, SolidStrokeContents::Cap::kSquare, + SolidStrokeContents::Cap::kRound}) { + paint.stroke_cap = cap; + canvas.DrawPath(path, paint); + canvas.Translate({80, 0}); + } + canvas.Translate({-240, 60}); + } + + return renderer.Render(canvas.EndRecordingAsPicture(), pass); + }; + + ASSERT_TRUE(OpenPlaygroundHere(callback)); } TEST_F(AiksTest, CoverageOriginShouldBeAccountedForInSubpasses) { diff --git a/impeller/entity/contents/solid_color_contents.cc b/impeller/entity/contents/solid_color_contents.cc index 41b09b85842a9..c809f037ca1c3 100644 --- a/impeller/entity/contents/solid_color_contents.cc +++ b/impeller/entity/contents/solid_color_contents.cc @@ -53,7 +53,7 @@ bool SolidColorContents::Render(const ContentContext& renderer, using VS = SolidFillPipeline::VertexShader; Command cmd; - cmd.label = "SolidFill"; + cmd.label = "Solid Fill"; cmd.pipeline = renderer.GetSolidFillPipeline(OptionsFromPassAndEntity(pass, entity)); cmd.stencil_reference = entity.GetStencilDepth(); @@ -63,7 +63,7 @@ bool SolidColorContents::Render(const ContentContext& renderer, VS::FrameInfo frame_info; frame_info.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) * entity.GetTransformation(); - frame_info.color = color_; + frame_info.color = color_.Premultiply(); VS::BindFrameInfo(cmd, pass.GetTransientsBuffer().EmplaceUniform(frame_info)); cmd.primitive_type = PrimitiveType::kTriangle; diff --git a/impeller/entity/contents/solid_stroke_contents.cc b/impeller/entity/contents/solid_stroke_contents.cc index abbc96687ce47..7e22f80e41f56 100644 --- a/impeller/entity/contents/solid_stroke_contents.cc +++ b/impeller/entity/contents/solid_stroke_contents.cc @@ -4,6 +4,7 @@ #include "solid_stroke_contents.h" +#include "impeller/entity/contents/clip_contents.h" #include "impeller/entity/contents/content_context.h" #include "impeller/entity/entity.h" #include "impeller/geometry/path_builder.h" @@ -78,12 +79,19 @@ static VertexBuffer CreateSolidStrokeVertices( vtx.vertex_position = polyline.points[contour_start_point_i - 1]; vtx.vertex_normal = {}; vtx.pen_down = 0.0; + // Append two transparent vertices when "picking up" the pen so that the + // triangle drawn when moving to the beginning of the new contour will + // have zero volume. This is necessary because strokes with a transparent + // color affect the stencil buffer to prevent overdraw. + vtx_builder.AppendVertex(vtx); vtx_builder.AppendVertex(vtx); vtx.vertex_position = polyline.points[contour_start_point_i]; - // Append two transparent vertices at the beginning of the new contour - // because it's a triangle strip. + // Append two vertices at the beginning of the new contour + // so that the next appended vertex will create a triangle with zero + // volume. vtx_builder.AppendVertex(vtx); + vtx.pen_down = 1.0; vtx_builder.AppendVertex(vtx); } @@ -147,14 +155,18 @@ bool SolidStrokeContents::Render(const ContentContext& renderer, entity.GetTransformation(); VS::StrokeInfo stroke_info; - stroke_info.color = color_; + stroke_info.color = color_.Premultiply(); stroke_info.size = stroke_size_; Command cmd; cmd.primitive_type = PrimitiveType::kTriangleStrip; - cmd.label = "SolidStroke"; - cmd.pipeline = - renderer.GetSolidStrokePipeline(OptionsFromPassAndEntity(pass, entity)); + cmd.label = "Solid Stroke"; + auto options = OptionsFromPassAndEntity(pass, entity); + if (!color_.IsOpaque()) { + options.stencil_compare = CompareFunction::kEqual; + options.stencil_operation = StencilOperation::kIncrementClamp; + } + cmd.pipeline = renderer.GetSolidStrokePipeline(options); cmd.stencil_reference = entity.GetStencilDepth(); auto smoothing = SmoothingApproximation( @@ -167,7 +179,11 @@ bool SolidStrokeContents::Render(const ContentContext& renderer, VS::BindStrokeInfo(cmd, pass.GetTransientsBuffer().EmplaceUniform(stroke_info)); - pass.AddCommand(std::move(cmd)); + pass.AddCommand(cmd); + + if (!color_.IsOpaque()) { + return ClipRestoreContents().Render(renderer, entity, pass); + } return true; } diff --git a/impeller/entity/entity_unittests.cc b/impeller/entity/entity_unittests.cc index ff1d07d278d4a..68db263d73384 100644 --- a/impeller/entity/entity_unittests.cc +++ b/impeller/entity/entity_unittests.cc @@ -752,8 +752,7 @@ TEST_F(EntityTest, GaussianBlurFilter) { // unfiltered input. Entity cover_entity; cover_entity.SetPath(PathBuilder{}.AddRect(rect).TakePath()); - cover_entity.SetContents( - SolidColorContents::Make(cover_color.Premultiply())); + cover_entity.SetContents(SolidColorContents::Make(cover_color)); cover_entity.SetTransformation(ctm); cover_entity.Render(context, pass); @@ -764,8 +763,7 @@ TEST_F(EntityTest, GaussianBlurFilter) { PathBuilder{} .AddRect(target_contents->GetCoverage(entity).value()) .TakePath()); - bounds_entity.SetContents( - SolidColorContents::Make(bounds_color.Premultiply())); + bounds_entity.SetContents(SolidColorContents::Make(bounds_color)); bounds_entity.SetTransformation(Matrix()); bounds_entity.Render(context, pass);