diff --git a/webrender/src/batch.rs b/webrender/src/batch.rs index 1e6225babc..9d7d97563e 100644 --- a/webrender/src/batch.rs +++ b/webrender/src/batch.rs @@ -15,8 +15,7 @@ use gpu_types::{PrimitiveInstanceData, RasterizationSpace, GlyphInstance}; use gpu_types::{PrimitiveHeader, PrimitiveHeaderIndex, TransformPaletteId, TransformPalette}; use internal_types::{FastHashMap, SavedTargetIndex, TextureSource}; use picture::{Picture3DContext, PictureCompositeMode, PicturePrimitive, PictureSurface}; -use prim_store::{DeferredResolve, PrimitiveTemplateKind}; -use prim_store::{EdgeAaSegmentMask, PrimitiveInstanceKind}; +use prim_store::{DeferredResolve, EdgeAaSegmentMask, PrimitiveInstanceKind}; use prim_store::{VisibleGradientTile, PrimitiveInstance, PrimitiveOpacity, SegmentInstanceIndex}; use prim_store::{BrushSegment, ClipMaskKind, ClipTaskIndex}; use prim_store::image::ImageSource; @@ -604,8 +603,9 @@ impl AlphaBatchBuilder { ); } PrimitiveInstanceKind::NormalBorder { data_handle, ref cache_handles, .. } => { - let prim_data = &ctx.resources.prim_data_store[data_handle]; - let prim_cache_address = gpu_cache.get_address(&prim_data.gpu_cache_handle); + let prim_data = &ctx.resources.normal_border_data_store[data_handle]; + let common_data = &prim_data.common; + let prim_cache_address = gpu_cache.get_address(&common_data.gpu_cache_handle); let cache_handles = &ctx.scratch.border_cache_handles[*cache_handles]; let specified_blend_mode = BlendMode::PremultipliedAlpha; let mut segment_data: SmallVec<[SegmentInstanceData; 8]> = SmallVec::new(); @@ -626,7 +626,7 @@ impl AlphaBatchBuilder { ); } - let non_segmented_blend_mode = if !prim_data.opacity.is_opaque || + let non_segmented_blend_mode = if !common_data.opacity.is_opaque || prim_instance.clip_task_index != ClipTaskIndex::INVALID || transform_kind == TransformedRectKind::Complex { @@ -660,13 +660,10 @@ impl AlphaBatchBuilder { batch_params.prim_user_data, ); - let template = match prim_data.kind { - PrimitiveTemplateKind::NormalBorder { ref template, .. } => template, - _ => unreachable!() - }; + let border_data = &prim_data.kind; self.add_segmented_prim_to_batch( - Some(template.brush_segments.as_slice()), - prim_data.opacity, + Some(border_data.brush_segments.as_slice()), + common_data.opacity, &batch_params, specified_blend_mode, non_segmented_blend_mode, @@ -1387,16 +1384,12 @@ impl AlphaBatchBuilder { } } PrimitiveInstanceKind::ImageBorder { data_handle, .. } => { - let prim_data = &ctx.resources.prim_data_store[data_handle]; - let (request, brush_segments) = match &prim_data.kind { - PrimitiveTemplateKind::ImageBorder { request, brush_segments, .. } => { - (request, brush_segments) - } - _ => unreachable!() - }; + let prim_data = &ctx.resources.image_border_data_store[data_handle]; + let common_data = &prim_data.common; + let border_data = &prim_data.kind; let cache_item = resolve_image( - *request, + border_data.request, ctx.resource_cache, gpu_cache, deferred_resolves, @@ -1406,9 +1399,9 @@ impl AlphaBatchBuilder { } let textures = BatchTextures::color(cache_item.texture_id); - let prim_cache_address = gpu_cache.get_address(&prim_data.gpu_cache_handle); + let prim_cache_address = gpu_cache.get_address(&common_data.gpu_cache_handle); let specified_blend_mode = BlendMode::PremultipliedAlpha; - let non_segmented_blend_mode = if !prim_data.opacity.is_opaque || + let non_segmented_blend_mode = if !common_data.opacity.is_opaque || prim_instance.clip_task_index != ClipTaskIndex::INVALID || transform_kind == TransformedRectKind::Complex { @@ -1444,8 +1437,8 @@ impl AlphaBatchBuilder { ); self.add_segmented_prim_to_batch( - Some(brush_segments.as_slice()), - prim_data.opacity, + Some(border_data.brush_segments.as_slice()), + common_data.opacity, &batch_params, specified_blend_mode, non_segmented_blend_mode, diff --git a/webrender/src/border.rs b/webrender/src/border.rs index 13aa052629..ac833628ff 100644 --- a/webrender/src/border.rs +++ b/webrender/src/border.rs @@ -4,14 +4,16 @@ use api::{BorderRadius, BorderSide, BorderStyle, ColorF, ColorU, DeviceRect, DeviceSize}; use api::{LayoutSideOffsets, LayoutSizeAu, LayoutPrimitiveInfo, LayoutToDeviceScale}; -use api::{DeviceVector2D, DevicePoint, LayoutRect, LayoutSize, NormalBorder, DeviceIntSize}; +use api::{DeviceVector2D, DevicePoint, LayoutRect, LayoutSize, DeviceIntSize}; use api::{AuHelpers, LayoutPoint, RepeatMode, TexelRect}; +use api::NormalBorder as ApiNormalBorder; use ellipse::Ellipse; use euclid::vec2; use display_list_flattener::DisplayListFlattener; use gpu_types::{BorderInstance, BorderSegment, BrushFlags}; use prim_store::{BorderSegmentInfo, BrushSegment, NinePatchDescriptor}; -use prim_store::{EdgeAaSegmentMask, ScrollNodeAndClipChain, PrimitiveKeyKind}; +use prim_store::{EdgeAaSegmentMask, ScrollNodeAndClipChain}; +use prim_store::borders::NormalBorderPrim; use util::{lerp, RectHelpers}; // Using 2048 as the maximum radius in device space before which we @@ -129,8 +131,8 @@ impl NormalBorderAu { } } -impl From for NormalBorderAu { - fn from(border: NormalBorder) -> Self { +impl From for NormalBorderAu { + fn from(border: ApiNormalBorder) -> Self { NormalBorderAu { left: border.left.into(), right: border.right.into(), @@ -142,9 +144,9 @@ impl From for NormalBorderAu { } } -impl From for NormalBorder { +impl From for ApiNormalBorder { fn from(border: NormalBorderAu) -> Self { - NormalBorder { + ApiNormalBorder { left: border.left.into(), right: border.right.into(), top: border.top.into(), @@ -218,7 +220,7 @@ impl<'a> DisplayListFlattener<'a> { pub fn add_normal_border( &mut self, info: &LayoutPrimitiveInfo, - border: &NormalBorder, + border: &ApiNormalBorder, widths: LayoutSideOffsets, clip_and_scroll: ScrollNodeAndClipChain, ) { @@ -229,7 +231,7 @@ impl<'a> DisplayListFlattener<'a> { clip_and_scroll, info, Vec::new(), - PrimitiveKeyKind::NormalBorder { + NormalBorderPrim { border: border.into(), widths: widths.to_au(), }, @@ -650,7 +652,7 @@ fn get_edge_info( /// cache keys for a given CSS border. pub fn create_border_segments( size: LayoutSize, - border: &NormalBorder, + border: &ApiNormalBorder, widths: &LayoutSideOffsets, border_segments: &mut Vec, brush_segments: &mut Vec, @@ -1096,7 +1098,7 @@ fn add_edge_segment( pub fn build_border_instances( cache_key: &BorderSegmentCacheKey, cache_size: DeviceIntSize, - border: &NormalBorder, + border: &ApiNormalBorder, scale: LayoutToDeviceScale, ) -> Vec { let mut instances = Vec::new(); diff --git a/webrender/src/display_list_flattener.rs b/webrender/src/display_list_flattener.rs index ccdac6e6ba..cce8d10918 100644 --- a/webrender/src/display_list_flattener.rs +++ b/webrender/src/display_list_flattener.rs @@ -26,6 +26,7 @@ use prim_store::{PrimitiveInstance, PrimitiveKeyKind, PictureCompositeKey}; use prim_store::{PrimitiveKey, PrimitiveSceneData, PrimitiveInstanceKind, NinePatchDescriptor}; use prim_store::{PrimitiveStore, PrimitiveStoreStats, LineDecorationCacheKey}; use prim_store::{ScrollNodeAndClipChain, PictureIndex, register_prim_chase_id, get_line_decoration_sizes}; +use prim_store::borders::{ImageBorder, NormalBorderPrim}; use prim_store::gradient::{GradientStopKey, LinearGradient, RadialGradient, RadialGradientParams}; use prim_store::image::{Image, YuvImage}; use prim_store::text_run::TextRun; @@ -1824,6 +1825,9 @@ impl<'a> DisplayListFlattener<'a> { ShadowItem::Image(ref pending_image) => { self.add_shadow_prim(&pending_shadow, pending_image, &mut prims) } + ShadowItem::NormalBorder(ref pending_border) => { + self.add_shadow_prim(&pending_shadow, pending_border, &mut prims) + } ShadowItem::Primitive(ref pending_primitive) => { self.add_shadow_prim(&pending_shadow, pending_primitive, &mut prims) } @@ -1903,6 +1907,9 @@ impl<'a> DisplayListFlattener<'a> { ShadowItem::Image(pending_image) => { self.add_shadow_prim_to_draw_list(pending_image) }, + ShadowItem::NormalBorder(pending_border) => { + self.add_shadow_prim_to_draw_list(pending_border) + } ShadowItem::Primitive(pending_primitive) => { self.add_shadow_prim_to_draw_list(pending_primitive) }, @@ -2115,7 +2122,7 @@ impl<'a> DisplayListFlattener<'a> { match border.source { NinePatchBorderSource::Image(image_key) => { - let prim = PrimitiveKeyKind::ImageBorder { + let prim = ImageBorder { request: ImageRequest { key: image_key, rendering: ImageRendering::Auto, @@ -2124,7 +2131,7 @@ impl<'a> DisplayListFlattener<'a> { nine_patch, }; - self.add_primitive( + self.add_nonshadowable_primitive( clip_and_scroll, info, Vec::new(), @@ -2676,6 +2683,7 @@ pub struct PendingShadow { pub enum ShadowItem { Shadow(PendingShadow), Image(PendingPrimitive), + NormalBorder(PendingPrimitive), Primitive(PendingPrimitive), TextRun(PendingPrimitive), } @@ -2686,6 +2694,12 @@ impl From> for ShadowItem { } } +impl From> for ShadowItem { + fn from(border: PendingPrimitive) -> Self { + ShadowItem::NormalBorder(border) + } +} + impl From> for ShadowItem { fn from(container: PendingPrimitive) -> Self { ShadowItem::Primitive(container) diff --git a/webrender/src/picture.rs b/webrender/src/picture.rs index de351a8f9c..39d2c999d1 100644 --- a/webrender/src/picture.rs +++ b/webrender/src/picture.rs @@ -1393,8 +1393,6 @@ impl PrimitiveList { let prim_data = match prim_instance.kind { PrimitiveInstanceKind::Picture { data_handle, .. } | PrimitiveInstanceKind::LineDecoration { data_handle, .. } | - PrimitiveInstanceKind::NormalBorder { data_handle, .. } | - PrimitiveInstanceKind::ImageBorder { data_handle, .. } | PrimitiveInstanceKind::Rectangle { data_handle, .. } | PrimitiveInstanceKind::Clear { data_handle, .. } => { &resources.prim_interner[data_handle] @@ -1402,9 +1400,15 @@ impl PrimitiveList { PrimitiveInstanceKind::Image { data_handle, .. } => { &resources.image_interner[data_handle] } + PrimitiveInstanceKind::ImageBorder { data_handle, .. } => { + &resources.image_border_interner[data_handle] + } PrimitiveInstanceKind::LinearGradient { data_handle, .. } => { &resources.linear_grad_interner[data_handle] } + PrimitiveInstanceKind::NormalBorder { data_handle, .. } => { + &resources.normal_border_interner[data_handle] + } PrimitiveInstanceKind::RadialGradient { data_handle, ..} => { &resources.radial_grad_interner[data_handle] } diff --git a/webrender/src/prim_store/borders.rs b/webrender/src/prim_store/borders.rs new file mode 100644 index 0000000000..ddc75a3118 --- /dev/null +++ b/webrender/src/prim_store/borders.rs @@ -0,0 +1,392 @@ +/* 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::{ + AuHelpers, LayoutPrimitiveInfo, LayoutRect, LayoutSideOffsets, + LayoutSideOffsetsAu, LayoutSize, NormalBorder, PremultipliedColorF, + Shadow +}; +use border::create_border_segments; +use border::NormalBorderAu; +use display_list_flattener::{AsInstanceKind, CreateShadow, IsVisible}; +use frame_builder::{FrameBuildingState}; +use gpu_cache::GpuDataRequest; +use intern; +use prim_store::{ + BorderSegmentInfo, BrushSegment, NinePatchDescriptor, PrimKey, + PrimKeyCommonData, PrimTemplate, PrimTemplateCommonData, + PrimitiveInstanceKind, PrimitiveOpacity, PrimitiveSceneData, + PrimitiveStore +}; +use resource_cache::ImageRequest; +use storage; + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub struct NormalBorderPrim { + pub border: NormalBorderAu, + pub widths: LayoutSideOffsetsAu, +} + +pub type NormalBorderKey = PrimKey; + +impl NormalBorderKey { + pub fn new( + info: &LayoutPrimitiveInfo, + prim_relative_clip_rect: LayoutRect, + normal_border: NormalBorderPrim, + ) -> Self { + NormalBorderKey { + common: PrimKeyCommonData::with_info( + info, + prim_relative_clip_rect, + ), + kind: normal_border, + } + } +} + +impl intern::InternDebug for NormalBorderKey {} + +impl AsInstanceKind for NormalBorderKey { + /// Construct a primitive instance that matches the type + /// of primitive key. + fn as_instance_kind( + &self, + data_handle: NormalBorderDataHandle, + _: &mut PrimitiveStore, + ) -> PrimitiveInstanceKind { + PrimitiveInstanceKind::NormalBorder { + data_handle, + cache_handles: storage::Range::empty(), + } + } +} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct NormalBorderData { + pub brush_segments: Vec, + pub border_segments: Vec, + pub border: NormalBorder, + pub widths: LayoutSideOffsets, +} + +impl NormalBorderData { + /// Update the GPU cache for a given primitive template. This may be called multiple + /// times per frame, by each primitive reference that refers to this interned + /// template. The initial request call to the GPU cache ensures that work is only + /// done if the cache entry is invalid (due to first use or eviction). + pub fn update( + &mut self, + common: &mut PrimTemplateCommonData, + frame_state: &mut FrameBuildingState, + ) { + if let Some(ref mut request) = frame_state.gpu_cache.request(&mut common.gpu_cache_handle) { + self.write_prim_gpu_blocks(request, common.prim_size); + self.write_segment_gpu_blocks(request); + } + + common.opacity = PrimitiveOpacity::translucent(); + } + + fn write_prim_gpu_blocks( + &self, + request: &mut GpuDataRequest, + prim_size: LayoutSize + ) { + // Border primitives currently used for + // image borders, and run through the + // normal brush_image shader. + request.push(PremultipliedColorF::WHITE); + request.push(PremultipliedColorF::WHITE); + request.push([ + prim_size.width, + prim_size.height, + 0.0, + 0.0, + ]); + } + + fn write_segment_gpu_blocks( + &self, + request: &mut GpuDataRequest, + ) { + for segment in &self.brush_segments { + // has to match VECS_PER_SEGMENT + request.write_segment( + segment.local_rect, + segment.extra_data, + ); + } + } +} + +pub type NormalBorderTemplate = PrimTemplate; + +impl From for NormalBorderTemplate { + fn from(key: NormalBorderKey) -> Self { + let common = PrimTemplateCommonData::with_key_common(key.common); + + let mut border: NormalBorder = key.kind.border.into(); + let widths = LayoutSideOffsets::from_au(key.kind.widths); + + // FIXME(emilio): Is this the best place to do this? + border.normalize(&widths); + + let mut brush_segments = Vec::new(); + let mut border_segments = Vec::new(); + + create_border_segments( + common.prim_size, + &border, + &widths, + &mut border_segments, + &mut brush_segments, + ); + + NormalBorderTemplate { + common, + kind: NormalBorderData { + brush_segments, + border_segments, + border, + widths, + } + } + } +} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)] +pub struct NormalBorderDataMarker; + +pub type NormalBorderDataStore = intern::DataStore; +pub type NormalBorderDataHandle = intern::Handle; +pub type NormalBorderDataUpdateList = intern::UpdateList; +pub type NormalBorderDataInterner = intern::Interner; + +impl intern::Internable for NormalBorderPrim { + type Marker = NormalBorderDataMarker; + type Source = NormalBorderKey; + type StoreData = NormalBorderTemplate; + type InternData = PrimitiveSceneData; + + /// Build a new key from self with `info`. + fn build_key( + self, + info: &LayoutPrimitiveInfo, + prim_relative_clip_rect: LayoutRect, + ) -> NormalBorderKey { + NormalBorderKey::new( + info, + prim_relative_clip_rect, + self, + ) + } +} + +impl CreateShadow for NormalBorderPrim { + fn create_shadow(&self, shadow: &Shadow) -> Self { + let border = self.border.with_color(shadow.color.into()); + NormalBorderPrim { + border, + widths: self.widths, + } + } +} + +impl IsVisible for NormalBorderPrim { + fn is_visible(&self) -> bool { + true + } +} + +//////////////////////////////////////////////////////////////////////////////// + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub struct ImageBorder { + pub request: ImageRequest, + pub nine_patch: NinePatchDescriptor, +} + +pub type ImageBorderKey = PrimKey; + +impl ImageBorderKey { + pub fn new( + info: &LayoutPrimitiveInfo, + prim_relative_clip_rect: LayoutRect, + image_border: ImageBorder, + ) -> Self { + ImageBorderKey { + common: PrimKeyCommonData::with_info( + info, + prim_relative_clip_rect, + ), + kind: image_border, + } + } +} + +impl intern::InternDebug for ImageBorderKey {} + +impl AsInstanceKind for ImageBorderKey { + /// Construct a primitive instance that matches the type + /// of primitive key. + fn as_instance_kind( + &self, + data_handle: ImageBorderDataHandle, + _: &mut PrimitiveStore, + ) -> PrimitiveInstanceKind { + PrimitiveInstanceKind::ImageBorder { + data_handle + } + } +} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +pub struct ImageBorderData { + pub request: ImageRequest, + pub brush_segments: Vec, +} + +impl ImageBorderData { + /// Update the GPU cache for a given primitive template. This may be called multiple + /// times per frame, by each primitive reference that refers to this interned + /// template. The initial request call to the GPU cache ensures that work is only + /// done if the cache entry is invalid (due to first use or eviction). + pub fn update( + &mut self, + common: &mut PrimTemplateCommonData, + frame_state: &mut FrameBuildingState, + ) { + if let Some(ref mut request) = frame_state.gpu_cache.request(&mut common.gpu_cache_handle) { + self.write_prim_gpu_blocks(request, &common.prim_size); + self.write_segment_gpu_blocks(request); + } + + let image_properties = frame_state + .resource_cache + .get_image_properties(self.request.key); + + common.opacity = if let Some(image_properties) = image_properties { + frame_state.resource_cache.request_image( + self.request, + frame_state.gpu_cache, + ); + PrimitiveOpacity { + is_opaque: image_properties.descriptor.is_opaque, + } + } else { + PrimitiveOpacity::opaque() + } + } + + fn write_prim_gpu_blocks( + &self, + request: &mut GpuDataRequest, + prim_size: &LayoutSize, + ) { + // Border primitives currently used for + // image borders, and run through the + // normal brush_image shader. + request.push(PremultipliedColorF::WHITE); + request.push(PremultipliedColorF::WHITE); + request.push([ + prim_size.width, + prim_size.height, + 0.0, + 0.0, + ]); + } + + fn write_segment_gpu_blocks( + &self, + request: &mut GpuDataRequest, + ) { + for segment in &self.brush_segments { + // has to match VECS_PER_SEGMENT + request.write_segment( + segment.local_rect, + segment.extra_data, + ); + } + } +} + +pub type ImageBorderTemplate = PrimTemplate; + +impl From for ImageBorderTemplate { + fn from(key: ImageBorderKey) -> Self { + let common = PrimTemplateCommonData::with_key_common(key.common); + + let brush_segments = key.kind.nine_patch.create_segments(common.prim_size); + ImageBorderTemplate { + common, + kind: ImageBorderData { + request: key.kind.request, + brush_segments, + } + } + } +} + +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)] +pub struct ImageBorderDataMarker; + +pub type ImageBorderDataStore = intern::DataStore; +pub type ImageBorderDataHandle = intern::Handle; +pub type ImageBorderDataUpdateList = intern::UpdateList; +pub type ImageBorderDataInterner = intern::Interner; + +impl intern::Internable for ImageBorder { + type Marker = ImageBorderDataMarker; + type Source = ImageBorderKey; + type StoreData = ImageBorderTemplate; + type InternData = PrimitiveSceneData; + + /// Build a new key from self with `info`. + fn build_key( + self, + info: &LayoutPrimitiveInfo, + prim_relative_clip_rect: LayoutRect, + ) -> ImageBorderKey { + ImageBorderKey::new( + info, + prim_relative_clip_rect, + self, + ) + } +} + +impl IsVisible for ImageBorder { + fn is_visible(&self) -> bool { + true + } +} + +#[test] +#[cfg(target_os = "linux")] +fn test_struct_sizes() { + use std::mem; + // The sizes of these structures are critical for performance on a number of + // talos stress tests. If you get a failure here on CI, there's two possibilities: + // (a) You made a structure smaller than it currently is. Great work! Update the + // test expectations and move on. + // (b) You made a structure larger. This is not necessarily a problem, but should only + // be done with care, and after checking if talos performance regresses badly. + assert_eq!(mem::size_of::(), 84, "NormalBorderPrim size changed"); + assert_eq!(mem::size_of::(), 240, "NormalBorderTemplate size changed"); + assert_eq!(mem::size_of::(), 112, "NormalBorderKey size changed"); + assert_eq!(mem::size_of::(), 92, "ImageBorder size changed"); + assert_eq!(mem::size_of::(), 104, "ImageBorderTemplate size changed"); + assert_eq!(mem::size_of::(), 120, "ImageBorderKey size changed"); +} diff --git a/webrender/src/prim_store/mod.rs b/webrender/src/prim_store/mod.rs index af5fc61a83..1361009119 100644 --- a/webrender/src/prim_store/mod.rs +++ b/webrender/src/prim_store/mod.rs @@ -3,16 +3,16 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use api::{BorderRadius, ClipMode, ColorF, PictureRect, ColorU, LayoutVector2D}; -use api::{DeviceIntRect, DevicePixelScale, DeviceRect, LayoutSideOffsetsAu}; +use api::{DeviceIntRect, DevicePixelScale, DeviceRect}; use api::{FilterOp, ImageRendering, TileOffset, RepeatMode, MixBlendMode}; use api::{LayoutPoint, LayoutRect, LayoutSideOffsets, LayoutSize, PropertyBindingId}; use api::{PremultipliedColorF, PropertyBinding, Shadow}; -use api::{WorldPixel, BoxShadowClipMode, NormalBorder, WorldRect, LayoutToWorldScale}; +use api::{WorldPixel, BoxShadowClipMode, WorldRect, LayoutToWorldScale}; use api::{PicturePixel, RasterPixel, LineStyle, LineOrientation, LayoutSizeAu, AuHelpers}; use api::LayoutPrimitiveInfo; use app_units::Au; -use border::{get_max_scale_for_border, build_border_instances, create_border_segments}; -use border::{BorderSegmentCacheKey, NormalBorderAu}; +use border::{get_max_scale_for_border, build_border_instances}; +use border::BorderSegmentCacheKey; use clip::{ClipStore}; use clip_scroll_tree::{ClipScrollTree, SpatialNodeIndex}; use clip::{ClipDataStore, ClipNodeFlags, ClipChainId, ClipChainInstance, ClipItem, ClipNodeCollector}; @@ -27,6 +27,7 @@ use image::{Repetition}; use intern; use picture::{PictureCompositeMode, PicturePrimitive, PictureUpdateState, TileCacheUpdateState}; use picture::{ClusterRange, PrimitiveList, SurfaceIndex, SurfaceInfo, RetainedTiles, RasterConfig}; +use prim_store::borders::{ImageBorderDataHandle, NormalBorderDataHandle}; use prim_store::gradient::{LinearGradientDataHandle, RadialGradientDataHandle}; use prim_store::image::{ImageDataHandle, ImageInstance, VisibleImageTile, YuvImageDataHandle}; use prim_store::text_run::{TextRunDataHandle, TextRunPrimitive}; @@ -47,6 +48,7 @@ use util::{ScaleOffset, MatrixHelpers, MaxRect, recycle_vec}; use util::{pack_as_float, project_rect, raster_rect_to_device_pixels}; use smallvec::SmallVec; +pub mod borders; pub mod gradient; pub mod image; pub mod text_run; @@ -471,14 +473,6 @@ pub enum PrimitiveKeyKind { }, /// Clear an existing rect, used for special effects on some platforms. Clear, - NormalBorder { - border: NormalBorderAu, - widths: LayoutSideOffsetsAu, - }, - ImageBorder { - request: ImageRequest, - nine_patch: NinePatchDescriptor, - }, Rectangle { color: ColorU, }, @@ -769,17 +763,6 @@ impl AsInstanceKind for PrimitiveKey { data_handle } } - PrimitiveKeyKind::NormalBorder { .. } => { - PrimitiveInstanceKind::NormalBorder { - data_handle, - cache_handles: storage::Range::empty(), - } - } - PrimitiveKeyKind::ImageBorder { .. } => { - PrimitiveInstanceKind::ImageBorder { - data_handle - } - } PrimitiveKeyKind::Rectangle { .. } => { PrimitiveInstanceKind::Rectangle { data_handle, @@ -796,15 +779,6 @@ impl AsInstanceKind for PrimitiveKey { } } -#[cfg_attr(feature = "capture", derive(Serialize))] -#[cfg_attr(feature = "replay", derive(Deserialize))] -pub struct NormalBorderTemplate { - pub brush_segments: Vec, - pub border_segments: Vec, - pub border: NormalBorder, - pub widths: LayoutSideOffsets, -} - /// The shared information for a given primitive. This is interned and retained /// both across frames and display lists, by comparing the matching PrimitiveKey. #[cfg_attr(feature = "capture", derive(Serialize))] @@ -814,13 +788,6 @@ pub enum PrimitiveTemplateKind { cache_key: Option, color: ColorF, }, - NormalBorder { - template: Box, - }, - ImageBorder { - request: ImageRequest, - brush_segments: Vec, - }, Rectangle { color: ColorF, }, @@ -834,10 +801,7 @@ pub enum PrimitiveTemplateKind { /// is invoked when a primitive key is created and the interner /// doesn't currently contain a primitive with this key. impl PrimitiveKeyKind { - fn into_template( - self, - size: LayoutSize, - ) -> PrimitiveTemplateKind { + fn into_template(self) -> PrimitiveTemplateKind { match self { PrimitiveKeyKind::Picture { .. } => { PrimitiveTemplateKind::Picture { @@ -847,45 +811,6 @@ impl PrimitiveKeyKind { PrimitiveKeyKind::Clear => { PrimitiveTemplateKind::Clear } - PrimitiveKeyKind::NormalBorder { widths, border, .. } => { - let mut border: NormalBorder = border.into(); - let widths = LayoutSideOffsets::from_au(widths); - - // FIXME(emilio): Is this the best place to do this? - border.normalize(&widths); - - let mut brush_segments = Vec::new(); - let mut border_segments = Vec::new(); - - create_border_segments( - size, - &border, - &widths, - &mut border_segments, - &mut brush_segments, - ); - - PrimitiveTemplateKind::NormalBorder { - template: Box::new(NormalBorderTemplate { - border, - widths, - border_segments, - brush_segments, - }) - } - } - PrimitiveKeyKind::ImageBorder { - request, - ref nine_patch, - .. - } => { - let brush_segments = nine_patch.create_segments(size); - - PrimitiveTemplateKind::ImageBorder { - request, - brush_segments, - } - } PrimitiveKeyKind::Rectangle { color, .. } => { PrimitiveTemplateKind::Rectangle { color: color.into(), @@ -957,7 +882,7 @@ impl ops::DerefMut for PrimitiveTemplate { impl From for PrimitiveTemplate { fn from(item: PrimitiveKey) -> Self { let common = PrimTemplateCommonData::with_key_common(item.common); - let kind = item.kind.into_template(common.prim_size); + let kind = item.kind.into_template(); PrimitiveTemplate { common, kind, } } @@ -967,8 +892,7 @@ impl PrimitiveTemplateKind { /// Write any GPU blocks for the primitive template to the given request object. fn write_prim_gpu_blocks( &self, - request: &mut GpuDataRequest, - prim_size: LayoutSize, + request: &mut GpuDataRequest ) { match *self { PrimitiveTemplateKind::Clear => { @@ -978,32 +902,6 @@ impl PrimitiveTemplateKind { PrimitiveTemplateKind::Rectangle { ref color, .. } => { request.push(color.premultiplied()); } - PrimitiveTemplateKind::NormalBorder { .. } => { - // Border primitives currently used for - // image borders, and run through the - // normal brush_image shader. - request.push(PremultipliedColorF::WHITE); - request.push(PremultipliedColorF::WHITE); - request.push([ - prim_size.width, - prim_size.height, - 0.0, - 0.0, - ]); - } - PrimitiveTemplateKind::ImageBorder { .. } => { - // Border primitives currently used for - // image borders, and run through the - // normal brush_image shader. - request.push(PremultipliedColorF::WHITE); - request.push(PremultipliedColorF::WHITE); - request.push([ - prim_size.width, - prim_size.height, - 0.0, - 0.0, - ]); - } PrimitiveTemplateKind::LineDecoration { ref cache_key, ref color } => { match cache_key { Some(cache_key) => { @@ -1024,36 +922,6 @@ impl PrimitiveTemplateKind { PrimitiveTemplateKind::Picture { .. } => {} } } - - fn write_segment_gpu_blocks( - &self, - request: &mut GpuDataRequest, - ) { - match *self { - PrimitiveTemplateKind::NormalBorder { ref template, .. } => { - for segment in &template.brush_segments { - // has to match VECS_PER_SEGMENT - request.write_segment( - segment.local_rect, - segment.extra_data, - ); - } - } - PrimitiveTemplateKind::ImageBorder { ref brush_segments, .. } => { - for segment in brush_segments { - // has to match VECS_PER_SEGMENT - request.write_segment( - segment.local_rect, - segment.extra_data, - ); - } - } - PrimitiveTemplateKind::Clear | - PrimitiveTemplateKind::LineDecoration { .. } | - PrimitiveTemplateKind::Rectangle { .. } | - PrimitiveTemplateKind::Picture { .. } => {} - } - } } impl PrimitiveTemplate { @@ -1066,11 +934,7 @@ impl PrimitiveTemplate { frame_state: &mut FrameBuildingState, ) { if let Some(mut request) = frame_state.gpu_cache.request(&mut self.common.gpu_cache_handle) { - self.kind.write_prim_gpu_blocks( - &mut request, - self.common.prim_size, - ); - self.kind.write_segment_gpu_blocks(&mut request); + self.kind.write_prim_gpu_blocks(&mut request); } self.opacity = match self.kind { @@ -1080,27 +944,6 @@ impl PrimitiveTemplate { PrimitiveTemplateKind::Rectangle { ref color, .. } => { PrimitiveOpacity::from_alpha(color.a) } - PrimitiveTemplateKind::NormalBorder { .. } => { - // Shouldn't matter, since the segment opacity is used instead - PrimitiveOpacity::translucent() - } - PrimitiveTemplateKind::ImageBorder { request, .. } => { - let image_properties = frame_state - .resource_cache - .get_image_properties(request.key); - - if let Some(image_properties) = image_properties { - frame_state.resource_cache.request_image( - request, - frame_state.gpu_cache, - ); - PrimitiveOpacity { - is_opaque: image_properties.descriptor.is_opaque, - } - } else { - PrimitiveOpacity::opaque() - } - } PrimitiveTemplateKind::LineDecoration { ref cache_key, ref color } => { match cache_key { Some(..) => PrimitiveOpacity::translucent(), @@ -1562,8 +1405,6 @@ impl IsVisible for PrimitiveKeyKind { // primitive types to use this. fn is_visible(&self) -> bool { match *self { - PrimitiveKeyKind::NormalBorder { .. } | - PrimitiveKeyKind::ImageBorder { .. } | PrimitiveKeyKind::Clear | PrimitiveKeyKind::Picture { .. } => { true @@ -1596,14 +1437,6 @@ impl CreateShadow for PrimitiveKeyKind { color: shadow.color.into(), } } - PrimitiveKeyKind::NormalBorder { ref border, widths, .. } => { - let border = border.with_color(shadow.color.into()); - PrimitiveKeyKind::NormalBorder { - border, - widths, - } - } - PrimitiveKeyKind::ImageBorder { .. } | PrimitiveKeyKind::Picture { .. } | PrimitiveKeyKind::Clear => { panic!("bug: this prim is not supported in shadow contexts"); @@ -1649,12 +1482,12 @@ pub enum PrimitiveInstanceKind { }, NormalBorder { /// Handle to the common interned data for this primitive. - data_handle: PrimitiveDataHandle, + data_handle: NormalBorderDataHandle, cache_handles: storage::Range, }, ImageBorder { /// Handle to the common interned data for this primitive. - data_handle: PrimitiveDataHandle, + data_handle: ImageBorderDataHandle, }, Rectangle { /// Handle to the common interned data for this primitive. @@ -1773,17 +1606,21 @@ impl PrimitiveInstance { PrimitiveInstanceKind::Picture { data_handle, .. } | PrimitiveInstanceKind::LineDecoration { data_handle, .. } | PrimitiveInstanceKind::Clear { data_handle, .. } | - PrimitiveInstanceKind::NormalBorder { data_handle, .. } | - PrimitiveInstanceKind::ImageBorder { data_handle, .. } | PrimitiveInstanceKind::Rectangle { data_handle, .. } => { data_handle.uid() } PrimitiveInstanceKind::Image { data_handle, .. } => { data_handle.uid() } + PrimitiveInstanceKind::ImageBorder { data_handle, .. } => { + data_handle.uid() + } PrimitiveInstanceKind::LinearGradient { data_handle, .. } => { data_handle.uid() } + PrimitiveInstanceKind::NormalBorder { data_handle, .. } => { + data_handle.uid() + } PrimitiveInstanceKind::RadialGradient { data_handle, .. } => { data_handle.uid() } @@ -2735,23 +2572,21 @@ impl PrimitiveStore { prim_data.update(frame_state); } PrimitiveInstanceKind::NormalBorder { data_handle, ref mut cache_handles, .. } => { - let prim_data = &mut resources.prim_data_store[*data_handle]; + let prim_data = &mut resources.normal_border_data_store[*data_handle]; + let common_data = &mut prim_data.common; + let border_data = &mut prim_data.kind; // Update the template this instane references, which may refresh the GPU // cache with any shared template data. - prim_data.update(frame_state); - - let template = match prim_data.kind { - PrimitiveTemplateKind::NormalBorder { ref template, .. } => template, - _ => unreachable!() - }; + border_data.update(common_data, frame_state); // TODO(gw): When drawing in screen raster mode, we should also incorporate a // scale factor from the world transform to get an appropriately // sized border task. let world_scale = LayoutToWorldScale::new(1.0); let mut scale = world_scale * frame_context.device_pixel_scale; - let max_scale = get_max_scale_for_border(&template.border.radius, &template.widths); + let max_scale = get_max_scale_for_border(&border_data.border.radius, + &border_data.widths); scale.0 = scale.0.min(max_scale.0); // For each edge and corner, request the render task by content key @@ -2760,7 +2595,7 @@ impl PrimitiveStore { let mut handles: SmallVec<[RenderTaskCacheEntryHandle; 8]> = SmallVec::new(); let surfaces = &mut frame_state.surfaces; - for segment in &template.border_segments { + for segment in &border_data.border_segments { // Update the cache key device size based on requested scale. let cache_size = to_cache_size(segment.local_task_size * scale); let cache_key = RenderTaskCacheKey { @@ -2780,7 +2615,7 @@ impl PrimitiveStore { build_border_instances( &segment.cache_key, cache_size, - &template.border, + &border_data.border, scale, ), ); @@ -2799,11 +2634,11 @@ impl PrimitiveStore { .extend(handles); } PrimitiveInstanceKind::ImageBorder { data_handle, .. } => { - let prim_data = &mut resources.prim_data_store[*data_handle]; + let prim_data = &mut resources.image_border_data_store[*data_handle]; // Update the template this instane references, which may refresh the GPU // cache with any shared template data. - prim_data.update(frame_state); + prim_data.kind.update(&mut prim_data.common, frame_state); } PrimitiveInstanceKind::Rectangle { data_handle, segment_instance_index, opacity_binding_index, .. } => { let prim_data = &mut resources.prim_data_store[*data_handle]; @@ -2821,7 +2656,6 @@ impl PrimitiveStore { write_segment(*segment_instance_index, frame_state, scratch, |request| { prim_data.kind.write_prim_gpu_blocks( request, - prim_data.prim_size, ); }); } @@ -3428,32 +3262,18 @@ impl PrimitiveInstance { &scratch.segments[segment_instance.segments_range] } PrimitiveInstanceKind::ImageBorder { data_handle, .. } => { - let prim_data = &resources.prim_data_store[data_handle]; + let border_data = &resources.image_border_data_store[data_handle].kind; // TODO: This is quite messy - once we remove legacy primitives we // can change this to be a tuple match on (instance, template) - match prim_data.kind { - PrimitiveTemplateKind::ImageBorder { ref brush_segments, .. } => { - brush_segments.as_slice() - } - _ => { - unreachable!(); - } - } + border_data.brush_segments.as_slice() } PrimitiveInstanceKind::NormalBorder { data_handle, .. } => { - let prim_data = &resources.prim_data_store[data_handle]; + let border_data = &resources.normal_border_data_store[data_handle].kind; // TODO: This is quite messy - once we remove legacy primitives we // can change this to be a tuple match on (instance, template) - match prim_data.kind { - PrimitiveTemplateKind::NormalBorder { ref template, .. } => { - template.brush_segments.as_slice() - } - _ => { - unreachable!(); - } - } + border_data.brush_segments.as_slice() } PrimitiveInstanceKind::LinearGradient { data_handle, .. } => { let prim_data = &resources.linear_grad_data_store[data_handle]; @@ -3738,8 +3558,8 @@ fn test_struct_sizes() { // be done with care, and after checking if talos performance regresses badly. assert_eq!(mem::size_of::(), 128, "PrimitiveInstance size changed"); assert_eq!(mem::size_of::(), 40, "PrimitiveInstanceKind size changed"); - assert_eq!(mem::size_of::(), 112, "PrimitiveTemplate size changed"); - assert_eq!(mem::size_of::(), 56, "PrimitiveTemplateKind size changed"); - assert_eq!(mem::size_of::(), 124, "PrimitiveKey size changed"); - assert_eq!(mem::size_of::(), 96, "PrimitiveKeyKind size changed"); + assert_eq!(mem::size_of::(), 96, "PrimitiveTemplate size changed"); + assert_eq!(mem::size_of::(), 36, "PrimitiveTemplateKind size changed"); + assert_eq!(mem::size_of::(), 116, "PrimitiveKey size changed"); + assert_eq!(mem::size_of::(), 88, "PrimitiveKeyKind size changed"); } diff --git a/webrender/src/profiler.rs b/webrender/src/profiler.rs index d4767aeb6a..b0c78a7bc0 100644 --- a/webrender/src/profiler.rs +++ b/webrender/src/profiler.rs @@ -405,7 +405,9 @@ pub struct IpcProfileCounters { pub struct InternProfileCounters { pub prims: ResourceProfileCounter, pub images: ResourceProfileCounter, + pub image_borders: ResourceProfileCounter, pub linear_gradients: ResourceProfileCounter, + pub normal_borders: ResourceProfileCounter, pub radial_gradients: ResourceProfileCounter, pub text_runs: ResourceProfileCounter, pub yuv_images: ResourceProfileCounter, @@ -453,7 +455,9 @@ impl BackendProfileCounters { intern: InternProfileCounters { prims: ResourceProfileCounter::new("Interned primitives"), images: ResourceProfileCounter::new("Interned images"), + image_borders: ResourceProfileCounter::new("Interned image borders"), linear_gradients: ResourceProfileCounter::new("Interned linear gradients"), + normal_borders: ResourceProfileCounter::new("Interner normal borders"), radial_gradients: ResourceProfileCounter::new("Interned radial gradients"), text_runs: ResourceProfileCounter::new("Interned text runs"), yuv_images: ResourceProfileCounter::new("Interned YUV images"), @@ -1108,7 +1112,9 @@ impl Profiler { &backend_profile.intern.clips, &backend_profile.intern.prims, &backend_profile.intern.images, + &backend_profile.intern.image_borders, &backend_profile.intern.linear_gradients, + &backend_profile.intern.normal_borders, &backend_profile.intern.radial_gradients, &backend_profile.intern.text_runs, &backend_profile.intern.yuv_images, diff --git a/webrender/src/render_backend.rs b/webrender/src/render_backend.rs index c01e2fbbda..9337baa349 100644 --- a/webrender/src/render_backend.rs +++ b/webrender/src/render_backend.rs @@ -33,6 +33,7 @@ use internal_types::{DebugOutput, FastHashMap, FastHashSet, RenderedDocument, Re use picture::RetainedTiles; use prim_store::{PrimitiveDataStore, PrimitiveScratchBuffer, PrimitiveInstance}; use prim_store::{PrimitiveInstanceKind, PrimTemplateCommonData}; +use prim_store::borders::{ImageBorderDataStore, NormalBorderDataStore}; use prim_store::gradient::{LinearGradientDataStore, RadialGradientDataStore}; use prim_store::image::{ImageDataStore, YuvImageDataStore}; use prim_store::text_run::TextRunDataStore; @@ -207,7 +208,9 @@ pub struct FrameResources { /// primitive interner in the scene builder, per document. pub prim_data_store: PrimitiveDataStore, pub image_data_store: ImageDataStore, + pub image_border_data_store: ImageBorderDataStore, pub linear_grad_data_store: LinearGradientDataStore, + pub normal_border_data_store: NormalBorderDataStore, pub radial_grad_data_store: RadialGradientDataStore, pub text_run_data_store: TextRunDataStore, pub yuv_image_data_store: YuvImageDataStore, @@ -221,8 +224,6 @@ impl FrameResources { match prim_inst.kind { PrimitiveInstanceKind::Picture { data_handle, .. } | PrimitiveInstanceKind::LineDecoration { data_handle, .. } | - PrimitiveInstanceKind::NormalBorder { data_handle, .. } | - PrimitiveInstanceKind::ImageBorder { data_handle, .. } | PrimitiveInstanceKind::Rectangle { data_handle, .. } | PrimitiveInstanceKind::Clear { data_handle, .. } => { let prim_data = &self.prim_data_store[data_handle]; @@ -232,10 +233,18 @@ impl FrameResources { let prim_data = &self.image_data_store[data_handle]; &prim_data.common } + PrimitiveInstanceKind::ImageBorder { data_handle, .. } => { + let prim_data = &self.image_border_data_store[data_handle]; + &prim_data.common + } PrimitiveInstanceKind::LinearGradient { data_handle, .. } => { let prim_data = &self.linear_grad_data_store[data_handle]; &prim_data.common } + PrimitiveInstanceKind::NormalBorder { data_handle, .. } => { + let prim_data = &self.normal_border_data_store[data_handle]; + &prim_data.common + } PrimitiveInstanceKind::RadialGradient { data_handle, .. } =>{ let prim_data = &self.radial_grad_data_store[data_handle]; &prim_data.common @@ -1234,10 +1243,18 @@ impl RenderBackend { updates.image_updates, &mut profile_counters.intern.images, ); + doc.resources.image_border_data_store.apply_updates( + updates.image_border_updates, + &mut profile_counters.intern.image_borders, + ); doc.resources.linear_grad_data_store.apply_updates( updates.linear_grad_updates, &mut profile_counters.intern.linear_gradients, ); + doc.resources.normal_border_data_store.apply_updates( + updates.normal_border_updates, + &mut profile_counters.intern.normal_borders, + ); doc.resources.radial_grad_data_store.apply_updates( updates.radial_grad_updates, &mut profile_counters.intern.radial_gradients, diff --git a/webrender/src/scene_builder.rs b/webrender/src/scene_builder.rs index 040a9943d8..530b4a3c3f 100644 --- a/webrender/src/scene_builder.rs +++ b/webrender/src/scene_builder.rs @@ -16,6 +16,10 @@ use intern::{Internable, Interner}; use internal_types::{FastHashMap, FastHashSet}; use prim_store::{PrimitiveDataInterner, PrimitiveDataUpdateList, PrimitiveKeyKind}; use prim_store::PrimitiveStoreStats; +use prim_store::borders::{ + ImageBorder, ImageBorderDataInterner, ImageBorderDataUpdateList, + NormalBorderPrim, NormalBorderDataInterner, NormalBorderDataUpdateList +}; use prim_store::gradient::{ LinearGradient, LinearGradientDataInterner, LinearGradientDataUpdateList, RadialGradient, RadialGradientDataInterner, RadialGradientDataUpdateList @@ -40,7 +44,9 @@ pub struct DocumentResourceUpdates { pub clip_updates: ClipDataUpdateList, pub prim_updates: PrimitiveDataUpdateList, pub image_updates: ImageDataUpdateList, + pub image_border_updates: ImageBorderDataUpdateList, pub linear_grad_updates: LinearGradientDataUpdateList, + pub normal_border_updates: NormalBorderDataUpdateList, pub radial_grad_updates: RadialGradientDataUpdateList, pub text_run_updates: TextRunDataUpdateList, pub yuv_image_updates: YuvImageDataUpdateList, @@ -192,7 +198,9 @@ pub struct DocumentResources { pub clip_interner: ClipDataInterner, pub prim_interner: PrimitiveDataInterner, pub image_interner: ImageDataInterner, + pub image_border_interner: ImageBorderDataInterner, pub linear_grad_interner: LinearGradientDataInterner, + pub normal_border_interner: NormalBorderDataInterner, pub radial_grad_interner: RadialGradientDataInterner, pub text_run_interner: TextRunDataInterner, pub yuv_image_interner: YuvImageDataInterner, @@ -220,10 +228,12 @@ macro_rules! impl_internet_mut { impl_internet_mut! { Image: image_interner, + ImageBorder: image_border_interner, LinearGradient: linear_grad_interner, + NormalBorderPrim: normal_border_interner, + PrimitiveKeyKind: prim_interner, RadialGradient: radial_grad_interner, TextRun: text_run_interner, - PrimitiveKeyKind: prim_interner, YuvImage: yuv_image_interner, } @@ -402,11 +412,21 @@ impl SceneBuilder { .image_interner .end_frame_and_get_pending_updates(); + let image_border_updates = item + .doc_resources + .image_border_interner + .end_frame_and_get_pending_updates(); + let linear_grad_updates = item .doc_resources .linear_grad_interner .end_frame_and_get_pending_updates(); + let normal_border_updates = item + .doc_resources + .normal_border_interner + .end_frame_and_get_pending_updates(); + let radial_grad_updates = item .doc_resources .radial_grad_interner @@ -427,7 +447,9 @@ impl SceneBuilder { clip_updates, prim_updates, image_updates, + image_border_updates, linear_grad_updates, + normal_border_updates, radial_grad_updates, text_run_updates, yuv_image_updates, @@ -544,11 +566,21 @@ impl SceneBuilder { .image_interner .end_frame_and_get_pending_updates(); + let image_border_updates = doc + .resources + .image_border_interner + .end_frame_and_get_pending_updates(); + let linear_grad_updates = doc .resources .linear_grad_interner .end_frame_and_get_pending_updates(); + let normal_border_updates = doc + .resources + .normal_border_interner + .end_frame_and_get_pending_updates(); + let radial_grad_updates = doc .resources .radial_grad_interner @@ -569,7 +601,9 @@ impl SceneBuilder { clip_updates, prim_updates, image_updates, + image_border_updates, linear_grad_updates, + normal_border_updates, radial_grad_updates, text_run_updates, yuv_image_updates,