Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Create a mechanism to manage layer state #36458

Merged
merged 18 commits into from
Oct 26, 2022

Conversation

flar
Copy link
Contributor

@flar flar commented Sep 27, 2022

A new mechanism is added to manage all state between layers, including transforms, clips, and saveLayer attributes like opacity, ColorFilter and ImageFilter.

The advantages of this mechanism include:

  • Can eventually be merged with MutatorsStack for a single mechanism for both Preroll and Paint (future work TBD)
  • We no longer need to use an SkNWayCanvas or DisplayListMultiplexer to broadcast all state changes to the multiple recorders used for the embedder layers - one mechanism records all state changes and updates the "current embedder layer canvas" when the time comes to switch these recorders. Only one recorder (the one currently being rendered into) will receive state changes at a time and when we switch, the appropriate contextual state will be swapped into the new recorder.
  • Now that all state is collected into a common mechanism, it is easier to implement peephole-type implementations without having to create an obscure inter-layer communication system - the state mechanism itself will be the communication channel for all of that information as well as the focal point for future optimizations along those lines.

TBD:

  • The layer caching mechanisms may have broken here, we should instead rely on the ContainerLayer::PaintChildren method to implement that, but the work to switch that over hasn't been done yet. Some of the current layer caching might work, but I may have broken it - further testing and review will be needed to verify that. Testing seems to show that the layer caching is working fine, but I feel like I just edited it until it compiled OK rather than specifically designing how it would work with the new mechanism so a good set of eyes looking it over would be recommended.
  • More testing of attribute interaction needed in the unittests. For example, adding an opacity after an image filter vs before it, and other combinations. Verify that they combine - or don't combine - as we expect. I believe I added a decent number of tests of these conditions.
  • We should duplicate the layer_state_stack unittests as layer tests that exercise the same operations to ensure that the integration into the layers behaves as the mechanism intends. Existing layer tests would confirm that the state is still applied as needed, though I didn't specifically write flutter::Layer based state-interaction tests.

@skia-gold
Copy link

Gold has detected about 4 new digest(s) on patchset 2.
View them at https://flutter-engine-gold.skia.org/cl/github/36458

Copy link
Member

@jonahwilliams jonahwilliams left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've only taken a quick look through this so far, some thoughts:

Really like how it cleans up the Layer::Paint implementations and generalizes the opacity special cases.

I'm not sure I'm getting the distinction between apply/reapply/reapply_all?

As someone reading the code for the first time, I would appreciate more breadcrumbs between mutator.applyFoo() and where the canvas or display list builder receives a saveLayer. (Granted, its no longer 1-1 with this change)

ContainerLayer::Preroll(context, child_matrix);
// We store the inheritance ability of our children for |Paint|
set_children_can_accept_opacity(context->subtree_can_inherit_opacity);
set_children_can_accept_opacity((context->renderable_state_flags &
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we use this for anything besides calling ShouldNotBeCached below?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bunch of unit tests use it to make sure that the right decision was reached.

// The state attribute flags that represent which attributes a
// layer can render if it will be rendering its content/children
// from a cached representation.
static constexpr int RASTER_CACHE_RENDER_FLAGS =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we also fold color filter or image filter into drawing from cache?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Layer caching was somewhat disrupted by this new mechanism. I'll have to resurrect it first.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Layer caching seems to be working OK, even though I thought I had left some wires hanging out of it...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My goal here is to achieve mostly parity with existing layer caching and leave enhancing the caching to new combinations for a future PR...

@flar
Copy link
Contributor Author

flar commented Sep 29, 2022

The unit test failure is caused by the following problem:

  • LayerTree::Flatten only sets up the Builder as the target of the state_stack.
  • all state like clips and transforms then go directly to the builder, bypassing the recorder which is associated with it.
  • PaintChildren asks layers if they need to be painted. These layers rely on canvas->quickReject() to determine that.
  • Since the recorder (which is the SkCanvas in this case) was not involved in the clip/transform state recordings, it makes bad decisions in quickReject.

The same problems should also happen with the Impeller track since it records layers into a builder.

I'll need to move the quickReject checking into the LayerStateStack so that it is based on the common state rather than the state of the (possibly out-of-the-loop) canvas.

(a7b615b fixes this issue)

@flar
Copy link
Contributor Author

flar commented Sep 30, 2022

I see a bug in the way I update the builder/canvas in platform_view_layer.cc. I'm going to try to write a test case that notices as I don't see any existing test cases that catch it... (c0f2262 fixes this issue)

@flar
Copy link
Contributor Author

flar commented Oct 3, 2022

Things that I think remain here:

  • Layer caching, driven from the new state mechanism probably implemented in ContainerLayer::PaintChildren()
  • Echoing (duplicating?) a number of the LayerStateStack unit tests using the associated Layers to drive the cases

@skia-gold
Copy link

Gold has detected about 1 new digest(s) on patchset 10.
View them at https://flutter-engine-gold.skia.org/cl/github/36458

@flar
Copy link
Contributor Author

flar commented Oct 14, 2022

This PR seems to be pretty much there, I'm mainly testing at this point to see what is broken that the tests don't show...

@flar
Copy link
Contributor Author

flar commented Oct 17, 2022

@JsouLiang what do you think of how this is integrating into the existing layer and DL caching mechanisms. I tried to leave those mostly untouched except for what was needed to integrate with the new locations/holders for the state they need.

@flar flar removed the Work in progress (WIP) Not ready (yet) for review! label Oct 17, 2022
@flar flar requested a review from cyanglaz October 18, 2022 19:35
@JsouLiang
Copy link
Contributor

JsouLiang commented Oct 19, 2022

@flar Most of the content is consistent with our previous discussion flow, and I think it is very clear. 👍 I need to take a closer look

Copy link
Contributor

@dnfield dnfield left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've read through most of this and mostly just have nits. I'd like a little clarification around the flags question - I think there's probably a slightly cleaner way to expose what the caller is allowed to do without unintentionally resetting the flags, but that's more of a nitpick.

I'd really like to see a TAP run using this patch if possible. I'm poking at FRoB to see if I can get it to do what I want.

SkMatrix matrix = getTransform();
// We don't need the inverse, but this method tells us if the matrix
// is singular in which case we can reject all rendering.
if (!matrix.invert(nullptr)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will exclude Z perspective right? That's probably fine for this patch, but should we have an issue to follow up on this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's complicated. Since the bounds have no Z then you don't need the Z column. Since we drop the Z while rendering, and we're looking for a 2D bounds, you don't need the Z row either. So, it doesn't drop anything that affects this particular operation.

It does not drop perspective, though. The 3x3 matrix has perspective components. It just drops Z. The reason that down-converting a 4x4 to a 3x3 is bad is because you can no longer compose it accurately with other 4x4 matrices. But, for a given 2D rendering operation (and bounds operation) I don't think you need those elements of the matrix.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should have said "for a leaf 2D operation". All you lose with the 3x3 is composing with future 4x4 matrices and ability to take or generate Z components. None of our geometry has Z and we don't output Z so the only thing that matters is the composability.

That composability is critical for TransformLayer because it could be nested with another TransformLayer, but not in single layer operations.

@@ -2040,6 +2040,7 @@ class CanvasCompareTester {
const uint32_t* test_row = test_pixels->addr32(0, y);
for (int x = 0; x < test_pixels->width(); x++) {
if (ref_row[x] != test_row[x]) {
// FML_LOG(ERROR) << std::hex << ref_row[x] << " != " << test_row[x];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

commented out code

Suggested change
// FML_LOG(ERROR) << std::hex << ref_row[x] << " != " << test_row[x];

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, it is one that I keep adding back in during testing, though. But, it's also easy to recreate when I need it. Maybe I should have it in contingent on "should_match".

// The state attribute flags that represent which attributes a
// layer can render if it plans to use a saveLayer call in its
// |Paint| method.
static constexpr int SAVE_LAYER_RENDER_FLAGS =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: here and elsewhere, constants should be kConstantName, e.g. kSaveLayerRenderFlags, kCallerCanApplyOpacity, etc.

https://google.github.io/styleguide/cppguide.html#Constant_Names

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done (for both these 2 constants and the ones in LayerStateStack)

}

void LayerStateStack::set_initial_cull_rect(const SkRect& cull_rect) {
FML_CHECK(is_empty()) << "set_initial_cull_rect() must be called before any "
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DCHECK is probably more appropriate for these.

A mistake here will cause an application crash that will be harder for developers to work through and report. We could consider logging a warning in this case if it's actionable by the developer, but this seems like it'd be a mistake in the engine if we allow this and that the developer would just have to file a bug to let us know. DCHECKs will still fire in unit tests and make them fail.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done - DCHECK

layer_state_stack_->restore_to_count(stack_restore_count_);
}

AutoRestore LayerStateStack::applyState(const SkRect& bounds,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is all new code and it's not sitting next to other code that has a mix of cases, let's just try to follow the style guide. Here and elsewhere: the methods in classes in this file should use a consistent naming scheme with the style guide. That means snake_case for simple accessors/mutators, otherwise PascalCase

https://google.github.io/styleguide/cppguide.html#Function_Names

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inside the mutator we have "saveLayer" with the capitalization from everywhere else that method appears. Then we have its variant methods like "applyOpacity" which are meant to mirror it. This capitalization scheme then starts leaking into this code. I'm trying to figure out where the line is.

About all I've done so far is get_draw_checkerboard -> draw_checkerboard since it is an accessor, but unfortunately that makes it sound like an operation rather than a getter... Sigh. Maybe I'll rename it to "checkerboard_renderer".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

applyState could probably be renamed to resolve or protect as it conditionally applies a protective saveLayer only if there are outstanding incompatible attributes...?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In Impeller it's Canvas::SaveLayer. We only have saveLayer from either Skia or legacy Blink code.

Comment on lines +210 to +212
// We could do a 4-point long-form conversion, but since this is
// only used for culling, let's just return a non-constricting
// cull rect.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FWIW, the Web engine does this or something like it regardless of perspective. It seems to work fine. That said, it'd be fine to do this in a separate patch to avoid new variance.

Comment on lines +141 to +143
.state_stack = state_stack,
.canvas = frame.canvas(),
.builder = builder,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is so much better.

Comment on lines +141 to +143
.state_stack = state_stack,
.canvas = frame.canvas(),
.builder = builder,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice

if (canvas) {
DrawCheckerboard(canvas, rect);
}
if (builder) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

else if?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That may be true, and true of any number of methods in this file, but very soon one of these variations will be eliminated entirely so I'd like to leave the lines separated. Originally I allowed both until I realized that we should only be updating a single delegate at a time.

Perhaps in another pass I could switch these to use a DelegateHandler with 3 variants for SkCanvas, DLBuilder, and MutatorsStack...

Comment on lines 183 to 186
static constexpr int CALLER_CAN_APPLY_OPACITY = 0x1;
static constexpr int CALLER_CAN_APPLY_COLOR_FILTER = 0x2;
static constexpr int CALLER_CAN_APPLY_IMAGE_FILTER = 0x4;
static constexpr int CALLER_CAN_APPLY_ANYTHING = 0x7;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we make this an enum for some more type safety where these get used?

Should we bother having these public instead of just having method(s) on the class that let you set/clear them? For example, it looks like TextureLayer sets the OPACITY flag and clears others, whereas other layers use |= to add flags. It's not entirely clear to me whether that's intentional or not, whereas methods would be a bit clearer.

Copy link
Contributor Author

@flar flar Oct 25, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are bit fields. Enum would not be appropriate since they are combined (see the ANYTHING value). I could probably make that more obvious by having the ANYTHING be a bit-OR of the 3 values if constexpr allows me to do that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed these to kConstant naming and the Anything version is now a bit-OR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part of the mechanism needs more work that goes beyond just translating existing support into a new mechanism. I would like to remove the state flags from the PrerollContext and manage the congregate values inside LSS itself, but I don't want to make that part of this initial design shift.

Some of the cases involve flags = kCallerCan... because in those cases the code knows that those flags are exactly what can be handled in the Paint method. Some involve |= kCallerCan... because those pieces of code are suggesting that someone else may have made an initial determination of what can be handled and the code in question is trying to say "whatever that other code determined, I now know enough additional information to state that the following attribute can also be handled". If I come up with an interface that manages these flags via method calls, then those method calls would have to be rich enough to describe all of these situations.

For now, I'm hoping to just barely capture existing functionality in a simple form and create a platform from which we can get more sophisticated through additional work.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good.

@flar flar force-pushed the layer-state-stack-2 branch from a2e56eb to 894bf2f Compare October 25, 2022 23:06
@flar
Copy link
Contributor Author

flar commented Oct 26, 2022

@dnfield I think I addressed all or most of your concerns in my last commit. Let me know if I missed something.

Also, I was at a loss to apply the naming conventions to most of the methods in LSS. They talk about using lower_case for getters and mutators which covers a lot of ground. They give an example of using it for a setter in later recommendations, but aren't clear what a mutator is. I also used it for state methods which can appear to be getters and I think it is better for methods like content_culled which are queries. Most of the methods are mixedCase because they model what we've used in the graphics classes in other cases. I'm torn between familiarity with what we've done and vagueness in the style guide. Can you provide specific suggestions?

@dnfield
Copy link
Contributor

dnfield commented Oct 26, 2022

I think we should in general avoid new code that introduces camelCase method names, and just use PascalCase or snake_case as appropriate. This is a nit and easily cleaned up later though. Unfortunately the code base is not consistent on this.

Copy link
Contributor

@dnfield dnfield left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@flar
Copy link
Contributor Author

flar commented Oct 26, 2022

I filed flutter/flutter#114089 to update not only the APIs here, but throughout DL. Soon, hopefully, we will have very Skia API usage within the engine sources and so we will have little legacy naming conventions to confuse things.

@flar flar added the autosubmit Merge PR when tree becomes green via auto submit App label Oct 26, 2022
@auto-submit auto-submit bot merged commit 705939b into flutter:main Oct 26, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Oct 27, 2022
flar added a commit to flar/engine that referenced this pull request Oct 27, 2022
@flar flar mentioned this pull request Oct 27, 2022
flar added a commit that referenced this pull request Oct 27, 2022
* Revert "Fix a clang-tidy warning in display_list_canvas_unittests.cc (#37062)"

This reverts commit 871de2f.

* Revert "Create a mechanism to manage layer state (#36458)"

This reverts commit 705939b.
@flar flar mentioned this pull request Oct 29, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
autosubmit Merge PR when tree becomes green via auto submit App
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants