Skip to content
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

Enable partial repaint for iOS/Metal #28801

Merged
merged 16 commits into from
Nov 2, 2021

Conversation

knopp
Copy link
Member

@knopp knopp commented Sep 22, 2021

This enables partial redraw for Metal on iOS. The PR itself is quite big, but it's organized in smaller commits.

List which issues are fixed by this PR:

flutter/flutter#33939 (partially)

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide and the C++, Objective-C, Java style guides.
  • I listed at least one issue that this PR fixes in the description above.
  • I added new tests to check the change I am making or feature I am adding, or Hixie said the PR is test-exempt. See testing the engine for instructions on
    writing and running engine tests.
  • I updated/added relevant documentation (doc comments with ///).
  • I signed the CLA.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.


std::optional<SkRect> clip_rect;

if (frame_damage && layer_tree.root_layer()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Consider making this a method of its own: std::optional<SkRect> ComputeClipRect(...).


frame_damage->damage_out =
context.ComputeDamage(frame_damage->additional_damage);
clip_rect = SkRect::Make(frame_damage->damage_out.buffer_damage);
Copy link
Contributor

Choose a reason for hiding this comment

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

It will be good to log a trace event counter with the percentage of screen repainted. This will let users have the ability to see how their app behaves w.r.t partial redraw.

@@ -85,9 +118,16 @@ RasterStatus CompositorContext::ScopedFrame::Raster(
if (post_preroll_result == PostPrerollResult::kSkipAndRetryFrame) {
return RasterStatus::kSkipAndRetry;
}

SkAutoCanvasRestore restore(canvas(), clip_rect.has_value());
Copy link
Contributor

Choose a reason for hiding this comment

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

why do we need this?

Copy link
Member Author

Choose a reason for hiding this comment

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

We don't if we always start with "fresh" canvas. Which seems to be the case right now for some reason, I'm not quite sure what has changed. I used to have the issue where canvas()->clipRect(*clip_rect); was making the rect smaller and smaller every frame until nothing was painted. Restoring the clip after rasterization fixed that of course.

It doesn't seem to be needed right now. Still I don't think removing the clip rect after rasterization is a bad thing.

flow/surface_frame.h Show resolved Hide resolved
shell/gpu/gpu_surface_metal.mm Outdated Show resolved Hide resolved
// used for this frame in order to not get evicted. This is needed during
// partial repaint for layers that are outside of current clip and are culled
// away.
void Touch(SkPicture* picture, const SkMatrix& transformation_matrix);
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you please add tests for this behavior?

Have a frame where each of these layers is culled away but raster cache still has them. Can be in rasterizer_unittests.cc.

shell/common/rasterizer.cc Show resolved Hide resolved
flow/surface_frame.h Show resolved Hide resolved
@@ -37,6 +36,10 @@ class SK_API_AVAILABLE_CA_METAL_LAYER GPUSurfaceMetal : public Surface {
// external view embedder is present.
bool render_to_surface_;

// Accumulated damage for each framebuffer; Key is address of underlying
// MTLTexture for each drawable
std::map<uintptr_t, SkIRect> damage_;
Copy link
Contributor

Choose a reason for hiding this comment

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

If you were to create a class for this say MetalFrameDamage, which contains this map and has a couple of methods:

  • typedef uintptr_t MetalTextureId
  • AccumulateDamage(MetalTextureId texture_to_exclude)
  • ResetDamage(MetalTextureId texture)
  • std::optional<SkRect> GetDamage(MetalTextureId texture)

This would help us unit test these methods better and make it more readable IMO.

@knopp knopp force-pushed the partial_update_metal branch 3 times, most recently from aa45f17 to 9f4ebe0 Compare October 4, 2021 19:31
@iskakaushik
Copy link
Contributor

Need to look into this, but there are some artifacts with the following example: https://gist.github.com/iskakaushik/219d9b16cc94bbaa8560fdb9361096f1

@knopp
Copy link
Member Author

knopp commented Oct 12, 2021

So, this is what is happening now: During diffing we only detects the rotating layer and repaint its bounds. The container ImageFilterLayer has same filter, so diffing assumes that it didn't change and doesn't repaint it. Only the area covered by rotating layer gets repainted.

The problem is that the rotating layer affects area outside of its bounds, because it get blurred by ImageFilterLayer. We don't currently have a good way to represent something like this. Closest to that we have a readback region, which is an area, which gets repainted whole if anything within this area changes. This is used to repaint BackdropFilterLayer, mostly because it's very difficult to repaint partial blur (because at the edges of clip we sample from outside of the clip).

We used to add readback region for ImageFilterLayer as well. This ensured that if anything changed in the area of ImageFilterLayer, the whole ImageFilterLayer got repainted. This should always give correct result, at the expense of possible unnecessary repaints in some situations.

After long discussion we decided to remove this here. It was not entirely correct decision though as this sample demonstrates (actually, removing the read back was perhaps correct thing to do, but not a complete solution). Repainting the entire ImageFilterLayer through readback region might be an overkill, but will give correct results.

I think ideal solution would be to detect the dirty area within the ImageFilterLayer and inflate it by ImageFilterLayer filter bounds. I'll try implementing this tomorrow - it should hopefully fix the issue without having to use readback region.

@knopp knopp changed the title Enable partial repaint for iOS/Metal WIP: Enable partial repaint for iOS/Metal Oct 13, 2021
@knopp
Copy link
Member Author

knopp commented Oct 13, 2021

I added a prototype fix for ImageFilterLayer. Assuming this is the right way to go I'll submit a proper pull request just for the fix (with tests).

What this does is that every paint bounds of child layers within ImageFilterLayer are adjusted according to the filter. So if layer within ImageFilterLayer is dirty, we repaint the correct (inflated) region instead of just the unfiltered region.

Filter adjustments are stacked so this should work for nested filters as well.

@chinmaygarde chinmaygarde added the Work in progress (WIP) Not ready (yet) for review! label Oct 21, 2021
@knopp knopp force-pushed the partial_update_metal branch 3 times, most recently from 9260218 to cddf409 Compare October 29, 2021 21:57
@knopp knopp changed the title WIP: Enable partial repaint for iOS/Metal Enable partial repaint for iOS/Metal Nov 1, 2021
@knopp knopp removed the Work in progress (WIP) Not ready (yet) for review! label Nov 2, 2021
Copy link
Contributor

@iskakaushik iskakaushik left a comment

Choose a reason for hiding this comment

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

LGTM, there need to be some tests added for the new raster cache behavior, but these can be done as part of a follow up PR.

@iskakaushik iskakaushik merged commit 4dbdc08 into flutter:master Nov 2, 2021
@zanderso
Copy link
Member

zanderso commented Nov 2, 2021

Great to see this land. Thanks for the hard work on this @knopp @iskakaushik @flar!

@eggfly
Copy link
Member

eggfly commented Jan 1, 2022

@wangying3426
Copy link
Contributor

Good, any plan for mac/windows?

@fzyzcjy
Copy link
Contributor

fzyzcjy commented Jun 12, 2022

Hi, I see an interesting figure at official release https://medium.com/flutter/whats-new-in-flutter-2-10-5aafb0314b12

image

So may I know where I can find it? Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants