diff --git a/packages/flame/lib/src/camera/camera_component.dart b/packages/flame/lib/src/camera/camera_component.dart index 8b0998f77a7..b96dd9a1c5c 100644 --- a/packages/flame/lib/src/camera/camera_component.dart +++ b/packages/flame/lib/src/camera/camera_component.dart @@ -203,16 +203,6 @@ class CameraComponent extends Component { canvas.save(); try { currentCameras.add(this); - void renderWorld(Canvas canvas) { - canvas.transform2D(viewfinder.transform); - world!.renderFromCamera(canvas); - - // Render the viewfinder elements, which will be in front of - // the world, - // but with the same transforms applied to them. - viewfinder.renderTree(canvas); - } - final postProcessors = children.query(); if (postProcessors.isNotEmpty) { assert( @@ -223,13 +213,11 @@ class CameraComponent extends Component { postProcessor.render( canvas, viewport.virtualSize, - renderWorld, - (context) { - renderContext.currentPostProcess = context; - }, + _renderWorld, + _updatePostProcessContext, ); } else { - renderWorld(canvas); + _renderWorld(canvas); } } finally { currentCameras.removeLast(); @@ -242,6 +230,24 @@ class CameraComponent extends Component { canvas.restore(); } + /// Renders the world and the viewfinder elements through the camera + /// transform. An instance method rather than a local function, so that the + /// render pass does not allocate a closure per camera per frame. + void _renderWorld(Canvas canvas) { + canvas.transform2D(viewfinder.transform); + world!.renderFromCamera(canvas); + // Render the viewfinder elements, which will be in front of the world, + // but with the same transforms applied to them. + viewfinder.renderTree(canvas); + } + + // Not a setter: this is passed as a `ValueSetter` tear-off to + // `PostProcess.render`. + // ignore: use_setters_to_change_properties + void _updatePostProcessContext(PostProcess? context) { + renderContext.currentPostProcess = context; + } + /// Converts from the global (canvas) coordinate space to /// local (camera = viewport + viewfinder). /// diff --git a/packages/flame/lib/src/components/mixins/has_decorator.dart b/packages/flame/lib/src/components/mixins/has_decorator.dart index b3da7be9104..9617ad1a773 100644 --- a/packages/flame/lib/src/components/mixins/has_decorator.dart +++ b/packages/flame/lib/src/components/mixins/has_decorator.dart @@ -16,12 +16,16 @@ import 'package:flame/src/rendering/decorator.dart'; mixin HasDecorator on Component { Decorator? decorator; + /// Cached `super.renderTree` tear-off, so that the render pass does not + /// allocate a fresh closure for [Decorator.applyChain] on every frame. + void Function(Canvas)? _superRenderTree; + @override void renderTree(Canvas canvas) { if (decorator == null) { super.renderTree(canvas); } else { - decorator!.applyChain(super.renderTree, canvas); + decorator!.applyChain(_superRenderTree ??= super.renderTree, canvas); } } } diff --git a/packages/flame/lib/src/components/position_component.dart b/packages/flame/lib/src/components/position_component.dart index 4eb3c2fa08c..80f74c1ca41 100644 --- a/packages/flame/lib/src/components/position_component.dart +++ b/packages/flame/lib/src/components/position_component.dart @@ -518,9 +518,13 @@ class PositionComponent extends Component } } + /// Cached `super.renderTree` tear-off, so that the render pass does not + /// allocate a fresh closure for [Decorator.applyChain] on every frame. + void Function(Canvas)? _superRenderTree; + @override void renderTree(Canvas canvas) { - decorator.applyChain(super.renderTree, canvas); + decorator.applyChain(_superRenderTree ??= super.renderTree, canvas); } @internal diff --git a/packages/flame/lib/src/components/router/route.dart b/packages/flame/lib/src/components/router/route.dart index 76beebde07d..b1e3aa7ae87 100644 --- a/packages/flame/lib/src/components/router/route.dart +++ b/packages/flame/lib/src/components/router/route.dart @@ -162,10 +162,14 @@ class Route extends PositionComponent } } + /// Cached `super.renderTree` tear-off, so that the render pass does not + /// allocate a fresh closure for [Decorator.applyChain] on every frame. + void Function(Canvas)? _superRenderTree; + @override void renderTree(Canvas canvas) { if (isRendered) { - _renderEffect.applyChain(super.renderTree, canvas); + _renderEffect.applyChain(_superRenderTree ??= super.renderTree, canvas); } } diff --git a/packages/flame/lib/src/post_process/post_process_component.dart b/packages/flame/lib/src/post_process/post_process_component.dart index cfa597b261a..eb8712fba3a 100644 --- a/packages/flame/lib/src/post_process/post_process_component.dart +++ b/packages/flame/lib/src/post_process/post_process_component.dart @@ -105,22 +105,25 @@ class PostProcessComponent extends PositionComponent { return superSize; } + /// Cached render chain, so that the render pass does not allocate fresh + /// closures for `Decorator.applyChain` on every frame. + void Function(Canvas)? _renderChain; + @override @mustCallSuper void renderTree(Canvas canvas) { - decorator.applyChain( - (canvas) { - postProcess.render( - canvas, - size, - super.renderTreeWithoutDecorator, - (context) { - _renderContext.postProcess = postProcess; - }, - ); - }, - canvas, - ); + decorator.applyChain(_renderChain ??= _buildRenderChain(), canvas); + } + + void Function(Canvas) _buildRenderChain() { + final renderTree = super.renderTreeWithoutDecorator; + void updateContext(PostProcess? context) { + _renderContext.postProcess = postProcess; + } + + return (canvas) { + postProcess.render(canvas, size, renderTree, updateContext); + }; } } diff --git a/packages/flame/lib/src/rendering/decorator.dart b/packages/flame/lib/src/rendering/decorator.dart index b9457223ef1..a5f3190b6ab 100644 --- a/packages/flame/lib/src/rendering/decorator.dart +++ b/packages/flame/lib/src/rendering/decorator.dart @@ -31,16 +31,26 @@ class Decorator { /// The next decorator in the chain, or null if there is none. Decorator? _next; + /// Cached closure that forwards the draw call to the rest of the chain, + /// so that no closure needs to be allocated per frame. It is keyed by the + /// identity of the [_chainedDrawSource] it wraps: callers that pass the + /// same (cached) draw callback every frame reuse the same chain closure. + late void Function(Canvas) _chainedDraw; + void Function(Canvas)? _chainedDrawSource; + /// Applies this and all subsequent decorators if any. /// /// This method is the main method through which the decorator is applied. void applyChain(void Function(Canvas) draw, Canvas canvas) { - apply( - _next == null - ? draw - : (nextCanvas) => _next!.applyChain(draw, nextCanvas), - canvas, - ); + if (_next == null) { + apply(draw, canvas); + } else { + if (!identical(_chainedDrawSource, draw)) { + _chainedDrawSource = draw; + _chainedDraw = (nextCanvas) => _next!.applyChain(draw, nextCanvas); + } + apply(_chainedDraw, canvas); + } } /// Applies visual effect while [draw]ing on the [canvas].