mirror of
https://github.com/penpot/penpot.git
synced 2026-08-18 02:39:02 +00:00
⚡ Soft-drain GPU during recursive drop-shadow passes
Frame drop shadows re-render every child silhouette inside a single walker node, so the usual partial drain cadence (every N tree nodes) lets GPU ops pile up. Flush every few shadow passes instead.
This commit is contained in:
parent
072730574d
commit
f25a2c701f
@ -30,6 +30,9 @@
|
|||||||
- Zoom settle: visible tiles present via `FrameType::ViewportReady` before interest-ring
|
- Zoom settle: visible tiles present via `FrameType::ViewportReady` before interest-ring
|
||||||
work; crop-cache rebuild is deferred to the later `Full` so the soft→sharp snap is
|
work; crop-cache rebuild is deferred to the later `Full` so the soft→sharp snap is
|
||||||
compose+present only.
|
compose+present only.
|
||||||
|
- Drop-shadow cadence: walker soft-drains every N tree nodes; recursive frame shadows
|
||||||
|
also soft-drain every M silhouette/filter passes (`partial_gpu_drain_shadow_every_n`,
|
||||||
|
default 2) because one frame node can trigger many child re-renders.
|
||||||
- Interactive transforms are distinct from viewport fast mode. `set_modifiers_start` enables fast mode and interactive transform; interactive transform still flushes each animation frame.
|
- 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.
|
- 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.
|
- `set_modifiers_end` disables fast/interactive state and cancels pending async render; the caller must request the final full-quality render.
|
||||||
|
|||||||
@ -424,6 +424,8 @@ pub(crate) struct RenderState {
|
|||||||
/// Visible tiles were already presented this pass; interest-ring fill may
|
/// Visible tiles were already presented this pass; interest-ring fill may
|
||||||
/// still be running. Final Full should not re-present.
|
/// still be running. Final Full should not re-present.
|
||||||
pub viewport_presented: bool,
|
pub viewport_presented: bool,
|
||||||
|
/// Drop-shadow silhouette ops since the last shadow cadence drain (reset per tile).
|
||||||
|
shadow_gpu_ops_since_drain: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct InteractiveDragCrop {
|
pub struct InteractiveDragCrop {
|
||||||
@ -609,6 +611,7 @@ impl RenderState {
|
|||||||
tile_atlas_flushed: false,
|
tile_atlas_flushed: false,
|
||||||
drop_shadows_ops_warmed: false,
|
drop_shadows_ops_warmed: false,
|
||||||
viewport_presented: false,
|
viewport_presented: false,
|
||||||
|
shadow_gpu_ops_since_drain: 0,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2298,6 +2301,7 @@ impl RenderState {
|
|||||||
// reorder by distance to the center.
|
// reorder by distance to the center.
|
||||||
self.current_tile = None;
|
self.current_tile = None;
|
||||||
self.viewport_presented = false;
|
self.viewport_presented = false;
|
||||||
|
self.shadow_gpu_ops_since_drain = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start_render_loop(
|
pub fn start_render_loop(
|
||||||
@ -2719,6 +2723,23 @@ impl RenderState {
|
|||||||
crate::get_gpu_state().context.flush(None);
|
crate::get_gpu_state().context.flush(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Count one expensive drop-shadow GPU pass and soft-drain on the shadow
|
||||||
|
/// cadence. Frame/group shadows re-render many children inside a single
|
||||||
|
/// walker node, so they need tighter flushing than [`Self::drain_partial_gpu_soft`]
|
||||||
|
/// alone (which runs every N tree nodes).
|
||||||
|
#[inline]
|
||||||
|
fn maybe_drain_after_shadow_gpu_op(&mut self) {
|
||||||
|
let every = self.options.partial_gpu_drain_shadow_every_n;
|
||||||
|
if every <= 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.shadow_gpu_ops_since_drain += 1;
|
||||||
|
if self.shadow_gpu_ops_since_drain >= every {
|
||||||
|
self.shadow_gpu_ops_since_drain = 0;
|
||||||
|
Self::drain_partial_gpu_soft();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Skip all drop/inner shadows in fast mode, or when even a large design-space
|
/// Skip all drop/inner shadows in fast mode, or when even a large design-space
|
||||||
/// shadow would be subpixel. Otherwise filter per shadow via
|
/// shadow would be subpixel. Otherwise filter per shadow via
|
||||||
/// [`Shadow::is_perceptible_at_scale_for`] (stricter for recursive shapes).
|
/// [`Shadow::is_perceptible_at_scale_for`] (stricter for recursive shapes).
|
||||||
@ -3136,6 +3157,7 @@ impl RenderState {
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
self.surfaces.canvas(SurfaceId::DropShadows).restore();
|
self.surfaces.canvas(SurfaceId::DropShadows).restore();
|
||||||
|
self.maybe_drain_after_shadow_gpu_op();
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3179,6 +3201,7 @@ impl RenderState {
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
self.surfaces.canvas(SurfaceId::DropShadows).restore();
|
self.surfaces.canvas(SurfaceId::DropShadows).restore();
|
||||||
|
self.maybe_drain_after_shadow_gpu_op();
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3260,6 +3283,7 @@ impl RenderState {
|
|||||||
drop_canvas.restore();
|
drop_canvas.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.maybe_drain_after_shadow_gpu_op();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3379,6 +3403,7 @@ impl RenderState {
|
|||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
self.surfaces.canvas(SurfaceId::DropShadows).restore();
|
self.surfaces.canvas(SurfaceId::DropShadows).restore();
|
||||||
|
self.maybe_drain_after_shadow_gpu_op();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3413,6 +3438,7 @@ impl RenderState {
|
|||||||
self.surfaces
|
self.surfaces
|
||||||
.canvas(SurfaceId::DropShadows)
|
.canvas(SurfaceId::DropShadows)
|
||||||
.clear(skia::Color::TRANSPARENT);
|
.clear(skia::Color::TRANSPARENT);
|
||||||
|
self.maybe_drain_after_shadow_gpu_op();
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3925,6 +3951,7 @@ impl RenderState {
|
|||||||
self.current_tile_had_shapes = false;
|
self.current_tile_had_shapes = false;
|
||||||
self.tile_atlas_flushed = false;
|
self.tile_atlas_flushed = false;
|
||||||
self.drop_shadows_ops_warmed = false;
|
self.drop_shadows_ops_warmed = false;
|
||||||
|
self.shadow_gpu_ops_since_drain = 0;
|
||||||
|
|
||||||
let viewer_masked_pass = self.viewer_masked_pass();
|
let viewer_masked_pass = self.viewer_masked_pass();
|
||||||
|
|
||||||
|
|||||||
@ -14,6 +14,10 @@ const NODE_BATCH_THRESHOLD: i32 = 3;
|
|||||||
/// buffers bounded when many shapes paint cheaply to Current (release packs
|
/// buffers bounded when many shapes paint cheaply to Current (release packs
|
||||||
/// far more per budget than debug).
|
/// far more per budget than debug).
|
||||||
const PARTIAL_GPU_DRAIN_EVERY_N: i32 = 64;
|
const PARTIAL_GPU_DRAIN_EVERY_N: i32 = 64;
|
||||||
|
/// Soft-drain during recursive drop-shadow passes (each silhouette or filter
|
||||||
|
/// surface). The walker counts tree nodes, not shadow children re-rendered
|
||||||
|
/// inside one frame, so frame shadows need a tighter cadence.
|
||||||
|
const PARTIAL_GPU_DRAIN_SHADOW_EVERY_N: i32 = 2;
|
||||||
const BLUR_DOWNSCALE_THRESHOLD: f32 = 8.0;
|
const BLUR_DOWNSCALE_THRESHOLD: f32 = 8.0;
|
||||||
const ANTIALIAS_THRESHOLD: f32 = 7.0;
|
const ANTIALIAS_THRESHOLD: f32 = 7.0;
|
||||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||||
@ -35,6 +39,9 @@ pub struct RenderOptions {
|
|||||||
/// Soft-flush GPU every N nodes during progressive tile walks (see
|
/// Soft-flush GPU every N nodes during progressive tile walks (see
|
||||||
/// [`PARTIAL_GPU_DRAIN_EVERY_N`]).
|
/// [`PARTIAL_GPU_DRAIN_EVERY_N`]).
|
||||||
pub partial_gpu_drain_every_n: i32,
|
pub partial_gpu_drain_every_n: i32,
|
||||||
|
/// Soft-flush GPU every N drop-shadow silhouette passes (see
|
||||||
|
/// [`PARTIAL_GPU_DRAIN_SHADOW_EVERY_N`]).
|
||||||
|
pub partial_gpu_drain_shadow_every_n: i32,
|
||||||
pub blur_downscale_threshold: f32,
|
pub blur_downscale_threshold: f32,
|
||||||
pub capture_frames: i32,
|
pub capture_frames: i32,
|
||||||
}
|
}
|
||||||
@ -52,6 +59,7 @@ impl Default for RenderOptions {
|
|||||||
max_blocking_time_ms: MAX_BLOCKING_TIME_MS,
|
max_blocking_time_ms: MAX_BLOCKING_TIME_MS,
|
||||||
node_batch_threshold: NODE_BATCH_THRESHOLD,
|
node_batch_threshold: NODE_BATCH_THRESHOLD,
|
||||||
partial_gpu_drain_every_n: PARTIAL_GPU_DRAIN_EVERY_N,
|
partial_gpu_drain_every_n: PARTIAL_GPU_DRAIN_EVERY_N,
|
||||||
|
partial_gpu_drain_shadow_every_n: PARTIAL_GPU_DRAIN_SHADOW_EVERY_N,
|
||||||
blur_downscale_threshold: BLUR_DOWNSCALE_THRESHOLD,
|
blur_downscale_threshold: BLUR_DOWNSCALE_THRESHOLD,
|
||||||
capture_frames: 0,
|
capture_frames: 0,
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user