Skip to content
This repository was archived by the owner on Apr 29, 2022. It is now read-only.

Fix solid stroke overdraw #121

Merged
merged 1 commit into from
Apr 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 67 additions & 29 deletions 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 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 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 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