Soft-drain GPU mid-walk on progressive Partials (#11127)

Release packs far more cheap Current draws (e.g. fills_none paths)
into one Partial than debug; a single end-of-Partial
flush_and_submit then stalls the browser. Soft-flush every N walker
nodes (and on Partial yield) keeps ops buffers bounded while Full
still submits via present_frame.
This commit is contained in:
Alejandro Alonso 2026-08-07 09:45:39 +02:00 committed by GitHub
parent e1c51442cd
commit 43b12bc4b9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 28 additions and 8 deletions

View File

@ -1386,8 +1386,8 @@ impl RenderState {
// the layered path without Fills/Strokes blits.
// Non-SrcOver blend, frame clip blur, and masked groups stay layered.
// Stroke-only (fills_none) can go direct: empty fills are a no-op and
// strokes paint into Current. Requires Partial GPU drain (dc1ab) so
// large SVG-icon files do not backlog commands until Full present.
// strokes paint into Current. Large files need mid-walk GPU drains so
// release builds do not backlog a huge ops buffer in one Partial.
let can_render_directly = apply_to_current_surface
&& offset.is_none()
&& parent_shadows.is_none()
@ -2461,12 +2461,9 @@ impl RenderState {
panic!("FrameType::None");
}
FrameType::Partial => {
// Drain tile GPU work (Current / tile atlas / cache) without
// presenting Target and without re-snapshotting the tile atlas —
// composition stays deferred to Full. A Backbuffer flush alone
// left commands queued until present_frame's flush_and_submit,
// which stalled the browser on large files.
crate::get_gpu_state().context.flush_and_submit();
// Final soft drain for this yield (mid-walk also drains; see
// `drain_partial_gpu_soft`). Full still submits via present_frame.
Self::drain_partial_gpu_soft();
}
FrameType::Full => {
// A full-quality frame is now complete. Rebuild the per-shape crop
@ -2669,6 +2666,15 @@ impl RenderState {
true
}
/// Soft-drain GPU command buffers during progressive tile walks.
/// Release packs far more cheap Current draws (e.g. fills_none paths) into
/// one Partial than debug; flushing only at Partial end then stalls. Call
/// periodically so each flush stays small. Full present still submits.
#[inline]
fn drain_partial_gpu_soft() {
crate::get_gpu_state().context.flush(None);
}
/// 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::is_perceptible_at_scale_for`] (stricter for recursive shapes).
@ -3775,6 +3781,12 @@ impl RenderState {
if allow_stop && self.should_stop_rendering(iteration, timestamp) {
return Ok((is_empty, true));
}
// Keep GPU ops buffers bounded when many shapes paint cheaply to
// Current (release packs far more per Partial than debug).
let drain_every = self.options.partial_gpu_drain_every_n;
if allow_stop && drain_every > 0 && iteration > 0 && iteration % drain_every == 0 {
Self::drain_partial_gpu_soft();
}
iteration += 1;
}

View File

@ -11,6 +11,10 @@ const VIEWPORT_INTEREST_AREA_THRESHOLD: i32 = 1;
const MIN_DPR_VIEWPORT_INTEREST_AREA_THRESHOLD: i32 = 2;
const MAX_BLOCKING_TIME_MS: i32 = 32;
const NODE_BATCH_THRESHOLD: i32 = 3;
/// Soft-drain GPU every N walker nodes on progressive Partials. Keeps ops
/// buffers bounded when many shapes paint cheaply to Current (release packs
/// far more per budget than debug).
const PARTIAL_GPU_DRAIN_EVERY_N: i32 = 64;
const BLUR_DOWNSCALE_THRESHOLD: f32 = 8.0;
const ANTIALIAS_THRESHOLD: f32 = 7.0;
#[derive(Debug, Copy, Clone, PartialEq)]
@ -29,6 +33,9 @@ pub struct RenderOptions {
pub dpr_viewport_interest_area_threshold: i32,
pub max_blocking_time_ms: i32,
pub node_batch_threshold: i32,
/// Soft-flush GPU every N nodes during progressive tile walks (see
/// [`PARTIAL_GPU_DRAIN_EVERY_N`]).
pub partial_gpu_drain_every_n: i32,
pub blur_downscale_threshold: f32,
pub capture_frames: i32,
}
@ -45,6 +52,7 @@ impl Default for RenderOptions {
dpr_viewport_interest_area_threshold: VIEWPORT_INTEREST_AREA_THRESHOLD,
max_blocking_time_ms: MAX_BLOCKING_TIME_MS,
node_batch_threshold: NODE_BATCH_THRESHOLD,
partial_gpu_drain_every_n: PARTIAL_GPU_DRAIN_EVERY_N,
blur_downscale_threshold: BLUR_DOWNSCALE_THRESHOLD,
capture_frames: 0,
}