Skip to content

Performance

Start with Haze's defaults, then tune the effects that cause problems on your screen. Check scrolling, transitions, and visual quality together: a setting is useful only if the screen still looks right and feels smooth.

Start with the defaults

Use HazePerformanceMode.Default for built-in Blur and Glass, and start Glass with GlassStyle.regular. Default selects the fixed Balanced profile. Custom effects use HazeSampling.Default.

Build the complete screen before choosing overrides. The area covered by effects, their number, and the content moving behind them all affect the cost.

Measure your screen

Use a release-like build on physical devices, including the slowest devices you support. Test scrolling, transitions, and the first appearance of an effect. For interactive Glass surfaces, also test hover, focus, and press responses.

Change one setting at a time. Keep device conditions and refresh rate consistent, then repeat the same interaction. Look for fewer missed frame deadlines and smoother motion without an unacceptable loss of detail.

On Android, Macrobenchmark can measure these interactions. Frame overrun tells you whether frames finish before their deadline; positive values indicate misses. CPU frame duration helps assess UI-thread and RenderThread cost, but does not directly measure GPU shader time. Check both metrics before deciding that a change helped.

If frames are slow

Try these changes in order, measuring after each:

  1. Reduce the affected area. Apply the effect only where it is visible. Smaller surfaces process less content.
  2. Reduce unnecessary effects. Remove effects that add little to the design. Where the design allows it, compare one shared surface with several independent surfaces.
  3. Try a lower quality setting. Test Performance, then increase quality if the result looks too soft or pixelated. See Choose a performance mode.
  4. Simplify the styling. Try uniform blur in place of progressive blur, or reduce Glass features such as Full chromatic aberration. Keep the simpler style if the visual difference does not justify the cost.
  5. Keep unrelated animation out of captured sources. With HazeInput.Sources, move animation outside the hazeSource subtree when the effect does not need to include it.

A stable source can reuse captured and processed output. Scrolling or animated backgrounds need fresh input, so always test those interactions as well as stationary content.

Share one source render between effects

By default, each effect that samples a hazeSource through HazeInput.Sources replays the source's content whenever it changes. A busy source, such as a feed with playing video, then costs more for every effect over it.

The experimental inputSharing parameter changes that. While sharing is active, the source renders once into an offscreen layer, and the window and every effect draw from that layer instead of replaying the source. The activation rules are HazeInputSharing's: Never is the default, Auto shares on Android once two or more effects sample the source, and Always shares on every platform once one does.

Sharing has two costs:

  • Clipping. While sharing is active, the source's drawing is clipped to its layout bounds, expanded by inputSharingOutsets, both on screen and in effects. Anything drawn beyond that is not shown. Outsets don't change the source's layout size or position, and they have no effect while sharing is inactive, when the source draws normally again, overflow included.
  • Memory. Each active source holds one texture, the size of the source plus its outsets.

For a source that stays inside its bounds, the default zero outsets are enough:

Modifier.hazeSource(state, inputSharing = HazeInputSharing.Auto)

If the source draws outside its bounds, for example a card with a shadow that reaches 4 dp to each side and 16 dp below it, declare that on each edge:

Modifier.hazeSource(
  state = state,
  inputSharing = HazeInputSharing.Auto,
  inputSharingOutsets = LayerOutsets(left = 4.dp, top = 0.dp, right = 4.dp, bottom = 16.dp),
)

Start with Never, and try sharing when the RenderThread spends its time replaying a source that several effects sample. Compare both settings with the same interaction on your target devices: look at CPU frame duration and frame overrun, at peak GPU memory, and at whether the clipped edges still look right. CPU frame duration does not measure GPU work, and the offscreen layer is re-rendered whenever the source changes, which adds GPU work while a source scrolls or animates.

In Haze's nested-source benchmark, sharing the inner feed source cut its replays from seven per frame to one. On a Pixel 8a, that lowered P90 CPU frame duration by 2–6% and mean DrawFrames time by 6–8%. Each active source added about 25–33 MiB of peak GPU memory. The P90 gains were 0.15–0.50 ms, comparable to the variation between passes of up to 0.42 ms, and sharing the outer source as well usually gave a smaller gain. This is one device and one synthetic feed, and the benchmark measured no GPU time, so it does not predict your screen. See the measurements and their limits.

Choose a performance mode

HazePerformanceMode controls the quality and rendering cost of built-in Blur and Glass. Choose based on the problem you can see:

What you need What to try
A starting point for most screens Default (Balanced).
Lower rendering cost Performance; check for softer or more pixelated detail.
A consistent middle quality setting Balanced.
Sharper detail Quality; check scrolling and transitions for missed deadlines.
A specific fixed quality setting Fixed(qualityFraction), after comparing the named modes.

For example, to try a lower-cost Blur setting with an existing source input:

Modifier.hazeBlur(
  input = HazeInput.Sources(hazeState),
  performanceMode = HazePerformanceMode.Performance,
)

The same performanceMode parameter is available on hazeGlass. To set a mode for a whole subtree, provide LocalHazePerformanceMode; an explicit performanceMode argument overrides it.

Performance, Balanced, and Quality correspond to Fixed(0f), Fixed(0.5f), and Fixed(1f). The fraction describes quality, not a percentage reduction in rendering time.

Measured quality trade-offs

In a single-pass test on a Pixel 8a (Android 17, 60 Hz, fixed-performance mode) with a changing background, Glass Balanced left about 7.8 ms of frame-deadline headroom at P90, compared with 6.6 ms for Quality. Glass Performance used roughly 9% less peak GPU memory than Quality in both stable and changing-background tests, comparing each method's peak-memory median. Each method ran eight measured iterations. These are reference points, not guaranteed savings or a universal mode ranking. See the full mode comparison and its repeat-coverage limits.

Keep a fixed override only when repeated measurements and visual checks show a useful benefit. A lower quality setting does not guarantee a lower CPU frame time in every workload.

Blur

If you only need to fade an effect's opacity, use a mask instead of progressive blur. Progressive blur is useful when the blur intensity itself needs to vary across the surface. See Progressive Blur and masks.

Keep expandLayerBounds enabled unless a measured improvement justifies changing it. It lets source-backed Blur sample surrounding content near the edges. Disabling it reduces the capture area but can change the edge appearance. See the modifier example.

Glass

Start with GlassStyle.regular and add optical features only where they improve the design. If Glass is expensive, compare simpler blur and chromatic aberration settings at the sizes and effect counts your screen actually uses.

Check an effect's first appearance separately from its ongoing animation: smooth scrolling does not rule out a pause when a Glass surface is first created. Include lighting, refraction, and transform changes during interaction in your measurements.

Use a Glass container only where surfaces should merge. A merged cluster draws one larger surface that covers its members and the bridges between them, so measure clustered screens against independent surfaces before adopting them. A container also shares one optical size, so one large member changes the diffusion of the small members next to it.

In a two-pass test on a Pixel 8a (Android 17, 60 Hz, fixed-performance mode), merging two nearby Regular Glass surfaces into one cluster over a changing source left about 4.5 ms less frame deadline margin at P90 and used about 7% more peak GPU memory than drawing them independently, even though its CPU frame duration P90 was 0.35 ms lower (conditions and limits).

For styling examples, see the Glass overview.

Backdrop versus Sources

HazeInput.Backdrop lets built-in Blur and Glass use pixels already drawn behind them in the current window. On supported Android devices it can filter those pixels directly, without marking the background with hazeSource.

Pass a HazeState or HazeInput.Sources to Backdrop and keep the hazeSource setup if the effect must work when native rendering is unavailable, including on other platforms or after a native failure. Without captured sources, the fallback has no background content to process.

Use explicit HazeInput.Sources when you need to select particular captured content. Native Backdrop uses the combined earlier pixels in the same window; it cannot include later drawing or content from another dialog, popup, or window. See Explicit inputs for examples.

Experimental native Backdrop

Native rendering requires a hardware-accelerated window on Android 37.2 or later and is disabled by default. Set HazeFeatureFlags.isPlatformBackdropEnabled = true before the effect is first composed to make it eligible. Unsupported configurations use source fallback. See Android window backdrops.

Compare native Backdrop with Sources before enabling it for performance reasons. Sources can reuse processed output when their input is stable; native Backdrop filters window pixels again when the effect draws.

Measured Backdrop cost

On a Pixel 8a (Android 17, 60 Hz), native Backdrop had 23–46% higher CPU frame P90 than Sources in the tested single-surface Glass workloads. Those figures are based on two passes in opposite orders at Quality, with eight iterations per method per pass. Native rendering can simplify setup, but these tests did not show a CPU performance benefit for Glass. See the full Backdrop comparison.

The benchmark results contain the complete tables and test conditions. Use them as reference points when measuring your own screen.