mirror of
https://github.com/penpot/penpot.git
synced 2026-08-14 00:38:42 +00:00
⚡ Split recursive frame shadows across partial render frames
After pan/zoom, a full-quality tile walk could spend one rAF inside a single frame drop shadow re-rendering every child silhouette. Yield shadow work across partial frames when allow_stop is set, resume in-progress passes before the walker continues, and flush Filter/ DropShadows surfaces after each heavy pass (default every 1). AI-assisted-by: composer-2.5-fast Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
f25a2c701f
commit
94e2e26786
@ -31,8 +31,8 @@
|
||||
work; crop-cache rebuild is deferred to the later `Full` so the soft→sharp snap is
|
||||
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.
|
||||
flush Filter/DropShadows/TextDropShadows every silhouette pass (default every 1)
|
||||
because one frame node can trigger many child re-renders with blur filter surfaces.
|
||||
- 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.
|
||||
- `set_modifiers_end` disables fast/interactive state and cancels pending async render; the caller must request the final full-quality render.
|
||||
|
||||
@ -64,7 +64,7 @@ pub enum RenderFlag {
|
||||
SyncTiles = 4,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NodeRenderState {
|
||||
pub id: Uuid,
|
||||
// We use this bool to keep that we've traversed all the children inside this node.
|
||||
@ -77,6 +77,42 @@ pub struct NodeRenderState {
|
||||
mask: bool,
|
||||
// True when this container was flattened (enter/exit skipped).
|
||||
flattened: bool,
|
||||
/// Drop shadows for this node were finished by a deferred progressive pass.
|
||||
drop_shadow_done: bool,
|
||||
/// Skip enter/blur when resuming after progressive shadows that yielded post-enter.
|
||||
skip_shape_enter: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum DropShadowPhase {
|
||||
Children,
|
||||
Colorize,
|
||||
Composite,
|
||||
}
|
||||
|
||||
/// Progressive drop-shadow work split across rAF partial frames.
|
||||
#[derive(Debug, Clone)]
|
||||
struct InProgressDropShadow {
|
||||
node_render_state: NodeRenderState,
|
||||
element_id: Uuid,
|
||||
pending_shadows: Vec<Shadow>,
|
||||
current_shadow: Shadow,
|
||||
child_ids: Vec<Uuid>,
|
||||
child_index: usize,
|
||||
phase: DropShadowPhase,
|
||||
clip_bounds: Option<ClipStack>,
|
||||
target_surface: SurfaceId,
|
||||
scale: f32,
|
||||
element_extrect: Rect,
|
||||
inherited_layer_blur: Option<Blur>,
|
||||
rendered_any: bool,
|
||||
/// True when progressive shadow work started after `render_shape_enter`.
|
||||
shape_enter_done: bool,
|
||||
}
|
||||
|
||||
enum DropShadowResult {
|
||||
Complete(bool),
|
||||
Yielded,
|
||||
}
|
||||
|
||||
/// Get simplified children of a container, flattening nested flattened containers
|
||||
@ -426,6 +462,12 @@ pub(crate) struct RenderState {
|
||||
pub viewport_presented: bool,
|
||||
/// Drop-shadow silhouette ops since the last shadow cadence drain (reset per tile).
|
||||
shadow_gpu_ops_since_drain: i32,
|
||||
/// True while `render_shape_tree_partial_uncached` runs with `allow_stop`.
|
||||
progressive_render: bool,
|
||||
/// Wall-clock anchor for progressive yield checks during shadow passes.
|
||||
render_budget_timestamp: i32,
|
||||
/// Drop-shadow pass resumed on the next partial frame.
|
||||
in_progress_drop_shadow: Option<InProgressDropShadow>,
|
||||
}
|
||||
|
||||
pub struct InteractiveDragCrop {
|
||||
@ -612,6 +654,9 @@ impl RenderState {
|
||||
drop_shadows_ops_warmed: false,
|
||||
viewport_presented: false,
|
||||
shadow_gpu_ops_since_drain: 0,
|
||||
progressive_render: false,
|
||||
render_budget_timestamp: 0,
|
||||
in_progress_drop_shadow: None,
|
||||
})
|
||||
}
|
||||
|
||||
@ -1120,7 +1165,11 @@ impl RenderState {
|
||||
.ok_or(Error::CriticalError("Current tile not found".to_string()))?;
|
||||
|
||||
if self.tile_atlas_flushed {
|
||||
crate::get_gpu_state().context.flush_and_submit();
|
||||
if self.progressive_render {
|
||||
crate::get_gpu_state().context.flush(None);
|
||||
} else {
|
||||
crate::get_gpu_state().context.flush_and_submit();
|
||||
}
|
||||
}
|
||||
|
||||
self.surfaces.draw_current_tile_into_tile_atlas(
|
||||
@ -2302,6 +2351,8 @@ impl RenderState {
|
||||
self.current_tile = None;
|
||||
self.viewport_presented = false;
|
||||
self.shadow_gpu_ops_since_drain = 0;
|
||||
self.in_progress_drop_shadow = None;
|
||||
self.progressive_render = false;
|
||||
}
|
||||
|
||||
pub fn start_render_loop(
|
||||
@ -2626,6 +2677,8 @@ impl RenderState {
|
||||
visited_mask: false,
|
||||
mask: false,
|
||||
flattened: false,
|
||||
drop_shadow_done: false,
|
||||
skip_shape_enter: false,
|
||||
});
|
||||
self.render_shape_tree_partial_uncached(tree, timestamp, false, true)?;
|
||||
}
|
||||
@ -2723,6 +2776,17 @@ impl RenderState {
|
||||
crate::get_gpu_state().context.flush(None);
|
||||
}
|
||||
|
||||
/// Flush the offscreen surfaces used by drop-shadow passes, then drain the
|
||||
/// GrContext. Shadow blur/filter work records into Filter and DropShadows;
|
||||
/// a context-only flush leaves those op chains unbounded on WebGL.
|
||||
#[inline]
|
||||
pub(crate) fn drain_shadow_gpu_soft(&mut self) {
|
||||
self.surfaces.flush(SurfaceId::Filter);
|
||||
self.surfaces.flush(SurfaceId::DropShadows);
|
||||
self.surfaces.flush(SurfaceId::TextDropShadows);
|
||||
Self::drain_partial_gpu_soft();
|
||||
}
|
||||
|
||||
/// 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`]
|
||||
@ -2736,10 +2800,27 @@ impl RenderState {
|
||||
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();
|
||||
self.drain_shadow_gpu_soft();
|
||||
}
|
||||
}
|
||||
|
||||
/// Text silhouettes in a shadow pass run a full `render_shape`; flush
|
||||
/// immediately so glyph/filter ops do not pile up behind one counter tick.
|
||||
#[inline]
|
||||
fn should_yield_render_budget(&self) -> bool {
|
||||
if !self.progressive_render {
|
||||
return false;
|
||||
}
|
||||
performance::get_time() - self.render_budget_timestamp
|
||||
> self.options.max_blocking_time_ms
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn drain_shadow_gpu_after_text_pass(&mut self) {
|
||||
self.shadow_gpu_ops_since_drain = 0;
|
||||
self.drain_shadow_gpu_soft();
|
||||
}
|
||||
|
||||
/// 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).
|
||||
@ -2918,6 +2999,8 @@ impl RenderState {
|
||||
visited_mask: true,
|
||||
mask: false,
|
||||
flattened: false,
|
||||
drop_shadow_done: false,
|
||||
skip_shape_enter: false,
|
||||
});
|
||||
if let Some(&mask_id) = element.mask_id() {
|
||||
self.pending_nodes.push(NodeRenderState {
|
||||
@ -2927,6 +3010,8 @@ impl RenderState {
|
||||
visited_mask: false,
|
||||
mask: true,
|
||||
flattened: false,
|
||||
drop_shadow_done: false,
|
||||
skip_shape_enter: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -3283,15 +3368,13 @@ impl RenderState {
|
||||
drop_canvas.restore();
|
||||
}
|
||||
|
||||
self.maybe_drain_after_shadow_gpu_op();
|
||||
self.drain_shadow_gpu_soft();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Renders element drop shadows to DropShadows surface and composites to Current.
|
||||
/// Used for both normal shadow rendering and pre-layer rendering (frame_clip_layer_blur).
|
||||
/// Returns `true` when at least one visible drop shadow was composited.
|
||||
/// Synchronous drop-shadow render (exports, sync tiles, non-recursive shapes).
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn render_element_drop_shadows_and_composite(
|
||||
fn render_element_drop_shadows_sync(
|
||||
&mut self,
|
||||
element: &Shape,
|
||||
tree: ShapesPoolRef,
|
||||
@ -3301,10 +3384,6 @@ impl RenderState {
|
||||
node_render_state: &NodeRenderState,
|
||||
target_surface: SurfaceId,
|
||||
) -> Result<bool> {
|
||||
// Avoid a blank DropShadows→Current blit + clear when nothing will paint
|
||||
// (no shadows, fast/overview skip, or all footprints subpixel). Callers
|
||||
// must still touch DropShadows once per tile when this returns false
|
||||
// (see `drop_shadows_ops_warmed`).
|
||||
if self.should_skip_drop_shadows()
|
||||
|| !element
|
||||
.drop_shadows_visible()
|
||||
@ -3403,7 +3482,7 @@ impl RenderState {
|
||||
)
|
||||
})?;
|
||||
self.surfaces.canvas(SurfaceId::DropShadows).restore();
|
||||
self.maybe_drain_after_shadow_gpu_op();
|
||||
self.drain_shadow_gpu_after_text_pass();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3416,12 +3495,24 @@ impl RenderState {
|
||||
.draw_paint(&paint);
|
||||
|
||||
self.surfaces.canvas(SurfaceId::DropShadows).restore();
|
||||
self.drain_shadow_gpu_soft();
|
||||
}
|
||||
|
||||
if !rendered_any {
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
self.composite_drop_shadows_to_target(element, clip_bounds, scale, target_surface);
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn composite_drop_shadows_to_target(
|
||||
&mut self,
|
||||
element: &Shape,
|
||||
clip_bounds: Option<ClipStack>,
|
||||
scale: f32,
|
||||
target_surface: SurfaceId,
|
||||
) {
|
||||
if let Some(clips) = clip_bounds.as_ref() {
|
||||
let antialias = !self.options.is_fast_mode()
|
||||
&& element.should_use_antialias(scale, self.options.antialias_threshold);
|
||||
@ -3438,8 +3529,339 @@ impl RenderState {
|
||||
self.surfaces
|
||||
.canvas(SurfaceId::DropShadows)
|
||||
.clear(skia::Color::TRANSPARENT);
|
||||
self.maybe_drain_after_shadow_gpu_op();
|
||||
Ok(true)
|
||||
self.drain_shadow_gpu_soft();
|
||||
}
|
||||
|
||||
fn render_drop_shadow_child_silhouette(
|
||||
&mut self,
|
||||
element: &Shape,
|
||||
shadow_shape_id: &Uuid,
|
||||
shadow: &Shadow,
|
||||
node_render_state: &NodeRenderState,
|
||||
scale: f32,
|
||||
inherited_layer_blur: Option<Blur>,
|
||||
target_surface: SurfaceId,
|
||||
tree: ShapesPoolRef,
|
||||
) -> Result<()> {
|
||||
let Some(shadow_shape) = tree.get(shadow_shape_id) else {
|
||||
return Ok(());
|
||||
};
|
||||
if shadow_shape.hidden {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let nested_clip_bounds = node_render_state.get_nested_shadow_clip_bounds(element, shadow);
|
||||
|
||||
if !matches!(shadow_shape.shape_type, Type::Text(_)) {
|
||||
self.render_drop_black_shadow(
|
||||
shadow_shape,
|
||||
&shadow_shape.extrect(tree, scale),
|
||||
shadow,
|
||||
nested_clip_bounds,
|
||||
scale,
|
||||
inherited_layer_blur,
|
||||
target_surface,
|
||||
)
|
||||
} else {
|
||||
let paint = skia::Paint::default();
|
||||
let layer_rec = skia::canvas::SaveLayerRec::default().paint(&paint);
|
||||
self.surfaces
|
||||
.canvas(SurfaceId::DropShadows)
|
||||
.save_layer(&layer_rec);
|
||||
|
||||
let mut transformed_shadow: Cow<Shadow> = Cow::Borrowed(shadow);
|
||||
transformed_shadow.to_mut().color = skia::Color::BLACK;
|
||||
transformed_shadow.to_mut().blur = transformed_shadow.blur;
|
||||
transformed_shadow.to_mut().spread = transformed_shadow.spread;
|
||||
|
||||
let mut new_shadow_paint = skia::Paint::default();
|
||||
new_shadow_paint.set_image_filter(transformed_shadow.get_drop_shadow_filter());
|
||||
new_shadow_paint.set_blend_mode(skia::BlendMode::SrcOver);
|
||||
|
||||
self.with_nested_blurs_suppressed(|state| {
|
||||
state.render_shape(
|
||||
shadow_shape,
|
||||
nested_clip_bounds,
|
||||
SurfaceId::DropShadows,
|
||||
SurfaceId::DropShadows,
|
||||
SurfaceId::DropShadows,
|
||||
SurfaceId::DropShadows,
|
||||
true,
|
||||
None,
|
||||
Some(vec![new_shadow_paint.clone()]),
|
||||
None,
|
||||
target_surface,
|
||||
)
|
||||
})?;
|
||||
self.surfaces.canvas(SurfaceId::DropShadows).restore();
|
||||
self.drain_shadow_gpu_after_text_pass();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn begin_drop_shadow_progressive(
|
||||
&mut self,
|
||||
element: &Shape,
|
||||
tree: ShapesPoolRef,
|
||||
extrect: &mut Option<Rect>,
|
||||
clip_bounds: Option<ClipStack>,
|
||||
scale: f32,
|
||||
node_render_state: &NodeRenderState,
|
||||
target_surface: SurfaceId,
|
||||
shape_enter_done: bool,
|
||||
) -> Result<DropShadowResult> {
|
||||
let element_extrect = extrect.get_or_insert_with(|| element.extrect(tree, scale));
|
||||
let inherited_layer_blur = match element.shape_type {
|
||||
Type::Frame(_) | Type::Group(_) => element.blur,
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let mut shadows: Vec<Shadow> = element
|
||||
.drop_shadows_visible()
|
||||
.filter(|s| s.is_perceptible_at_scale_for(scale, element.is_recursive()))
|
||||
.cloned()
|
||||
.collect();
|
||||
if shadows.is_empty() {
|
||||
return Ok(DropShadowResult::Complete(false));
|
||||
}
|
||||
|
||||
let current_shadow = shadows.remove(0);
|
||||
let paint = skia::Paint::default();
|
||||
let layer_rec = skia::canvas::SaveLayerRec::default().paint(&paint);
|
||||
self.surfaces
|
||||
.canvas(SurfaceId::DropShadows)
|
||||
.save_layer(&layer_rec);
|
||||
|
||||
self.render_drop_black_shadow(
|
||||
element,
|
||||
element_extrect,
|
||||
¤t_shadow,
|
||||
clip_bounds.clone(),
|
||||
scale,
|
||||
None,
|
||||
target_surface,
|
||||
)?;
|
||||
self.drain_shadow_gpu_soft();
|
||||
|
||||
let child_ids = get_simplified_children(tree, element);
|
||||
let progress = InProgressDropShadow {
|
||||
node_render_state: node_render_state.clone(),
|
||||
element_id: element.id,
|
||||
pending_shadows: shadows,
|
||||
current_shadow,
|
||||
child_ids,
|
||||
child_index: 0,
|
||||
phase: DropShadowPhase::Children,
|
||||
clip_bounds,
|
||||
target_surface,
|
||||
scale,
|
||||
element_extrect: *element_extrect,
|
||||
inherited_layer_blur,
|
||||
rendered_any: true,
|
||||
shape_enter_done,
|
||||
};
|
||||
|
||||
self.in_progress_drop_shadow = Some(progress);
|
||||
self.advance_drop_shadow_progressive(element, tree, false)
|
||||
}
|
||||
|
||||
fn advance_drop_shadow_progressive(
|
||||
&mut self,
|
||||
element: &Shape,
|
||||
tree: ShapesPoolRef,
|
||||
requeue_on_complete: bool,
|
||||
) -> Result<DropShadowResult> {
|
||||
let Some(mut progress) = self.in_progress_drop_shadow.take() else {
|
||||
return Ok(DropShadowResult::Complete(false));
|
||||
};
|
||||
|
||||
let node_render_state = progress.node_render_state.clone();
|
||||
let result = self.advance_drop_shadow_progressive_inner(
|
||||
element,
|
||||
tree,
|
||||
&node_render_state,
|
||||
&mut progress,
|
||||
);
|
||||
|
||||
match result {
|
||||
Ok(DropShadowResult::Yielded) => {
|
||||
self.in_progress_drop_shadow = Some(progress);
|
||||
Ok(DropShadowResult::Yielded)
|
||||
}
|
||||
Ok(DropShadowResult::Complete(rendered)) => {
|
||||
self.in_progress_drop_shadow = None;
|
||||
if requeue_on_complete {
|
||||
let mut ns = progress.node_render_state;
|
||||
ns.drop_shadow_done = true;
|
||||
ns.skip_shape_enter = progress.shape_enter_done;
|
||||
self.pending_nodes.push(ns);
|
||||
}
|
||||
Ok(DropShadowResult::Complete(rendered))
|
||||
}
|
||||
Err(e) => {
|
||||
self.in_progress_drop_shadow = Some(progress);
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn advance_drop_shadow_progressive_inner(
|
||||
&mut self,
|
||||
element: &Shape,
|
||||
tree: ShapesPoolRef,
|
||||
node_render_state: &NodeRenderState,
|
||||
progress: &mut InProgressDropShadow,
|
||||
) -> Result<DropShadowResult> {
|
||||
loop {
|
||||
match progress.phase {
|
||||
DropShadowPhase::Children => {
|
||||
while progress.child_index < progress.child_ids.len() {
|
||||
let child_id = progress.child_ids[progress.child_index];
|
||||
progress.child_index += 1;
|
||||
self.render_drop_shadow_child_silhouette(
|
||||
element,
|
||||
&child_id,
|
||||
&progress.current_shadow,
|
||||
node_render_state,
|
||||
progress.scale,
|
||||
progress.inherited_layer_blur,
|
||||
progress.target_surface,
|
||||
tree,
|
||||
)?;
|
||||
self.drain_shadow_gpu_soft();
|
||||
if self.should_yield_render_budget() {
|
||||
return Ok(DropShadowResult::Yielded);
|
||||
}
|
||||
}
|
||||
progress.phase = DropShadowPhase::Colorize;
|
||||
}
|
||||
DropShadowPhase::Colorize => {
|
||||
let mut paint = skia::Paint::default();
|
||||
paint.set_color(progress.current_shadow.color);
|
||||
paint.set_blend_mode(skia::BlendMode::SrcIn);
|
||||
self.surfaces
|
||||
.canvas(SurfaceId::DropShadows)
|
||||
.draw_paint(&paint);
|
||||
self.surfaces.canvas(SurfaceId::DropShadows).restore();
|
||||
self.drain_shadow_gpu_soft();
|
||||
|
||||
if progress.pending_shadows.is_empty() {
|
||||
progress.phase = DropShadowPhase::Composite;
|
||||
} else {
|
||||
progress.current_shadow = progress.pending_shadows.remove(0);
|
||||
progress.child_index = 0;
|
||||
progress.phase = DropShadowPhase::Children;
|
||||
|
||||
let paint = skia::Paint::default();
|
||||
let layer_rec = skia::canvas::SaveLayerRec::default().paint(&paint);
|
||||
self.surfaces
|
||||
.canvas(SurfaceId::DropShadows)
|
||||
.save_layer(&layer_rec);
|
||||
self.render_drop_black_shadow(
|
||||
element,
|
||||
&progress.element_extrect,
|
||||
&progress.current_shadow,
|
||||
progress.clip_bounds.clone(),
|
||||
progress.scale,
|
||||
None,
|
||||
progress.target_surface,
|
||||
)?;
|
||||
self.drain_shadow_gpu_soft();
|
||||
if self.should_yield_render_budget() {
|
||||
return Ok(DropShadowResult::Yielded);
|
||||
}
|
||||
}
|
||||
}
|
||||
DropShadowPhase::Composite => {
|
||||
self.composite_drop_shadows_to_target(
|
||||
element,
|
||||
progress.clip_bounds.clone(),
|
||||
progress.scale,
|
||||
progress.target_surface,
|
||||
);
|
||||
return Ok(DropShadowResult::Complete(progress.rendered_any));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn resume_drop_shadow_progressive(
|
||||
&mut self,
|
||||
tree: ShapesPoolRef,
|
||||
) -> Result<DropShadowResult> {
|
||||
let element_id = self
|
||||
.in_progress_drop_shadow
|
||||
.as_ref()
|
||||
.map(|p| p.element_id)
|
||||
.ok_or_else(|| Error::CriticalError("Missing in-progress drop shadow".into()))?;
|
||||
let Some(element) = tree.get(&element_id) else {
|
||||
self.in_progress_drop_shadow = None;
|
||||
return Ok(DropShadowResult::Complete(false));
|
||||
};
|
||||
self.advance_drop_shadow_progressive(element, tree, true)
|
||||
}
|
||||
|
||||
/// Renders element drop shadows to DropShadows surface and composites to Current.
|
||||
/// Used for both normal shadow rendering and pre-layer rendering (frame_clip_layer_blur).
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn render_element_drop_shadows_and_composite(
|
||||
&mut self,
|
||||
element: &Shape,
|
||||
tree: ShapesPoolRef,
|
||||
extrect: &mut Option<Rect>,
|
||||
clip_bounds: Option<ClipStack>,
|
||||
scale: f32,
|
||||
node_render_state: &NodeRenderState,
|
||||
target_surface: SurfaceId,
|
||||
shape_enter_done: bool,
|
||||
) -> Result<DropShadowResult> {
|
||||
if self.should_skip_drop_shadows()
|
||||
|| !element
|
||||
.drop_shadows_visible()
|
||||
.any(|s| s.is_perceptible_at_scale_for(scale, element.is_recursive()))
|
||||
{
|
||||
return Ok(DropShadowResult::Complete(false));
|
||||
}
|
||||
|
||||
if node_render_state.drop_shadow_done {
|
||||
return Ok(DropShadowResult::Complete(false));
|
||||
}
|
||||
|
||||
let use_progressive = self.progressive_render
|
||||
&& element.is_recursive()
|
||||
&& !matches!(element.shape_type, Type::Bool(_))
|
||||
&& !get_simplified_children(tree, element).is_empty();
|
||||
|
||||
if use_progressive {
|
||||
if self
|
||||
.in_progress_drop_shadow
|
||||
.as_ref()
|
||||
.is_some_and(|p| p.element_id == element.id)
|
||||
{
|
||||
return self.advance_drop_shadow_progressive(element, tree, false);
|
||||
}
|
||||
return self.begin_drop_shadow_progressive(
|
||||
element,
|
||||
tree,
|
||||
extrect,
|
||||
clip_bounds,
|
||||
scale,
|
||||
node_render_state,
|
||||
target_surface,
|
||||
shape_enter_done,
|
||||
);
|
||||
}
|
||||
|
||||
let rendered = self.render_element_drop_shadows_sync(
|
||||
element,
|
||||
tree,
|
||||
extrect,
|
||||
clip_bounds,
|
||||
scale,
|
||||
node_render_state,
|
||||
target_surface,
|
||||
)?;
|
||||
Ok(DropShadowResult::Complete(rendered))
|
||||
}
|
||||
|
||||
pub fn render_shape_tree_partial_uncached(
|
||||
@ -3452,6 +3874,9 @@ impl RenderState {
|
||||
let mut iteration = 0;
|
||||
let mut is_empty = true;
|
||||
|
||||
self.progressive_render = allow_stop;
|
||||
self.render_budget_timestamp = performance::get_time();
|
||||
|
||||
let mut target_surface = SurfaceId::Current;
|
||||
if export {
|
||||
target_surface = SurfaceId::Export;
|
||||
@ -3499,6 +3924,16 @@ impl RenderState {
|
||||
None
|
||||
};
|
||||
|
||||
if self.in_progress_drop_shadow.is_some() {
|
||||
match self.resume_drop_shadow_progressive(tree)? {
|
||||
DropShadowResult::Yielded => return Ok((false, true)),
|
||||
DropShadowResult::Complete(true) => {
|
||||
self.drop_shadows_ops_warmed = true;
|
||||
}
|
||||
DropShadowResult::Complete(false) => {}
|
||||
}
|
||||
}
|
||||
|
||||
while let Some(node_render_state) = self.pending_nodes.pop() {
|
||||
let node_id = node_render_state.id;
|
||||
let visited_children = node_render_state.visited_children;
|
||||
@ -3557,6 +3992,8 @@ impl RenderState {
|
||||
visited_mask: false,
|
||||
mask: false,
|
||||
flattened: false,
|
||||
drop_shadow_done: false,
|
||||
skip_shape_enter: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -3681,8 +4118,8 @@ impl RenderState {
|
||||
.drop_shadows_visible()
|
||||
.any(|s| s.is_perceptible_at_scale_for(scale, element.is_recursive()));
|
||||
|
||||
if shadow_before_layer
|
||||
&& self.render_element_drop_shadows_and_composite(
|
||||
if shadow_before_layer && !node_render_state.drop_shadow_done {
|
||||
match self.render_element_drop_shadows_and_composite(
|
||||
element,
|
||||
tree,
|
||||
&mut extrect,
|
||||
@ -3690,18 +4127,28 @@ impl RenderState {
|
||||
scale,
|
||||
&node_render_state,
|
||||
target_surface,
|
||||
)?
|
||||
{
|
||||
self.drop_shadows_ops_warmed = true;
|
||||
false,
|
||||
)? {
|
||||
DropShadowResult::Yielded => return Ok((is_empty, true)),
|
||||
DropShadowResult::Complete(true) => {
|
||||
self.drop_shadows_ops_warmed = true;
|
||||
}
|
||||
DropShadowResult::Complete(false) => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Render background blur BEFORE save_layer so it modifies
|
||||
// the backdrop independently of the shape's opacity.
|
||||
if !node_render_state.is_root() && self.focus_mode.is_active() {
|
||||
if !node_render_state.skip_shape_enter
|
||||
&& !node_render_state.is_root()
|
||||
&& self.focus_mode.is_active()
|
||||
{
|
||||
self.render_background_blur(element, target_surface);
|
||||
}
|
||||
|
||||
self.render_shape_enter(element, mask, clip_bounds.as_ref(), target_surface);
|
||||
if !node_render_state.skip_shape_enter {
|
||||
self.render_shape_enter(element, mask, clip_bounds.as_ref(), target_surface);
|
||||
}
|
||||
}
|
||||
|
||||
if !node_render_state.is_root() && self.focus_mode.is_active() {
|
||||
@ -3709,13 +4156,16 @@ impl RenderState {
|
||||
let skip_shadows = self.should_skip_drop_shadows();
|
||||
|
||||
// Skip shadow block when already rendered before the layer (frame_clip_layer_blur)
|
||||
let shadows_already_rendered = Self::frame_clip_layer_blur(element).is_some();
|
||||
// or when a deferred progressive pass finished shadows for this node.
|
||||
let shadows_already_rendered = node_render_state.drop_shadow_done
|
||||
|| Self::frame_clip_layer_blur(element).is_some();
|
||||
|
||||
// For text shapes, render drop shadow using text rendering logic
|
||||
if !skip_shadows
|
||||
&& !shadows_already_rendered
|
||||
&& !matches!(element.shape_type, Type::Text(_))
|
||||
&& self.render_element_drop_shadows_and_composite(
|
||||
{
|
||||
match self.render_element_drop_shadows_and_composite(
|
||||
element,
|
||||
tree,
|
||||
&mut extrect,
|
||||
@ -3723,10 +4173,14 @@ impl RenderState {
|
||||
scale,
|
||||
&node_render_state,
|
||||
target_surface,
|
||||
)?
|
||||
{
|
||||
// Real shadow composite already clears DropShadows.
|
||||
self.drop_shadows_ops_warmed = true;
|
||||
true,
|
||||
)? {
|
||||
DropShadowResult::Yielded => return Ok((is_empty, true)),
|
||||
DropShadowResult::Complete(true) => {
|
||||
self.drop_shadows_ops_warmed = true;
|
||||
}
|
||||
DropShadowResult::Complete(false) => {}
|
||||
}
|
||||
}
|
||||
|
||||
if !self.drop_shadows_ops_warmed {
|
||||
@ -3807,6 +4261,8 @@ impl RenderState {
|
||||
visited_mask: false,
|
||||
mask,
|
||||
flattened: can_flatten,
|
||||
drop_shadow_done: false,
|
||||
skip_shape_enter: false,
|
||||
});
|
||||
|
||||
if element.is_recursive() {
|
||||
@ -3841,6 +4297,8 @@ impl RenderState {
|
||||
visited_mask: false,
|
||||
mask: false,
|
||||
flattened: false,
|
||||
drop_shadow_done: false,
|
||||
skip_shape_enter: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -3952,6 +4410,7 @@ impl RenderState {
|
||||
self.tile_atlas_flushed = false;
|
||||
self.drop_shadows_ops_warmed = false;
|
||||
self.shadow_gpu_ops_since_drain = 0;
|
||||
self.in_progress_drop_shadow = None;
|
||||
|
||||
let viewer_masked_pass = self.viewer_masked_pass();
|
||||
|
||||
@ -4006,6 +4465,8 @@ impl RenderState {
|
||||
visited_mask: false,
|
||||
mask: false,
|
||||
flattened: false,
|
||||
drop_shadow_done: false,
|
||||
skip_shape_enter: false,
|
||||
}));
|
||||
} else {
|
||||
// Visible tiles finished. Promote deferred interest-ring work
|
||||
|
||||
@ -16,8 +16,8 @@ const NODE_BATCH_THRESHOLD: i32 = 3;
|
||||
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;
|
||||
/// inside one frame, so frame shadows drain after every heavy pass by default.
|
||||
const PARTIAL_GPU_DRAIN_SHADOW_EVERY_N: i32 = 1;
|
||||
const BLUR_DOWNSCALE_THRESHOLD: f32 = 8.0;
|
||||
const ANTIALIAS_THRESHOLD: f32 = 7.0;
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user