Avoid per-tile image_snapshot when filling atlases (#11093)

Copy Current into DocAtlas and the tile atlas with Surface::draw
instead of image_snapshot_with_bounds, matching the interactive
path and removing a GPU sync stall on every completed tile.
This commit is contained in:
Alejandro Alonso 2026-08-05 17:12:09 +02:00 committed by GitHub
parent 6df045b194
commit 35bdcde183
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -27,6 +27,29 @@ const TILE_DRAWABLE_RECT: IRect = IRect {
}; };
const DOC_ATLAS_MAX_DIM: i32 = 4096; const DOC_ATLAS_MAX_DIM: i32 = 4096;
/// GPU→GPU copy of `src` from `from` into `dst` on `to_canvas`, without
/// `image_snapshot` (avoids per-tile sync stalls on WebGL).
fn draw_surface_src_rect_to_dst(
from: &mut skia::Surface,
to_canvas: &skia::Canvas,
src: skia::Rect,
dst: skia::Rect,
sampling: skia::SamplingOptions,
) {
if src.is_empty() || dst.is_empty() {
return;
}
to_canvas.save();
to_canvas.clip_rect(dst, None, true);
let sx = dst.width() / src.width();
let sy = dst.height() / src.height();
to_canvas.translate((dst.left, dst.top));
to_canvas.scale((sx, sy));
to_canvas.translate((-src.left, -src.top));
from.draw(to_canvas, (0.0, 0.0), sampling, None);
to_canvas.restore();
}
pub fn get_cache_size(viewbox: &Viewbox, interest: i32) -> skia::ISize { pub fn get_cache_size(viewbox: &Viewbox, interest: i32) -> skia::ISize {
// First we retrieve the extended area of the viewport that we could render. // First we retrieve the extended area of the viewport that we could render.
let TileRect(isx, isy, iex, iey) = let TileRect(isx, isy, iex, iey) =
@ -238,17 +261,20 @@ impl DocAtlas {
Ok(()) Ok(())
} }
fn blit_tile_image_into_atlas( /// Blit a Current-surface drawable rect into the doc atlas without
/// `image_snapshot` (GPU→GPU draw; avoids per-tile sync stalls).
fn blit_current_drawable_into_atlas(
&mut self, &mut self,
gpu_state: &mut GpuState, gpu_state: &mut GpuState,
tile_image: &skia::Image, current: &mut skia::Surface,
drawable_src: skia::Rect,
tile_doc_rect: skia::Rect, tile_doc_rect: skia::Rect,
sampling: skia::SamplingOptions,
) -> Result<()> { ) -> Result<()> {
if tile_doc_rect.is_empty() { if tile_doc_rect.is_empty() || drawable_src.is_empty() {
return Ok(()); return Ok(());
} }
// Clamp to document bounds (if any) and compute a matching source-rect in tile pixels.
let mut clipped_doc_rect = tile_doc_rect; let mut clipped_doc_rect = tile_doc_rect;
if let Some(bounds) = self.doc_bounds { if let Some(bounds) = self.doc_bounds {
if !clipped_doc_rect.intersect(bounds) { if !clipped_doc_rect.intersect(bounds) {
@ -261,7 +287,6 @@ impl DocAtlas {
self.ensure_atlas_contains(gpu_state, clipped_doc_rect)?; self.ensure_atlas_contains(gpu_state, clipped_doc_rect)?;
// Destination is document-space rect mapped into atlas pixel coords.
let dst = skia::Rect::from_xywh( let dst = skia::Rect::from_xywh(
(clipped_doc_rect.left - self.origin.x) * self.scale, (clipped_doc_rect.left - self.origin.x) * self.scale,
(clipped_doc_rect.top - self.origin.y) * self.scale, (clipped_doc_rect.top - self.origin.y) * self.scale,
@ -269,24 +294,18 @@ impl DocAtlas {
clipped_doc_rect.height() * self.scale, clipped_doc_rect.height() * self.scale,
); );
// Compute source rect in tile_image pixel coordinates.
let img_w = tile_image.width() as f32;
let img_h = tile_image.height() as f32;
let tw = tile_doc_rect.width().max(1.0); let tw = tile_doc_rect.width().max(1.0);
let th = tile_doc_rect.height().max(1.0); let th = tile_doc_rect.height().max(1.0);
let dw = drawable_src.width();
let sx = ((clipped_doc_rect.left - tile_doc_rect.left) / tw) * img_w; let dh = drawable_src.height();
let sy = ((clipped_doc_rect.top - tile_doc_rect.top) / th) * img_h; let src = skia::Rect::from_xywh(
let sw = (clipped_doc_rect.width() / tw) * img_w; drawable_src.left + ((clipped_doc_rect.left - tile_doc_rect.left) / tw) * dw,
let sh = (clipped_doc_rect.height() / th) * img_h; drawable_src.top + ((clipped_doc_rect.top - tile_doc_rect.top) / th) * dh,
let src = skia::Rect::from_xywh(sx, sy, sw, sh); (clipped_doc_rect.width() / tw) * dw,
(clipped_doc_rect.height() / th) * dh,
self.surface.canvas().draw_image_rect(
tile_image,
Some((&src, skia::canvas::SrcRectConstraint::Fast)),
dst,
&skia::Paint::default(),
); );
draw_surface_src_rect_to_dst(current, self.surface.canvas(), src, dst, sampling);
Ok(()) Ok(())
} }
@ -1198,34 +1217,34 @@ impl Surfaces {
tile_doc_rect: skia::Rect, tile_doc_rect: skia::Rect,
) { ) {
let gpu_state = get_gpu_state(); let gpu_state = get_gpu_state();
let rect = TILE_DRAWABLE_RECT; let src = skia::Rect::from(TILE_DRAWABLE_RECT);
let sampling = self.sampling_options;
let tile_image_opt = self.current.image_snapshot_with_bounds(rect); // DocAtlas + tile atlas via Surface::draw (no image_snapshot sync).
if let Some(tile_image) = tile_image_opt { let _ = self.atlas.blit_current_drawable_into_atlas(
if !skip_cache_surface { gpu_state,
// Draw to cache surface for render_from_cache &mut self.current,
self.cache.canvas().draw_image_rect( src,
&tile_image, tile_doc_rect,
None, sampling,
tile_rect, );
&skia::Paint::default(), self.atlas.tile_doc_rects.insert(*tile, tile_doc_rect);
);
}
// Incrementally update persistent 1:1 atlas in document space. let tile_ref = self.tiles.add(tile_viewbox, tile);
// `tile_doc_rect` is in world/document coordinates (1 unit == 1 px at 100%). let dst = tile_ref.rect;
let _ = self let mut current = self.current.clone();
.atlas draw_surface_src_rect_to_dst(&mut current, self.tile_atlas.canvas(), src, dst, sampling);
.blit_tile_image_into_atlas(gpu_state, &tile_image, tile_doc_rect);
self.atlas.tile_doc_rects.insert(*tile, tile_doc_rect);
// Draws current tile into tile atlas if !skip_cache_surface {
let tile_ref = self.tiles.add(tile_viewbox, tile); // Optional legacy Cache surface fill (debug). Pan/zoom preview
self.tile_atlas.canvas().draw_image_rect( // uses DocAtlas + tile-atlas textures via render_from_cache.
&tile_image, let mut current = self.current.clone();
None, draw_surface_src_rect_to_dst(
tile_ref.rect, &mut current,
&skia::Paint::default(), self.cache.canvas(),
src,
*tile_rect,
sampling,
); );
} }
} }