Skip to content

Commit

Permalink
Use the stencil buffer to avoid overdraw when rendering non-opaque so…
Browse files Browse the repository at this point in the history
…lid strokes. (flutter#121)

Fixes flutter#101330
  • Loading branch information
bdero authored and dnfield committed Apr 27, 2022
1 parent af922f9 commit 4248b5d
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 42 deletions.
96 changes: 67 additions & 29 deletions impeller/aiks/aiks_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// found in the LICENSE file.

#include <array>

#include "flutter/testing/testing.h"
#include "impeller/aiks/aiks_playground.h"
#include "impeller/aiks/canvas.h"
Expand Down Expand Up @@ -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<float*>(&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) {
Expand Down
4 changes: 2 additions & 2 deletions impeller/entity/contents/solid_color_contents.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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;
Expand Down
30 changes: 23 additions & 7 deletions impeller/entity/contents/solid_stroke_contents.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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(
Expand All @@ -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;
}
Expand Down
6 changes: 2 additions & 4 deletions impeller/entity/entity_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand Down

0 comments on commit 4248b5d

Please sign in to comment.