-
Notifications
You must be signed in to change notification settings - Fork 6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WIP: API to track damage rectangles #21126
Conversation
matrix.preTranslate(x,y) is identical to matrix.setConcat(matrix, SkMatrix::Translate(x,y));
Damage rectangle can be determined by providing optional FrameDamage argument to CompositorContext::ScopedFrame::Render
6e39c76
to
495210e
Compare
bool BackdropFilterLayer::compare(const Layer* l1, const Layer* l2) { | ||
const auto* bf1 = reinterpret_cast<const BackdropFilterLayer*>(l1); | ||
const auto* bf2 = reinterpret_cast<const BackdropFilterLayer*>(l2); | ||
auto d1 = bf1->filter_->serialize(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ImageFilter objects should be stable from frame to frame. They should only be replaced in a tree when their contents differ. Are you seeing a lot of cases where the object is different even though the serialized form is the same? If so, then we should fix that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll look into it.
bool ImageFilterLayer::compare(const Layer* l1, const Layer* l2) { | ||
const auto* bf1 = reinterpret_cast<const ImageFilterLayer*>(l1); | ||
const auto* bf2 = reinterpret_cast<const ImageFilterLayer*>(l2); | ||
auto d1 = bf1->filter_->serialize(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment about ImageFilter stability allowing a comparison of the object rather than the contents.
if (index == size_t(-1)) { | ||
layer_entries_.push_back(std::move(e)); | ||
} else { | ||
layer_entries_.insert(layer_entries_.begin() + index, std::move(e)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While it isn't likely to be a problem in practice due to the tree-traversal nature of when this operation is called, an insertion here can invalidate tracking places by remembering a "count". It would be more stable to have layers that need to "go back and insert a contribution" to insert the contribution before recursing to children, or if they need to do it after the fact then to insert a dummy contribution to claim the slot that gets replaced after recursing to the children?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason for this is that pushing layer contribution requires paint bounds. For some layers (i.e. image filter) those are known only after prerolling the children. So we first preroll the children to determine paint bounds and then contribute the layer at order below children.
if (context->damage_context) { | ||
// Adjust display bounds to layers below this layer | ||
context->damage_context->ApplyImageFilter(0, count, filter_.get(), matrix, | ||
paint_bounds()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this not done up above before recursing to children?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because before recursing to children we don't know the bounds.
However this is not going to work anyway I'm afraid. I don't think we can partially redraw clipped portion of backdrop filter. I didn't really think this through. The problem is, that at the edges of clip, it samples pixels outside of clip, which means it would sample pixels from its own children painted in previous frame.
So right now it seems that the easiest solution to get correct output would be to check if the final damage area intersect any backdrop filter, and if it does, extend it to cover entire backdrop filter (+filter bounds?). That way we always redraw entire backdrop filter, but only if any layer that needs to be redrawn intersects it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I understand what you are getting at, but if the BDF expands the dirty regions below it to include its source region then I think it is OK. It has to do that in a way that is not clipped, though.
For most layers there is a simple "I require no input from the screen and I affect these pixels".
For BDF it has both "I require input from this region (regardless of clipping) and I then affect these pixels (which is subject to the clip)". The trick is to express that "where you get your data from" concept with a different handling of clipping.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see that BDF suffers from a cancerous growth of bounds. If it needs to produce output for x,y,w,h
then it needs input from x-d,y-d,w+2d,h+2d
. But, if we then redraw that new rectangle in order to give it the input it needs and we do that in the original framebuffer then that rendering would require input from x-2d,y-2d,w+4d,h+4d
, rinse and repeat up to the maximum clipped bounds of the BDF output (+d).
If we render into an intermediate buffer instead and then just copy the clipped output into the framebuffer, then we can get away with just one expansion of the bounds. The original need to render x,y,w,h
would be performed into a surface that was w+2d,h+2d
in size. When it was done we'd copy out the middle w,h
part of it into the framebuffer. That requires an extra surface to do, but if the repaint region was tiny then it wouldn't matter.
Alternately, for a clipped BDF, we just repaint its clipped size +d and if that is small enough then doing it in place might be the best choice rather than using an intermediate buffer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed. Rendering everything in w+2d,h+2d surface and then just blitting w,h should fix this. But I was also thinking about another optimization. With damage context we should be able determine, that between past and current frame no layer below BDF (that intersects it) needs to be repainted. If that's the case for n subsequent frames, perhaps we could take snapshot of BDF area (after blur and before children) and use it until invalidated. To go even further, if only small area underneath changes, blur it (+d) and for the rest use the snapshot.
I have to think this through. I think for the next iteration I'll focus on making things render correctly (at the expense of repainting entire BDF if needed).
paint_bounds()); | ||
// Push entry before children | ||
context->damage_context->PushLayerContribution(this, compare, matrix, *context, | ||
count); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why does this need to be pushed after the children are prerolled?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It needs to be done after children are prerolled, because otherwise we don't know the paint bounds.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can it be pushed and then have its bounds updated later? Something like:
Contribution c = push(me);
...recurse...;
c.adjustBounds();
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure. I wanted to make LayerContribution private but I don't have any strong feelings about it. Pushing it on arbitrary index does feel bit weird, so this is probably better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it only needs to adjust the bounds, then it could be private and hidden behind a more public interface that exposes just that functionality.
std::shared_ptr<const Layer> layer; | ||
LayerComparator comparator; | ||
|
||
std::vector<std::shared_ptr<Mutator>> mutators; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the DamageContext (and/or the PrerollContext) kept a linked list of Mutator objects then that would save a lot of vector creation of vectors that mostly contain the same objects.
A LayerContribution would simply copy over the pointer from the Context at the time of its creation.
Adding a mutator would simply be "mutator_links = MutatorLink(Mutator, mutator_links)"
Removing a mutator would simply be "mutator_links = mutator_links.next"
Comparisons could even be short circuited. Once we determine that linkX from the previous frame matches linkY from the new frame, then we can tag them as being equal (which implies that their "next" chains are also equal) then we can tag them as equal and avoid recursive comparisons when they appear opposite each other on future chain tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I remember this from first review. I like the idea, but I think the list would need to be double linked (mutator stack exposes both iterator and reverse_iterator). And the iterators would also need to be implemented.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Double linking the list would remove the ability to snapshot it with a pointer copy. The idea was that the earlier entries on the chain, coming from the top-level layers, would stay there and occasionally have a new mutator point to them. But if they also need a reverse pointer then we run into a problem.
The only place I saw iteration was in the comparison of the list. Is that any simpler than double-side-by-side iterations of the two linked lists instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The stack seems to be iterated from bottom to top in EmbedderLayers::PushPlatformViewLayer
and in FuchsiaExternalViewEmbedder::BeginFrame
. I mean technically even with double linked the 'prev' pointer (top to bottom) never changes, so we can "snapshot" the top mutator, as long as we ignore the other pointer and only iterate to the bottom, which is enough for damage_context.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, yes, if we replace the stack in the PrerollContext, then that would be an issue. If we shadow the stack in the DamageContext then we can get away with it. Since we are currently shadowing the stack in every Contribution object then shadowing the stack once in DamageContext would still reduce storage requirements by a large margin.
I think you are correct that snapshotting would still work if the snapshot list is only iterated forward, but I need to mull it a little more.
Also, only doing the linked list in a shadow copy means we don't need to modify the entire engine. Perhaps we could do that for starters and if this gets in there and gets vetted then we can have a follow-on task to promote that to the PrerollContext.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Possible solution: 76bca33#diff-7b84dd247cd3f78e219d26495e874ef4
It doesn't disrupt existing code in any way and reference to MutatorNode
is essentially snapshot of MutatorStack
.
// Adjust display bounds for newly added layers | ||
context->damage_context->ApplyImageFilter( | ||
count, context->damage_context->layer_entries_count(), filter_.get(), | ||
matrix, paint_bounds()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps add a synthetic mutator to the DamageContext that performs these bounds adjustments on the fly?
|
||
// Push entry before children | ||
context->damage_context->PushLayerContribution(this, compare, matrix, *context, | ||
count); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this be inserted before recursion to avoid having to remember the count?
Description
This PR replaces #21009
This PR adds api to track damage rectangles during rendering. Usage is relatively simple, it introduces the following structure in
compositor_context.h
This can be provided as argument to
CompositorContext::ScopedFrame::Raster
. The caller is responsible for providingprevious_frame_description
, which was obtained during previous rasterization to current framebuffer, as well as storing incomingframe_description
.Related Issues
flutter/flutter#33939
Checklist
Before you create this PR confirm that it meets all requirements listed below by checking the relevant checkboxes (
[x]
). This will ensure a smooth and quick review process.Breaking Change
This shouldn't be breaking change, if
frame_damage
is not provided (default).However it does prevent rendering of picture, physical shape and texture layers that are outside of cull rect.
Did any tests fail when you ran them? Please read [handling breaking changes].