mirror of
https://github.com/penpot/penpot.git
synced 2026-08-09 22:38:40 +00:00
WIP
This commit is contained in:
parent
201c5f9816
commit
e9641951d7
@ -3924,72 +3924,9 @@ impl RenderState {
|
||||
area
|
||||
}
|
||||
|
||||
/// Peel outer columns/rows from `tiles` until the AABB fits
|
||||
/// `max_w`×`max_h` (+1px ceil slack). Returns deferred tiles.
|
||||
fn shrink_tiles_to_paint_fit(
|
||||
tiles: &mut Vec<tiles::Tile>,
|
||||
scale: f32,
|
||||
margins: skia::ISize,
|
||||
max_w: i32,
|
||||
max_h: i32,
|
||||
) -> Vec<tiles::Tile> {
|
||||
let mut deferred = Vec::new();
|
||||
let cx = tiles.iter().map(|t| t.x()).sum::<i32>() as f32 / tiles.len() as f32;
|
||||
let cy = tiles.iter().map(|t| t.y()).sum::<i32>() as f32 / tiles.len() as f32;
|
||||
let slack_w = max_w.saturating_add(1);
|
||||
let slack_h = max_h.saturating_add(1);
|
||||
|
||||
while tiles.len() > 1 {
|
||||
let area = Self::tiles_doc_area(tiles, scale);
|
||||
let need_w = (area.width() * scale).ceil() as i32 + 2 * margins.width;
|
||||
let need_h = (area.height() * scale).ceil() as i32 + 2 * margins.height;
|
||||
if need_w <= slack_w && need_h <= slack_h {
|
||||
break;
|
||||
}
|
||||
|
||||
let min_x = tiles.iter().map(|t| t.x()).min().unwrap();
|
||||
let max_x = tiles.iter().map(|t| t.x()).max().unwrap();
|
||||
let min_y = tiles.iter().map(|t| t.y()).min().unwrap();
|
||||
let max_y = tiles.iter().map(|t| t.y()).max().unwrap();
|
||||
|
||||
// Prefer shrinking the oversize axis; drop the edge farthest from centroid.
|
||||
let drop_x = need_w > slack_w || (max_x - min_x) >= (max_y - min_y);
|
||||
let drop_lo = if drop_x {
|
||||
(cx - min_x as f32) >= (max_x as f32 - cx)
|
||||
} else {
|
||||
(cy - min_y as f32) >= (max_y as f32 - cy)
|
||||
};
|
||||
|
||||
let edge_x = if drop_lo { min_x } else { max_x };
|
||||
let edge_y = if drop_lo { min_y } else { max_y };
|
||||
|
||||
let mut kept = Vec::with_capacity(tiles.len());
|
||||
for tile in tiles.drain(..) {
|
||||
let on_edge = if drop_x {
|
||||
tile.x() == edge_x
|
||||
} else {
|
||||
tile.y() == edge_y
|
||||
};
|
||||
if on_edge {
|
||||
deferred.push(tile);
|
||||
} else {
|
||||
kept.push(tile);
|
||||
}
|
||||
}
|
||||
*tiles = kept;
|
||||
if tiles.is_empty() {
|
||||
if let Some(t) = deferred.pop() {
|
||||
tiles.push(t);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
deferred
|
||||
}
|
||||
|
||||
/// Drain pending uncached tiles into a paint-once region when safe.
|
||||
/// Returns true when `paint_region` was started and nodes were seeded.
|
||||
fn try_begin_paint_region(&mut self, root_ids: &[Uuid], tree: ShapesPoolRef) -> Result<bool> {
|
||||
/// Drain pending uncached tiles into one paint-once region: a single tree
|
||||
/// walk into Current, then crop to atlas slots. No banding / re-walks.
|
||||
fn try_begin_paint_region(&mut self, root_ids: &[Uuid], _tree: ShapesPoolRef) -> Result<bool> {
|
||||
if self.viewer_masked_pass() || self.options.is_interactive_transform() {
|
||||
println!(
|
||||
"[PAINT-PERF] skip_paint_region viewer_mask={} interactive={}",
|
||||
@ -4006,14 +3943,10 @@ impl RenderState {
|
||||
let mut region_tiles = Vec::new();
|
||||
let mut cached_n = 0usize;
|
||||
for tile in self.pending_tiles.list.drain(..) {
|
||||
// Cached tiles are composed from the atlas; drop them from pending.
|
||||
if self.surfaces.has_cached_tile_surface(tile) {
|
||||
cached_n += 1;
|
||||
continue;
|
||||
}
|
||||
// Include empty tiles too: the region AABB is the interest set, and
|
||||
// the walker seeds roots (below) so shapes still get indexed/painted
|
||||
// even when the tile grid is stale or sparse.
|
||||
region_tiles.push(tile);
|
||||
}
|
||||
|
||||
@ -4025,116 +3958,64 @@ impl RenderState {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let empty_n = region_tiles
|
||||
.iter()
|
||||
.filter(|t| self.tiles.is_empty_at(**t))
|
||||
.count();
|
||||
|
||||
let scale = self.get_scale();
|
||||
let mut area = Self::tiles_doc_area(®ion_tiles, scale);
|
||||
let mut need = self.surfaces.paint_region_need_dims(area, scale);
|
||||
let have = self.surfaces.paint_surface_size();
|
||||
|
||||
// Band to the current viewport-sized Current (do not grow to GPU max —
|
||||
// that packed too much GPU work and hung the browser on Full present).
|
||||
if region_tiles.len() > 1 && !self.surfaces.region_fits_paint_surface(area, scale) {
|
||||
let before = region_tiles.len();
|
||||
let deferred = Self::shrink_tiles_to_paint_fit(
|
||||
&mut region_tiles,
|
||||
scale,
|
||||
self.surfaces.margins(),
|
||||
have.width,
|
||||
have.height,
|
||||
);
|
||||
self.pending_tiles.list.extend(deferred);
|
||||
area = Self::tiles_doc_area(®ion_tiles, scale);
|
||||
need = self.surfaces.paint_region_need_dims(area, scale);
|
||||
if region_tiles.len() == 1 {
|
||||
println!(
|
||||
"[PAINT-PERF] band_paint_region {}→{} tiles need={}x{} have={}x{}",
|
||||
before,
|
||||
region_tiles.len(),
|
||||
need.width,
|
||||
need.height,
|
||||
have.width,
|
||||
have.height
|
||||
"[PAINT-PERF] skip_paint_region reason=single_tile pending={} cached={}",
|
||||
pending_n, cached_n
|
||||
);
|
||||
self.pending_tiles.list.extend(region_tiles);
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let fits = self.surfaces.region_fits_paint_surface(area, scale);
|
||||
let scale = self.get_scale();
|
||||
let area = Self::tiles_doc_area(®ion_tiles, scale);
|
||||
let need = self.surfaces.paint_region_need_dims(area, scale);
|
||||
let have = self.surfaces.paint_surface_size();
|
||||
let max = crate::get_gpu_state().max_texture_size();
|
||||
|
||||
if region_tiles.len() == 1 || !fits {
|
||||
let reason = if region_tiles.len() == 1 {
|
||||
"single_tile"
|
||||
} else {
|
||||
"too_big"
|
||||
};
|
||||
// One walk for the whole pending set. Only give up when the AABB cannot
|
||||
// fit the GPU max texture (then fall back to per-tile).
|
||||
if !self.surfaces.region_fits_paint_surface(area, scale) {
|
||||
println!(
|
||||
"[PAINT-PERF] skip_paint_region reason={} tiles={} empty≈{} area=({:.0}x{:.0})@{:.3} need={}x{} have={}x{} pending={} cached={}",
|
||||
reason,
|
||||
"[PAINT-PERF] skip_paint_region reason=too_big tiles={} area=({:.0}x{:.0})@{:.3} need={}x{} max={} have={}x{}",
|
||||
region_tiles.len(),
|
||||
empty_n,
|
||||
area.width(),
|
||||
area.height(),
|
||||
scale,
|
||||
need.width,
|
||||
need.height,
|
||||
max,
|
||||
have.width,
|
||||
have.height,
|
||||
pending_n,
|
||||
cached_n
|
||||
);
|
||||
// Restore for the single-tile path (pop from end).
|
||||
self.pending_tiles.list.extend(region_tiles);
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let mut shape_ids: HashSet<Uuid> = HashSet::default();
|
||||
let mut region_has_bg_blur = false;
|
||||
for tile in ®ion_tiles {
|
||||
if let Some(ids) = self.tiles.get_shapes_at(*tile) {
|
||||
for id in ids {
|
||||
shape_ids.insert(*id);
|
||||
if !region_has_bg_blur {
|
||||
region_has_bg_blur = tree
|
||||
.get(id)
|
||||
.is_some_and(|s| s.visible_background_blur().is_some());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut valid_ids = Vec::new();
|
||||
// Empty/stale tile index, or any bg-blur in the region: walk all roots
|
||||
// so shapes are (re)indexed and painted into the region in one pass.
|
||||
if shape_ids.is_empty() || region_has_bg_blur {
|
||||
valid_ids.extend(root_ids.iter().copied());
|
||||
} else {
|
||||
for root_id in root_ids {
|
||||
if shape_ids.contains(root_id) {
|
||||
valid_ids.push(*root_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if valid_ids.is_empty() {
|
||||
println!(
|
||||
"[PAINT-PERF] skip_paint_region reason=no_root_ids tiles={} shapes_in_tiles={}",
|
||||
region_tiles.len(),
|
||||
shape_ids.len()
|
||||
have.height
|
||||
);
|
||||
self.pending_tiles.list.extend(region_tiles);
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
// Current already covers viewport+interest; region was banded to fit.
|
||||
// Resize Current to the full region (clamped to GPU max) so one walk
|
||||
// covers every pending tile.
|
||||
let t_resize = performance::get_time();
|
||||
self.surfaces.resize_paint_surfaces_to(need)?;
|
||||
let after = self.surfaces.paint_surface_size();
|
||||
println!(
|
||||
"[PAINT-PERF] resize_paint_region {}x{}→{}x{} ms={}",
|
||||
have.width,
|
||||
have.height,
|
||||
after.width,
|
||||
after.height,
|
||||
performance::get_time() - t_resize
|
||||
);
|
||||
|
||||
self.update_render_context_for_area(area);
|
||||
self.current_tile = Some(region_tiles[0]);
|
||||
self.current_tile_had_shapes = true;
|
||||
self.tile_atlas_flushed = false;
|
||||
self.drop_shadows_ops_warmed = false;
|
||||
|
||||
// One tree walk: seed every root. Off-region shapes cull cheaply; a
|
||||
// single frame with many children is painted once.
|
||||
self.pending_nodes
|
||||
.extend(valid_ids.into_iter().map(|id| NodeRenderState {
|
||||
.extend(root_ids.iter().copied().map(|id| NodeRenderState {
|
||||
id,
|
||||
visited_children: false,
|
||||
clip_bounds: None,
|
||||
@ -4148,25 +4029,22 @@ impl RenderState {
|
||||
tiles: region_tiles,
|
||||
});
|
||||
println!(
|
||||
"[PAINT-PERF] begin_paint_region tiles={} area=({:.0},{:.0},{:.0}x{:.0}) scale={:.3} need={}x{} have={}x{}",
|
||||
"[PAINT-PERF] begin_paint_region tiles={} roots={} area=({:.0},{:.0},{:.0}x{:.0}) scale={:.3} need={}x{}",
|
||||
n_tiles,
|
||||
root_ids.len(),
|
||||
area.left,
|
||||
area.top,
|
||||
area.width(),
|
||||
area.height(),
|
||||
scale,
|
||||
need.width,
|
||||
need.height,
|
||||
have.width,
|
||||
have.height
|
||||
need.height
|
||||
);
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn apply_paint_region_to_atlas(&mut self, region: &PaintRegion) -> Result<()> {
|
||||
let t0 = performance::get_time();
|
||||
// Soft-drain only: hard flush_and_submit here waited on the whole
|
||||
// paint-once backlog and hung the browser after zoom.
|
||||
Self::drain_partial_gpu_soft();
|
||||
self.cache_cleared_this_render = true;
|
||||
let scale = self.get_scale();
|
||||
@ -4192,12 +4070,32 @@ impl RenderState {
|
||||
region.tiles.len(),
|
||||
performance::get_time() - t0
|
||||
);
|
||||
self.restore_paint_surfaces_to_viewport()?;
|
||||
// Yield one Partial before Full present so GPU work from this region
|
||||
// is not synced in the same rAF as compose+present.
|
||||
self.defer_full_after_paint_region = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn restore_paint_surfaces_to_viewport(&mut self) -> Result<()> {
|
||||
let dpr_width = (self.viewbox.width() * self.options.dpr).floor() as i32;
|
||||
let dpr_height = (self.viewbox.height() * self.options.dpr).floor() as i32;
|
||||
let before = self.surfaces.paint_surface_size();
|
||||
self.surfaces.resize_paint_surfaces(
|
||||
dpr_width,
|
||||
dpr_height,
|
||||
self.options.dpr_viewport_interest_area_threshold,
|
||||
)?;
|
||||
let after = self.surfaces.paint_surface_size();
|
||||
if before != after {
|
||||
println!(
|
||||
"[PAINT-PERF] restore_paint_surfaces {}x{}→{}x{}",
|
||||
before.width, before.height, after.width, after.height
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn render_shape_tree_partial(
|
||||
&mut self,
|
||||
base_object: Option<&Uuid>,
|
||||
|
||||
@ -1348,16 +1348,15 @@ impl Surfaces {
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether a doc-space region fits in the **current** paint surface at
|
||||
/// `scale`. Paint-once bands to this size instead of growing Current up to
|
||||
/// the GPU max — a 4096² region packs too much GPU work and stalls the
|
||||
/// browser on Full present after zoom.
|
||||
/// Whether a doc-space region fits the **GPU max texture** at `scale`.
|
||||
/// Current is resized to the region for a single tree walk; only fall back
|
||||
/// to per-tile when the AABB exceeds the GPU limit.
|
||||
///
|
||||
/// Allows 1px of ceil slack from `paint_region_need_dims`.
|
||||
pub fn region_fits_paint_surface(&self, render_area: skia::Rect, scale: f32) -> bool {
|
||||
let need = self.paint_region_need_dims(render_area, scale);
|
||||
need.width <= self.current.width().saturating_add(1)
|
||||
&& need.height <= self.current.height().saturating_add(1)
|
||||
let max = get_gpu_state().max_texture_size();
|
||||
need.width <= max.saturating_add(1) && need.height <= max.saturating_add(1)
|
||||
}
|
||||
|
||||
/// Pixel rect inside Current for a tile given the region `render_area` and scale.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user