Skip save_layer for plain image fills (#11230)

Avoid an offscreen buffer per Fill::Image during tile walks: only use
save_layer when a shape image filter is present; axis-aligned rects and
frames without corner radii also skip the redundant container clip.
This commit is contained in:
Alejandro Alonso 2026-08-13 12:12:43 +02:00 committed by GitHub
parent be83656d55
commit cb57fd9dfa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 47 additions and 12 deletions

View File

@ -17,6 +17,8 @@
## Tile/render behavior
- Raster `Fill::Image`: skip `save_layer` unless the shape has an image filter; plain
Rect/Frame (no corners) also skip the container clip (`draw_image_fill` in fills.rs).
- Interactive transforms are distinct from viewport fast mode. `set_modifiers_start` enables fast mode and interactive transform; interactive transform still flushes each animation frame.
- During interactive transform, modifier tile invalidation is deferred to `render()` once per rAF. Outside interactive transform, `set_modifiers` rebuilds modifier tiles immediately.
- `set_modifiers_end` disables fast/interactive state and cancels pending async render; the caller must request the final full-quality render.

View File

@ -56,6 +56,15 @@ fn clip_to_shape(
}
}
/// Axis-aligned rect/frame with no corner radii: `dest` fills `selrect`, so a
/// clip to the container is a no-op before `draw_image_rect`.
fn is_axis_aligned_image_rect(shape: &Shape) -> bool {
matches!(
&shape.shape_type,
Type::Rect(Rect { corners: None }) | Type::Frame(Frame { corners: None, .. })
)
}
fn draw_image_fill(
render_state: &mut RenderState,
shape: &Shape,
@ -85,29 +94,53 @@ fn draw_image_fill(
let src_rect = get_source_rect(size, container, image_fill);
let dest_rect = container;
let sampling = get_resources().sampling_options;
let mut image_paint = skia::Paint::default();
image_paint.set_anti_alias(antialias);
// `save_layer` is only required when a shape-level image filter (blur) must
// run over the clipped image. Otherwise a plain save/clip (or no clip for
// axis-aligned rects) avoids an offscreen buffer per fill — the hot path
// for photo-heavy boards during tile walks.
if let Some(filter) = shape.image_filter(1.) {
image_paint.set_image_filter(filter.clone());
let mut layer_paint = skia::Paint::default();
layer_paint.set_anti_alias(antialias);
layer_paint.set_image_filter(filter);
let layer_rec = skia::canvas::SaveLayerRec::default().paint(&layer_paint);
canvas.save_layer(&layer_rec);
clip_to_shape(canvas, shape, container, antialias);
canvas.draw_image_rect_with_sampling_options(
image,
Some((&src_rect, skia::canvas::SrcRectConstraint::Strict)),
dest_rect,
sampling,
paint,
);
canvas.restore();
return;
}
let layer_rec = skia::canvas::SaveLayerRec::default().paint(&image_paint);
// Save the current canvas state
canvas.save_layer(&layer_rec);
let mut draw_paint = paint.clone();
draw_paint.set_anti_alias(antialias);
if is_axis_aligned_image_rect(shape) {
canvas.draw_image_rect_with_sampling_options(
image,
Some((&src_rect, skia::canvas::SrcRectConstraint::Strict)),
dest_rect,
sampling,
&draw_paint,
);
return;
}
canvas.save();
clip_to_shape(canvas, shape, container, antialias);
// Draw the image with the calculated destination rectangle
canvas.draw_image_rect_with_sampling_options(
image,
Some((&src_rect, skia::canvas::SrcRectConstraint::Strict)),
dest_rect,
get_resources().sampling_options,
paint,
sampling,
&draw_paint,
);
// Restore the canvas to remove the clipping
canvas.restore();
}