Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[framework] simplify raster widget, rename, combine painters #109485

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

Merged
merged 28 commits into from
Aug 25, 2022

Conversation

jonahwilliams
Copy link
Member

@jonahwilliams jonahwilliams commented Aug 13, 2022

Greatly simplifies the raster widget, renaming to snapshot widget.

While working to use the raster widget in more animated examples, I found that generally it was still difficult to switch between the custom painter like behavior and regular widgets - especially if the child may have a platform view, but also because we may need to pause the animation on a rotation or partial opacity. If an animation is paused, then we should of course render the regular widget children so they are interactable. Thus we need to add a painter and fallback for almost every usage, which doesn't feel like its working well enough.

While using regular widgets instead of the painter still turned out to be a performance regression, we can atleast simplify the delegates and rendering modes so that it is more obvious what a "normal" usage of the snapshot widget is

@flutter-dashboard flutter-dashboard bot added a: text input Entering text in a text field or keyboard related problems f: material design flutter/packages/flutter/material repository. framework flutter/packages/flutter repository. See also f: labels. labels Aug 13, 2022
@jonahwilliams jonahwilliams marked this pull request as ready for review August 15, 2022 16:07
@flutter-dashboard
Copy link

Golden file changes have been found for this pull request. Click here to view and triage (e.g. because this is an intentional change).

If you are still iterating on this change and are not ready to resolve the images on the Flutter Gold dashboard, consider marking this PR as a draft pull request above. You will still be able to view image results on the dashboard, commenting will be silenced, and the check will not try to resolve itself until marked ready for review.

For more guidance, visit Writing a golden file test for package:flutter.

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

Changes reported for pull request #109485 at sha 5bddb9a

@flutter-dashboard flutter-dashboard bot added the will affect goldens Changes to golden files label Aug 15, 2022
Copy link
Member

@goderbauer goderbauer left a comment

Choose a reason for hiding this comment

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

I like how this change simplifies usage of the widget.


/// Controls how the [SnapshotWidget] paints its child via the [SnapshotWidgetController].
enum SnapshotMode {
/// the child is snapshotted, but only if all descendants can be snapshotted.
Copy link
Member

Choose a reason for hiding this comment

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

the -> The

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

/// snapshot will not be used and the child will be rendered as normal.
permissive,

/// the child is snapshotted, but only if all descendants can be snapshotted.
Copy link
Member

Choose a reason for hiding this comment

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

The

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

/// the child is snapshotted, but only if all descendants can be snapshotted.
///
/// If there is a platform view in the children of a raster widget, the
/// snapshot will not be used and the child will be rendered as normal.
Copy link
Member

Choose a reason for hiding this comment

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

normal as an SnapshotMode.normal (which seems to indicate that this would throw) or is this a different normal?

Copy link
Member Author

Choose a reason for hiding this comment

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

hmm, no - not sure how else to phrase this - the normal way as in ... super.paint

Copy link
Member

Choose a reason for hiding this comment

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

instead of calling it normal maybe say things will be rendered like they would if no SnapshotWidget were to be used?

Copy link
Member Author

Choose a reason for hiding this comment

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

but we still use the painter

}

/// Whether a snapshot of this child widget is painted in its place.
bool get enabled => _enabled;
Copy link
Member

Choose a reason for hiding this comment

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

When first reading through this I got confused what the "enabled" state is. Maybe rename this to allowSnapshotting to avoid confusion? It implies that if that to true, snapshotting may happen if other parameters line up, but it is not guaranteed.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

/// be expensive or that cannot rely on raster caching. For example, scale and
/// skew animations are often expensive to perform on complex children, as are
/// blurs. For a short animation, a widget that contains these expensive effects
/// can be replaced with a snapshot of it self and manipulated instead.
Copy link
Member

Choose a reason for hiding this comment

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

it self -> itself? (I think)

Copy link
Member Author

Choose a reason for hiding this comment

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

Done


// Now invoke clear and the raster is re-generated.
controller.clear();
await tester.pumpAndSettle();
Copy link
Member

Choose a reason for hiding this comment

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

await tester.pump() should do here? This all is expected to happen in the next frame, no?

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

}

class RenderTestPlatformView extends RenderProxyBox {

Copy link
Member

Choose a reason for hiding this comment

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

nit: remove blank line.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

@@ -392,10 +401,8 @@ class _ZoomExitTransition extends StatefulWidget {
}

class _ZoomExitTransitionState extends State<_ZoomExitTransition> with _ZoomTransitionBase {
late _ZoomExitTransitionDelegate delegate;

// TODO(jonahwilliams): https://github.com/flutter/flutter/issues/106689
Copy link
Member

Choose a reason for hiding this comment

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

Does this TODO still apply?

Copy link
Member Author

Choose a reason for hiding this comment

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

Same as above

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

@@ -285,10 +283,8 @@ class _ZoomEnterTransition extends StatefulWidget {
}

class _ZoomEnterTransitionState extends State<_ZoomEnterTransition> with _ZoomTransitionBase {
// TODO(jonahwilliams): https://github.com/flutter/flutter/issues/106689
Copy link
Member

Choose a reason for hiding this comment

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

Does this TODO still apply?

Copy link
Member Author

Choose a reason for hiding this comment

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

From benchmarking, we don't want to use snapshotting on the web due to the single frame

Copy link
Member

Choose a reason for hiding this comment

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

Maybe add that context as a comment here to explain why we don't want this on the web?

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

/// defaults to [SnapshotMode.normal] which will throw an exception if a
/// platform view is encountered.
///
/// * This widget is not supported on the HTML backend of Flutter for the web.
Copy link
Member

Choose a reason for hiding this comment

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

document what "not supported" means. Is this widget always a no-op on the web? Are you never allowed to instantiate this on the web? What happens if you do?

Copy link
Member

Choose a reason for hiding this comment

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

I arrived at this question because the ZoomTransition appears to unconditionally be using this widget. So, some modes of this widget must be supported on the web??

Copy link
Member Author

Choose a reason for hiding this comment

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

Not sure the best way to phrase this, I tried some updates

@goderbauer
Copy link
Member

Do you have benchmark numbers to check whether this approach does anything to the performance of the page transition? Or does this have the same performance characteristics?

@jonahwilliams

This comment was marked as outdated.

@jonahwilliams jonahwilliams marked this pull request as draft August 16, 2022 05:22
@jonahwilliams
Copy link
Member Author

I backed out the widget changes, I couldn't claw back enough performance to justify removing the delegate

@flutter-dashboard flutter-dashboard bot added f: cupertino flutter/packages/flutter/cupertino repository f: routes Navigator, Router, and related APIs. labels Aug 16, 2022
@jonahwilliams jonahwilliams marked this pull request as ready for review August 16, 2022 19:57
@jonahwilliams jonahwilliams marked this pull request as draft August 17, 2022 04:04
@flutter-dashboard
Copy link

This pull request has been changed to a draft. The currently pending flutter-gold status will not be able to resolve until a new commit is pushed or the change is marked ready for review again.

For more guidance, visit Writing a golden file test for package:flutter.

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

@jonahwilliams jonahwilliams marked this pull request as ready for review August 17, 2022 19:04
@jonahwilliams
Copy link
Member Author

@goderbauer I added some questions on documentation updates

@@ -285,10 +283,8 @@ class _ZoomEnterTransition extends StatefulWidget {
}

class _ZoomEnterTransitionState extends State<_ZoomEnterTransition> with _ZoomTransitionBase {
// TODO(jonahwilliams): https://github.com/flutter/flutter/issues/106689
Copy link
Member

Choose a reason for hiding this comment

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

Maybe add that context as a comment here to explain why we don't want this on the web?

),
),
));
await tester.pump();
Copy link
Member

Choose a reason for hiding this comment

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

Do you need this pump? The snapshot widget should be doing all its magic in one frame, no?

Copy link
Member

Choose a reason for hiding this comment

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

(same for the extra pumps below)

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

/// the child is snapshotted, but only if all descendants can be snapshotted.
///
/// If there is a platform view in the children of a raster widget, the
/// snapshot will not be used and the child will be rendered as normal.
Copy link
Member

Choose a reason for hiding this comment

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

instead of calling it normal maybe say things will be rendered like they would if no SnapshotWidget were to be used?

enum SnapshotMode {
/// The child is snapshotted, but only if all descendants can be snapshotted.
///
/// If there is a platform view in the children of a raster widget, the
Copy link
Member

Choose a reason for hiding this comment

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

nit: its not called raster widget anymore..

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

}

@override
// ignore: library_private_types_in_public_api
Copy link
Member

Choose a reason for hiding this comment

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

Elsewhere, we just declare the parameter as RenderObject and cast inside the method to the private type. Any reason not to do that here?

Copy link
Member Author

Choose a reason for hiding this comment

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

Nope, TIL

return;
}
_devicePixelRatio = value;
markNeedsPaint();
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 need to throw away the rastered image to force re-rasterization with the new paint?

Also tiny optimization: do we need a paint if _childRaster is currently null and not used?

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

markNeedsPaint();
}

/// The painter used to paint the child snapshot or child widgets.
Copy link
Member

Choose a reason for hiding this comment

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

nit: indentation

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

if (mode == SnapshotMode.normal) {
throw FlutterError('SnapshotWidget used with a child that contains a PlatformView.');
}
_disableSnapshotAttempt = true;
Copy link
Member

Choose a reason for hiding this comment

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

There doesn't appear to be a path that ever resets this to false? Is that expected? There are no conditions where we can try snapshotting again?

Copy link
Member Author

Choose a reason for hiding this comment

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

Perhaps on detach we can reset the value?

Copy link
Member Author

Choose a reason for hiding this comment

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

and also on reset

Copy link
Member Author

Choose a reason for hiding this comment

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

or just on notification from the controller

/// The [offset] and [size] are the location and dimensions of the render object.
void paint(PaintingContext context, Offset offset, Size size, PaintingContextCallback painter);

/// Called whenever a new instance of the raster widget delegate class is
Copy link
Member

Choose a reason for hiding this comment

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

nit: What's a raster widget? My name is snapshot widget!

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

final SnapshotPainter oldPainter = painter;
oldPainter.removeListener(markNeedsPaint);
_painter = value;
if (painter.shouldRepaint(oldPainter)) {
Copy link
Member

Choose a reason for hiding this comment

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

if painter and oldPainter have different runtimetypes you probably always want to repaint without asking shouldRepaint similarly to how CustomPaint does it?

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

@flutter-dashboard
Copy link

Golden file changes are available for triage from new commit, Click here to view.

For more guidance, visit Writing a golden file test for package:flutter.

Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing.

Changes reported for pull request #109485 at sha 8fd8693

@jonahwilliams
Copy link
Member Author

I've updated the PR based on the last round of comments

Copy link
Member

@goderbauer goderbauer left a comment

Choose a reason for hiding this comment

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

LGTM

expect(painter.count, 1);
}, skip: kIsWeb); // TODO(jonahwilliams): https://github.com/flutter/flutter/issues/106689


Copy link
Member

Choose a reason for hiding this comment

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

nit: remove one blank line?

expect(tester.layers.last, isA<PictureLayer>());
}, skip: kIsWeb); // TODO(jonahwilliams): https://github.com/flutter/flutter/issues/106689


Copy link
Member

Choose a reason for hiding this comment

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

same

}

class TestPainter2 extends TestPainter {
@override
Copy link
Member

Choose a reason for hiding this comment

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

nit: indentation

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
a: text input Entering text in a text field or keyboard related problems autosubmit Merge PR when tree becomes green via auto submit App f: cupertino flutter/packages/flutter/cupertino repository f: material design flutter/packages/flutter/material repository. f: routes Navigator, Router, and related APIs. framework flutter/packages/flutter repository. See also f: labels. will affect goldens Changes to golden files
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants