Skip to content

[Impeller] Support managed static branches for ImpellerC shaders. #116103

@bdero

Description

@bdero

When authoring shaders which perform a given rendering task, it's often convenient to customize the task at runtime by dynamically branching on a discrete uniform input (constant with respect to the pipeline invocation). Shaders which are extended this way are both easy to author and maintain.
For example, a uniform could be used to turn on/off an optional rendering feature, or to enumerate different UV mapping behaviors when sampling from bound textures.

This technique can work great for simple branches that contribute minimal constant time overhead to the computation, but can quickly pose a performance problem if the branch is nontrivial and/or needs to be used in a superconstant code path.

  • Non-trivial branch: By non-trivial branch, I really mean many simple branches clumped together. It's not uncommon to run into cases where a large enum is used to customize a rendering subtask in Flutter. For example: When Impeller receives a DrawVertices command, it needs to blend the input colors with the paint color; all of Flutter's 27 blend functions are supported (at the time of writing, we perform this particular blend on the CPU, but performing this per-vertex operation on the GPU would scale better given parallelism in the vertex pipeline stage). GLSL does not support function pointers, rendering dynamic dispatch impossible for shaders targeting the GLES backend (as well as any other backend when GLSL is used as the source language). This means dispatching with a blend enum in a shader would require something like a 27 item switch (although this could be reduced to at most 5 branches in a binary decision tree).
  • Superconstant context: Convolution shaders such as Impeller's two pass Gaussian blur contain an expensive code path whose execution count scales linearly with respect to a uniform kernel radius. Even seemingly small overhead savings in such code paths can make a meaningful performance impact, as the computation may be executed hundreds or thousands of times per fragment per render pass.

The fast alternative to uniform dynamic branching is to produce individual specialized shaders for each branch case. Then, the correct shader can be chosen at render time on the CPU.

However, converting uniform branches to static branches is currently an incredibly cumbersome process that outputs maintainability nightmares.

  • A cumbersome time sink: Static shader variants need to be authored as individual shader files with individual names. The resulting shaders end up looking very different from the original with parts spread across a number of files connected through imports. Each of these shaders need to be hooked up as separate pipeline variants in the Impeller Entities renderer, which is quite a lot of boilerplate for each discrete enum option. This problem is exacerbated in cases where static shader variants are made for multiple distinct enums resulting in a combinatorial explosion.
  • Unmaintainable results: Code between the variant shaders can be shared through relative path importing, but the resulting code is fragmented across at least as many files as there are shader variants. Per-pipeline boilerplate in the Entities renderer means updating discrete variants is an error prone slog. Need to add a binary static branch to your 4 existing variants? Well, you need to make 4 more shaders and probably rename all of the existing boilerplate so that everything continues making sense, see you in a couple of hours.

These are the problems that managed static branches can solve. Static branching should be no more difficult (and perhaps even easier) than dynamic branching on discrete uniforms. For shader authors, defining and using static branches can be made trivial.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Important issues not at the top of the work listc: proposalA detailed proposal for a change to Fluttere: impellerImpeller rendering backend issues and features requestsengineflutter/engine related. See also e: labels.team-engineOwned by Engine teamtriaged-engineTriaged by Engine team

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions