Skip to content

Commit f196dd6

Browse files
committed
Use the stencil buffer to avoid overdraw when rendering non-opaque solid strokes.
Fixes flutter/flutter#101330
1 parent 1a59d38 commit f196dd6

File tree

4 files changed

+94
-42
lines changed

4 files changed

+94
-42
lines changed

aiks/aiks_unittests.cc

Lines changed: 67 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// found in the LICENSE file.
44

55
#include <array>
6+
67
#include "flutter/testing/testing.h"
78
#include "impeller/aiks/aiks_playground.h"
89
#include "impeller/aiks/canvas.h"
@@ -483,40 +484,77 @@ TEST_F(AiksTest, TransformMultipliesCorrectly) {
483484
// clang-format on
484485
}
485486

486-
TEST_F(AiksTest, PathsShouldHaveUniformAlpha) {
487+
TEST_F(AiksTest, SolidStrokesRenderCorrectly) {
487488
// Compare with https://fiddle.skia.org/c/027392122bec8ac2b5d5de00a4b9bbe2
488-
Canvas canvas;
489-
Paint paint;
489+
bool first_frame = true;
490+
auto callback = [&](AiksContext& renderer, RenderPass& pass) {
491+
if (first_frame) {
492+
first_frame = false;
493+
ImGui::SetNextWindowSize({480, 100});
494+
ImGui::SetNextWindowPos({100, 550});
495+
}
490496

491-
paint.color = Color::White();
492-
canvas.DrawPaint(paint);
497+
static Color color = Color::Black().WithAlpha(0.5);
498+
static float scale = 3;
499+
static bool add_circle_clip = true;
493500

494-
paint.color = Color::Black().WithAlpha(0.5);
495-
paint.style = Paint::Style::kStroke;
496-
paint.stroke_width = 10;
501+
ImGui::Begin("Controls");
502+
ImGui::ColorEdit4("Color", reinterpret_cast<float*>(&color));
503+
ImGui::SliderFloat("Scale", &scale, 0, 6);
504+
ImGui::Checkbox("Circle clip", &add_circle_clip);
505+
ImGui::End();
506+
507+
Canvas canvas;
508+
Paint paint;
509+
510+
paint.color = Color::White();
511+
canvas.DrawPaint(paint);
512+
513+
paint.color = color;
514+
paint.style = Paint::Style::kStroke;
515+
paint.stroke_width = 10;
516+
517+
Path path = PathBuilder{}
518+
.MoveTo({20, 20})
519+
.QuadraticCurveTo({60, 20}, {60, 60})
520+
.Close()
521+
.MoveTo({60, 20})
522+
.QuadraticCurveTo({60, 60}, {20, 60})
523+
.TakePath();
497524

498-
Path path = PathBuilder{}
499-
.MoveTo({20, 20})
500-
.QuadraticCurveTo({60, 20}, {60, 60})
501-
.Close()
502-
.MoveTo({60, 20})
503-
.QuadraticCurveTo({60, 60}, {20, 60})
504-
.TakePath();
505-
506-
canvas.Scale({3, 3});
507-
for (auto join :
508-
{SolidStrokeContents::Join::kBevel, SolidStrokeContents::Join::kRound,
509-
SolidStrokeContents::Join::kMiter}) {
510-
paint.stroke_join = join;
511-
for (auto cap :
512-
{SolidStrokeContents::Cap::kButt, SolidStrokeContents::Cap::kSquare,
513-
SolidStrokeContents::Cap::kRound}) {
514-
paint.stroke_cap = cap;
515-
canvas.DrawPath(path, paint);
516-
canvas.Translate({80, 0});
525+
canvas.Scale(Vector2(scale, scale));
526+
527+
if (add_circle_clip) {
528+
auto [handle_a, handle_b] = IMPELLER_PLAYGROUND_LINE(
529+
Point(60, 300), Point(600, 300), 20, Color::Red(), Color::Red());
530+
531+
auto screen_to_canvas = canvas.GetCurrentTransformation().Invert();
532+
Point point_a = screen_to_canvas * handle_a;
533+
Point point_b = screen_to_canvas * handle_b;
534+
535+
Point middle = (point_a + point_b) / 2;
536+
auto radius = point_a.GetDistance(middle);
537+
canvas.ClipPath(PathBuilder{}.AddCircle(middle, radius).TakePath());
517538
}
518-
canvas.Translate({-240, 60});
519-
}
539+
540+
for (auto join :
541+
{SolidStrokeContents::Join::kBevel, SolidStrokeContents::Join::kRound,
542+
SolidStrokeContents::Join::kMiter}) {
543+
paint.stroke_join = join;
544+
for (auto cap :
545+
{SolidStrokeContents::Cap::kButt, SolidStrokeContents::Cap::kSquare,
546+
SolidStrokeContents::Cap::kRound}) {
547+
paint.stroke_cap = cap;
548+
canvas.DrawPath(path, paint);
549+
canvas.Translate({80, 0});
550+
}
551+
canvas.Translate({-240, 60});
552+
}
553+
554+
return renderer.Render(canvas.EndRecordingAsPicture(), pass);
555+
};
556+
557+
ASSERT_TRUE(OpenPlaygroundHere(callback));
520558
}
521559

522560
TEST_F(AiksTest, CoverageOriginShouldBeAccountedForInSubpasses) {

entity/contents/solid_color_contents.cc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ bool SolidColorContents::Render(const ContentContext& renderer,
5353
using VS = SolidFillPipeline::VertexShader;
5454

5555
Command cmd;
56-
cmd.label = "SolidFill";
56+
cmd.label = "Solid Fill";
5757
cmd.pipeline =
5858
renderer.GetSolidFillPipeline(OptionsFromPassAndEntity(pass, entity));
5959
cmd.stencil_reference = entity.GetStencilDepth();
@@ -63,7 +63,7 @@ bool SolidColorContents::Render(const ContentContext& renderer,
6363
VS::FrameInfo frame_info;
6464
frame_info.mvp = Matrix::MakeOrthographic(pass.GetRenderTargetSize()) *
6565
entity.GetTransformation();
66-
frame_info.color = color_;
66+
frame_info.color = color_.Premultiply();
6767
VS::BindFrameInfo(cmd, pass.GetTransientsBuffer().EmplaceUniform(frame_info));
6868

6969
cmd.primitive_type = PrimitiveType::kTriangle;

entity/contents/solid_stroke_contents.cc

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
#include "solid_stroke_contents.h"
66

7+
#include "impeller/entity/contents/clip_contents.h"
78
#include "impeller/entity/contents/content_context.h"
89
#include "impeller/entity/entity.h"
910
#include "impeller/geometry/path_builder.h"
@@ -78,12 +79,19 @@ static VertexBuffer CreateSolidStrokeVertices(
7879
vtx.vertex_position = polyline.points[contour_start_point_i - 1];
7980
vtx.vertex_normal = {};
8081
vtx.pen_down = 0.0;
82+
// Append two transparent vertices when "picking up" the pen so that the
83+
// triangle drawn when moving to the beginning of the new contour will
84+
// have zero volume. This is necessary because strokes with a transparent
85+
// color affect the stencil buffer to prevent overdraw.
86+
vtx_builder.AppendVertex(vtx);
8187
vtx_builder.AppendVertex(vtx);
8288

8389
vtx.vertex_position = polyline.points[contour_start_point_i];
84-
// Append two transparent vertices at the beginning of the new contour
85-
// because it's a triangle strip.
90+
// Append two vertices at the beginning of the new contour
91+
// so that the next appended vertex will create a triangle with zero
92+
// volume.
8693
vtx_builder.AppendVertex(vtx);
94+
vtx.pen_down = 1.0;
8795
vtx_builder.AppendVertex(vtx);
8896
}
8997

@@ -147,14 +155,18 @@ bool SolidStrokeContents::Render(const ContentContext& renderer,
147155
entity.GetTransformation();
148156

149157
VS::StrokeInfo stroke_info;
150-
stroke_info.color = color_;
158+
stroke_info.color = color_.Premultiply();
151159
stroke_info.size = stroke_size_;
152160

153161
Command cmd;
154162
cmd.primitive_type = PrimitiveType::kTriangleStrip;
155-
cmd.label = "SolidStroke";
156-
cmd.pipeline =
157-
renderer.GetSolidStrokePipeline(OptionsFromPassAndEntity(pass, entity));
163+
cmd.label = "Solid Stroke";
164+
auto options = OptionsFromPassAndEntity(pass, entity);
165+
if (!color_.IsOpaque()) {
166+
options.stencil_compare = CompareFunction::kEqual;
167+
options.stencil_operation = StencilOperation::kIncrementClamp;
168+
}
169+
cmd.pipeline = renderer.GetSolidStrokePipeline(options);
158170
cmd.stencil_reference = entity.GetStencilDepth();
159171

160172
auto smoothing = SmoothingApproximation(
@@ -167,7 +179,11 @@ bool SolidStrokeContents::Render(const ContentContext& renderer,
167179
VS::BindStrokeInfo(cmd,
168180
pass.GetTransientsBuffer().EmplaceUniform(stroke_info));
169181

170-
pass.AddCommand(std::move(cmd));
182+
pass.AddCommand(cmd);
183+
184+
if (!color_.IsOpaque()) {
185+
return ClipRestoreContents().Render(renderer, entity, pass);
186+
}
171187

172188
return true;
173189
}

entity/entity_unittests.cc

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -752,8 +752,7 @@ TEST_F(EntityTest, GaussianBlurFilter) {
752752
// unfiltered input.
753753
Entity cover_entity;
754754
cover_entity.SetPath(PathBuilder{}.AddRect(rect).TakePath());
755-
cover_entity.SetContents(
756-
SolidColorContents::Make(cover_color.Premultiply()));
755+
cover_entity.SetContents(SolidColorContents::Make(cover_color));
757756
cover_entity.SetTransformation(ctm);
758757

759758
cover_entity.Render(context, pass);
@@ -764,8 +763,7 @@ TEST_F(EntityTest, GaussianBlurFilter) {
764763
PathBuilder{}
765764
.AddRect(target_contents->GetCoverage(entity).value())
766765
.TakePath());
767-
bounds_entity.SetContents(
768-
SolidColorContents::Make(bounds_color.Premultiply()));
766+
bounds_entity.SetContents(SolidColorContents::Make(bounds_color));
769767
bounds_entity.SetTransformation(Matrix());
770768

771769
bounds_entity.Render(context, pass);

0 commit comments

Comments
 (0)