Batch
Any number of lenses, one read of the backdrop — on Impeller.
A lens on Impeller works by reading back what is already painted behind it and running the glass shader over that copy. The read is the expensive part, and it is per lens: a toolbar of eight glass buttons means eight readbacks a frame, sixteen with blur. Wrap them in a LiquidGlassBatch and they are all tagged with one shared backdrop key, so the engine takes the copy once and every member samples it.
LiquidGlassBatch(
child: Column(
children: [
for (final action in actions)
LiquidGlassLens(
style: buttonStyle,
child: ActionButton(action),
),
],
),
)Nothing else about the members changes: each keeps its own shape, its own style, its own child, its own adaptivity, its own touch response. They are still separate sheets of glass — the batch only makes them share the read. Members are found by context, so they can sit at any depth, and the components join the same way: a button, a FAB, an app bar, a LiquidGlassSheet placed in the tree — any of them inside a batch shares its read with no wiring at all. What showLiquidGlassSheet and showLiquidGlassDialog present is a route, outside every batch's tree, so it takes a read of its own unless the call asks to join one — see the note below. The tab bar always keeps a read of its own, even inside a batch: its capsule and its moving pill then draw cleanly at every frame of the pill's travel, and one read for the whole bar costs next to nothing.
| Parameter | Default | What it does |
|---|---|---|
child | — | The subtree whose LiquidGlassLens descendants share the read. |
enabled | true | Flip to false and every lens goes back to a read of its own — the pre-batch behaviour, and the A/B for measuring what the batch saves. |
mode | null | How a member blurred by the engine shares the read — see below. null uses LiquidGlassConfiguration.batchMode. |
blurMethod | null | Where the members blur: the engine's Gaussian or their own shader. null uses LiquidGlassConfiguration.batchBlurMethod. |
shaderBlurDispersion | null | Whether a member's shader blur keeps its style's dispersion. null uses LiquidGlassConfiguration.batchShaderBlurDispersion. |
Where the members blur#
A member with blur can blur in two places. With LiquidGlassBlurMethod.shader, the lens blurs the backdrop as its shader samples it, so the blur needs no pass of its own and the member stays on the batch's single read. The frost carries a fine grain instead of the engine's perfectly smooth Gaussian. With LiquidGlassBlurMethod.engine — the default — the engine's Gaussian blurs it, and the batch's mode decides how that blur shares the read:
| Mode | What a blurred member costs |
|---|---|
separatePass (default) | Two passes: the blur is a keyed pass of its own over the batch's copy, and the shader takes a second pass with its own read on top of it. One read for the batch plus one per member; equal blurs may be computed once for all of them. |
singlePass | One keyed pass: the blur rides inside the shader's pass through ImageFilter.compose. The whole batch costs one read — but see the Flutter bug below. |
A member with no blur, a shader blur or lite glass is one keyed pass in either mode. The blender keeps its own useEngineBlur and always batches as singlePass. Each parameter left null takes the matching batch value on LiquidGlassConfiguration, so one line in main() sets them for every batch in the app — the view's and the scaffold's included:
void main() {
// Batched lenses blur with the engine's Gaussian, in one pass each.
LiquidGlassConfiguration.batchBlurMethod = LiquidGlassBlurMethod.engine;
LiquidGlassConfiguration.batchMode = LiquidGlassBatchMode.singlePass;
runApp(const MyApp());
}Batch or blender?#
A blender draws its members with one shader: one silhouette, one material, one read — and at most eight members, because everything it draws has to fit in that shader. With smoothness: 0 it does not even fuse them; it is one pass for the set, which is what the deprecated LiquidGlassGroup was. The batch shares nothing but the read. Members stay separate passes, keep their own looks, and there is no limit on how many there can be. Reach for the blender when the lenses should read as one piece of glass, and for the batch when there are simply a lot of them. The two nest: a blender inside a batch is one member like any other, drawn from the batch's copy.
The one rule: members must not overlap#
Every member samples the same copy, taken before any of them painted — so a member cannot see another member's glass. Where two overlap, the one on top refracts what was behind the one below, and the stack reads as a single lens. Lists, grids and rows of controls never trip this. A lens that must overlap another belongs out of the batch:
LiquidGlassBatch(
child: Column(
children: [
Expanded(child: cards), // shared read
LiquidGlassBatch.exclude( // its own read again
child: myTabBar,
),
],
),
)The same goes for glass inside a pop-up. Say a glass button sits in a pop-up, and the pop-up and the page are inside one batch. The button does not see the pop-up behind it: it refracts the content the batch was wrapped around, as if the pop-up were not there. To make the button refract what is really behind it, take it out of the batch with LiquidGlassBatch.exclude (or enabled: false). If the pop-up holds a group of such buttons, put the group in a batch of its own — a LiquidGlassBatch inside the pop-up — so they share one read taken after the pop-up is painted.
// Inside the pop-up, which sits inside the page's batch:
LiquidGlassBatch.exclude( // one button: its own read
child: glassButton,
)
LiquidGlassBatch( // a group: one read of its own,
child: Row(children: glassButtons), // taken over the pop-up
)Where it applies#
The shared key is an Impeller mechanism, so the batch is what changes the cost there. On the Skia / web capture path a LiquidGlassView already captures its background once for every lens inside it, so the batch is inert rather than wrong — the same tree runs on both backends.
One layer of glass, floating over the page#
The batch exists because a backdrop read is the expensive thing Impeller does, and sharing it is the single biggest saving available on that engine — many lenses for the price of one. But it is worth saying what the batch is for, because the layouts it makes cheap are also the layouts that are right.
Glass is chrome. In almost every case it should be one layer floating over the page — a bar, a set of controls, a row of actions — with the content passing underneath it. That is what the material is for: you are meant to see what is under the glass, which is why it refracts rather than covers.
Comments
Comments are GitHub Discussions — reply from either place.