-
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
Workaround for iOS embedder preroll issue #25161
Conversation
It looks like this pull request may not have tests. Please make sure to add tests before merging. If you need an exemption to this rule, contact Hixie on the #hackers channel in Chat. If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix? Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing. |
Note that the original issue used a Map view to demonstrate the problem, but it was discovered that the problem manifests with a WebView which is simpler to use for diagnosis and testing. Here is my WebView version of the original test case in case someone wants to investigate the potential fix further: Simpler test case
|
@flar I agree with your assessment that this fix wouldn't account for all cases where I'm trying to understand why iOS still sees the last sliver of the platform view when the composition shouldn't contain the platform view in the first place. My question is when the frame shouldn't contain the platform view (which can presumably be determined by the framework), why is the layer tree containing the platform view? |
@iskakaushik I agree that's the pressing question. It must be something that happens in the Preroll methods in the embedder since this fix is basically just omitting those calls. Also, I can confirm that the PlatformViewLayer Paint method is not called when the engine thinks that the view is not visible - so whatever is showing up on screen is planned by the actions taken during preroll and there is no stopping them once that happens. |
My theory, based entirely on symptoms and when and how they present is that the logic is as follows: iOS embedder preroll: records "I'm going to be painted", does not update anything about how much and where. iOS embedder paint: determines exactly what parts should appear So, on the last visible frame, the paint method updated the "how much and where". When the next preroll comes in, it leaves that information in some data structure somewhere and marks it as "likely to paint". When the paint method doesn't run to discover that the "how much" is "nothing", then the final compositing step for the frame just inserts the part of the view that had been visible the last time its visibility was computed...? If so, then the fix would be to clear the visibility region calculations from the previous frame during preroll, or to set the "I need to be painted" flag only when called from the Paint part of the layer mechanism. |
The question I'm trying to get an answer to is, Why does the scene even contain a I see what you are saying about
I still want to figure out why @cyanglaz do you know the answer to any of these questions? |
+1, we need to figure out why a
I don't think RTree's purpose was to determine whether the platform view is in the scene. We might be potentially use it to determine that, but it doesn't seem to be very straight forward. @blasten might know. And sorry to add more questions here but why would a layer needs to be prerolled but not painted? Was there some change regarding how the layer-tree is built because of this? previously, I think layer tree is the source of truth as what should be painted on the scene, which means everything on the layer tree should be painted. |
The view is in the scene. But it is hidden out of sight by a scrolling clip. The framework does cull offscreen pieces of scrolling views, but it does not do so tightly. I think it keeps all items that are visible plus an extra item on each side. I'm guessing this is so that the items can be warmed up as they come into view. In either case, the view here does still intersect the screen space, but other items on the page cause it to be clipped out - not something easy to determine at scene building time. |
We need to preroll all layers because they can be incidentally rendered during layer caching even when they are currently off screen. They may not "show up" in this particular frame, but they might show up in a layer cache that is being prepared during this frame. When we were making assumptions about not prerolling children and then those children were painted via the layer cache mechanism, they were producing bad output because their preroll was not called, but their paint was. |
Layer tree is not the source of truth, it is an approximation of the truth based on first approximations available at the framework level. The operations that are done on the layer tree inside the engine determine the final truth. |
One source of inspiration could be the Android platform view embedder which does not suffer from this phenomenon. Perhaps it isn't directly comparable because the two platforms might be quite different, but it might provide some insight. |
I'm marking this PR "WIP" since this is just a(n ill-advised) workaround for now. |
@flar and I discussed this in person and the consensus was that eliding the Paint was causing platform view mutators to not be applied. Even though there is nothing for Flutter to paint, the platform views might still need to be updated. IMO, the most simple fix for this would be to defeat the optimization when a platform view is present in the hierarchy. A slightly more involved fix could be to tell the platform view embedder (in the preroll) that it might not be receiving the paint soon. But that may run the risk of too many unnecessary updates. I think the simple fix makes the most sense. W.r.t the contents of this patch, I don't think we should land this now that we understand the issue. |
Closing in favor of new fix (actually still mostly a workaround, but the new workaround should be more correct) |
These changes are a potential fix for flutter/flutter#76097
The problem appears to be that a fix integrated in November will now lead to all layers being prerolled even if they may not paint. Previously we would try to avoid prerolling them if we didn't think we would call their paint method, but that turned out to be too aggressive a heuristic and so the new code will always preroll every layer. As such, a layer (or an embedder) cannot assume that because it was prerolled, that it will also paint.
The changes here attempt to avoid calling the embedder's preroll method if the PlatformViewLayer does not think that its Paint method will be called., but the issue with that workaround is that the condition used here to predict if the Paint method will be called on the PlatformViewLayer is not precise. The actual test that determines culling in the Paint cycle uses a different algorithm that is based on information maintained by the SkCanvas. As a result we can't guarantee that this test will catch 100% of cases where paint will or will not be called.
Without this fix, the Android embedder does not exhibit the issue linked above, so that version of the embedder proves that it is possible for an embedder implementation to have its preroll methods called, but not paint residue on the screen. Only the iOS embedder is leaving a last sliver of the platform view visible after it is scrolled behind something.
A better fix would be to modify the iOS embedder so that it paints nothing if its Paint() method is not actually called.
I'm tagging some of the names that show up in the iOS FlutterPlatformViews.mm file s possible reviewers or contributors who can suggest a more complete fix: @blasten @cyanglaz @amirh @cbracken @iskakaushik