Skip to content

Opacity/FadeTransition should allow child widgets to avoid repainting #101597

@jonahwilliams

Description

@jonahwilliams

In the engine an opacity layer is rendered by rendering child layers, performing a render target switch, and then blending the resulting texture with the opacity. This texture created by child rendering is cached, and if the child layers have not changed (as determined by the layer identity/id), the texture can be re-used on subsequent frames even as the opacity itself updates.

Unfortunately in many circumstances the framework does not hit this fast path. For example, consider the following code which uses AnimatedSwitcher and its default FadeTransition:

import 'package:flutter/material.dart';

void main() {
  runApp(const MaterialApp(
    checkerboardRasterCacheImages: true,
    home: Scaffold(
      body: Example(),
    ),
  ));
}

class Example extends StatefulWidget {
  const Example({Key? key}) : super(key: key);

  @override
  State<Example> createState() => _ExampleState();
}

class _ExampleState extends State<Example> {
  bool alternate = false;

  void _switch() {
    setState(() {
      alternate = !alternate;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        Container(height: 100),
        TextButton(onPressed: _switch, child: const Text('Switch')),
        AnimatedSwitcher(
          duration: const Duration(milliseconds: 250),
          child: Container(
            key: ValueKey<bool>(alternate),
            child: Column(
              children: <Widget>[
                Container(
                  width: 200,
                  height: 200,
                  color: Colors.red,
                  child:
                      alternate ? const Text('TITLE A') : const Text('TITLE B'),
                ),
              ],
            ),
          ),
        ),
      ],
    );
  }
}

If you observe this on device, you will notice that the raster cache checkerboard flashes - because each new frame gets a new entry as the children are not reused. This can also be observed in the timeline, where each frame has a RasterCachePopulate call indicating the texture created by child rendering has been recreated. Note that this is significantly easier to observe if the child rasterization is more complex.

ezgif-1-9af8f23920

If we replace the Container in the example above with a RepaintBoundary, then we get cached rendering:

ezgif-1-0b4c0c3469

To ensure that opacity performance can hit the fast path more frequently, we should update the Opacity and FadeTransition widgets to insert a RepaintBoundary child. This may have an impact on subpixel rendering. To evaluate I will run tests in google3 and report back. The expected impact of this change is improved raster times in applications that use AnimatedSwitcher, FadeTransition, and Opacity.

Metadata

Metadata

Assignees

Labels

P1High-priority issues at the top of the work listc: performanceRelates to speed or footprint issues (see "perf:" labels)frameworkflutter/packages/flutter repository. See also f: labels.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions