This commit is contained in:
Elena Torro 2026-06-18 15:51:59 +02:00
parent bfef6ea089
commit 4ad31a2b8d
3 changed files with 71 additions and 2 deletions

View File

@ -42,6 +42,7 @@ pub use images::*;
type ClipStack = Vec<(Rect, Option<Corners>, Matrix)>;
#[cfg_attr(feature = "profile-macros", derive(Debug))]
#[repr(u8)]
pub enum FrameType {
None = 0,
@ -877,6 +878,7 @@ impl RenderState {
/// on top of Target, then present. Backbuffer is left clean so it can be reused
/// as-is across interactive-transform frames without stale overlay pixels.
pub fn present_frame(&mut self, tree: ShapesPoolRef) {
performance::begin_measure!("present_frame");
// Viewer masked passes render a partial scene onto a transparent backbuffer.
// SrcOver would keep pass-1 pixels wherever the backbuffer stays transparent.
if self.viewer_masked_pass() {
@ -892,7 +894,10 @@ impl RenderState {
}
ui::render(self, tree);
debug::render_wasm_label(self);
performance::begin_measure!("present_frame:flush_submit");
self.surfaces.flush_and_submit(SurfaceId::Target);
performance::end_measure!("present_frame:flush_submit");
performance::end_measure!("present_frame");
}
/// Renders only the canvas background and UI surface (rulers/frame), without
@ -2346,11 +2351,37 @@ impl RenderState {
self.render_shape_tree_partial(base_object, tree, timestamp, allow_stop)?;
if !self.options.is_interactive_transform() {
performance::begin_measure!("atlas_to_backbuffer");
self.surfaces.draw_tile_atlas_to_backbuffer(
&self.viewbox,
&self.tile_viewbox,
self.background_color,
);
performance::end_measure!("atlas_to_backbuffer");
}
#[cfg(feature = "profile-macros")]
{
let usage = get_gpu_state().context.resource_cache_usage();
let limit_mb = get_gpu_state().context.resource_cache_limit() as f64 / 1_048_576.0;
let mts = get_gpu_state().max_texture_size();
let atlas_mb = (mts as f64 * mts as f64 * 4.0) / 1_048_576.0;
let cdim = self.surfaces.cache_dimensions();
let cache_mb = (cdim.width as f64 * cdim.height as f64 * 4.0) / 1_048_576.0;
crate::console_log!(
"[GPU] frame={:?} interactive={} fast={} rcache_mb={:.1} limit_mb={:.1} rcache_n={} cache={}x{} cache_mb={:.1} max_tex={} atlas_mb={:.1}",
frame_type,
self.options.is_interactive_transform(),
self.options.is_fast_mode(),
usage.resource_bytes as f64 / 1_048_576.0,
limit_mb,
usage.resource_count,
cdim.width,
cdim.height,
cache_mb,
mts,
atlas_mb
);
}
match frame_type {

View File

@ -6,7 +6,7 @@ use skia_safe::gpu::{
use skia_safe::{self as skia, ISize};
const MIN_MAX_TEXTURE_SIZE: i32 = 512;
const MAX_MAX_TEXTURE_SIZE: i32 = 8192 * 2;
const MAX_MAX_TEXTURE_SIZE: i32 = 4096 * 2;
#[derive(Debug, Clone)]
pub struct GpuState {

View File

@ -532,7 +532,16 @@ impl Surfaces {
) {
self.tiles.update(viewbox, tile_viewbox);
if self.tiles.needs_snapshot() || self.tile_atlas_image.is_none() {
self.tile_atlas_image = Some(self.tile_atlas.image_snapshot());
// Snapshot only the occupied sub-region (origin stays at 0,0 so the
// `textures` atlas coordinates remain valid) instead of the whole
// max_texture_size² atlas — avoids a full-atlas COW copy each settle.
let snap = match self.tiles.used_bounds() {
Some(bounds) => self.tile_atlas.image_snapshot_with_bounds(bounds),
None => Some(self.tile_atlas.image_snapshot()),
};
if let Some(image) = snap {
self.tile_atlas_image = Some(image);
}
self.tiles.snapshot();
}
let Some(atlas_image) = self.tile_atlas_image.as_ref() else {
@ -1579,6 +1588,35 @@ impl TileTextureCache {
self.is_updated = false;
}
/// Bounding box (from the atlas origin) of the slots actually sampled this
/// frame. Slots are allocated row-major from index 0 and freed slots are
/// reused first, so occupied slots cluster near (0,0): snapshotting only
/// `(0,0)..(max_right,max_bottom)` keeps the `textures`/`transforms`
/// coordinates valid (origin stays 0) while avoiding a full-atlas
/// `image_snapshot` (1GB→content-sized). `None` when nothing is in use.
pub fn used_bounds(&self) -> Option<skia::IRect> {
let mut max_right = 0.0f32;
let mut max_bottom = 0.0f32;
let mut any = false;
for r in self.textures.iter() {
if r.is_empty() {
continue;
}
any = true;
max_right = max_right.max(r.right);
max_bottom = max_bottom.max(r.bottom);
}
if !any {
return None;
}
Some(skia::IRect::new(
0,
0,
max_right.ceil() as i32,
max_bottom.ceil() as i32,
))
}
fn gc_non_visible(&mut self, tile_viewbox: &TileViewbox) {
let marked: Vec<_> = self
.grid