mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Use the stencil buffer to avoid overdraw when rendering non-opaque solid strokes. (flutter/engine#121)
Fixes https://github.com/flutter/flutter/issues/101330
This commit is contained in:
parent
15efb79d10
commit
bc4e15c4cb
@ -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"
|
||||
@ -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;
|
||||
|
||||
paint.color = Color::White();
|
||||
canvas.DrawPaint(paint);
|
||||
|
||||
paint.color = Color::Black().WithAlpha(0.5);
|
||||
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();
|
||||
|
||||
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});
|
||||
bool first_frame = true;
|
||||
auto callback = [&](AiksContext& renderer, RenderPass& pass) {
|
||||
if (first_frame) {
|
||||
first_frame = false;
|
||||
ImGui::SetNextWindowSize({480, 100});
|
||||
ImGui::SetNextWindowPos({100, 550});
|
||||
}
|
||||
canvas.Translate({-240, 60});
|
||||
}
|
||||
|
||||
static Color color = Color::Black().WithAlpha(0.5);
|
||||
static float scale = 3;
|
||||
static bool add_circle_clip = true;
|
||||
|
||||
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();
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user