From 83fb97d34759c96dcb0115074477aba34344124f Mon Sep 17 00:00:00 2001 From: Glenn Watson Date: Mon, 12 Aug 2024 22:52:21 +0000 Subject: [PATCH] Bug 1908834 - Support outset box-shadows with quad based rendering r=gfx-reviewers,lsalzman This initial patch does not contain any of the more subtle or complex optimizations that we'll land as follow ups, and thus only enables the new path for a subset of box-shadow primitives. Differential Revision: https://phabricator.services.mozilla.com/D217060 --- webrender/src/batch.rs | 8 +- webrender/src/border.rs | 32 +- webrender/src/box_shadow.rs | 386 +++++++++++++++----- webrender/src/clip.rs | 23 ++ webrender/src/command_buffer.rs | 8 +- webrender/src/pattern.rs | 42 ++- webrender/src/picture.rs | 2 +- webrender/src/prepare.rs | 31 +- webrender/src/prim_store/gradient/conic.rs | 14 + webrender/src/prim_store/gradient/radial.rs | 14 + webrender/src/prim_store/mod.rs | 19 + webrender/src/quad.rs | 235 ++++++++---- webrender/src/render_target.rs | 6 +- webrender/src/renderer/gpu_buffer.rs | 4 +- webrender/src/scene_building.rs | 5 +- 15 files changed, 637 insertions(+), 192 deletions(-) diff --git a/webrender/src/batch.rs b/webrender/src/batch.rs index 9bd9644793..29cfe694a4 100644 --- a/webrender/src/batch.rs +++ b/webrender/src/batch.rs @@ -829,7 +829,7 @@ impl BatchBuilder { PrimitiveCommand::Instance { prim_instance_index, gpu_buffer_address } => { (prim_instance_index, Some(gpu_buffer_address.as_int())) } - PrimitiveCommand::Quad { pattern, pattern_input, prim_instance_index, gpu_buffer_address, quad_flags, edge_flags, transform_id } => { + PrimitiveCommand::Quad { pattern, pattern_input, prim_instance_index, gpu_buffer_address, quad_flags, edge_flags, transform_id, src_color_task_id } => { let prim_instance = &prim_instances[prim_instance_index.0 as usize]; let prim_info = &prim_instance.vis; let bounding_rect = &prim_info.clip_chain.pic_coverage_rect; @@ -837,10 +837,6 @@ impl BatchBuilder { if segments.is_empty() { let z_id = z_generator.next(); - // TODO: Some pattern types will sample from render tasks. - // At the moment quads only use a render task as source for - // segments which have been pre-rendered and masked. - let src_color_task_id = RenderTaskId::INVALID; quad::add_to_batch( *pattern, @@ -851,7 +847,7 @@ impl BatchBuilder { *quad_flags, *edge_flags, INVALID_SEGMENT_INDEX as u8, - src_color_task_id, + *src_color_task_id, z_id, render_tasks, gpu_buffer_builder, diff --git a/webrender/src/border.rs b/webrender/src/border.rs index 9fd54563db..f2a1ae2c4f 100644 --- a/webrender/src/border.rs +++ b/webrender/src/border.rs @@ -175,24 +175,28 @@ pub fn ensure_no_corner_overlap( let bottom_right_radius = &mut radius.bottom_right; let bottom_left_radius = &mut radius.bottom_left; - let sum = top_left_radius.width + top_right_radius.width; - if size.width < sum { - ratio = f32::min(ratio, size.width / sum); - } + if size.width > 0.0 { + let sum = top_left_radius.width + top_right_radius.width; + if size.width < sum { + ratio = f32::min(ratio, size.width / sum); + } - let sum = bottom_left_radius.width + bottom_right_radius.width; - if size.width < sum { - ratio = f32::min(ratio, size.width / sum); + let sum = bottom_left_radius.width + bottom_right_radius.width; + if size.width < sum { + ratio = f32::min(ratio, size.width / sum); + } } - let sum = top_left_radius.height + bottom_left_radius.height; - if size.height < sum { - ratio = f32::min(ratio, size.height / sum); - } + if size.height > 0.0 { + let sum = top_left_radius.height + bottom_left_radius.height; + if size.height < sum { + ratio = f32::min(ratio, size.height / sum); + } - let sum = top_right_radius.height + bottom_right_radius.height; - if size.height < sum { - ratio = f32::min(ratio, size.height / sum); + let sum = top_right_radius.height + bottom_right_radius.height; + if size.height < sum { + ratio = f32::min(ratio, size.height / sum); + } } if ratio < 1. { diff --git a/webrender/src/box_shadow.rs b/webrender/src/box_shadow.rs index 8ca3be5a65..360dffe7e9 100644 --- a/webrender/src/box_shadow.rs +++ b/webrender/src/box_shadow.rs @@ -1,17 +1,26 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - use api::{BorderRadius, BoxShadowClipMode, ClipMode, ColorF, ColorU, PrimitiveKeyKind, PropertyBinding}; - use api::units::*; - use crate::clip::{ClipItemKey, ClipItemKeyKind, ClipNodeId}; - use crate::intern::{Handle as InternHandle, InternDebug, Internable}; - use crate::prim_store::{InternablePrimitive, PrimKey, PrimTemplate, PrimTemplateCommonData}; - use crate::prim_store::{PrimitiveInstanceKind, PrimitiveStore}; - use crate::scene_building::{SceneBuilder, IsVisible}; - use crate::spatial_tree::SpatialNodeIndex; - use crate::gpu_types::BoxShadowStretchMode; - use crate::render_task_graph::RenderTaskId; - use crate::internal_types::LayoutPrimitiveInfo; +use api::{BorderRadius, BoxShadowClipMode, ClipMode, ColorF, ColorU, PrimitiveKeyKind, PropertyBinding}; +use api::units::*; +use crate::border::{ensure_no_corner_overlap, BorderRadiusAu}; +use crate::clip::{ClipDataHandle, ClipInternData, ClipItemKey, ClipItemKeyKind, ClipNodeId}; +use crate::command_buffer::QuadFlags; +use crate::intern::{Handle as InternHandle, InternDebug, Internable}; +use crate::pattern::{Pattern, PatternBuilder, PatternBuilderContext, PatternBuilderState}; +use crate::picture::calculate_uv_rect_kind; +use crate::prim_store::{InternablePrimitive, PrimKey, PrimTemplate, PrimTemplateCommonData}; +use crate::prim_store::{PrimitiveInstanceKind, PrimitiveStore, RectangleKey}; +use crate::quad; +use crate::render_target::RenderTargetKind; +use crate::render_task::{BlurTask, MaskSubPass, PrimTask, RenderTask, RenderTaskKind, SubPass}; +use crate::scene_building::{SceneBuilder, IsVisible}; +use crate::segment::EdgeAaSegmentMask; +use crate::spatial_tree::SpatialNodeIndex; +use crate::gpu_types::{BoxShadowStretchMode, TransformPaletteId, UvRectKind}; +use crate::render_task_graph::RenderTaskId; +use crate::internal_types::LayoutPrimitiveInfo; +use crate::util::{extract_inner_rect_k, ScaleOffset}; pub type BoxShadowKey = PrimKey; @@ -36,6 +45,10 @@ pub struct BoxShadow { pub color: ColorU, pub blur_radius: Au, pub clip_mode: BoxShadowClipMode, + pub inner_shadow_rect: RectangleKey, + pub outer_shadow_rect: RectangleKey, + pub shadow_radius: BorderRadiusAu, + pub clip: ClipDataHandle, } impl IsVisible for BoxShadow { @@ -46,6 +59,126 @@ impl IsVisible for BoxShadow { pub type BoxShadowDataHandle = InternHandle; +impl PatternBuilder for BoxShadowTemplate { + fn build( + &self, + sub_rect: Option, + ctx: &PatternBuilderContext, + state: &mut PatternBuilderState, + ) -> crate::pattern::Pattern { + + let raster_spatial_node_index = ctx.spatial_tree.root_reference_frame_index(); + let pattern_rect = self.kind.outer_shadow_rect; + + // TODO(gw): Correctly account for scaled blur radius inflation, and device + // pixel scale here. + + let (task_size, content_origin, scale_factor, uv_rect_kind) = match sub_rect { + Some(rect) => { + let expanded_rect = rect.inflate(32.0, 32.0); + let uv_rect_kind = calculate_uv_rect_kind(expanded_rect, pattern_rect.cast_unit()); + + ( + expanded_rect.size().cast_unit().to_i32(), + expanded_rect.min.cast_unit(), + DevicePixelScale::new(1.0), + uv_rect_kind, + ) + } + None => { + ( + pattern_rect.size().cast_unit().to_i32(), + pattern_rect.min.cast_unit(), + DevicePixelScale::new(1.0), + UvRectKind::Rect, + ) + } + }; + + let blur_radius = self.kind.blur_radius * scale_factor.0; + let clips_range = state.clip_store.push_clip_instance(self.kind.clip); + let color_pattern = Pattern::color(self.kind.color); + + let pattern_prim_address_f = quad::write_prim_blocks( + &mut state.frame_gpu_data.f32, + pattern_rect, + pattern_rect, + color_pattern.base_color, + color_pattern.texture_input.task_id, + &[], + ScaleOffset::identity(), + ); + + let pattern_task_id = state.rg_builder.add().init(RenderTask::new_dynamic( + task_size, + RenderTaskKind::Prim(PrimTask { + pattern: color_pattern.kind, + pattern_input: color_pattern.shader_input, + raster_spatial_node_index, + device_pixel_scale: DevicePixelScale::new(1.0), + content_origin, + prim_address_f: pattern_prim_address_f, + transform_id: TransformPaletteId::IDENTITY, + edge_flags: EdgeAaSegmentMask::empty(), + quad_flags: QuadFlags::APPLY_RENDER_TASK_CLIP | QuadFlags::IGNORE_DEVICE_PIXEL_SCALE, + prim_needs_scissor_rect: false, + texture_input: color_pattern.texture_input.task_id, + }), + )); + + let masks = MaskSubPass { + clip_node_range: clips_range, + prim_spatial_node_index: raster_spatial_node_index, + prim_address_f: pattern_prim_address_f, + }; + + let task = state.rg_builder.get_task_mut(pattern_task_id); + task.add_sub_pass(SubPass::Masks { masks }); + + let blur_task_v = state.rg_builder.add().init(RenderTask::new_dynamic( + task_size, + RenderTaskKind::VerticalBlur(BlurTask { + blur_std_deviation: blur_radius, + target_kind: RenderTargetKind::Color, + blur_region: task_size, + }), + )); + state.rg_builder.add_dependency(blur_task_v, pattern_task_id); + + let blur_task_h = state.rg_builder.add().init(RenderTask::new_dynamic( + task_size, + RenderTaskKind::HorizontalBlur(BlurTask { + blur_std_deviation: blur_radius, + target_kind: RenderTargetKind::Color, + blur_region: task_size, + }), + ).with_uv_rect_kind(uv_rect_kind)); + state.rg_builder.add_dependency(blur_task_h, blur_task_v); + + Pattern::texture( + blur_task_h, + self.kind.color, + ) + } + + fn get_base_color( + &self, + _ctx: &PatternBuilderContext, + ) -> ColorF { + self.kind.color + } + + fn use_shared_pattern( + &self, + ) -> bool { + false + } + + fn can_use_nine_patch(&self) -> bool { + false + } +} + impl InternablePrimitive for BoxShadow { fn into_key( self, @@ -72,6 +205,10 @@ pub struct BoxShadowData { pub color: ColorF, pub blur_radius: f32, pub clip_mode: BoxShadowClipMode, + pub inner_shadow_rect: LayoutRect, + pub outer_shadow_rect: LayoutRect, + pub shadow_radius: BorderRadius, + pub clip: ClipDataHandle, } impl From for BoxShadowData { @@ -80,6 +217,10 @@ impl From for BoxShadowData { color: shadow.color.into(), blur_radius: shadow.blur_radius.to_f32_px(), clip_mode: shadow.clip_mode, + inner_shadow_rect: shadow.inner_shadow_rect.into(), + outer_shadow_rect: shadow.outer_shadow_rect.into(), + shadow_radius: shadow.shadow_radius.into(), + clip: shadow.clip, } } } @@ -172,6 +313,7 @@ impl<'a> SceneBuilder<'a> { spread_radius: f32, border_radius: BorderRadius, clip_mode: BoxShadowClipMode, + is_root_coord_system: bool, ) { if color.a == 0.0 { return; @@ -187,7 +329,7 @@ impl<'a> SceneBuilder<'a> { blur_radius = f32::min(blur_radius, MAX_BLUR_RADIUS); // Adjust the border radius of the box shadow per CSS-spec. - let shadow_radius = adjust_border_radius_for_box_shadow(border_radius, spread_amount); + let mut shadow_radius = adjust_border_radius_for_box_shadow(border_radius, spread_amount); // Apply parameters that affect where the shadow rect // exists in the local space of the primitive. @@ -258,86 +400,160 @@ impl<'a> SceneBuilder<'a> { }, ); } else { - // Normal path for box-shadows with a valid blur radius. - let blur_offset = (BLUR_SAMPLE_SCALE * blur_radius).ceil(); - let mut extra_clips = vec![]; - - // Add a normal clip mask to clip out the contents - // of the surrounding primitive. - extra_clips.push(ClipItemKey { - kind: ClipItemKeyKind::rounded_rect( - prim_info.rect, - border_radius, - prim_clip_mode, - ), - spatial_node_index, - }); - - // Get the local rect of where the shadow will be drawn, - // expanded to include room for the blurred region. - let dest_rect = shadow_rect.inflate(blur_offset, blur_offset); + // If we know that this is an axis-aligned (root-coord) outset box-shadow, + // enable the new quad based render path. Complex transformed and inset + // box-shadow support will be added to this path as a follow up. + if is_root_coord_system && + clip_mode == BoxShadowClipMode::Outset && + blur_radius < 32.0 && + false { + // Make sure corners don't overlap. + ensure_no_corner_overlap(&mut shadow_radius, shadow_rect.size()); + + // Create clip that gets applied to the primitive + let prim_clip = ClipItemKey { + kind: ClipItemKeyKind::rounded_rect( + prim_info.rect, + border_radius, + ClipMode::ClipOut, + ), + spatial_node_index, + }; + + // Per https://drafts.csswg.org/css-backgrounds/#shadow-blur + let blur_radius = (blur_radius * 0.5).round(); + // Range over which blue radius affects pixels (~99% within 3 * sigma) + let sig3 = blur_radius * 3.0; + + // Clip for the pattern primitive + let item = ClipItemKey { + kind: ClipItemKeyKind::rounded_rect( + shadow_rect, + shadow_radius, + ClipMode::Clip, + ), + spatial_node_index: self.spatial_tree.root_reference_frame_index(), + }; + + let clip = self + .interners + .clip + .intern(&item, || { + ClipInternData { + key: item, + } + }); - // Draw the box-shadow as a solid rect, using a box-shadow - // clip mask item. - let prim = PrimitiveKeyKind::Rectangle { - color: PropertyBinding::Value(color.into()), - }; + let inner_shadow_rect = shadow_rect.inflate(-sig3, -sig3); + let outer_shadow_rect = shadow_rect.inflate( sig3, sig3); + let inner_shadow_rect = extract_inner_rect_k(&inner_shadow_rect, &shadow_radius, 0.5).unwrap_or(LayoutRect::zero()); - // Create the box-shadow clip item. - let shadow_clip_source = ClipItemKey { - kind: ClipItemKeyKind::box_shadow( - shadow_rect, - shadow_radius, - dest_rect, - blur_radius, + let prim = BoxShadow { + color: color.into(), + blur_radius: Au::from_f32_px(blur_radius), clip_mode, - ), - spatial_node_index, - }; - - let prim_info = match clip_mode { - BoxShadowClipMode::Outset => { - // Certain spread-radii make the shadow invalid. - if shadow_rect.is_empty() { - return; - } - // Add the box-shadow clip source. - extra_clips.push(shadow_clip_source); + inner_shadow_rect: inner_shadow_rect.into(), + outer_shadow_rect: outer_shadow_rect.into(), + shadow_radius: shadow_radius.into(), + clip, + }; + + // Out rect is the shadow rect + extent of blur + let prim_info = LayoutPrimitiveInfo::with_clip_rect( + outer_shadow_rect, + prim_info.clip_rect, + ); + + self.add_nonshadowable_primitive( + spatial_node_index, + clip_node_id, + &prim_info, + vec![prim_clip], + prim, + ); + } else { + // Normal path for box-shadows with a valid blur radius. + let blur_offset = (BLUR_SAMPLE_SCALE * blur_radius).ceil(); + let mut extra_clips = vec![]; + + // Add a normal clip mask to clip out the contents + // of the surrounding primitive. + extra_clips.push(ClipItemKey { + kind: ClipItemKeyKind::rounded_rect( + prim_info.rect, + border_radius, + prim_clip_mode, + ), + spatial_node_index, + }); + + // Get the local rect of where the shadow will be drawn, + // expanded to include room for the blurred region. + let dest_rect = shadow_rect.inflate(blur_offset, blur_offset); + + // Draw the box-shadow as a solid rect, using a box-shadow + // clip mask item. + let prim = PrimitiveKeyKind::Rectangle { + color: PropertyBinding::Value(color.into()), + }; + + // Create the box-shadow clip item. + let shadow_clip_source = ClipItemKey { + kind: ClipItemKeyKind::box_shadow( + shadow_rect, + shadow_radius, + dest_rect, + blur_radius, + clip_mode, + ), + spatial_node_index, + }; + + let prim_info = match clip_mode { + BoxShadowClipMode::Outset => { + // Certain spread-radii make the shadow invalid. + if shadow_rect.is_empty() { + return; + } + + // Add the box-shadow clip source. + extra_clips.push(shadow_clip_source); - // Outset shadows are expanded by the shadow - // region from the original primitive. - LayoutPrimitiveInfo::with_clip_rect(dest_rect, prim_info.clip_rect) - } - BoxShadowClipMode::Inset => { - // If the inner shadow rect contains the prim - // rect, no pixels will be shadowed. - if border_radius.is_zero() && shadow_rect - .inflate(-blur_radius, -blur_radius) - .contains_box(&prim_info.rect) - { - return; + // Outset shadows are expanded by the shadow + // region from the original primitive. + LayoutPrimitiveInfo::with_clip_rect(dest_rect, prim_info.clip_rect) } - - // Inset shadows are still visible, even if the - // inset shadow rect becomes invalid (they will - // just look like a solid rectangle). - if !shadow_rect.is_empty() { - extra_clips.push(shadow_clip_source); + BoxShadowClipMode::Inset => { + // If the inner shadow rect contains the prim + // rect, no pixels will be shadowed. + if border_radius.is_zero() && shadow_rect + .inflate(-blur_radius, -blur_radius) + .contains_box(&prim_info.rect) + { + return; + } + + // Inset shadows are still visible, even if the + // inset shadow rect becomes invalid (they will + // just look like a solid rectangle). + if !shadow_rect.is_empty() { + extra_clips.push(shadow_clip_source); + } + + // Inset shadows draw inside the original primitive. + prim_info.clone() } - - // Inset shadows draw inside the original primitive. - prim_info.clone() - } - }; - - self.add_primitive( - spatial_node_index, - clip_node_id, - &prim_info, - extra_clips, - prim, - ); + }; + + self.add_primitive( + spatial_node_index, + clip_node_id, + &prim_info, + extra_clips, + prim, + ); + } } } } diff --git a/webrender/src/clip.rs b/webrender/src/clip.rs index 1591aa177f..79389129e9 100644 --- a/webrender/src/clip.rs +++ b/webrender/src/clip.rs @@ -1440,6 +1440,29 @@ impl ClipStore { Some(inner_rect) } + // Directly construct a clip node range, ready for rendering, from an interned clip handle. + // Typically useful for drawing specific clips on custom pattern / child render tasks that + // aren't primitives. + // TODO(gw): For now, we assume they are local clips only - in future we might want to support + // non-local clips. + pub fn push_clip_instance( + &mut self, + handle: ClipDataHandle, + ) -> ClipNodeRange { + let first = self.clip_node_instances.len() as u32; + + self.clip_node_instances.push(ClipNodeInstance { + handle, + flags: ClipNodeFlags::SAME_COORD_SYSTEM | ClipNodeFlags::SAME_SPATIAL_NODE, + visible_tiles: None, + }); + + ClipNodeRange { + first, + count: 1, + } + } + /// The main interface external code uses. Given a local primitive, positioning /// information, and a clip chain id, build an optimized clip chain instance. pub fn build_clip_chain_instance( diff --git a/webrender/src/command_buffer.rs b/webrender/src/command_buffer.rs index 336f023f1b..5cff77bad2 100644 --- a/webrender/src/command_buffer.rs +++ b/webrender/src/command_buffer.rs @@ -121,6 +121,7 @@ pub enum PrimitiveCommand { Quad { pattern: PatternKind, pattern_input: PatternShaderInput, + src_color_task_id: RenderTaskId, // TODO(gw): Used for bounding rect only, could possibly remove prim_instance_index: PrimitiveInstanceIndex, gpu_buffer_address: GpuBufferAddress, @@ -152,6 +153,7 @@ impl PrimitiveCommand { pub fn quad( pattern: PatternKind, pattern_input: PatternShaderInput, + src_color_task_id: RenderTaskId, prim_instance_index: PrimitiveInstanceIndex, gpu_buffer_address: GpuBufferAddress, transform_id: TransformPaletteId, @@ -161,6 +163,7 @@ impl PrimitiveCommand { PrimitiveCommand::Quad { pattern, pattern_input, + src_color_task_id, prim_instance_index, gpu_buffer_address, transform_id, @@ -242,11 +245,12 @@ impl CommandBuffer { self.commands.push(Command::draw_instance(prim_instance_index)); self.commands.push(Command::data((gpu_buffer_address.u as u32) << 16 | gpu_buffer_address.v as u32)); } - PrimitiveCommand::Quad { pattern, pattern_input, prim_instance_index, gpu_buffer_address, transform_id, quad_flags, edge_flags } => { + PrimitiveCommand::Quad { pattern, pattern_input, prim_instance_index, gpu_buffer_address, transform_id, quad_flags, edge_flags, src_color_task_id } => { self.commands.push(Command::draw_quad(prim_instance_index)); self.commands.push(Command::data(pattern as u32)); self.commands.push(Command::data(pattern_input.0 as u32)); self.commands.push(Command::data(pattern_input.1 as u32)); + self.commands.push(Command::data(src_color_task_id.index)); self.commands.push(Command::data((gpu_buffer_address.u as u32) << 16 | gpu_buffer_address.v as u32)); self.commands.push(Command::data(transform_id.0)); self.commands.push(Command::data((quad_flags.bits() as u32) << 16 | edge_flags.bits() as u32)); @@ -297,6 +301,7 @@ impl CommandBuffer { cmd_iter.next().unwrap().0 as i32, cmd_iter.next().unwrap().0 as i32, ); + let src_color_task_id = RenderTaskId { index: cmd_iter.next().unwrap().0 }; let data = cmd_iter.next().unwrap(); let transform_id = TransformPaletteId(cmd_iter.next().unwrap().0); let bits = cmd_iter.next().unwrap().0; @@ -309,6 +314,7 @@ impl CommandBuffer { let cmd = PrimitiveCommand::quad( pattern, pattern_input, + src_color_task_id, prim_instance_index, gpu_buffer_address, transform_id, diff --git a/webrender/src/pattern.rs b/webrender/src/pattern.rs index 795f44f37b..3d73d9a746 100644 --- a/webrender/src/pattern.rs +++ b/webrender/src/pattern.rs @@ -2,9 +2,9 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use api::ColorF; +use api::{units::DeviceRect, ColorF}; -use crate::{render_task_graph::RenderTaskId, renderer::GpuBufferBuilder, scene::SceneProperties}; +use crate::{clip::ClipStore, render_task_graph::{RenderTaskGraphBuilder, RenderTaskId}, renderer::GpuBufferBuilder, scene::SceneProperties, spatial_tree::SpatialTree}; #[repr(u32)] #[cfg_attr(feature = "capture", derive(Serialize))] @@ -57,22 +57,48 @@ impl Default for PatternTextureInput { } } +impl PatternTextureInput { + pub fn new(task_id: RenderTaskId) -> Self { + PatternTextureInput { + task_id, + } + } +} + pub struct PatternBuilderContext<'a> { pub scene_properties: &'a SceneProperties, + pub spatial_tree: &'a SpatialTree, } pub struct PatternBuilderState<'a> { pub frame_gpu_data: &'a mut GpuBufferBuilder, + pub rg_builder: &'a mut RenderTaskGraphBuilder, + pub clip_store: &'a mut ClipStore, } pub trait PatternBuilder { fn build( &self, + _sub_rect: Option, _ctx: &PatternBuilderContext, _state: &mut PatternBuilderState, ) -> Pattern; + + fn get_base_color( + &self, + _ctx: &PatternBuilderContext, + ) -> ColorF; + + fn use_shared_pattern( + &self, + ) -> bool; + + fn can_use_nine_patch(&self) -> bool { + true + } } +#[cfg_attr(feature = "capture", derive(Serialize))] #[derive(Clone, Debug)] pub struct Pattern { pub kind: PatternKind, @@ -83,6 +109,18 @@ pub struct Pattern { } impl Pattern { + pub fn texture(task_id: RenderTaskId, color: ColorF) -> Self { + Pattern { + kind: PatternKind::ColorOrTexture, + shader_input: PatternShaderInput::default(), + texture_input: PatternTextureInput::new(task_id), + base_color: color, + // TODO(gw): We may want to add support to render tasks to query + // if they are known to be opaque. + is_opaque: false, + } + } + pub fn color(color: ColorF) -> Self { Pattern { kind: PatternKind::ColorOrTexture, diff --git a/webrender/src/picture.rs b/webrender/src/picture.rs index 318fbb6f22..82a53678d5 100644 --- a/webrender/src/picture.rs +++ b/webrender/src/picture.rs @@ -7853,7 +7853,7 @@ fn get_surface_rects( }) } -fn calculate_uv_rect_kind( +pub fn calculate_uv_rect_kind( clipped: DeviceRect, unclipped: DeviceRect, ) -> UvRectKind { diff --git a/webrender/src/prepare.rs b/webrender/src/prepare.rs index 838b1f8548..0a7c0d3188 100644 --- a/webrender/src/prepare.rs +++ b/webrender/src/prepare.rs @@ -12,7 +12,7 @@ use api::units::*; use euclid::Scale; use smallvec::SmallVec; use crate::composite::CompositorSurfaceKind; -use crate::command_buffer::{PrimitiveCommand, CommandBufferIndex}; +use crate::command_buffer::{CommandBufferIndex, PrimitiveCommand}; use crate::image_tiling::{self, Repetition}; use crate::border::{get_max_scale_for_border, build_border_instances}; use crate::clip::{ClipStore, ClipNodeRange}; @@ -23,7 +23,7 @@ use crate::frame_builder::{FrameBuildingContext, FrameBuildingState, PictureCont use crate::gpu_cache::{GpuCacheHandle, GpuDataRequest}; use crate::gpu_types::BrushFlags; use crate::internal_types::{FastHashMap, PlaneSplitAnchor, Filter}; -use crate::picture::{PicturePrimitive, SliceId, ClusterFlags, PictureCompositeMode}; +use crate::picture::{ClusterFlags, PictureCompositeMode, PicturePrimitive, SliceId}; use crate::picture::{PrimitiveList, PrimitiveCluster, SurfaceIndex, TileCacheInstance, SubpixelMode, Picture3DContext}; use crate::prim_store::line_dec::MAX_LINE_DECORATION_RESOLUTION; use crate::prim_store::*; @@ -33,7 +33,7 @@ use crate::render_backend::DataStores; use crate::render_task_graph::RenderTaskId; use crate::render_task_cache::RenderTaskCacheKeyKind; use crate::render_task_cache::{RenderTaskCacheKey, to_cache_size, RenderTaskParent}; -use crate::render_task::{RenderTaskKind, RenderTask, SubPass, MaskSubPass, EmptyTask}; +use crate::render_task::{EmptyTask, MaskSubPass, RenderTask, RenderTaskKind, SubPass}; use crate::segment::SegmentBuilder; use crate::util::{clamp_to_scale_factor, pack_as_float, ScaleOffset}; use crate::visibility::{compute_conservative_visible_rect, PrimitiveVisibility, VisibilityState}; @@ -321,8 +321,26 @@ fn prepare_interned_prim_for_render( let device_pixel_scale = frame_state.surfaces[pic_context.surface_index.0].device_pixel_scale; match &mut prim_instance.kind { - PrimitiveInstanceKind::BoxShadow { .. } => { - unreachable!("Native box shadow prims are not enabled yet"); + PrimitiveInstanceKind::BoxShadow { data_handle } => { + let prim_data = &mut data_stores.box_shadow[*data_handle]; + + quad::prepare_quad( + prim_data, + &prim_data.kind.outer_shadow_rect, + prim_instance_index, + prim_spatial_node_index, + &prim_instance.vis.clip_chain, + device_pixel_scale, + frame_context, + pic_context, + targets, + &data_stores.clip, + frame_state, + pic_state, + scratch, + ); + + return; } PrimitiveInstanceKind::LineDecoration { data_handle, ref mut render_task, .. } => { profile_scope!("LineDecoration"); @@ -959,7 +977,8 @@ fn prepare_interned_prim_for_render( &mut frame_state.frame_gpu_data.f32, prim_local_rect, prim_instance.vis.clip_chain.local_clip_rect, - &pattern, + pattern.base_color, + pattern.texture_input.task_id, &[], ScaleOffset::identity(), ); diff --git a/webrender/src/prim_store/gradient/conic.rs b/webrender/src/prim_store/gradient/conic.rs index e472396eec..69040206b3 100644 --- a/webrender/src/prim_store/gradient/conic.rs +++ b/webrender/src/prim_store/gradient/conic.rs @@ -104,6 +104,7 @@ pub struct ConicGradientTemplate { impl PatternBuilder for ConicGradientTemplate { fn build( &self, + _sub_rect: Option, _ctx: &PatternBuilderContext, state: &mut PatternBuilderState, ) -> Pattern { @@ -120,6 +121,19 @@ impl PatternBuilder for ConicGradientTemplate { state.frame_gpu_data, ) } + + fn get_base_color( + &self, + _ctx: &PatternBuilderContext, + ) -> ColorF { + ColorF::WHITE + } + + fn use_shared_pattern( + &self, + ) -> bool { + true + } } impl Deref for ConicGradientTemplate { diff --git a/webrender/src/prim_store/gradient/radial.rs b/webrender/src/prim_store/gradient/radial.rs index 6ab1e298c3..9d0e6f8f1d 100644 --- a/webrender/src/prim_store/gradient/radial.rs +++ b/webrender/src/prim_store/gradient/radial.rs @@ -108,6 +108,7 @@ pub struct RadialGradientTemplate { impl PatternBuilder for RadialGradientTemplate { fn build( &self, + _sub_rect: Option, _ctx: &PatternBuilderContext, state: &mut PatternBuilderState, ) -> Pattern { @@ -124,6 +125,19 @@ impl PatternBuilder for RadialGradientTemplate { state.frame_gpu_data, ) } + + fn get_base_color( + &self, + _ctx: &PatternBuilderContext, + ) -> ColorF { + ColorF::WHITE + } + + fn use_shared_pattern( + &self, + ) -> bool { + true + } } impl Deref for RadialGradientTemplate { diff --git a/webrender/src/prim_store/mod.rs b/webrender/src/prim_store/mod.rs index ddd0f4ac0c..a6b0ed66dd 100644 --- a/webrender/src/prim_store/mod.rs +++ b/webrender/src/prim_store/mod.rs @@ -574,6 +574,7 @@ pub struct PrimitiveTemplate { impl PatternBuilder for PrimitiveTemplate { fn build( &self, + _sub_rect: Option, ctx: &PatternBuilderContext, _state: &mut PatternBuilderState, ) -> crate::pattern::Pattern { @@ -585,6 +586,24 @@ impl PatternBuilder for PrimitiveTemplate { } } } + + fn get_base_color( + &self, + ctx: &PatternBuilderContext, + ) -> ColorF { + match self.kind { + PrimitiveTemplateKind::Clear => ColorF::BLACK, + PrimitiveTemplateKind::Rectangle { ref color, .. } => { + ctx.scene_properties.resolve_color(color) + } + } + } + + fn use_shared_pattern( + &self, + ) -> bool { + true + } } impl ops::Deref for PrimitiveTemplate { diff --git a/webrender/src/quad.rs b/webrender/src/quad.rs index a025542350..fe6827851b 100644 --- a/webrender/src/quad.rs +++ b/webrender/src/quad.rs @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use api::{units::*, ClipMode}; +use api::{units::*, ClipMode, ColorF}; use euclid::point2; use crate::batch::{BatchKey, BatchKind, BatchTextures}; @@ -15,11 +15,12 @@ use crate::internal_types::TextureSource; use crate::pattern::{Pattern, PatternBuilder, PatternBuilderContext, PatternBuilderState, PatternKind, PatternShaderInput}; use crate::prim_store::{PrimitiveInstanceIndex, PrimitiveScratchBuffer}; use crate::render_task::{MaskSubPass, RenderTask, RenderTaskAddress, RenderTaskKind, SubPass}; -use crate::render_task_graph::{RenderTaskGraph, RenderTaskId}; +use crate::render_task_graph::{RenderTaskGraph, RenderTaskGraphBuilder, RenderTaskId}; use crate::renderer::{BlendMode, GpuBufferAddress, GpuBufferBuilder, GpuBufferBuilderF}; use crate::segment::EdgeAaSegmentMask; use crate::space::SpaceMapper; use crate::spatial_tree::{SpatialNodeIndex, SpatialTree}; +use crate::surface::SurfaceBuilder; use crate::util::{extract_inner_rect_k, MaxRect, ScaleOffset}; const MIN_AA_SEGMENTS_SIZE: f32 = 4.0; @@ -75,32 +76,46 @@ pub fn prepare_quad( pic_state: &mut PictureState, scratch: &mut PrimitiveScratchBuffer, ) { + let map_prim_to_raster = frame_context.spatial_tree.get_relative_transform( + prim_spatial_node_index, + pic_context.raster_spatial_node_index, + ); + let ctx = PatternBuilderContext { scene_properties: frame_context.scene_properties, + spatial_tree: frame_context.spatial_tree, }; let mut state = PatternBuilderState { frame_gpu_data: frame_state.frame_gpu_data, + rg_builder: frame_state.rg_builder, + clip_store: frame_state.clip_store, }; - let pattern = pattern_builder.build( - &ctx, - &mut state, - ); + let shared_pattern = if pattern_builder.use_shared_pattern() { + Some(pattern_builder.build( + None, + &ctx, + &mut state, + )) + } else { + None + }; - let map_prim_to_raster = frame_context.spatial_tree.get_relative_transform( - prim_spatial_node_index, - pic_context.raster_spatial_node_index, - ); let prim_is_2d_scale_translation = map_prim_to_raster.is_2d_scale_translation(); let prim_is_2d_axis_aligned = map_prim_to_raster.is_2d_axis_aligned(); + // TODO(gw): Can't support 9-patch for box-shadows for now as should_create_task + // assumes pattern is solid. This is a temporary hack until as once that's + // fixed we can select 9-patch for box-shadows + let can_use_nine_patch = prim_is_2d_scale_translation && pattern_builder.can_use_nine_patch(); + let strategy = get_prim_render_strategy( prim_spatial_node_index, clip_chain, - frame_state.clip_store, + state.clip_store, interned_clips, - prim_is_2d_scale_translation, + can_use_nine_patch, frame_context.spatial_tree, ); @@ -112,9 +127,6 @@ pub fn prepare_quad( quad_flags |= QuadFlags::USE_AA_SEGMENTS; } - if pattern.is_opaque { - quad_flags |= QuadFlags::IS_OPAQUE; - } let needs_scissor = !prim_is_2d_scale_translation; if !needs_scissor { quad_flags |= QuadFlags::APPLY_RENDER_TASK_CLIP; @@ -136,25 +148,36 @@ pub fn prepare_quad( frame_context.spatial_tree, ); - // TODO(gw): Perhaps rather than writing untyped data here (we at least do validate - // the written block count) to gpu-buffer, we could add a trait for - // writing typed data? - let main_prim_address = write_prim_blocks( - &mut frame_state.frame_gpu_data.f32, - *local_rect, - clip_chain.local_clip_rect, - &pattern, - &[], - ScaleOffset::identity(), - ); - if let QuadRenderStrategy::Direct = strategy { + let pattern = shared_pattern.unwrap_or_else(|| { + pattern_builder.build( + None, + &ctx, + &mut state, + ) + }); + + if pattern.is_opaque { + quad_flags |= QuadFlags::IS_OPAQUE; + } + + let main_prim_address = write_prim_blocks( + &mut frame_state.frame_gpu_data.f32, + *local_rect, + clip_chain.local_clip_rect, + pattern.base_color, + pattern.texture_input.task_id, + &[], + ScaleOffset::identity(), + ); + // Render the primitive as a single instance. Coordinates are provided to the // shader in layout space. frame_state.push_prim( &PrimitiveCommand::quad( pattern.kind, pattern.shader_input, + pattern.texture_input.task_id, prim_instance_index, main_prim_address, transform_id, @@ -180,23 +203,34 @@ pub fn prepare_quad( let Some(clipped_surface_rect) = surface.get_surface_rect( &clip_chain.pic_coverage_rect, frame_context.spatial_tree ) else { - // In the rare case of getting to here with a prim that wasn't culled - // earlier, but that gets clipped here (e.g. float issues), we need - // to add any render task created by the pattern as a dependency to - // the surface so it gets freed when building the graph. - // TODO(gw): We should maybe have a proper way to cancel render tasks... - if pattern.texture_input.task_id != RenderTaskId::INVALID { - frame_state - .surface_builder - .add_child_render_task(pattern.texture_input.task_id, frame_state.rg_builder); - } - return; }; match strategy { QuadRenderStrategy::Direct => {} QuadRenderStrategy::Indirect => { + let pattern = shared_pattern.unwrap_or_else(|| { + pattern_builder.build( + None, + &ctx, + &mut state, + ) + }); + + if pattern.is_opaque { + quad_flags |= QuadFlags::IS_OPAQUE; + } + + let main_prim_address = write_prim_blocks( + &mut frame_state.frame_gpu_data.f32, + *local_rect, + clip_chain.local_clip_rect, + pattern.base_color, + pattern.texture_input.task_id, + &[], + ScaleOffset::identity(), + ); + // Render the primtive as a single instance in a render task, apply a mask // and composite it in the current picture. // The coordinates are provided to the shaders: @@ -215,14 +249,13 @@ pub fn prepare_quad( quad_flags, device_pixel_scale, needs_scissor, - frame_state, + frame_state.rg_builder, + &mut frame_state.surface_builder, ); let rect = clipped_surface_rect.to_f32().cast_unit(); - let is_masked = true; add_composite_prim( - &pattern, - is_masked, + pattern_builder.get_base_color(&ctx), prim_instance_index, rect, frame_state, @@ -261,7 +294,7 @@ pub fn prepare_quad( // Walk each clip, extract the local mask regions and add them to the tile classifier. for i in 0 .. clip_chain.clips_range.count { - let clip_instance = frame_state.clip_store.get_instance_from_range(&clip_chain.clips_range, i); + let clip_instance = state.clip_store.get_instance_from_range(&clip_chain.clips_range, i); let clip_node = &interned_clips[clip_instance.handle]; // Construct a prim <-> clip space converter @@ -401,7 +434,7 @@ pub fn prepare_quad( continue; } QuadTileKind::Pattern { has_mask } => { - prim_is_2d_scale_translation && !has_mask + prim_is_2d_scale_translation && !has_mask && shared_pattern.is_some() } }; @@ -420,6 +453,31 @@ pub fn prepare_quad( if is_direct { scratch.quad_direct_segments.push(QuadSegment { rect: rect.cast_unit(), task_id: RenderTaskId::INVALID }); } else { + let pattern = match shared_pattern { + Some(ref shared_pattern) => shared_pattern.clone(), + None => { + pattern_builder.build( + Some(rect), + &ctx, + &mut state, + ) + } + }; + + if pattern.is_opaque { + quad_flags |= QuadFlags::IS_OPAQUE; + } + + let main_prim_address = write_prim_blocks( + &mut state.frame_gpu_data.f32, + *local_rect, + clip_chain.local_clip_rect, + pattern.base_color, + pattern.texture_input.task_id, + &[], + ScaleOffset::identity(), + ); + let task_id = add_render_task_with_mask( &pattern, int_rect.round().to_i32().size(), @@ -433,7 +491,8 @@ pub fn prepare_quad( quad_flags, device_pixel_scale, needs_scissor, - frame_state, + state.rg_builder, + &mut frame_state.surface_builder, ); scratch.quad_indirect_segments.push(QuadSegment { rect: rect.cast_unit(), task_id }); @@ -448,6 +507,17 @@ pub fn prepare_quad( let device_prim_rect: DeviceRect = local_to_device.map_rect(&local_rect); + let pattern = match shared_pattern { + Some(ref shared_pattern) => shared_pattern.clone(), + None => { + pattern_builder.build( + Some(device_prim_rect), + &ctx, + &mut state, + ) + } + }; + add_pattern_prim( &pattern, local_to_device.inverse(), @@ -463,8 +533,7 @@ pub fn prepare_quad( if !scratch.quad_indirect_segments.is_empty() { add_composite_prim( - &pattern, - true, // is_masked + pattern_builder.get_base_color(&ctx), prim_instance_index, clip_coverage_rect.cast_unit(), frame_state, @@ -569,6 +638,24 @@ pub fn prepare_quad( }; if should_create_task(mode, x, y) { + let pattern = shared_pattern + .as_ref() + .expect("bug: nine-patch expects shared pattern, for now"); + + if pattern.is_opaque { + quad_flags |= QuadFlags::IS_OPAQUE; + } + + let main_prim_address = write_prim_blocks( + &mut state.frame_gpu_data.f32, + *local_rect, + clip_chain.local_clip_rect, + pattern.base_color, + pattern.texture_input.task_id, + &[], + ScaleOffset::identity(), + ); + let task_id = add_render_task_with_mask( &pattern, device_rect.size(), @@ -582,7 +669,8 @@ pub fn prepare_quad( quad_flags, device_pixel_scale, false, - frame_state, + state.rg_builder, + &mut frame_state.surface_builder, ); scratch.quad_indirect_segments.push(QuadSegment { rect: device_rect.to_f32().cast_unit(), @@ -598,6 +686,12 @@ pub fn prepare_quad( } if !scratch.quad_direct_segments.is_empty() { + let pattern = pattern_builder.build( + None, + &ctx, + &mut state, + ); + add_pattern_prim( &pattern, local_to_device.inverse(), @@ -612,10 +706,8 @@ pub fn prepare_quad( } if !scratch.quad_indirect_segments.is_empty() { - let is_masked = true; add_composite_prim( - &pattern, - is_masked, + pattern_builder.get_base_color(&ctx), prim_instance_index, clip_coverage_rect.cast_unit(), frame_state, @@ -712,9 +804,10 @@ fn add_render_task_with_mask( quad_flags: QuadFlags, device_pixel_scale: DevicePixelScale, needs_scissor_rect: bool, - frame_state: &mut FrameBuildingState, + rg_builder: &mut RenderTaskGraphBuilder, + surface_builder: &mut SurfaceBuilder, ) -> RenderTaskId { - let task_id = frame_state.rg_builder.add().init(RenderTask::new_dynamic( + let task_id = rg_builder.add().init(RenderTask::new_dynamic( task_size, RenderTaskKind::new_prim( pattern.kind, @@ -734,7 +827,7 @@ fn add_render_task_with_mask( // If the pattern samples from a texture, add it as a dependency // of the indirect render task that relies on it. if pattern.texture_input.task_id != RenderTaskId::INVALID { - frame_state.rg_builder.add_dependency(task_id, pattern.texture_input.task_id); + rg_builder.add_dependency(task_id, pattern.texture_input.task_id); } if clips_range.count > 0 { @@ -744,13 +837,11 @@ fn add_render_task_with_mask( prim_address_f, }; - let task = frame_state.rg_builder.get_task_mut(task_id); + let task = rg_builder.get_task_mut(task_id); task.add_sub_pass(SubPass::Masks { masks }); } - frame_state - .surface_builder - .add_child_render_task(task_id, frame_state.rg_builder); + surface_builder.add_child_render_task(task_id, rg_builder); task_id } @@ -770,7 +861,8 @@ fn add_pattern_prim( &mut frame_state.frame_gpu_data.f32, rect, clip_rect, - pattern, + pattern.base_color, + pattern.texture_input.task_id, segments, pattern_transform, ); @@ -788,6 +880,7 @@ fn add_pattern_prim( &PrimitiveCommand::quad( pattern.kind, pattern.shader_input, + pattern.texture_input.task_id, prim_instance_index, prim_address, TransformPaletteId::IDENTITY, @@ -800,14 +893,15 @@ fn add_pattern_prim( } fn add_composite_prim( - pattern: &Pattern, - is_masked: bool, + base_color: ColorF, prim_instance_index: PrimitiveInstanceIndex, rect: LayoutRect, frame_state: &mut FrameBuildingState, targets: &[CommandBufferIndex], segments: &[QuadSegment], ) { + assert!(!segments.is_empty()); + let composite_prim_address = write_prim_blocks( &mut frame_state.frame_gpu_data.f32, rect, @@ -817,24 +911,22 @@ fn add_composite_prim( // in the quad primitive). However, passing opaque white // here causes glitches with Adreno GPUs on Windows specifically // (See bug 1897444). - pattern, + base_color, + RenderTaskId::INVALID, segments, ScaleOffset::identity(), ); frame_state.set_segments(segments, targets); - let mut quad_flags = QuadFlags::IGNORE_DEVICE_PIXEL_SCALE + let quad_flags = QuadFlags::IGNORE_DEVICE_PIXEL_SCALE | QuadFlags::APPLY_RENDER_TASK_CLIP; - if pattern.is_opaque && !is_masked { - quad_flags |= QuadFlags::IS_OPAQUE; - } - frame_state.push_cmd( &PrimitiveCommand::quad( PatternKind::ColorOrTexture, - pattern.shader_input, + PatternShaderInput::default(), + RenderTaskId::INVALID, prim_instance_index, composite_prim_address, TransformPaletteId::IDENTITY, @@ -850,7 +942,8 @@ pub fn write_prim_blocks( builder: &mut GpuBufferBuilderF, prim_rect: LayoutRect, clip_rect: LayoutRect, - pattern: &Pattern, + pattern_base_color: ColorF, + pattern_texture_input: RenderTaskId, segments: &[QuadSegment], scale_offset: ScaleOffset, ) -> GpuBufferAddress { @@ -858,9 +951,9 @@ pub fn write_prim_blocks( writer.push_one(prim_rect); writer.push_one(clip_rect); - writer.push_render_task(pattern.texture_input.task_id); + writer.push_render_task(pattern_texture_input); writer.push_one(scale_offset); - writer.push_one(pattern.base_color.premultiplied()); + writer.push_one(pattern_base_color.premultiplied()); for segment in segments { writer.push_one(segment.rect); diff --git a/webrender/src/render_target.rs b/webrender/src/render_target.rs index e1edf2d232..a3159647ec 100644 --- a/webrender/src/render_target.rs +++ b/webrender/src/render_target.rs @@ -1253,7 +1253,8 @@ fn build_mask_tasks( &mut gpu_buffer_builder.f32, rect, rect, - &pattern, + pattern.base_color, + pattern.texture_input.task_id, &[QuadSegment { rect: tile.tile_rect, task_id: tile.task_id, @@ -1322,7 +1323,8 @@ fn build_mask_tasks( &mut gpu_buffer_builder.f32, task_world_rect.cast_unit(), task_world_rect.cast_unit(), - &pattern, + pattern.base_color, + pattern.texture_input.task_id, &[], ScaleOffset::identity(), ); diff --git a/webrender/src/renderer/gpu_buffer.rs b/webrender/src/renderer/gpu_buffer.rs index 659c386972..bf11b11769 100644 --- a/webrender/src/renderer/gpu_buffer.rs +++ b/webrender/src/renderer/gpu_buffer.rs @@ -359,11 +359,11 @@ impl GpuBufferBuilderImpl where T: Texel + std::convert::From { pub config: FrameBuilderConfig, /// Reference to the set of data that is interned across display lists. - interners: &'a mut Interners, + pub interners: &'a mut Interners, /// Helper struct to map spatial nodes to external scroll offsets. external_scroll_mapper: ScrollOffsetMapper, @@ -1777,6 +1777,7 @@ impl<'a> SceneBuilder<'a> { info.spread_radius, info.border_radius, info.clip_mode, + self.spatial_tree.is_root_coord_system(spatial_node_index), ); } DisplayItem::Border(ref info) => { @@ -1991,7 +1992,7 @@ impl<'a> SceneBuilder<'a> { /// Convenience interface that creates a primitive entry and adds it /// to the draw list. - fn add_nonshadowable_primitive

( + pub fn add_nonshadowable_primitive

( &mut self, spatial_node_index: SpatialNodeIndex, clip_node_id: ClipNodeId,