Skip to content

Conversation

@jonahwilliams
Copy link
Contributor

Fixes #102179

Removes the force compositing and repaint boundary status of the stand alone opacity widget. In practice, i found the conditional repaint boundary based on child a bit hard to implement and I think this change will be more impactful. We could revisit conditional repaint boundaries in the future, or instead lean towards separating out animating and non-animating render objects more distinctly.

@flutter-dashboard flutter-dashboard bot added the framework flutter/packages/flutter repository. See also f: labels. label Apr 27, 2022
@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 #102687 at sha f4e2b67

@flutter-dashboard flutter-dashboard bot added the will affect goldens Changes to golden files label Apr 27, 2022
@jonahwilliams
Copy link
Contributor Author

actually the goldens I'm seeing are making me think this isn't working correctly, investigating

@jonahwilliams
Copy link
Contributor Author

running a few of these locally on windows and chrome they look identical

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.

The skia diffs do look odd: Colors are getting lighter/darker (transparency not getting applied correctly?) and the material.material.refresh_progress_indicator looks very low quality now...

/// [RenderObject.alwaysNeedsCompositing] property to return true. That informs
/// ancestor render objects that this render object will include a composited
/// layer, which, for example, causes them to use composited clips.
@Deprecated(
Copy link
Member

Choose a reason for hiding this comment

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

add this also to the documentation above.

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

/// is called synchronously during the call to [pushOpacityLayer].
///
/// {@macro flutter.rendering.PaintingContext.pushClipRect.oldLayer}
OpacityLayer? pushOpacityLayer(bool needsCompositing, Size size, Offset offset, int alpha, PaintingContextCallback painter, { OpacityLayer? oldLayer }) {
Copy link
Member

Choose a reason for hiding this comment

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

The name seems misleading since it doesn't always push a layer...

We should at least document the behavior.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah I don't know what to do for naming, since the old name is taken

Copy link
Contributor

Choose a reason for hiding this comment

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

Doesn't pushTransform do similar things? It may or may not push a layer.

Copy link
Member

Choose a reason for hiding this comment

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

Doesn't pushTransform do similar things? It may or may not push a layer.

Yeah, that's why it isn't called pushTransformLayer :) Ideally, we call this pushOpacity, but that name is already taken by the old method.

/// is called synchronously during the call to [pushOpacityLayer].
///
/// {@macro flutter.rendering.PaintingContext.pushClipRect.oldLayer}
OpacityLayer? pushOpacityLayer(bool needsCompositing, Size size, Offset offset, int alpha, PaintingContextCallback painter, { OpacityLayer? oldLayer }) {
Copy link
Member

Choose a reason for hiding this comment

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

Add some tests for the new behavior of this method to verify when it creates a layer and when it doesn't, etc.?

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

if (alpha == 0) {
return null;
}
canvas.saveLayer(offset & size, Paint()..color = Color(alpha << 24)); // TODO(ianh): Expose the alpha somewhere.
Copy link
Member

Choose a reason for hiding this comment

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

use one of the Color constructros where you can pass in the alpha value directly without the need for the magic << 24?

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

if (alpha == 0) {
return null;
}
canvas.saveLayer(offset & size, Paint()..color = Color(alpha << 24)); // TODO(ianh): Expose the alpha somewhere.
Copy link
Member

Choose a reason for hiding this comment

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

Also, is the TODO still relevant? Can you access the alpha in a test from the paint object after the fact?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No longer relevant, removed

jonahwilliams added 2 commits April 28, 2022 12:56
@jonahwilliams
Copy link
Contributor Author

Also skip saveLayer at 255 alpha

@jonahwilliams
Copy link
Contributor Author

I'm going to do a g3 run to see what those scubas do

@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 #102687 at sha 1ad0e1a

@jonahwilliams
Copy link
Contributor Author

Running google3 goldens I don't see any issues, its like opacity doesn't work but only on skia gold

@flar
Copy link
Contributor

flar commented Apr 29, 2022

I agree that we only want the forced compositing and isolated children on animating things. Some apps might drive opacity animations directly themselves using an Opacity widget, but they should look to use an animated widget instead for multiple reasons (for one thing, fewer rebuilds of their tree).

@Deprecated(
'Use pushOpacityLayer instead to avoid forced compositing. '
'This feature was deprecated after v2.13.0-0.0.pre.'
)
Copy link
Contributor

Choose a reason for hiding this comment

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

What about adding a {needsCompositing = false} parameter to this method and it only will add a layer if it needs compositing?

Copy link
Contributor

Choose a reason for hiding this comment

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

It's backwards from the way the other methods work, though, most have pushThingy() that take a positional needsCompositing...

Copy link
Contributor

Choose a reason for hiding this comment

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

How much pain would we have if we just changed this method to have the positional parameter? Are these methods used directly in a lot of apps?

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 don't see any uses in google3 or in flutter/tests, which technically means we could change it with just a breaking change announcement.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

we could call it pushAlpha instead, which accurately describes the value it takes...

Copy link
Contributor

Choose a reason for hiding this comment

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

Alpha is fine, but it has lower resolution than opacity. Eventually these things are stored as floats in both Skia and Impeller because GPUs tend to take float values for their shaders.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sorry, I'm just talking about the name. The Paint/saveLayer API can only accept an alpha

Copy link
Contributor

Choose a reason for hiding this comment

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

...currently...

We'll be supporting higher precision color info in the engine (SkPaint/Impeller Paint both already do so, with the Impeller object only taking float components - DlPaint currently takes int colors, but I will be enhancing it to allow float colors as well soon).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

time for Color2

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We may actually be able to swap out the backing implementing and add new APIs to handle this.

@goderbauer
Copy link
Member

goderbauer commented Apr 29, 2022

I agree that we only want the forced compositing and isolated children on animating things. Some apps might drive opacity animations directly themselves using an Opacity widget, but they should look to use an animated widget instead for multiple reasons (for one thing, fewer rebuilds of their tree).

Agreed. The docs already push people strongly away from Opacity if they want to animate.

@jonahwilliams
Copy link
Contributor Author

Yeah, use an animated opacity widget instead of opacity to animate is fairly straightforward advice

@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 #102687 at sha c94bc6e

@jonahwilliams
Copy link
Contributor Author

So I reproduced the skia gold issues locally. This is due to #48417

To make this landable I will force compositing only on the web

@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 #102687 at sha 23a216d

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.

Overall LGTM

/// (as described at [Layer]).
int? get alpha => _alpha;
int? _alpha;
@Deprecated(
Copy link
Member

Choose a reason for hiding this comment

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

Note in the doc comment here (and maybe on the constructor as well) that providing an alpha is deprecated and opacity should be used instead.

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

assert(value != null);
if (value != _alpha) {
if (value == 255 || _alpha == 255) {
final double valueAsOpacity = value! / 255.0;
Copy link
Member

Choose a reason for hiding this comment

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

Maybe have a private static method for this conversion since it is happening in (at least two places) and to encapsulate the magic number 255 a little bit.

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


/// The opacity to composite with child layers.
///
/// The opacity is expressed as a double from 0.0 to 1.0, where 00 is fully
Copy link
Member

Choose a reason for hiding this comment

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

typo: 00 -> 0.0

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

return layer;
}

if (!kReleaseMode) {
Copy link
Member

Choose a reason for hiding this comment

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

Unless we changed it (or I am misremembering), I thought elsewhere debugDisableOpacityLayers is only honored in debug mode and not profile? (we should probably be consistent about this...)

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, OpacityLayer's addToScene only honors this when asserts are enabled, i.e. debug mode. Let's make this consistent.

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

}

if (!kReleaseMode) {
if (debugDisableOpacityLayers) {
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't this be placed before the if branch to push the layer above to disable those opacity effects as well?

Copy link
Member

Choose a reason for hiding this comment

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

Ah, those are disabled in the OpacityLayer's addToScene. Never mind then. Maybe add a comment about that here, though since it does look strange that we seemingly disable it in one code path, but not the other...

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

return;
}
super.paint(context, offset);
layer = context.pushOpacity(needsCompositing, offset, _opacity, super.paint, oldLayer: layer as OpacityLayer?, size: size);
Copy link
Member

Choose a reason for hiding this comment

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

nit: maybe add some line breaks to this to make it a little easier to read (add trailing comma and then one property per line)

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

double? opacity,
super.offset,
}) : _alpha = alpha;
}) : _opacity = opacity ?? (alpha != null ? _alphaToOpacity(alpha) : null);
Copy link
Contributor

Choose a reason for hiding this comment

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

we should assert that we're not passing both (i.e. that at least one of them is null)

builder.pop();
}

@override
Copy link
Contributor

Choose a reason for hiding this comment

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

why all the work to switch to using a double if the engine just takes an int?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Well there is also: flutter/engine#33035

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, we need to be driving towards deeper components just from the perspective of round off errors on parameters that will be multiplied a lot during the rendering pipeline, and >8-bit color components on screens is already happening.

pushLayer(layer, painter, Offset.zero);
return layer;
/// {@macro flutter.rendering.PaintingContext.pushClipRect.oldLayer}
OpacityLayer? pushOpacity(bool needsCompositing, Offset offset, double opacity, PaintingContextCallback painter, { OpacityLayer? oldLayer, Size? size }) {
Copy link
Contributor

Choose a reason for hiding this comment

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

can we move this into the Opacity render object? (we might not be able to, i forget the constraints, but in general the more we can push out of PaintingContext the simpler it gets to eventually remove it entirely)

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 is used in a few different places, I think the flow layout and a cupertino widget. The code itself isn't that complicated so I could repeat it, or I could move it into a toplevel helper method in proxy_box. WDUT?

return null;
}
// Due to https://github.com/flutter/flutter/issues/48417 this will always need to be
// composited on the web.
Copy link
Contributor

Choose a reason for hiding this comment

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

can't we just fix #48417 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.

if we could easily detect canvaskit mode, this would only be an issue on HTML renderer. @yjbanov for the feasibility of either fixing that or giving us a better flag

Copy link
Contributor Author

Choose a reason for hiding this comment

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

from discussion offline, this isn't fixable in html mode.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

(not fixable with the current renderer)

Copy link
Contributor

@flar flar left a comment

Choose a reason for hiding this comment

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

Generally LGTM, but I was a bit confused over the null-ness handling of the parameters so I left questions to be answered.

}) : assert(
(alpha == null && opacity == null) ||
(alpha == null && opacity != null) ||
(alpha != null && opacity == null),
Copy link
Contributor

Choose a reason for hiding this comment

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

Couldn't this be rewritten as alpha == null || opacity == null?

Also, shouldn't they be required to specify at least one?

Copy link
Contributor

Choose a reason for hiding this comment

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

Looking below, you can set either to a non-null, but there is no way to have both be null using setters, so there should not be a way to have both arguments null in the constructor, no?

Copy link
Contributor

Choose a reason for hiding this comment

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

I see that it used to allow a null alpha so maybe this is for code that constructs first and sets later?

Also, my original comment on this thread stands - the 3 tests can be combined into one.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed

)
int? get alpha => _opacity == null ? null : _alphaFromOpacity(_opacity!);
set alpha(int? value) {
assert(value != null);
Copy link
Contributor

Choose a reason for hiding this comment

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

Why isn't this done by changing the parameter to a non-null type?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The language lints having getters and setters of a different type. I think we could work around this by making both non-nullable but changing the backing storage to have a late modifier so we get around that.

double? get opacity => _opacity;
double? _opacity;
set opacity(double? value) {
assert(value != null);
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this just use a non-nullable type instead?

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 looked into changing this by using late, but then that doesn't allow whether the field has been set to be observed so we can't compare the new value to the old in the setter safely. We could default to 0.0 or 1.0 but that would make current misuse of the API that throws an exception instead silently do ... something else.

There is likely a way we can re-arrange this API to work better but I think it would be best as part of a separate effort, so that we can cover all of the layer types

Copy link
Contributor

Choose a reason for hiding this comment

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

I meant as an argument type. The internal fields could be nullable if that makes things easier, but having an "assert(argument != null)" as the first line in a method indicates that arg should probably have been expressed as non-nullable...

builder.pop();
}

@override
Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, we need to be driving towards deeper components just from the perspective of round off errors on parameters that will be multiplied a lot during the rendering pipeline, and >8-bit color components on screens is already happening.

@jonahwilliams
Copy link
Contributor Author

I believe that I've addressed all feedback and will plan on landing this on green CI assuming no other problems....

Comment on lines 1747 to 1748
alpha == null || opacity == null,
'Only one of alpha and opacity should be provided.'
Copy link
Member

Choose a reason for hiding this comment

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

nitty-nit: indent these two lines by a little more?

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

@jonahwilliams
Copy link
Contributor Author

Based on our experience trying to update the physical model layer, I'm going to make this one opt in

@jonahwilliams
Copy link
Contributor Author

I'm currently waiting on a g3 frob run to confirm this has a smaller number of breakages


/// This is an experimental flag which should not be used in production and will
/// be removed without warning.
bool forceOpacityCompositing = true;
Copy link
Member

Choose a reason for hiding this comment

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

Mark this as deprecated so people don't accidentally use it since the plan is to presumably remove this once migration is complete? That's what we usually do with these kind of flags that assist with migration.

Copy link
Member

Choose a reason for hiding this comment

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

Also, can this just be a static on PaintingContext instead of a global to not expose this in the global namespace?

@jonahwilliams
Copy link
Contributor Author

this has gotten stale, will reopen once we've resolved the scuba issues

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

Labels

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.

Opacity widget always assumes compositing

4 participants