Skip to content

Commit ed720e8

Browse files
authored
Fix SaveLayer/entity subpass behavioral problems (flutter#111)
1 parent e4db675 commit ed720e8

11 files changed

+116
-29
lines changed

impeller/aiks/aiks_playground.cc

+9-2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,13 @@ AiksPlayground::AiksPlayground() = default;
1313
AiksPlayground::~AiksPlayground() = default;
1414

1515
bool AiksPlayground::OpenPlaygroundHere(const Picture& picture) {
16+
return OpenPlaygroundHere(
17+
[&picture](AiksContext& renderer, RenderPass& pass) -> bool {
18+
return renderer.Render(picture, pass);
19+
});
20+
}
21+
22+
bool AiksPlayground::OpenPlaygroundHere(AiksPlaygroundCallback callback) {
1623
if (!Playground::is_enabled()) {
1724
return true;
1825
}
@@ -24,8 +31,8 @@ bool AiksPlayground::OpenPlaygroundHere(const Picture& picture) {
2431
}
2532

2633
return Playground::OpenPlaygroundHere(
27-
[&renderer, &picture](RenderPass& pass) -> bool {
28-
return renderer.Render(picture, pass);
34+
[&renderer, &callback](RenderPass& pass) -> bool {
35+
return callback(renderer, pass);
2936
});
3037
}
3138

impeller/aiks/aiks_playground.h

+5
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,24 @@
55
#pragma once
66

77
#include "flutter/fml/macros.h"
8+
#include "impeller/aiks/aiks_context.h"
89
#include "impeller/aiks/picture.h"
910
#include "impeller/playground/playground.h"
1011

1112
namespace impeller {
1213

1314
class AiksPlayground : public Playground {
1415
public:
16+
using AiksPlaygroundCallback = std::function<bool(AiksContext& renderer, RenderPass& pass)>;
17+
1518
AiksPlayground();
1619

1720
~AiksPlayground();
1821

1922
bool OpenPlaygroundHere(const Picture& picture);
2023

24+
bool OpenPlaygroundHere(AiksPlaygroundCallback callback);
25+
2126
private:
2227
FML_DISALLOW_COPY_AND_ASSIGN(AiksPlayground);
2328
};

impeller/aiks/aiks_unittests.cc

+32-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "impeller/aiks/image.h"
1010
#include "impeller/geometry/geometry_unittests.h"
1111
#include "impeller/geometry/path_builder.h"
12+
#include "impeller/playground/widgets.h"
1213
#include "impeller/typographer/backends/skia/text_frame_skia.h"
1314
#include "impeller/typographer/backends/skia/text_render_context_skia.h"
1415
#include "third_party/skia/include/core/SkData.h"
@@ -516,8 +517,38 @@ TEST_F(AiksTest, PathsShouldHaveUniformAlpha) {
516517
}
517518
canvas.Translate({-240, 60});
518519
}
520+
}
519521

520-
ASSERT_TRUE(OpenPlaygroundHere(canvas.EndRecordingAsPicture()));
522+
TEST_F(AiksTest, CoverageOriginShouldBeAccountedForInSubpasses) {
523+
auto callback = [](AiksContext& renderer, RenderPass& pass) {
524+
Canvas canvas;
525+
Paint alpha;
526+
alpha.color = Color::Red().WithAlpha(0.5);
527+
528+
auto current = Point{25, 25};
529+
const auto offset = Point{25, 25};
530+
const auto size = Size(100, 100);
531+
532+
auto [b0, b1] = IMPELLER_PLAYGROUND_LINE(Point(40, 40), Point(160, 160), 10,
533+
Color::White(), Color::White());
534+
auto bounds = Rect::MakeLTRB(b0.x, b0.y, b1.x, b1.y);
535+
536+
canvas.DrawRect(bounds, Paint{.color = Color::Yellow(),
537+
.stroke_width = 5.0f,
538+
.style = Paint::Style::kStroke});
539+
540+
canvas.SaveLayer(alpha, bounds);
541+
542+
canvas.DrawRect({current, size}, Paint{.color = Color::Red()});
543+
canvas.DrawRect({current += offset, size}, Paint{.color = Color::Green()});
544+
canvas.DrawRect({current += offset, size}, Paint{.color = Color::Blue()});
545+
546+
canvas.Restore();
547+
548+
return renderer.Render(canvas.EndRecordingAsPicture(), pass);
549+
};
550+
551+
ASSERT_TRUE(OpenPlaygroundHere(callback));
521552
}
522553

523554
} // namespace testing

impeller/aiks/canvas.cc

+2-3
Original file line numberDiff line numberDiff line change
@@ -264,14 +264,13 @@ size_t Canvas::GetStencilDepth() const {
264264

265265
void Canvas::Save(bool create_subpass) {
266266
auto entry = CanvasStackEntry{};
267+
entry.xformation = xformation_stack_.back().xformation;
268+
entry.stencil_depth = xformation_stack_.back().stencil_depth;
267269
if (create_subpass) {
268270
entry.is_subpass = true;
269271
current_pass_ = GetCurrentPass().AddSubpass(std::make_unique<EntityPass>());
270272
current_pass_->SetTransformation(xformation_stack_.back().xformation);
271273
current_pass_->SetStencilDepth(xformation_stack_.back().stencil_depth);
272-
} else {
273-
entry.xformation = xformation_stack_.back().xformation;
274-
entry.stencil_depth = xformation_stack_.back().stencil_depth;
275274
}
276275
xformation_stack_.emplace_back(std::move(entry));
277276
}

impeller/entity/contents/contents.cc

+1-6
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,7 @@ Contents::Contents() = default;
3030
Contents::~Contents() = default;
3131

3232
Rect Contents::GetBounds(const Entity& entity) const {
33-
const auto& transform = entity.GetTransformation();
34-
auto points = entity.GetPath().GetBoundingBox()->GetPoints();
35-
for (uint i = 0; i < points.size(); i++) {
36-
points[i] = transform * points[i];
37-
}
38-
return Rect::MakePointBounds({points.begin(), points.end()});
33+
return entity.GetTransformedPathBounds();
3934
}
4035

4136
std::optional<Snapshot> Contents::RenderToTexture(

impeller/entity/entity.cc

+4-6
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,11 @@ void Entity::SetPath(Path path) {
3131
}
3232

3333
Rect Entity::GetTransformedPathBounds() const {
34-
auto points = GetPath().GetBoundingBox()->GetPoints();
35-
36-
const auto& transform = GetTransformation();
37-
for (uint i = 0; i < points.size(); i++) {
38-
points[i] = transform * points[i];
34+
auto bounds = GetPath().GetBoundingBox();
35+
if (!bounds.has_value()) {
36+
return Rect();
3937
}
40-
return Rect::MakePointBounds({points.begin(), points.end()});
38+
return bounds->TransformBounds(GetTransformation());
4139
}
4240

4341
void Entity::SetAddsToCoverage(bool adds) {

impeller/entity/entity_pass.cc

+27-6
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@
55
#include "impeller/entity/entity_pass.h"
66

77
#include "flutter/fml/trace_event.h"
8+
#include "impeller/base/validation.h"
89
#include "impeller/entity/contents/content_context.h"
910
#include "impeller/geometry/path_builder.h"
1011
#include "impeller/renderer/command_buffer.h"
1112
#include "impeller/renderer/render_pass.h"
13+
#include "impeller/renderer/texture.h"
1214

1315
namespace impeller {
1416

@@ -78,6 +80,9 @@ std::optional<Rect> EntityPass::GetSubpassCoverage(
7880
if (!delegate_coverage.has_value()) {
7981
return entities_coverage;
8082
}
83+
// The delegate coverage hint is in given in local space, so apply the subpass
84+
// transformation.
85+
delegate_coverage = delegate_coverage->TransformBounds(subpass.xformation_);
8186

8287
// If the delegate tells us the coverage is smaller than it needs to be, then
8388
// great. OTOH, if the delegate is being wasteful, limit coverage to what is
@@ -103,22 +108,31 @@ EntityPass* EntityPass::AddSubpass(std::unique_ptr<EntityPass> pass) {
103108
}
104109

105110
bool EntityPass::Render(ContentContext& renderer,
106-
RenderPass& parent_pass) const {
111+
RenderPass& parent_pass,
112+
Point position) const {
107113
TRACE_EVENT0("impeller", "EntityPass::Render");
108114

109-
for (const auto& entity : entities_) {
115+
for (Entity entity : entities_) {
116+
if (!position.IsZero()) {
117+
// If the pass image is going to be rendered with a non-zero position,
118+
// apply the negative translation to entity copies before rendering them
119+
// so that they'll end up rendering to the correct on-screen position.
120+
entity.SetTransformation(Matrix::MakeTranslation(Vector3(-position)) *
121+
entity.GetTransformation());
122+
}
110123
if (!entity.Render(renderer, parent_pass)) {
111124
return false;
112125
}
113126
}
127+
114128
for (const auto& subpass : subpasses_) {
115129
if (delegate_->CanElide()) {
116130
continue;
117131
}
118132

119133
if (delegate_->CanCollapseIntoParentPass()) {
120134
// Directly render into the parent pass and move on.
121-
if (!subpass->Render(renderer, parent_pass)) {
135+
if (!subpass->Render(renderer, parent_pass, position)) {
122136
return false;
123137
}
124138
continue;
@@ -178,7 +192,7 @@ bool EntityPass::Render(ContentContext& renderer,
178192

179193
sub_renderpass->SetLabel("OffscreenPass");
180194

181-
if (!subpass->Render(renderer, *sub_renderpass)) {
195+
if (!subpass->Render(renderer, *sub_renderpass, subpass_coverage->origin)) {
182196
return false;
183197
}
184198

@@ -191,10 +205,17 @@ bool EntityPass::Render(ContentContext& renderer,
191205
}
192206

193207
Entity entity;
194-
entity.SetPath(PathBuilder{}.AddRect(subpass_coverage.value()).TakePath());
208+
entity.SetPath(PathBuilder{}
209+
.AddRect(Rect::MakeSize(subpass_coverage->size))
210+
.TakePath());
195211
entity.SetContents(std::move(offscreen_texture_contents));
196212
entity.SetStencilDepth(stencil_depth_);
197-
entity.SetTransformation(xformation_);
213+
// Once we have filters being applied for SaveLayer, some special sauce
214+
// may be needed here (or in PaintPassDelegate) to ensure the filter
215+
// parameters are transformed by the `xformation_` matrix, while continuing
216+
// to apply only the subpass offset to the offscreen texture.
217+
entity.SetTransformation(
218+
Matrix::MakeTranslation(Vector3(subpass_coverage->origin - position)));
198219
if (!entity.Render(renderer, parent_pass)) {
199220
return false;
200221
}

impeller/entity/entity_pass.h

+3-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,9 @@ class EntityPass {
4848

4949
EntityPass* GetSuperpass() const;
5050

51-
bool Render(ContentContext& renderer, RenderPass& parent_pass) const;
51+
bool Render(ContentContext& renderer,
52+
RenderPass& parent_pass,
53+
Point position = Point()) const;
5254

5355
void IterateAllEntities(std::function<bool(Entity&)> iterator);
5456

impeller/entity/entity_unittests.cc

+8
Original file line numberDiff line numberDiff line change
@@ -779,5 +779,13 @@ TEST_F(EntityTest, SetBlendMode) {
779779
ASSERT_EQ(entity.GetBlendMode(), Entity::BlendMode::kClear);
780780
}
781781

782+
TEST_F(EntityTest, ContentsGetBoundsForEmptyPathReturnsZero) {
783+
Entity entity;
784+
entity.SetContents(std::make_shared<SolidColorContents>());
785+
entity.SetPath({});
786+
ASSERT_TRUE(entity.GetCoverage()->IsZero());
787+
ASSERT_TRUE(entity.GetTransformedPathBounds().IsZero());
788+
}
789+
782790
} // namespace testing
783791
} // namespace impeller

impeller/geometry/geometry_unittests.cc

+10-3
Original file line numberDiff line numberDiff line change
@@ -839,9 +839,16 @@ TEST(GeometryTest, RectGetPoints) {
839839
}
840840

841841
TEST(GeometryTest, RectMakePointBounds) {
842-
auto r = Rect::MakePointBounds({Point(1, 5), Point(4, -1), Point(0, 6)});
843-
auto expected = Rect(0, -1, 4, 7);
844-
ASSERT_RECT_NEAR(r, expected);
842+
{
843+
Rect r =
844+
Rect::MakePointBounds({Point(1, 5), Point(4, -1), Point(0, 6)}).value();
845+
auto expected = Rect(0, -1, 4, 7);
846+
ASSERT_RECT_NEAR(r, expected);
847+
}
848+
{
849+
std::optional<Rect> r = Rect::MakePointBounds({});
850+
ASSERT_FALSE(r.has_value());
851+
}
845852
}
846853

847854
TEST(GeometryTest, CubicPathComponentPolylineDoesNotIncludePointOne) {

impeller/geometry/rect.h

+15-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <ostream>
1010
#include <vector>
1111

12+
#include "impeller/geometry/matrix.h"
1213
#include "impeller/geometry/point.h"
1314
#include "impeller/geometry/scalar.h"
1415
#include "impeller/geometry/size.h"
@@ -51,8 +52,11 @@ struct TRect {
5152
return TRect(0.0, 0.0, size.width, size.height);
5253
}
5354

54-
constexpr static TRect MakePointBounds(
55+
constexpr static std::optional<TRect> MakePointBounds(
5556
const std::vector<TPoint<Type>>& points) {
57+
if (points.empty()) {
58+
return std::nullopt;
59+
}
5660
auto left = points[0].x;
5761
auto top = points[0].y;
5862
auto right = points[0].x;
@@ -143,6 +147,16 @@ struct TRect {
143147
TPoint(right, bottom)};
144148
}
145149

150+
/// @brief Creates a new bounding box that contains this transformed
151+
/// rectangle.
152+
constexpr TRect TransformBounds(const Matrix& transform) const {
153+
auto points = GetPoints();
154+
for (uint i = 0; i < points.size(); i++) {
155+
points[i] = transform * points[i];
156+
}
157+
return TRect::MakePointBounds({points.begin(), points.end()}).value();
158+
}
159+
146160
constexpr TRect Union(const TRect& o) const {
147161
auto this_ltrb = GetLTRB();
148162
auto other_ltrb = o.GetLTRB();

0 commit comments

Comments
 (0)