Unverified Commit d1d426e5 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

[cupertino] improve cupertino picker performance by using at most one opacity layer (#124719)

Fixes https://github.com/flutter/flutter/issues/124703

Rather than applying an opacity layer per picker item, apply them to all partially opaque picker items at once. This dramatically improves performance for impeller, by reducing the number of required subpasses and texture allocations signficantly.

Before:
Doesn't finish, 100s of passes

After:

![image](https://user-images.githubusercontent.com/8975114/231569280-91f55c9a-53a5-4b75-8728-59a4dceebe81.png)

![image](https://user-images.githubusercontent.com/8975114/231569309-7c82e5ff-46c7-4f00-80f0-9b4096aa4b14.png)

See also:

https://github.com/flutter/flutter/issues/124658
parent 68ec71f4
...@@ -828,22 +828,41 @@ class RenderListWheelViewport ...@@ -828,22 +828,41 @@ class RenderListWheelViewport
/// Paints all children visible in the current viewport. /// Paints all children visible in the current viewport.
void _paintVisibleChildren(PaintingContext context, Offset offset) { void _paintVisibleChildren(PaintingContext context, Offset offset) {
// The magnifier cannot be turned off if the opacity is less than 1.0.
if (overAndUnderCenterOpacity >= 1) {
_paintAllChildren(context, offset);
return;
}
// In order to reduce the number of opacity layers, we first paint all
// partially opaque children, then finally paint the fully opaque children.
context.pushOpacity(offset, (overAndUnderCenterOpacity * 255).round(), (PaintingContext context, Offset offset) {
_paintAllChildren(context, offset, center: false);
});
_paintAllChildren(context, offset, center: true);
}
void _paintAllChildren(PaintingContext context, Offset offset, { bool? center }) {
RenderBox? childToPaint = firstChild; RenderBox? childToPaint = firstChild;
while (childToPaint != null) { while (childToPaint != null) {
final ListWheelParentData childParentData = childToPaint.parentData! as ListWheelParentData; final ListWheelParentData childParentData = childToPaint.parentData! as ListWheelParentData;
_paintTransformedChild(childToPaint, context, offset, childParentData.offset); _paintTransformedChild(childToPaint, context, offset, childParentData.offset, center: center);
childToPaint = childAfter(childToPaint); childToPaint = childAfter(childToPaint);
} }
} }
/// Takes in a child with a **scrollable layout offset** and paints it in the // Takes in a child with a **scrollable layout offset** and paints it in the
/// **transformed cylindrical space viewport painting coordinates**. // **transformed cylindrical space viewport painting coordinates**.
//
// The value of `center` is passed through to _paintChildWithMagnifier only
// if the magnifier is enabled and/or opacity is < 1.0.
void _paintTransformedChild( void _paintTransformedChild(
RenderBox child, RenderBox child,
PaintingContext context, PaintingContext context,
Offset offset, Offset offset,
Offset layoutOffset, Offset layoutOffset, {
) { required bool? center,
}) {
final Offset untransformedPaintingCoordinates = offset final Offset untransformedPaintingCoordinates = offset
+ Offset( + Offset(
layoutOffset.dx, layoutOffset.dx,
...@@ -876,22 +895,35 @@ class RenderListWheelViewport ...@@ -876,22 +895,35 @@ class RenderListWheelViewport
final bool shouldApplyOffCenterDim = overAndUnderCenterOpacity < 1; final bool shouldApplyOffCenterDim = overAndUnderCenterOpacity < 1;
if (useMagnifier || shouldApplyOffCenterDim) { if (useMagnifier || shouldApplyOffCenterDim) {
_paintChildWithMagnifier(context, offset, child, transform, offsetToCenter, untransformedPaintingCoordinates); _paintChildWithMagnifier(context, offset, child, transform, offsetToCenter, untransformedPaintingCoordinates, center: center);
} else { } else {
assert(center == null);
_paintChildCylindrically(context, offset, child, transform, offsetToCenter); _paintChildCylindrically(context, offset, child, transform, offsetToCenter);
} }
} }
/// Paint child with the magnifier active - the child will be rendered // Paint child with the magnifier active - the child will be rendered
/// differently if it intersects with the magnifier. // differently if it intersects with the magnifier.
//
// `center` controls how items that partially intersect the center magnifier
// are rendered. If `center` is false, items are only painted cynlindrically.
// If `center` is true, only the clipped magnifier items are painted.
// If `center` is null, partially intersecting items are painted both as the
// magnifier and cynlidrical item, while non-intersecting items are painted
// only cylindrically.
//
// This property is used to lift the opacity that would be applied to each
// cylindrical item into a single layer, reducing the rendering cost of the
// pickers which use this viewport.
void _paintChildWithMagnifier( void _paintChildWithMagnifier(
PaintingContext context, PaintingContext context,
Offset offset, Offset offset,
RenderBox child, RenderBox child,
Matrix4 cylindricalTransform, Matrix4 cylindricalTransform,
Offset offsetToCenter, Offset offsetToCenter,
Offset untransformedPaintingCoordinates, Offset untransformedPaintingCoordinates, {
) { required bool? center,
}) {
final double magnifierTopLinePosition = final double magnifierTopLinePosition =
size.height / 2 - _itemExtent * _magnification / 2; size.height / 2 - _itemExtent * _magnification / 2;
final double magnifierBottomLinePosition = final double magnifierBottomLinePosition =
...@@ -902,27 +934,28 @@ class RenderListWheelViewport ...@@ -902,27 +934,28 @@ class RenderListWheelViewport
final bool isBeforeMagnifierBottomLine = untransformedPaintingCoordinates.dy final bool isBeforeMagnifierBottomLine = untransformedPaintingCoordinates.dy
<= magnifierBottomLinePosition; <= magnifierBottomLinePosition;
final Rect centerRect = Rect.fromLTWH(
0.0,
magnifierTopLinePosition,
size.width,
_itemExtent * _magnification,
);
final Rect topHalfRect = Rect.fromLTWH(
0.0,
0.0,
size.width,
magnifierTopLinePosition,
);
final Rect bottomHalfRect = Rect.fromLTWH(
0.0,
magnifierBottomLinePosition,
size.width,
magnifierTopLinePosition,
);
// Some part of the child is in the center magnifier. // Some part of the child is in the center magnifier.
if (isAfterMagnifierTopLine && isBeforeMagnifierBottomLine) { final bool inCenter = isAfterMagnifierTopLine && isBeforeMagnifierBottomLine;
final Rect centerRect = Rect.fromLTWH(
0.0,
magnifierTopLinePosition,
size.width,
_itemExtent * _magnification,
);
final Rect topHalfRect = Rect.fromLTWH(
0.0,
0.0,
size.width,
magnifierTopLinePosition,
);
final Rect bottomHalfRect = Rect.fromLTWH(
0.0,
magnifierBottomLinePosition,
size.width,
magnifierTopLinePosition,
);
if ((center == null || center) && inCenter) {
// Clipping the part in the center. // Clipping the part in the center.
context.pushClipRect( context.pushClipRect(
needsCompositing, needsCompositing,
...@@ -939,8 +972,10 @@ class RenderListWheelViewport ...@@ -939,8 +972,10 @@ class RenderListWheelViewport
); );
}, },
); );
}
// Clipping the part in either the top-half or bottom-half of the wheel. // Clipping the part in either the top-half or bottom-half of the wheel.
if ((center == null || !center) && inCenter) {
context.pushClipRect( context.pushClipRect(
needsCompositing, needsCompositing,
offset, offset,
...@@ -948,16 +983,18 @@ class RenderListWheelViewport ...@@ -948,16 +983,18 @@ class RenderListWheelViewport
? topHalfRect ? topHalfRect
: bottomHalfRect, : bottomHalfRect,
(PaintingContext context, Offset offset) { (PaintingContext context, Offset offset) {
_paintChildCylindrically( _paintChildCylindrically(
context, context,
offset, offset,
child, child,
cylindricalTransform, cylindricalTransform,
offsetToCenter, offsetToCenter,
); );
}, },
); );
} else { }
if ((center == null || !center) && !inCenter) {
_paintChildCylindrically( _paintChildCylindrically(
context, context,
offset, offset,
...@@ -987,17 +1024,12 @@ class RenderListWheelViewport ...@@ -987,17 +1024,12 @@ class RenderListWheelViewport
); );
} }
// Paint child cylindrically, with [overAndUnderCenterOpacity].
void opacityPainter(PaintingContext context, Offset offset) {
context.pushOpacity(offset, (overAndUnderCenterOpacity * 255).round(), painter);
}
context.pushTransform( context.pushTransform(
needsCompositing, needsCompositing,
offset, offset,
_centerOriginTransform(cylindricalTransform), _centerOriginTransform(cylindricalTransform),
// Pre-transform painting function. // Pre-transform painting function.
overAndUnderCenterOpacity == 1 ? painter : opacityPainter, painter,
); );
final ListWheelParentData childParentData = child.parentData! as ListWheelParentData; final ListWheelParentData childParentData = child.parentData! as ListWheelParentData;
......
...@@ -1608,4 +1608,19 @@ void main() { ...@@ -1608,4 +1608,19 @@ void main() {
expect(pageController.page, 1.0); expect(pageController.page, 1.0);
}); });
}); });
testWidgets('ListWheelScrollView creates only one opacity layer for all children', (WidgetTester tester) async {
await tester.pumpWidget(
ListWheelScrollView(
overAndUnderCenterOpacity: 0.5,
itemExtent: 20.0,
children: <Widget>[
for (int i = 0; i < 20; i++)
Container(),
],
),
);
expect(tester.layers.whereType<OpacityLayer>(), hasLength(1));
});
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment