mirror of
https://github.com/penpot/penpot.git
synced 2026-08-06 12:58:55 +00:00
⚡ Prefer direct painting when effects are imperceptible
Skip the Fills/Strokes layered path when drop/inner shadows would not paint at the current scale, and allow stroke-only shapes (fills_none) on the direct path. Apply the same footprint LOD to inner-shadow painting. Raise the render budget slightly during multi-tile paint regions.
This commit is contained in:
parent
fa23668001
commit
18381f3e09
@ -1540,11 +1540,29 @@ impl RenderState {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Only perceptible shadows need the layered Fills/Strokes path. Use the
|
||||
// same footprint LOD as when painting drop and inner shadows.
|
||||
let scale = self.get_scale();
|
||||
let shadows_need_layered = !skip_drop_shadows
|
||||
&& (shape
|
||||
.drop_shadows_visible()
|
||||
.any(|s| s.is_perceptible_at_scale_for(scale, shape.is_recursive()))
|
||||
|| shape
|
||||
.inner_shadows_visible()
|
||||
.any(|s| s.is_perceptible_at_scale_for(scale, shape.is_recursive())));
|
||||
|
||||
// Clip is allowed: we apply the same stack on Current after scale+translate.
|
||||
// Opacity < 1 with SrcOver is OK: render_shape_enter already opened a
|
||||
// save_layer on Current; painting fills/strokes into that layer matches
|
||||
// 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.
|
||||
let type_ok = matches!(
|
||||
shape.shape_type,
|
||||
Type::Rect(_) | Type::Circle | Type::Path(_) | Type::Bool(_) | Type::Frame(_)
|
||||
);
|
||||
let needs_nested_fills = shape.fills.is_empty() && has_nested_fills;
|
||||
let can_render_directly = apply_to_current_surface
|
||||
&& offset.is_none()
|
||||
&& parent_shadows.is_none()
|
||||
@ -1554,20 +1572,12 @@ impl RenderState {
|
||||
&& shape.blur.is_none()
|
||||
&& shape.background_blur.is_none()
|
||||
&& !has_inherited_blur
|
||||
&& shape.shadows.is_empty()
|
||||
&& matches!(
|
||||
shape.shape_type,
|
||||
Type::Rect(_) | Type::Circle | Type::Path(_) | Type::Bool(_) | Type::Frame(_)
|
||||
)
|
||||
&& !(shape.fills.is_empty() && has_nested_fills)
|
||||
&& !shape
|
||||
.svg_attrs
|
||||
.as_ref()
|
||||
.is_some_and(|attrs| attrs.fill_none)
|
||||
&& !shadows_need_layered
|
||||
&& type_ok
|
||||
&& !needs_nested_fills
|
||||
&& target_surface != SurfaceId::Export;
|
||||
|
||||
if can_render_directly {
|
||||
let scale = self.get_scale();
|
||||
let translation = self
|
||||
.surfaces
|
||||
.get_render_context_translation(self.render_area, scale);
|
||||
@ -2976,7 +2986,13 @@ impl RenderState {
|
||||
if iteration % self.options.node_batch_threshold != 0 {
|
||||
return false;
|
||||
}
|
||||
if performance::get_time() - timestamp <= self.options.max_blocking_time_ms {
|
||||
// Multi-tile paint regions need fewer yields to finish visible HQ work.
|
||||
let budget = if self.paint_region.is_some() {
|
||||
self.options.max_blocking_time_ms.max(48)
|
||||
} else {
|
||||
self.options.max_blocking_time_ms
|
||||
};
|
||||
if performance::get_time() - timestamp <= budget {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -2995,11 +3011,11 @@ impl RenderState {
|
||||
true
|
||||
}
|
||||
|
||||
/// Skip all drop 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::is_perceptible_at_scale_for`] (stricter for recursive shapes).
|
||||
#[inline]
|
||||
fn should_skip_drop_shadows(&self) -> bool {
|
||||
pub(crate) fn should_skip_drop_shadows(&self) -> bool {
|
||||
if self.options.is_fast_mode() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -13,10 +13,16 @@ pub fn render_fill_inner_shadows(
|
||||
antialias: bool,
|
||||
surface_id: SurfaceId,
|
||||
) {
|
||||
if shape.has_fills() {
|
||||
for shadow in shape.inner_shadows_visible() {
|
||||
render_fill_inner_shadow(render_state, shape, shadow, antialias, surface_id);
|
||||
if !shape.has_fills() || render_state.should_skip_drop_shadows() {
|
||||
return;
|
||||
}
|
||||
let scale = render_state.get_scale();
|
||||
let recursive = shape.is_recursive();
|
||||
for shadow in shape.inner_shadows_visible() {
|
||||
if !shadow.is_perceptible_at_scale_for(scale, recursive) {
|
||||
continue;
|
||||
}
|
||||
render_fill_inner_shadow(render_state, shape, shadow, antialias, surface_id);
|
||||
}
|
||||
}
|
||||
|
||||
@ -38,19 +44,25 @@ pub fn render_stroke_inner_shadows(
|
||||
antialias: bool,
|
||||
surface_id: SurfaceId,
|
||||
) -> Result<()> {
|
||||
if !shape.has_fills() {
|
||||
for shadow in shape.inner_shadows_visible() {
|
||||
let filter = shadow.get_inner_shadow_filter();
|
||||
strokes::render_single(
|
||||
render_state,
|
||||
shape,
|
||||
stroke,
|
||||
Some(surface_id),
|
||||
filter.as_ref(),
|
||||
antialias,
|
||||
None, // Inner shadows don't use spread
|
||||
)?;
|
||||
if shape.has_fills() || render_state.should_skip_drop_shadows() {
|
||||
return Ok(());
|
||||
}
|
||||
let scale = render_state.get_scale();
|
||||
let recursive = shape.is_recursive();
|
||||
for shadow in shape.inner_shadows_visible() {
|
||||
if !shadow.is_perceptible_at_scale_for(scale, recursive) {
|
||||
continue;
|
||||
}
|
||||
let filter = shadow.get_inner_shadow_filter();
|
||||
strokes::render_single(
|
||||
render_state,
|
||||
shape,
|
||||
stroke,
|
||||
Some(surface_id),
|
||||
filter.as_ref(),
|
||||
antialias,
|
||||
None, // Inner shadows don't use spread
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user