mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Adds API for retaining intermediate engine layers (flutter/engine#9461)
Add new optional named oldLayer arguments to all push* methods of the SceneBuilder class. When not null oldLayer signals to the engine that the intent is to update a layer rendered in a previous frame. The engine may optionally use that signal to reuse the resources allocated for that layer in the previous frame. For example, on the Web we can reuse existing DOM nodes and some of their properties and move fewer nodes around the tree. The return type of each push method has been tightened up. Instead of having all methods return the same EngineLayer type, each method has its own unique layer type, e.g. OffsetEngineLayer. oldLayer parameters match the returned type. This prevents the framework (and other developers using dart:ui directly) from accidentally supplying an engine layer of the wrong type.
This commit is contained in:
parent
c012430e17
commit
34839e477a
@ -38,6 +38,137 @@ class Scene extends NativeFieldWrapperClass2 {
|
||||
void dispose() native 'Scene_dispose';
|
||||
}
|
||||
|
||||
// Lightweight wrapper of a native layer object.
|
||||
//
|
||||
// This is used to provide a typed API for engine layers to prevent
|
||||
// incompatible layers from being passed to [SceneBuilder]'s push methods.
|
||||
// For example, this prevents a layer returned from `pushOpacity` from being
|
||||
// passed as `oldLayer` to `pushTransform`. This is achieved by having one
|
||||
// concrete subclass of this class per push method.
|
||||
abstract class _EngineLayerWrapper implements EngineLayer {
|
||||
_EngineLayerWrapper._(this._nativeLayer);
|
||||
|
||||
EngineLayer _nativeLayer;
|
||||
|
||||
// Children of this layer.
|
||||
//
|
||||
// Null if this layer has no children. This field is populated only in debug
|
||||
// mode.
|
||||
List<_EngineLayerWrapper> _debugChildren;
|
||||
|
||||
// Whether this layer was used as `oldLayer` in a past frame.
|
||||
//
|
||||
// It is illegal to use a layer object again after it is passed as an
|
||||
// `oldLayer` argument.
|
||||
bool _debugWasUsedAsOldLayer = false;
|
||||
|
||||
bool _debugCheckNotUsedAsOldLayer() {
|
||||
assert(
|
||||
!_debugWasUsedAsOldLayer,
|
||||
'Layer $runtimeType was previously used as oldLayer.\n'
|
||||
'Once a layer is used as oldLayer, it may not be used again. Instead, '
|
||||
'after calling one of the SceneBuilder.push* methods and passing an oldLayer '
|
||||
'to it, use the layer returned by the method as oldLayer in subsequent '
|
||||
'frames.'
|
||||
);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// An opaque handle to a transform engine layer.
|
||||
///
|
||||
/// Instances of this class are created by [SceneBuilder.pushTransform].
|
||||
///
|
||||
/// {@template dart.ui.sceneBuilder.oldLayerCompatibility}
|
||||
/// `oldLayer` parameter in [SceneBuilder] methods only accepts objects created
|
||||
/// by the engine. [SceneBuilder] will throw an [AssertionError] if you pass it
|
||||
/// a custom implementation of this class.
|
||||
/// {@endtemplate}
|
||||
class TransformEngineLayer extends _EngineLayerWrapper {
|
||||
TransformEngineLayer._(EngineLayer nativeLayer) : super._(nativeLayer);
|
||||
}
|
||||
|
||||
/// An opaque handle to an offset engine layer.
|
||||
///
|
||||
/// Instances of this class are created by [SceneBuilder.pushOffset].
|
||||
///
|
||||
/// {@macro dart.ui.sceneBuilder.oldLayerCompatibility}
|
||||
class OffsetEngineLayer extends _EngineLayerWrapper {
|
||||
OffsetEngineLayer._(EngineLayer nativeLayer) : super._(nativeLayer);
|
||||
}
|
||||
|
||||
/// An opaque handle to a clip rect engine layer.
|
||||
///
|
||||
/// Instances of this class are created by [SceneBuilder.pushClipRect].
|
||||
///
|
||||
/// {@macro dart.ui.sceneBuilder.oldLayerCompatibility}
|
||||
class ClipRectEngineLayer extends _EngineLayerWrapper {
|
||||
ClipRectEngineLayer._(EngineLayer nativeLayer) : super._(nativeLayer);
|
||||
}
|
||||
|
||||
/// An opaque handle to a clip rounded rect engine layer.
|
||||
///
|
||||
/// Instances of this class are created by [SceneBuilder.pushClipRRect].
|
||||
///
|
||||
/// {@macro dart.ui.sceneBuilder.oldLayerCompatibility}
|
||||
class ClipRRectEngineLayer extends _EngineLayerWrapper {
|
||||
ClipRRectEngineLayer._(EngineLayer nativeLayer) : super._(nativeLayer);
|
||||
}
|
||||
|
||||
/// An opaque handle to a clip path engine layer.
|
||||
///
|
||||
/// Instances of this class are created by [SceneBuilder.pushClipPath].
|
||||
///
|
||||
/// {@macro dart.ui.sceneBuilder.oldLayerCompatibility}
|
||||
class ClipPathEngineLayer extends _EngineLayerWrapper {
|
||||
ClipPathEngineLayer._(EngineLayer nativeLayer) : super._(nativeLayer);
|
||||
}
|
||||
|
||||
/// An opaque handle to an opacity engine layer.
|
||||
///
|
||||
/// Instances of this class are created by [SceneBuilder.pushOpacity].
|
||||
///
|
||||
/// {@macro dart.ui.sceneBuilder.oldLayerCompatibility}
|
||||
class OpacityEngineLayer extends _EngineLayerWrapper {
|
||||
OpacityEngineLayer._(EngineLayer nativeLayer) : super._(nativeLayer);
|
||||
}
|
||||
|
||||
/// An opaque handle to a color filter engine layer.
|
||||
///
|
||||
/// Instances of this class are created by [SceneBuilder.pushColorFilter].
|
||||
///
|
||||
/// {@macro dart.ui.sceneBuilder.oldLayerCompatibility}
|
||||
class ColorFilterEngineLayer extends _EngineLayerWrapper {
|
||||
ColorFilterEngineLayer._(EngineLayer nativeLayer) : super._(nativeLayer);
|
||||
}
|
||||
|
||||
/// An opaque handle to a backdrop filter engine layer.
|
||||
///
|
||||
/// Instances of this class are created by [SceneBuilder.pushBackdropFilter].
|
||||
///
|
||||
/// {@macro dart.ui.sceneBuilder.oldLayerCompatibility}
|
||||
class BackdropFilterEngineLayer extends _EngineLayerWrapper {
|
||||
BackdropFilterEngineLayer._(EngineLayer nativeLayer) : super._(nativeLayer);
|
||||
}
|
||||
|
||||
/// An opaque handle to a shader mask engine layer.
|
||||
///
|
||||
/// Instances of this class are created by [SceneBuilder.pushShaderMask].
|
||||
///
|
||||
/// {@macro dart.ui.sceneBuilder.oldLayerCompatibility}
|
||||
class ShaderMaskEngineLayer extends _EngineLayerWrapper {
|
||||
ShaderMaskEngineLayer._(EngineLayer nativeLayer) : super._(nativeLayer);
|
||||
}
|
||||
|
||||
/// An opaque handle to a physical shape engine layer.
|
||||
///
|
||||
/// Instances of this class are created by [SceneBuilder.pushPhysicalShape].
|
||||
///
|
||||
/// {@macro dart.ui.sceneBuilder.oldLayerCompatibility}
|
||||
class PhysicalShapeEngineLayer extends _EngineLayerWrapper {
|
||||
PhysicalShapeEngineLayer._(EngineLayer nativeLayer) : super._(nativeLayer);
|
||||
}
|
||||
|
||||
/// Builds a [Scene] containing the given visuals.
|
||||
///
|
||||
/// A [Scene] can then be rendered using [Window.render].
|
||||
@ -51,14 +182,97 @@ class SceneBuilder extends NativeFieldWrapperClass2 {
|
||||
SceneBuilder() { _constructor(); }
|
||||
void _constructor() native 'SceneBuilder_constructor';
|
||||
|
||||
// Layers used in this scene.
|
||||
//
|
||||
// The key is the layer used. The value is the description of what the layer
|
||||
// is used for, e.g. "pushOpacity" or "addRetained".
|
||||
Map<EngineLayer, String> _usedLayers = <EngineLayer, String>{};
|
||||
|
||||
// In debug mode checks that the `layer` is only used once in a given scene.
|
||||
bool _debugCheckUsedOnce(EngineLayer layer, String usage) {
|
||||
assert(() {
|
||||
if (layer == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
assert(
|
||||
!_usedLayers.containsKey(layer),
|
||||
'Layer ${layer.runtimeType} already used.\n'
|
||||
'The layer is already being used as ${_usedLayers[layer]} in this scene.\n'
|
||||
'A layer may only be used once in a given scene.'
|
||||
);
|
||||
|
||||
_usedLayers[layer] = usage;
|
||||
return true;
|
||||
}());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _debugCheckCanBeUsedAsOldLayer(_EngineLayerWrapper layer, String methodName) {
|
||||
assert(() {
|
||||
if (layer == null) {
|
||||
return true;
|
||||
}
|
||||
layer._debugCheckNotUsedAsOldLayer();
|
||||
assert(_debugCheckUsedOnce(layer, 'oldLayer in $methodName'));
|
||||
layer._debugWasUsedAsOldLayer = true;
|
||||
return true;
|
||||
}());
|
||||
return true;
|
||||
}
|
||||
|
||||
final List<_EngineLayerWrapper> _layerStack = <_EngineLayerWrapper>[];
|
||||
|
||||
// Pushes the `newLayer` onto the `_layerStack` and adds it to the
|
||||
// `_debugChildren` of the current layer in the stack, if any.
|
||||
bool _debugPushLayer(_EngineLayerWrapper newLayer) {
|
||||
assert(() {
|
||||
if (_layerStack.isNotEmpty) {
|
||||
final _EngineLayerWrapper currentLayer = _layerStack.last;
|
||||
currentLayer._debugChildren ??= <_EngineLayerWrapper>[];
|
||||
currentLayer._debugChildren.add(newLayer);
|
||||
}
|
||||
_layerStack.add(newLayer);
|
||||
return true;
|
||||
}());
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Pushes a transform operation onto the operation stack.
|
||||
///
|
||||
/// The objects are transformed by the given matrix before rasterization.
|
||||
///
|
||||
/// {@template dart.ui.sceneBuilder.oldLayer}
|
||||
/// If `oldLayer` is not null the engine will attempt to reuse the resources
|
||||
/// allocated for the old layer when rendering the new layer. This is purely
|
||||
/// an optimization. It has no effect on the correctness of rendering.
|
||||
/// {@endtemplate}
|
||||
///
|
||||
/// {@template dart.ui.sceneBuilder.oldLayerVsRetained}
|
||||
/// Passing a layer to [addRetained] or as `oldLayer` argument to a push
|
||||
/// method counts as _usage_. A layer can be used no more than once in a scene.
|
||||
/// For example, it may not be passed simultaneously to two push methods, or
|
||||
/// to a push method and to `addRetained`.
|
||||
///
|
||||
/// When a layer is passed to [addRetained] all descendant layers are also
|
||||
/// considered as used in this scene. The same single-usage restriction
|
||||
/// applies to descendants.
|
||||
///
|
||||
/// When a layer is passed as an `oldLayer` argument to a push method, it may
|
||||
/// no longer be used in subsequent frames. If you would like to continue
|
||||
/// reusing the resources associated with the layer, store the layer object
|
||||
/// returned by the push method and use that in the next frame instead of the
|
||||
/// original object.
|
||||
/// {@endtemplate}
|
||||
///
|
||||
/// See [pop] for details about the operation stack.
|
||||
EngineLayer pushTransform(Float64List matrix4) {
|
||||
TransformEngineLayer pushTransform(Float64List matrix4, { TransformEngineLayer oldLayer }) {
|
||||
assert(_matrix4IsValid(matrix4));
|
||||
return _pushTransform(matrix4);
|
||||
assert(_debugCheckCanBeUsedAsOldLayer(oldLayer, 'pushTransform'));
|
||||
final TransformEngineLayer layer = TransformEngineLayer._(_pushTransform(matrix4));
|
||||
assert(_debugPushLayer(layer));
|
||||
return layer;
|
||||
}
|
||||
EngineLayer _pushTransform(Float64List matrix4) native 'SceneBuilder_pushTransform';
|
||||
|
||||
@ -66,19 +280,36 @@ class SceneBuilder extends NativeFieldWrapperClass2 {
|
||||
///
|
||||
/// This is equivalent to [pushTransform] with a matrix with only translation.
|
||||
///
|
||||
/// {@macro dart.ui.sceneBuilder.oldLayer}
|
||||
///
|
||||
/// {@macro dart.ui.sceneBuilder.oldLayerVsRetained}
|
||||
///
|
||||
/// See [pop] for details about the operation stack.
|
||||
EngineLayer pushOffset(double dx, double dy) native 'SceneBuilder_pushOffset';
|
||||
OffsetEngineLayer pushOffset(double dx, double dy, { OffsetEngineLayer oldLayer }) {
|
||||
assert(_debugCheckCanBeUsedAsOldLayer(oldLayer, 'pushOffset'));
|
||||
final OffsetEngineLayer layer = OffsetEngineLayer._(_pushOffset(dx, dy));
|
||||
assert(_debugPushLayer(layer));
|
||||
return layer;
|
||||
}
|
||||
EngineLayer _pushOffset(double dx, double dy) native 'SceneBuilder_pushOffset';
|
||||
|
||||
/// Pushes a rectangular clip operation onto the operation stack.
|
||||
///
|
||||
/// Rasterization outside the given rectangle is discarded.
|
||||
///
|
||||
/// {@macro dart.ui.sceneBuilder.oldLayer}
|
||||
///
|
||||
/// {@macro dart.ui.sceneBuilder.oldLayerVsRetained}
|
||||
///
|
||||
/// See [pop] for details about the operation stack, and [Clip] for different clip modes.
|
||||
/// By default, the clip will be anti-aliased (clip = [Clip.antiAlias]).
|
||||
EngineLayer pushClipRect(Rect rect, {Clip clipBehavior = Clip.antiAlias}) {
|
||||
ClipRectEngineLayer pushClipRect(Rect rect, {Clip clipBehavior = Clip.antiAlias, ClipRectEngineLayer oldLayer }) {
|
||||
assert(clipBehavior != null);
|
||||
assert(clipBehavior != Clip.none);
|
||||
return _pushClipRect(rect.left, rect.right, rect.top, rect.bottom, clipBehavior.index);
|
||||
assert(_debugCheckCanBeUsedAsOldLayer(oldLayer, 'pushClipRect'));
|
||||
final ClipRectEngineLayer layer = ClipRectEngineLayer._(_pushClipRect(rect.left, rect.right, rect.top, rect.bottom, clipBehavior.index));
|
||||
assert(_debugPushLayer(layer));
|
||||
return layer;
|
||||
}
|
||||
EngineLayer _pushClipRect(double left,
|
||||
double right,
|
||||
@ -90,12 +321,19 @@ class SceneBuilder extends NativeFieldWrapperClass2 {
|
||||
///
|
||||
/// Rasterization outside the given rounded rectangle is discarded.
|
||||
///
|
||||
/// {@macro dart.ui.sceneBuilder.oldLayer}
|
||||
///
|
||||
/// {@macro dart.ui.sceneBuilder.oldLayerVsRetained}
|
||||
///
|
||||
/// See [pop] for details about the operation stack, and [Clip] for different clip modes.
|
||||
/// By default, the clip will be anti-aliased (clip = [Clip.antiAlias]).
|
||||
EngineLayer pushClipRRect(RRect rrect, {Clip clipBehavior = Clip.antiAlias}) {
|
||||
ClipRRectEngineLayer pushClipRRect(RRect rrect, {Clip clipBehavior = Clip.antiAlias, ClipRRectEngineLayer oldLayer}) {
|
||||
assert(clipBehavior != null);
|
||||
assert(clipBehavior != Clip.none);
|
||||
return _pushClipRRect(rrect._value32, clipBehavior.index);
|
||||
assert(_debugCheckCanBeUsedAsOldLayer(oldLayer, 'pushClipRRect'));
|
||||
final ClipRRectEngineLayer layer = ClipRRectEngineLayer._(_pushClipRRect(rrect._value32, clipBehavior.index));
|
||||
assert(_debugPushLayer(layer));
|
||||
return layer;
|
||||
}
|
||||
EngineLayer _pushClipRRect(Float32List rrect, int clipBehavior) native 'SceneBuilder_pushClipRRect';
|
||||
|
||||
@ -103,12 +341,19 @@ class SceneBuilder extends NativeFieldWrapperClass2 {
|
||||
///
|
||||
/// Rasterization outside the given path is discarded.
|
||||
///
|
||||
/// {@macro dart.ui.sceneBuilder.oldLayer}
|
||||
///
|
||||
/// {@macro dart.ui.sceneBuilder.oldLayerVsRetained}
|
||||
///
|
||||
/// See [pop] for details about the operation stack. See [Clip] for different clip modes.
|
||||
/// By default, the clip will be anti-aliased (clip = [Clip.antiAlias]).
|
||||
EngineLayer pushClipPath(Path path, {Clip clipBehavior = Clip.antiAlias}) {
|
||||
ClipPathEngineLayer pushClipPath(Path path, {Clip clipBehavior = Clip.antiAlias, ClipPathEngineLayer oldLayer}) {
|
||||
assert(clipBehavior != null);
|
||||
assert(clipBehavior != Clip.none);
|
||||
return _pushClipPath(path, clipBehavior.index);
|
||||
assert(_debugCheckCanBeUsedAsOldLayer(oldLayer, 'pushClipPath'));
|
||||
final ClipPathEngineLayer layer = ClipPathEngineLayer._(_pushClipPath(path, clipBehavior.index));
|
||||
assert(_debugPushLayer(layer));
|
||||
return layer;
|
||||
}
|
||||
EngineLayer _pushClipPath(Path path, int clipBehavior) native 'SceneBuilder_pushClipPath';
|
||||
|
||||
@ -119,9 +364,16 @@ class SceneBuilder extends NativeFieldWrapperClass2 {
|
||||
/// An alpha value of 255 has no effect (i.e., the objects retain the current
|
||||
/// opacity).
|
||||
///
|
||||
/// {@macro dart.ui.sceneBuilder.oldLayer}
|
||||
///
|
||||
/// {@macro dart.ui.sceneBuilder.oldLayerVsRetained}
|
||||
///
|
||||
/// See [pop] for details about the operation stack.
|
||||
EngineLayer pushOpacity(int alpha, {Offset offset = Offset.zero}) {
|
||||
return _pushOpacity(alpha, offset.dx, offset.dy);
|
||||
OpacityEngineLayer pushOpacity(int alpha, {Offset offset = Offset.zero, OpacityEngineLayer oldLayer}) {
|
||||
assert(_debugCheckCanBeUsedAsOldLayer(oldLayer, 'pushOpacity'));
|
||||
final OpacityEngineLayer layer = OpacityEngineLayer._(_pushOpacity(alpha, offset.dx, offset.dy));
|
||||
assert(_debugPushLayer(layer));
|
||||
return layer;
|
||||
}
|
||||
EngineLayer _pushOpacity(int alpha, double dx, double dy) native 'SceneBuilder_pushOpacity';
|
||||
|
||||
@ -130,9 +382,16 @@ class SceneBuilder extends NativeFieldWrapperClass2 {
|
||||
/// The given color is applied to the objects' rasterization using the given
|
||||
/// blend mode.
|
||||
///
|
||||
/// {@macro dart.ui.sceneBuilder.oldLayer}
|
||||
///
|
||||
/// {@macro dart.ui.sceneBuilder.oldLayerVsRetained}
|
||||
///
|
||||
/// See [pop] for details about the operation stack.
|
||||
EngineLayer pushColorFilter(Color color, BlendMode blendMode) {
|
||||
return _pushColorFilter(color.value, blendMode.index);
|
||||
ColorFilterEngineLayer pushColorFilter(Color color, BlendMode blendMode, { ColorFilterEngineLayer oldLayer }) {
|
||||
assert(_debugCheckCanBeUsedAsOldLayer(oldLayer, 'pushColorFilter'));
|
||||
final ColorFilterEngineLayer layer = ColorFilterEngineLayer._(_pushColorFilter(color.value, blendMode.index));
|
||||
assert(_debugPushLayer(layer));
|
||||
return layer;
|
||||
}
|
||||
EngineLayer _pushColorFilter(int color, int blendMode) native 'SceneBuilder_pushColorFilter';
|
||||
|
||||
@ -141,22 +400,39 @@ class SceneBuilder extends NativeFieldWrapperClass2 {
|
||||
/// The given filter is applied to the current contents of the scene prior to
|
||||
/// rasterizing the given objects.
|
||||
///
|
||||
/// {@macro dart.ui.sceneBuilder.oldLayer}
|
||||
///
|
||||
/// {@macro dart.ui.sceneBuilder.oldLayerVsRetained}
|
||||
///
|
||||
/// See [pop] for details about the operation stack.
|
||||
EngineLayer pushBackdropFilter(ImageFilter filter) native 'SceneBuilder_pushBackdropFilter';
|
||||
BackdropFilterEngineLayer pushBackdropFilter(ImageFilter filter, { BackdropFilterEngineLayer oldLayer }) {
|
||||
assert(_debugCheckCanBeUsedAsOldLayer(oldLayer, 'pushBackdropFilter'));
|
||||
final BackdropFilterEngineLayer layer = BackdropFilterEngineLayer._(_pushBackdropFilter(filter));
|
||||
assert(_debugPushLayer(layer));
|
||||
return layer;
|
||||
}
|
||||
EngineLayer _pushBackdropFilter(ImageFilter filter) native 'SceneBuilder_pushBackdropFilter';
|
||||
|
||||
/// Pushes a shader mask operation onto the operation stack.
|
||||
///
|
||||
/// The given shader is applied to the object's rasterization in the given
|
||||
/// rectangle using the given blend mode.
|
||||
///
|
||||
/// {@macro dart.ui.sceneBuilder.oldLayer}
|
||||
///
|
||||
/// {@macro dart.ui.sceneBuilder.oldLayerVsRetained}
|
||||
///
|
||||
/// See [pop] for details about the operation stack.
|
||||
EngineLayer pushShaderMask(Shader shader, Rect maskRect, BlendMode blendMode) {
|
||||
return _pushShaderMask(shader,
|
||||
ShaderMaskEngineLayer pushShaderMask(Shader shader, Rect maskRect, BlendMode blendMode, { ShaderMaskEngineLayer oldLayer }) {
|
||||
assert(_debugCheckCanBeUsedAsOldLayer(oldLayer, 'pushShaderMask'));
|
||||
final ShaderMaskEngineLayer layer = ShaderMaskEngineLayer._(_pushShaderMask(shader,
|
||||
maskRect.left,
|
||||
maskRect.right,
|
||||
maskRect.top,
|
||||
maskRect.bottom,
|
||||
blendMode.index);
|
||||
blendMode.index));
|
||||
assert(_debugPushLayer(layer));
|
||||
return layer;
|
||||
}
|
||||
EngineLayer _pushShaderMask(Shader shader,
|
||||
double maskRectLeft,
|
||||
@ -176,10 +452,17 @@ class SceneBuilder extends NativeFieldWrapperClass2 {
|
||||
/// [shadowColor] defines the color of the shadow if present and [color] defines the
|
||||
/// color of the layer background.
|
||||
///
|
||||
/// {@macro dart.ui.sceneBuilder.oldLayer}
|
||||
///
|
||||
/// {@macro dart.ui.sceneBuilder.oldLayerVsRetained}
|
||||
///
|
||||
/// See [pop] for details about the operation stack, and [Clip] for different clip modes.
|
||||
// ignore: deprecated_member_use
|
||||
EngineLayer pushPhysicalShape({ Path path, double elevation, Color color, Color shadowColor, Clip clipBehavior = Clip.none}) {
|
||||
return _pushPhysicalShape(path, elevation, color.value, shadowColor?.value ?? 0xFF000000, clipBehavior.index);
|
||||
PhysicalShapeEngineLayer pushPhysicalShape({ Path path, double elevation, Color color, Color shadowColor, Clip clipBehavior = Clip.none, PhysicalShapeEngineLayer oldLayer }) {
|
||||
assert(_debugCheckCanBeUsedAsOldLayer(oldLayer, 'pushPhysicalShape'));
|
||||
final PhysicalShapeEngineLayer layer = PhysicalShapeEngineLayer._(_pushPhysicalShape(path, elevation, color.value, shadowColor?.value ?? 0xFF000000, clipBehavior.index));
|
||||
assert(_debugPushLayer(layer));
|
||||
return layer;
|
||||
}
|
||||
EngineLayer _pushPhysicalShape(Path path, double elevation, int color, int shadowColor, int clipBehavior) native
|
||||
'SceneBuilder_pushPhysicalShape';
|
||||
@ -190,7 +473,13 @@ class SceneBuilder extends NativeFieldWrapperClass2 {
|
||||
/// operations in the stack applies to each of the objects added to the scene.
|
||||
/// Calling this function removes the most recently added operation from the
|
||||
/// stack.
|
||||
void pop() native 'SceneBuilder_pop';
|
||||
void pop() {
|
||||
if (_layerStack.isNotEmpty) {
|
||||
_layerStack.removeLast();
|
||||
}
|
||||
_pop();
|
||||
}
|
||||
void _pop() native 'SceneBuilder_pop';
|
||||
|
||||
/// Add a retained engine layer subtree from previous frames.
|
||||
///
|
||||
@ -200,7 +489,33 @@ class SceneBuilder extends NativeFieldWrapperClass2 {
|
||||
/// Therefore, when implementing a subclass of the [Layer] concept defined in
|
||||
/// the rendering layer of Flutter's framework, once this is called, there's
|
||||
/// no need to call [addToScene] for its children layers.
|
||||
void addRetained(EngineLayer retainedLayer) native 'SceneBuilder_addRetained';
|
||||
///
|
||||
/// {@macro dart.ui.sceneBuilder.oldLayerVsRetained}
|
||||
void addRetained(EngineLayer retainedLayer) {
|
||||
assert(retainedLayer is _EngineLayerWrapper);
|
||||
assert(() {
|
||||
final _EngineLayerWrapper layer = retainedLayer;
|
||||
|
||||
void recursivelyCheckChildrenUsedOnce(_EngineLayerWrapper parentLayer) {
|
||||
_debugCheckUsedOnce(parentLayer, 'retained layer');
|
||||
parentLayer._debugCheckNotUsedAsOldLayer();
|
||||
|
||||
if (parentLayer._debugChildren == null || parentLayer._debugChildren.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
parentLayer._debugChildren.forEach(recursivelyCheckChildrenUsedOnce);
|
||||
}
|
||||
|
||||
recursivelyCheckChildrenUsedOnce(layer);
|
||||
|
||||
return true;
|
||||
}());
|
||||
|
||||
final _EngineLayerWrapper wrapper = retainedLayer;
|
||||
_addRetained(wrapper._nativeLayer);
|
||||
}
|
||||
void _addRetained(EngineLayer retainedLayer) native 'SceneBuilder_addRetained';
|
||||
|
||||
/// Adds an object to the scene that displays performance statistics.
|
||||
///
|
||||
|
||||
@ -51,4 +51,250 @@ void main() {
|
||||
throwsA(const TypeMatcher<AssertionError>()),
|
||||
);
|
||||
});
|
||||
|
||||
test('SceneBuilder accepts typed layers', () {
|
||||
final SceneBuilder builder1 = SceneBuilder();
|
||||
final OpacityEngineLayer opacity1 = builder1.pushOpacity(100);
|
||||
expect(opacity1, isNotNull);
|
||||
builder1.pop();
|
||||
builder1.build();
|
||||
|
||||
final SceneBuilder builder2 = SceneBuilder();
|
||||
final OpacityEngineLayer opacity2 = builder2.pushOpacity(200, oldLayer: opacity1);
|
||||
expect(opacity2, isNotNull);
|
||||
builder2.pop();
|
||||
builder2.build();
|
||||
});
|
||||
|
||||
// Attempts to use the same layer first as `oldLayer` then in `addRetained`.
|
||||
void testPushThenIllegalRetain(_TestNoSharingFunction pushFunction) {
|
||||
final SceneBuilder builder1 = SceneBuilder();
|
||||
final EngineLayer layer = pushFunction(builder1, null);
|
||||
builder1.pop();
|
||||
builder1.build();
|
||||
|
||||
final SceneBuilder builder2 = SceneBuilder();
|
||||
pushFunction(builder2, layer);
|
||||
builder2.pop();
|
||||
try {
|
||||
builder2.addRetained(layer);
|
||||
fail('Expected addRetained to throw AssertionError but it returned successully');
|
||||
} on AssertionError catch (error) {
|
||||
expect(error.toString(), contains('The layer is already being used'));
|
||||
}
|
||||
builder2.build();
|
||||
}
|
||||
|
||||
// Attempts to use the same layer first in `addRetained` then as `oldLayer`.
|
||||
void testAddRetainedThenIllegalPush(_TestNoSharingFunction pushFunction) {
|
||||
final SceneBuilder builder1 = SceneBuilder();
|
||||
final EngineLayer layer = pushFunction(builder1, null);
|
||||
builder1.pop();
|
||||
builder1.build();
|
||||
|
||||
final SceneBuilder builder2 = SceneBuilder();
|
||||
builder2.addRetained(layer);
|
||||
try {
|
||||
pushFunction(builder2, layer);
|
||||
fail('Expected push to throw AssertionError but it returned successully');
|
||||
} on AssertionError catch (error) {
|
||||
expect(error.toString(), contains('The layer is already being used'));
|
||||
}
|
||||
builder2.build();
|
||||
}
|
||||
|
||||
// Attempts to retain the same layer twice in the same scene.
|
||||
void testDoubleAddRetained(_TestNoSharingFunction pushFunction) {
|
||||
final SceneBuilder builder1 = SceneBuilder();
|
||||
final EngineLayer layer = pushFunction(builder1, null);
|
||||
builder1.pop();
|
||||
builder1.build();
|
||||
|
||||
final SceneBuilder builder2 = SceneBuilder();
|
||||
builder2.addRetained(layer);
|
||||
try {
|
||||
builder2.addRetained(layer);
|
||||
fail('Expected second addRetained to throw AssertionError but it returned successully');
|
||||
} on AssertionError catch (error) {
|
||||
expect(error.toString(), contains('The layer is already being used'));
|
||||
}
|
||||
builder2.build();
|
||||
}
|
||||
|
||||
// Attempts to use the same layer as `oldLayer` twice in the same scene.
|
||||
void testPushOldLayerTwice(_TestNoSharingFunction pushFunction) {
|
||||
final SceneBuilder builder1 = SceneBuilder();
|
||||
final EngineLayer layer = pushFunction(builder1, null);
|
||||
builder1.pop();
|
||||
builder1.build();
|
||||
|
||||
final SceneBuilder builder2 = SceneBuilder();
|
||||
pushFunction(builder2, layer);
|
||||
try {
|
||||
pushFunction(builder2, layer);
|
||||
fail('Expected push to throw AssertionError but it returned successully');
|
||||
} on AssertionError catch (error) {
|
||||
expect(error.toString(), contains('was previously used as oldLayer'));
|
||||
}
|
||||
builder2.build();
|
||||
}
|
||||
|
||||
// Attempts to use a child of a retained layer as an `oldLayer`.
|
||||
void testPushChildLayerOfRetainedLayer(_TestNoSharingFunction pushFunction) {
|
||||
final SceneBuilder builder1 = SceneBuilder();
|
||||
final EngineLayer layer = pushFunction(builder1, null);
|
||||
final EngineLayer childLayer = builder1.pushOpacity(123);
|
||||
builder1.pop();
|
||||
builder1.pop();
|
||||
builder1.build();
|
||||
|
||||
final SceneBuilder builder2 = SceneBuilder();
|
||||
builder2.addRetained(layer);
|
||||
try {
|
||||
builder2.pushOpacity(321, oldLayer: childLayer);
|
||||
fail('Expected pushOpacity to throw AssertionError but it returned successully');
|
||||
} on AssertionError catch (error) {
|
||||
expect(error.toString(), contains('The layer is already being used'));
|
||||
}
|
||||
builder2.build();
|
||||
}
|
||||
|
||||
// Attempts to retain a layer whose child is already used as `oldLayer` elsewhere in the scene.
|
||||
void testRetainParentLayerOfPushedChild(_TestNoSharingFunction pushFunction) {
|
||||
final SceneBuilder builder1 = SceneBuilder();
|
||||
final EngineLayer layer = pushFunction(builder1, null);
|
||||
final EngineLayer childLayer = builder1.pushOpacity(123);
|
||||
builder1.pop();
|
||||
builder1.pop();
|
||||
builder1.build();
|
||||
|
||||
final SceneBuilder builder2 = SceneBuilder();
|
||||
builder2.pushOpacity(234, oldLayer: childLayer);
|
||||
builder2.pop();
|
||||
try {
|
||||
builder2.addRetained(layer);
|
||||
fail('Expected addRetained to throw AssertionError but it returned successully');
|
||||
} on AssertionError catch (error) {
|
||||
expect(error.toString(), contains('The layer is already being used'));
|
||||
}
|
||||
builder2.build();
|
||||
}
|
||||
|
||||
// Attempts to retain a layer that has been used as `oldLayer` in a previous frame.
|
||||
void testRetainOldLayer(_TestNoSharingFunction pushFunction) {
|
||||
final SceneBuilder builder1 = SceneBuilder();
|
||||
final EngineLayer layer = pushFunction(builder1, null);
|
||||
builder1.pop();
|
||||
builder1.build();
|
||||
|
||||
final SceneBuilder builder2 = SceneBuilder();
|
||||
pushFunction(builder2, layer);
|
||||
builder2.pop();
|
||||
try {
|
||||
final SceneBuilder builder3 = SceneBuilder();
|
||||
builder3.addRetained(layer);
|
||||
fail('Expected addRetained to throw AssertionError but it returned successully');
|
||||
} on AssertionError catch (error) {
|
||||
expect(error.toString(), contains('was previously used as oldLayer'));
|
||||
}
|
||||
builder2.build();
|
||||
}
|
||||
|
||||
// Attempts to pass layer as `oldLayer` that has been used as `oldLayer` in a previous frame.
|
||||
void testPushOldLayer(_TestNoSharingFunction pushFunction) {
|
||||
final SceneBuilder builder1 = SceneBuilder();
|
||||
final EngineLayer layer = pushFunction(builder1, null);
|
||||
builder1.pop();
|
||||
builder1.build();
|
||||
|
||||
final SceneBuilder builder2 = SceneBuilder();
|
||||
pushFunction(builder2, layer);
|
||||
builder2.pop();
|
||||
try {
|
||||
final SceneBuilder builder3 = SceneBuilder();
|
||||
pushFunction(builder3, layer);
|
||||
fail('Expected addRetained to throw AssertionError but it returned successully');
|
||||
} on AssertionError catch (error) {
|
||||
expect(error.toString(), contains('was previously used as oldLayer'));
|
||||
}
|
||||
builder2.build();
|
||||
}
|
||||
|
||||
// Attempts to retain a parent of a layer used as `oldLayer` in a previous frame.
|
||||
void testRetainsParentOfOldLayer(_TestNoSharingFunction pushFunction) {
|
||||
final SceneBuilder builder1 = SceneBuilder();
|
||||
final EngineLayer parentLayer = pushFunction(builder1, null);
|
||||
final OpacityEngineLayer childLayer = builder1.pushOpacity(123);
|
||||
builder1.pop();
|
||||
builder1.pop();
|
||||
builder1.build();
|
||||
|
||||
final SceneBuilder builder2 = SceneBuilder();
|
||||
builder2.pushOpacity(321, oldLayer: childLayer);
|
||||
builder2.pop();
|
||||
try {
|
||||
final SceneBuilder builder3 = SceneBuilder();
|
||||
builder3.addRetained(parentLayer);
|
||||
fail('Expected addRetained to throw AssertionError but it returned successully');
|
||||
} on AssertionError catch (error) {
|
||||
expect(error.toString(), contains('was previously used as oldLayer'));
|
||||
}
|
||||
builder2.build();
|
||||
}
|
||||
|
||||
void testNoSharing(_TestNoSharingFunction pushFunction) {
|
||||
testPushThenIllegalRetain(pushFunction);
|
||||
testAddRetainedThenIllegalPush(pushFunction);
|
||||
testDoubleAddRetained(pushFunction);
|
||||
testPushOldLayerTwice(pushFunction);
|
||||
testPushChildLayerOfRetainedLayer(pushFunction);
|
||||
testRetainParentLayerOfPushedChild(pushFunction);
|
||||
testRetainOldLayer(pushFunction);
|
||||
testPushOldLayer(pushFunction);
|
||||
testRetainsParentOfOldLayer(pushFunction);
|
||||
}
|
||||
|
||||
test('SceneBuilder does not share a layer between addRetained and push*', () {
|
||||
testNoSharing((SceneBuilder builder, EngineLayer oldLayer) {
|
||||
return builder.pushOffset(0, 0, oldLayer: oldLayer);
|
||||
});
|
||||
testNoSharing((SceneBuilder builder, EngineLayer oldLayer) {
|
||||
return builder.pushTransform(Float64List(16), oldLayer: oldLayer);
|
||||
});
|
||||
testNoSharing((SceneBuilder builder, EngineLayer oldLayer) {
|
||||
return builder.pushClipRect(Rect.zero, oldLayer: oldLayer);
|
||||
});
|
||||
testNoSharing((SceneBuilder builder, EngineLayer oldLayer) {
|
||||
return builder.pushClipRRect(RRect.zero, oldLayer: oldLayer);
|
||||
});
|
||||
testNoSharing((SceneBuilder builder, EngineLayer oldLayer) {
|
||||
return builder.pushClipPath(Path(), oldLayer: oldLayer);
|
||||
});
|
||||
testNoSharing((SceneBuilder builder, EngineLayer oldLayer) {
|
||||
return builder.pushOpacity(100, oldLayer: oldLayer);
|
||||
});
|
||||
testNoSharing((SceneBuilder builder, EngineLayer oldLayer) {
|
||||
return builder.pushColorFilter(const Color.fromARGB(0, 0, 0, 0), BlendMode.color, oldLayer: oldLayer);
|
||||
});
|
||||
testNoSharing((SceneBuilder builder, EngineLayer oldLayer) {
|
||||
return builder.pushBackdropFilter(ImageFilter.blur(), oldLayer: oldLayer);
|
||||
});
|
||||
testNoSharing((SceneBuilder builder, EngineLayer oldLayer) {
|
||||
return builder.pushShaderMask(
|
||||
Gradient.radial(
|
||||
const Offset(0, 0),
|
||||
10,
|
||||
const <Color>[Color.fromARGB(0, 0, 0, 0), Color.fromARGB(0, 255, 255, 255)],
|
||||
),
|
||||
Rect.zero,
|
||||
BlendMode.color,
|
||||
oldLayer: oldLayer,
|
||||
);
|
||||
});
|
||||
testNoSharing((SceneBuilder builder, EngineLayer oldLayer) {
|
||||
return builder.pushPhysicalShape(path: Path(), color: const Color.fromARGB(0, 0, 0, 0), oldLayer: oldLayer);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
typedef _TestNoSharingFunction = EngineLayer Function(SceneBuilder builder, EngineLayer oldLayer);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user