diff --git a/webrender/src/batch.rs b/webrender/src/batch.rs
index 69742d65ab..8a29fcdd7e 100644
--- a/webrender/src/batch.rs
+++ b/webrender/src/batch.rs
@@ -1790,13 +1790,7 @@ impl AlphaBatchBuilder {
}
}
PrimitiveInstanceKind::LinearGradient { data_handle, ref visible_tiles_range, .. } => {
- let prim_data = &ctx.resources.prim_data_store[data_handle];
- let (ref stops_handle, brush_segments) = match prim_data.kind {
- PrimitiveTemplateKind::LinearGradient { stops_handle, ref brush_segments, .. } => {
- (stops_handle, brush_segments)
- }
- _ => unreachable!()
- };
+ let prim_data = &ctx.resources.linear_grad_data_store[data_handle];
let specified_blend_mode = BlendMode::PremultipliedAlpha;
let mut prim_header = PrimitiveHeader {
@@ -1822,7 +1816,7 @@ impl AlphaBatchBuilder {
BrushBatchKind::LinearGradient,
BatchTextures::no_texture(),
[
- stops_handle.as_int(gpu_cache),
+ prim_data.stops_handle.as_int(gpu_cache),
0,
0,
],
@@ -1837,10 +1831,10 @@ impl AlphaBatchBuilder {
batch_params.prim_user_data,
);
- let segments = if brush_segments.is_empty() {
+ let segments = if prim_data.brush_segments.is_empty() {
None
} else {
- Some(brush_segments.as_slice())
+ Some(prim_data.brush_segments.as_slice())
};
self.add_segmented_prim_to_batch(
@@ -1863,7 +1857,7 @@ impl AlphaBatchBuilder {
add_gradient_tiles(
visible_tiles,
- stops_handle,
+ &prim_data.stops_handle,
BrushBatchKind::LinearGradient,
specified_blend_mode,
bounding_rect,
@@ -1877,13 +1871,7 @@ impl AlphaBatchBuilder {
}
}
PrimitiveInstanceKind::RadialGradient { data_handle, ref visible_tiles_range, .. } => {
- let prim_data = &ctx.resources.prim_data_store[data_handle];
- let (stops_handle, brush_segments) = match prim_data.kind {
- PrimitiveTemplateKind::RadialGradient { ref stops_handle, ref brush_segments, .. } => {
- (stops_handle, brush_segments)
- }
- _ => unreachable!()
- };
+ let prim_data = &ctx.resources.radial_grad_data_store[data_handle];
let specified_blend_mode = BlendMode::PremultipliedAlpha;
let mut prim_header = PrimitiveHeader {
@@ -1909,7 +1897,7 @@ impl AlphaBatchBuilder {
BrushBatchKind::RadialGradient,
BatchTextures::no_texture(),
[
- stops_handle.as_int(gpu_cache),
+ prim_data.stops_handle.as_int(gpu_cache),
0,
0,
],
@@ -1924,10 +1912,10 @@ impl AlphaBatchBuilder {
batch_params.prim_user_data,
);
- let segments = if brush_segments.is_empty() {
+ let segments = if prim_data.brush_segments.is_empty() {
None
} else {
- Some(brush_segments.as_slice())
+ Some(prim_data.brush_segments.as_slice())
};
self.add_segmented_prim_to_batch(
@@ -1950,7 +1938,7 @@ impl AlphaBatchBuilder {
add_gradient_tiles(
visible_tiles,
- stops_handle,
+ &prim_data.stops_handle,
BrushBatchKind::RadialGradient,
specified_blend_mode,
bounding_rect,
diff --git a/webrender/src/display_list_flattener.rs b/webrender/src/display_list_flattener.rs
index d886551d94..2b361b432c 100644
--- a/webrender/src/display_list_flattener.rs
+++ b/webrender/src/display_list_flattener.rs
@@ -22,10 +22,11 @@ use image::simplify_repeated_primitive;
use intern::{Handle, Internable};
use internal_types::{FastHashMap, FastHashSet};
use picture::{Picture3DContext, PictureCompositeMode, PicturePrimitive, PrimitiveList};
-use prim_store::{PrimitiveInstance, PrimitiveKeyKind, RadialGradientParams};
-use prim_store::{PrimitiveKey, PrimitiveSceneData, PrimitiveInstanceKind, GradientStopKey, NinePatchDescriptor};
+use prim_store::{PrimitiveInstance, PrimitiveKeyKind};
+use prim_store::{PrimitiveKey, PrimitiveSceneData, PrimitiveInstanceKind, NinePatchDescriptor};
use prim_store::{PrimitiveDataHandle, PrimitiveStore, PrimitiveStoreStats, LineDecorationCacheKey};
use prim_store::{ScrollNodeAndClipChain, PictureIndex, register_prim_chase_id, get_line_decoration_sizes};
+use prim_store::gradient::{GradientStopKey, LinearGradient, RadialGradient, RadialGradientParams};
use prim_store::text_run::TextRun;
use render_backend::{DocumentView};
use resource_cache::{FontInstanceMap, ImageRequest};
@@ -781,7 +782,7 @@ impl<'a> DisplayListFlattener<'a> {
pipeline_id,
None,
) {
- self.add_primitive(
+ self.add_nonshadowable_primitive(
clip_and_scroll,
&prim_info,
Vec::new(),
@@ -803,7 +804,7 @@ impl<'a> DisplayListFlattener<'a> {
pipeline_id,
None,
);
- self.add_primitive(
+ self.add_nonshadowable_primitive(
clip_and_scroll,
&prim_info,
Vec::new(),
@@ -1113,6 +1114,33 @@ impl<'a> DisplayListFlattener<'a> {
/// Convenience interface that creates a primitive entry and adds it
/// to the draw list.
+ fn add_nonshadowable_primitive
(
+ &mut self,
+ clip_and_scroll: ScrollNodeAndClipChain,
+ info: &LayoutPrimitiveInfo,
+ clip_items: Vec<(LayoutPoint, ClipItemKey)>,
+ prim: P,
+ )
+ where
+ P: Internable + IsVisible,
+ P::Source: AsInstanceKind>,
+ DocumentResources: InternerMut,
+ {
+ if prim.is_visible() {
+ let clip_chain_id = self.build_clip_chain(
+ clip_items,
+ clip_and_scroll.spatial_node_index,
+ clip_and_scroll.clip_chain_id,
+ );
+ self.add_prim_to_draw_list(
+ info,
+ clip_chain_id,
+ clip_and_scroll,
+ prim
+ );
+ }
+ }
+
pub fn add_primitive
(
&mut self,
clip_and_scroll: ScrollNodeAndClipChain,
@@ -1129,19 +1157,7 @@ impl<'a> DisplayListFlattener<'a> {
// If a shadow context is not active, then add the primitive
// directly to the parent picture.
if self.pending_shadow_items.is_empty() {
- if prim.is_visible() {
- let clip_chain_id = self.build_clip_chain(
- clip_items,
- clip_and_scroll.spatial_node_index,
- clip_and_scroll.clip_chain_id,
- );
- self.add_prim_to_draw_list(
- info,
- clip_chain_id,
- clip_and_scroll,
- prim
- );
- }
+ self.add_nonshadowable_primitive(clip_and_scroll, info, clip_items, prim);
} else {
debug_assert!(clip_items.is_empty(), "No per-prim clips expected for shadowed primitives");
@@ -2075,19 +2091,26 @@ impl<'a> DisplayListFlattener<'a> {
widths: border_item.widths.into(),
};
- let prim = match border.source {
+ match border.source {
NinePatchBorderSource::Image(image_key) => {
- PrimitiveKeyKind::ImageBorder {
+ let prim = PrimitiveKeyKind::ImageBorder {
request: ImageRequest {
key: image_key,
rendering: ImageRendering::Auto,
tile: None,
},
nine_patch,
- }
+ };
+
+ self.add_primitive(
+ clip_and_scroll,
+ info,
+ Vec::new(),
+ prim,
+ );
}
NinePatchBorderSource::Gradient(gradient) => {
- match self.create_linear_gradient_prim(
+ let prim = match self.create_linear_gradient_prim(
&info,
gradient.start_point,
gradient.end_point,
@@ -2100,10 +2123,17 @@ impl<'a> DisplayListFlattener<'a> {
) {
Some(prim) => prim,
None => return,
- }
+ };
+
+ self.add_nonshadowable_primitive(
+ clip_and_scroll,
+ info,
+ Vec::new(),
+ prim,
+ );
}
NinePatchBorderSource::RadialGradient(gradient) => {
- self.create_radial_gradient_prim(
+ let prim = self.create_radial_gradient_prim(
&info,
gradient.center,
gradient.start_offset * gradient.radius.width,
@@ -2115,16 +2145,16 @@ impl<'a> DisplayListFlattener<'a> {
LayoutSize::zero(),
pipeline_id,
Some(Box::new(nine_patch)),
- )
+ );
+
+ self.add_nonshadowable_primitive(
+ clip_and_scroll,
+ info,
+ Vec::new(),
+ prim,
+ );
}
};
-
- self.add_primitive(
- clip_and_scroll,
- info,
- Vec::new(),
- prim,
- );
}
BorderDetails::Normal(ref border) => {
self.add_normal_border(
@@ -2148,7 +2178,7 @@ impl<'a> DisplayListFlattener<'a> {
mut tile_spacing: LayoutSize,
pipeline_id: PipelineId,
nine_patch: Option>,
- ) -> Option {
+ ) -> Option {
let mut prim_rect = info.rect;
simplify_repeated_primitive(&stretch_size, &mut tile_spacing, &mut prim_rect);
@@ -2190,7 +2220,7 @@ impl<'a> DisplayListFlattener<'a> {
(start_point, end_point)
};
- Some(PrimitiveKeyKind::LinearGradient {
+ Some(LinearGradient {
extend_mode,
start_point: sp.into(),
end_point: ep.into(),
@@ -2215,7 +2245,7 @@ impl<'a> DisplayListFlattener<'a> {
mut tile_spacing: LayoutSize,
pipeline_id: PipelineId,
nine_patch: Option>,
- ) -> PrimitiveKeyKind {
+ ) -> RadialGradient {
let mut prim_rect = info.rect;
simplify_repeated_primitive(&stretch_size, &mut tile_spacing, &mut prim_rect);
@@ -2237,7 +2267,7 @@ impl<'a> DisplayListFlattener<'a> {
}
}).collect();
- PrimitiveKeyKind::RadialGradient {
+ RadialGradient {
extend_mode,
center: center.into(),
params,
diff --git a/webrender/src/picture.rs b/webrender/src/picture.rs
index 8f66ff28ef..c62d1e7760 100644
--- a/webrender/src/picture.rs
+++ b/webrender/src/picture.rs
@@ -1301,11 +1301,15 @@ impl PrimitiveList {
PrimitiveInstanceKind::Rectangle { data_handle, .. } |
PrimitiveInstanceKind::YuvImage { data_handle, .. } |
PrimitiveInstanceKind::Image { data_handle, .. } |
- PrimitiveInstanceKind::LinearGradient { data_handle, .. } |
- PrimitiveInstanceKind::RadialGradient { data_handle, ..} |
- PrimitiveInstanceKind::Clear { data_handle, .. } => {
+ PrimitiveInstanceKind::Clear { data_handle, .. } => {
&resources.prim_interner[data_handle]
}
+ PrimitiveInstanceKind::LinearGradient { data_handle, .. } => {
+ &resources.linear_grad_interner[data_handle]
+ }
+ PrimitiveInstanceKind::RadialGradient { data_handle, ..} => {
+ &resources.radial_grad_interner[data_handle]
+ }
PrimitiveInstanceKind::TextRun { data_handle, .. } => {
&resources.text_run_interner[data_handle]
}
diff --git a/webrender/src/prim_store/gradient.rs b/webrender/src/prim_store/gradient.rs
new file mode 100644
index 0000000000..081f7d1f90
--- /dev/null
+++ b/webrender/src/prim_store/gradient.rs
@@ -0,0 +1,729 @@
+/* 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::{
+ ColorF, ColorU,ExtendMode, GradientStop, LayoutPoint, LayoutSize,
+ LayoutPrimitiveInfo, LayoutRect, PremultipliedColorF
+};
+use display_list_flattener::{AsInstanceKind, IsVisible};
+use frame_builder::FrameBuildingState;
+use gpu_cache::{GpuCacheHandle, GpuDataRequest};
+use intern::{DataStore, Handle, Internable, Interner, UpdateList};
+use prim_store::{BrushSegment, GradientTileRange};
+use prim_store::{PrimitiveInstanceKind, PrimitiveOpacity, PrimitiveSceneData};
+use prim_store::{PrimKeyCommonData, PrimTemplateCommonData, PrimitiveStore};
+use prim_store::{NinePatchDescriptor, PointKey, SizeKey};
+use std::{hash, ops::{Deref, DerefMut}, mem};
+use util::pack_as_float;
+
+/// A hashable gradient stop that can be used in primitive keys.
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Debug, Clone, PartialEq)]
+pub struct GradientStopKey {
+ pub offset: f32,
+ pub color: ColorU,
+}
+
+impl Eq for GradientStopKey {}
+
+impl hash::Hash for GradientStopKey {
+ fn hash(&self, state: &mut H) {
+ self.offset.to_bits().hash(state);
+ self.color.hash(state);
+ }
+}
+
+/// Identifying key for a line decoration.
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Debug, Clone, Eq, PartialEq, Hash)]
+pub struct LinearGradientKey {
+ pub common: PrimKeyCommonData,
+ pub extend_mode: ExtendMode,
+ pub start_point: PointKey,
+ pub end_point: PointKey,
+ pub stretch_size: SizeKey,
+ pub tile_spacing: SizeKey,
+ pub stops: Vec,
+ pub reverse_stops: bool,
+ pub nine_patch: Option>,
+}
+
+impl LinearGradientKey {
+ pub fn new(
+ is_backface_visible: bool,
+ prim_size: LayoutSize,
+ prim_relative_clip_rect: LayoutRect,
+ linear_grad: LinearGradient,
+ ) -> Self {
+ LinearGradientKey {
+ common: PrimKeyCommonData {
+ is_backface_visible,
+ prim_size: prim_size.into(),
+ prim_relative_clip_rect: prim_relative_clip_rect.into(),
+ },
+ extend_mode: linear_grad.extend_mode,
+ start_point: linear_grad.start_point,
+ end_point: linear_grad.end_point,
+ stretch_size: linear_grad.stretch_size,
+ tile_spacing: linear_grad.tile_spacing,
+ stops: linear_grad.stops,
+ reverse_stops: linear_grad.reverse_stops,
+ nine_patch: linear_grad.nine_patch,
+ }
+ }
+}
+
+impl AsInstanceKind for LinearGradientKey {
+ /// Construct a primitive instance that matches the type
+ /// of primitive key.
+ fn as_instance_kind(
+ &self,
+ data_handle: LinearGradientDataHandle,
+ _prim_store: &mut PrimitiveStore,
+ ) -> PrimitiveInstanceKind {
+ PrimitiveInstanceKind::LinearGradient {
+ data_handle,
+ visible_tiles_range: GradientTileRange::empty(),
+ }
+ }
+}
+
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct LinearGradientTemplate {
+ pub common: PrimTemplateCommonData,
+ pub extend_mode: ExtendMode,
+ pub start_point: LayoutPoint,
+ pub end_point: LayoutPoint,
+ pub stretch_size: LayoutSize,
+ pub tile_spacing: LayoutSize,
+ pub stops_opacity: PrimitiveOpacity,
+ pub stops: Vec,
+ pub brush_segments: Vec,
+ pub reverse_stops: bool,
+ pub stops_handle: GpuCacheHandle,
+}
+
+impl Deref for LinearGradientTemplate {
+ type Target = PrimTemplateCommonData;
+ fn deref(&self) -> &Self::Target {
+ &self.common
+ }
+}
+
+impl DerefMut for LinearGradientTemplate {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.common
+ }
+}
+
+impl From for LinearGradientTemplate {
+ fn from(item: LinearGradientKey) -> Self {
+ let common = PrimTemplateCommonData::with_key_common(item.common);
+ let mut min_alpha: f32 = 1.0;
+
+ // Convert the stops to more convenient representation
+ // for the current gradient builder.
+ let stops = item.stops.iter().map(|stop| {
+ let color: ColorF = stop.color.into();
+ min_alpha = min_alpha.min(color.a);
+
+ GradientStop {
+ offset: stop.offset,
+ color,
+ }
+ }).collect();
+
+ let mut brush_segments = Vec::new();
+
+ if let Some(ref nine_patch) = item.nine_patch {
+ brush_segments = nine_patch.create_segments(common.prim_size);
+ }
+
+ // Save opacity of the stops for use in
+ // selecting which pass this gradient
+ // should be drawn in.
+ let stops_opacity = PrimitiveOpacity::from_alpha(min_alpha);
+
+ LinearGradientTemplate {
+ common,
+ extend_mode: item.extend_mode,
+ start_point: item.start_point.into(),
+ end_point: item.end_point.into(),
+ stretch_size: item.stretch_size.into(),
+ tile_spacing: item.tile_spacing.into(),
+ stops_opacity,
+ stops,
+ brush_segments,
+ reverse_stops: item.reverse_stops,
+ stops_handle: GpuCacheHandle::new(),
+ }
+ }
+}
+
+impl LinearGradientTemplate {
+ /// 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,
+ frame_state: &mut FrameBuildingState,
+ ) {
+ if let Some(mut request) =
+ frame_state.gpu_cache.request(&mut self.common.gpu_cache_handle) {
+ // write_prim_gpu_blocks
+ request.push([
+ self.start_point.x,
+ self.start_point.y,
+ self.end_point.x,
+ self.end_point.y,
+ ]);
+ request.push([
+ pack_as_float(self.extend_mode as u32),
+ self.stretch_size.width,
+ self.stretch_size.height,
+ 0.0,
+ ]);
+
+ // write_segment_gpu_blocks
+ for segment in &self.brush_segments {
+ // has to match VECS_PER_SEGMENT
+ request.write_segment(
+ segment.local_rect,
+ segment.extra_data,
+ );
+ }
+ }
+
+ if let Some(mut request) = frame_state.gpu_cache.request(&mut self.stops_handle) {
+ GradientGpuBlockBuilder::build(
+ self.reverse_stops,
+ &mut request,
+ &self.stops,
+ );
+ }
+
+ self.opacity = {
+ // If the coverage of the gradient extends to or beyond
+ // the primitive rect, then the opacity can be determined
+ // by the colors of the stops. If we have tiling / spacing
+ // then we just assume the gradient is translucent for now.
+ // (In the future we could consider segmenting in some cases).
+ let stride = self.stretch_size + self.tile_spacing;
+ if stride.width >= self.common.prim_size.width &&
+ stride.height >= self.common.prim_size.height {
+ self.stops_opacity
+ } else {
+ PrimitiveOpacity::translucent()
+ }
+ }
+ }
+}
+
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
+pub struct LinearGradientDataMarker;
+
+pub type LinearGradientDataStore = DataStore;
+pub type LinearGradientDataHandle = Handle;
+pub type LinearGradientDataUpdateList = UpdateList;
+pub type LinearGradientDataInterner = Interner;
+
+pub struct LinearGradient {
+ pub extend_mode: ExtendMode,
+ pub start_point: PointKey,
+ pub end_point: PointKey,
+ pub stretch_size: SizeKey,
+ pub tile_spacing: SizeKey,
+ pub stops: Vec,
+ pub reverse_stops: bool,
+ pub nine_patch: Option>,
+}
+
+impl Internable for LinearGradient {
+ type Marker = LinearGradientDataMarker;
+ type Source = LinearGradientKey;
+ type StoreData = LinearGradientTemplate;
+ type InternData = PrimitiveSceneData;
+
+ /// Build a new key from self with `info`.
+ fn build_key(
+ self,
+ info: &LayoutPrimitiveInfo,
+ prim_relative_clip_rect: LayoutRect
+ ) -> LinearGradientKey {
+ LinearGradientKey::new(
+ info.is_backface_visible,
+ info.rect.size,
+ prim_relative_clip_rect,
+ self
+ )
+ }
+}
+
+impl IsVisible for LinearGradient {
+ fn is_visible(&self) -> bool {
+ true
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+/// Hashable radial gradient parameters, for use during prim interning.
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Debug, Clone, PartialEq)]
+pub struct RadialGradientParams {
+ pub start_radius: f32,
+ pub end_radius: f32,
+ pub ratio_xy: f32,
+}
+
+impl Eq for RadialGradientParams {}
+
+impl hash::Hash for RadialGradientParams {
+ fn hash(&self, state: &mut H) {
+ self.start_radius.to_bits().hash(state);
+ self.end_radius.to_bits().hash(state);
+ self.ratio_xy.to_bits().hash(state);
+ }
+}
+
+/// Identifying key for a line decoration.
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Debug, Clone, Eq, PartialEq, Hash)]
+pub struct RadialGradientKey {
+ pub common: PrimKeyCommonData,
+ pub extend_mode: ExtendMode,
+ pub center: PointKey,
+ pub params: RadialGradientParams,
+ pub stretch_size: SizeKey,
+ pub stops: Vec,
+ pub tile_spacing: SizeKey,
+ pub nine_patch: Option>,
+}
+
+impl RadialGradientKey {
+ pub fn new(
+ is_backface_visible: bool,
+ prim_size: LayoutSize,
+ prim_relative_clip_rect: LayoutRect,
+ radial_grad: RadialGradient,
+ ) -> Self {
+ RadialGradientKey {
+ common: PrimKeyCommonData {
+ is_backface_visible,
+ prim_size: prim_size.into(),
+ prim_relative_clip_rect: prim_relative_clip_rect.into(),
+ },
+ extend_mode: radial_grad.extend_mode,
+ center: radial_grad.center,
+ params: radial_grad.params,
+ stretch_size: radial_grad.stretch_size,
+ stops: radial_grad.stops,
+ tile_spacing: radial_grad.tile_spacing,
+ nine_patch: radial_grad.nine_patch,
+ }
+ }
+}
+
+impl AsInstanceKind for RadialGradientKey {
+ /// Construct a primitive instance that matches the type
+ /// of primitive key.
+ fn as_instance_kind(
+ &self,
+ data_handle: RadialGradientDataHandle,
+ _prim_store: &mut PrimitiveStore,
+ ) -> PrimitiveInstanceKind {
+ PrimitiveInstanceKind::RadialGradient {
+ data_handle,
+ visible_tiles_range: GradientTileRange::empty(),
+ }
+ }
+}
+
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct RadialGradientTemplate {
+ pub common: PrimTemplateCommonData,
+ pub extend_mode: ExtendMode,
+ pub center: LayoutPoint,
+ pub params: RadialGradientParams,
+ pub stretch_size: LayoutSize,
+ pub tile_spacing: LayoutSize,
+ pub brush_segments: Vec,
+ pub stops: Vec,
+ pub stops_handle: GpuCacheHandle,
+}
+
+impl Deref for RadialGradientTemplate {
+ type Target = PrimTemplateCommonData;
+ fn deref(&self) -> &Self::Target {
+ &self.common
+ }
+}
+
+impl DerefMut for RadialGradientTemplate {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.common
+ }
+}
+
+impl From for RadialGradientTemplate {
+ fn from(item: RadialGradientKey) -> Self {
+ let common = PrimTemplateCommonData::with_key_common(item.common);
+ let mut brush_segments = Vec::new();
+
+ if let Some(ref nine_patch) = item.nine_patch {
+ brush_segments = nine_patch.create_segments(common.prim_size);
+ }
+
+ let stops = item.stops.iter().map(|stop| {
+ GradientStop {
+ offset: stop.offset,
+ color: stop.color.into(),
+ }
+ }).collect();
+
+ RadialGradientTemplate {
+ common,
+ center: item.center.into(),
+ extend_mode: item.extend_mode,
+ params: item.params,
+ stretch_size: item.stretch_size.into(),
+ tile_spacing: item.tile_spacing.into(),
+ brush_segments: brush_segments,
+ stops,
+ stops_handle: GpuCacheHandle::new(),
+ }
+ }
+}
+
+impl RadialGradientTemplate {
+ /// 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,
+ frame_state: &mut FrameBuildingState,
+ ) {
+ if let Some(mut request) =
+ frame_state.gpu_cache.request(&mut self.common.gpu_cache_handle) {
+ // write_prim_gpu_blocks
+ request.push([
+ self.center.x,
+ self.center.y,
+ self.params.start_radius,
+ self.params.end_radius,
+ ]);
+ request.push([
+ self.params.ratio_xy,
+ pack_as_float(self.extend_mode as u32),
+ self.stretch_size.width,
+ self.stretch_size.height,
+ ]);
+
+ // write_segment_gpu_blocks
+ for segment in &self.brush_segments {
+ // has to match VECS_PER_SEGMENT
+ request.write_segment(
+ segment.local_rect,
+ segment.extra_data,
+ );
+ }
+ }
+
+ if let Some(mut request) = frame_state.gpu_cache.request(&mut self.stops_handle) {
+ GradientGpuBlockBuilder::build(
+ false,
+ &mut request,
+ &self.stops,
+ );
+ }
+
+ self.opacity = PrimitiveOpacity::translucent();
+ }
+}
+
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)]
+pub struct RadialGradientDataMarker;
+
+pub type RadialGradientDataStore = DataStore;
+pub type RadialGradientDataHandle = Handle;
+pub type RadialGradientDataUpdateList = UpdateList;
+pub type RadialGradientDataInterner = Interner;
+
+pub struct RadialGradient {
+ pub extend_mode: ExtendMode,
+ pub center: PointKey,
+ pub params: RadialGradientParams,
+ pub stretch_size: SizeKey,
+ pub stops: Vec,
+ pub tile_spacing: SizeKey,
+ pub nine_patch: Option>,
+}
+
+impl Internable for RadialGradient {
+ type Marker = RadialGradientDataMarker;
+ type Source = RadialGradientKey;
+ type StoreData = RadialGradientTemplate;
+ type InternData = PrimitiveSceneData;
+
+ /// Build a new key from self with `info`.
+ fn build_key(
+ self,
+ info: &LayoutPrimitiveInfo,
+ prim_relative_clip_rect: LayoutRect,
+ ) -> RadialGradientKey {
+ RadialGradientKey::new(
+ info.is_backface_visible,
+ info.rect.size,
+ prim_relative_clip_rect,
+ self,
+ )
+ }
+}
+
+impl IsVisible for RadialGradient {
+ fn is_visible(&self) -> bool {
+ true
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+// The gradient entry index for the first color stop
+pub const GRADIENT_DATA_FIRST_STOP: usize = 0;
+// The gradient entry index for the last color stop
+pub const GRADIENT_DATA_LAST_STOP: usize = GRADIENT_DATA_SIZE - 1;
+
+// The start of the gradient data table
+pub const GRADIENT_DATA_TABLE_BEGIN: usize = GRADIENT_DATA_FIRST_STOP + 1;
+// The exclusive bound of the gradient data table
+pub const GRADIENT_DATA_TABLE_END: usize = GRADIENT_DATA_LAST_STOP;
+// The number of entries in the gradient data table.
+pub const GRADIENT_DATA_TABLE_SIZE: usize = 128;
+
+// The number of entries in a gradient data: GRADIENT_DATA_TABLE_SIZE + first stop entry + last stop entry
+pub const GRADIENT_DATA_SIZE: usize = GRADIENT_DATA_TABLE_SIZE + 2;
+
+#[derive(Debug)]
+#[repr(C)]
+// An entry in a gradient data table representing a segment of the gradient color space.
+pub struct GradientDataEntry {
+ pub start_color: PremultipliedColorF,
+ pub end_color: PremultipliedColorF,
+}
+
+// TODO(gw): Tidy this up to be a free function / module?
+struct GradientGpuBlockBuilder {}
+
+impl GradientGpuBlockBuilder {
+ /// Generate a color ramp filling the indices in [start_idx, end_idx) and interpolating
+ /// from start_color to end_color.
+ fn fill_colors(
+ start_idx: usize,
+ end_idx: usize,
+ start_color: &PremultipliedColorF,
+ end_color: &PremultipliedColorF,
+ entries: &mut [GradientDataEntry; GRADIENT_DATA_SIZE],
+ ) {
+ // Calculate the color difference for individual steps in the ramp.
+ let inv_steps = 1.0 / (end_idx - start_idx) as f32;
+ let step_r = (end_color.r - start_color.r) * inv_steps;
+ let step_g = (end_color.g - start_color.g) * inv_steps;
+ let step_b = (end_color.b - start_color.b) * inv_steps;
+ let step_a = (end_color.a - start_color.a) * inv_steps;
+
+ let mut cur_color = *start_color;
+
+ // Walk the ramp writing start and end colors for each entry.
+ for index in start_idx .. end_idx {
+ let entry = &mut entries[index];
+ entry.start_color = cur_color;
+ cur_color.r += step_r;
+ cur_color.g += step_g;
+ cur_color.b += step_b;
+ cur_color.a += step_a;
+ entry.end_color = cur_color;
+ }
+ }
+
+ /// Compute an index into the gradient entry table based on a gradient stop offset. This
+ /// function maps offsets from [0, 1] to indices in [GRADIENT_DATA_TABLE_BEGIN, GRADIENT_DATA_TABLE_END].
+ #[inline]
+ fn get_index(offset: f32) -> usize {
+ (offset.max(0.0).min(1.0) * GRADIENT_DATA_TABLE_SIZE as f32 +
+ GRADIENT_DATA_TABLE_BEGIN as f32)
+ .round() as usize
+ }
+
+ // Build the gradient data from the supplied stops, reversing them if necessary.
+ fn build(
+ reverse_stops: bool,
+ request: &mut GpuDataRequest,
+ src_stops: &[GradientStop],
+ ) {
+ // Preconditions (should be ensured by DisplayListBuilder):
+ // * we have at least two stops
+ // * first stop has offset 0.0
+ // * last stop has offset 1.0
+ let mut src_stops = src_stops.into_iter();
+ let mut cur_color = match src_stops.next() {
+ Some(stop) => {
+ debug_assert_eq!(stop.offset, 0.0);
+ stop.color.premultiplied()
+ }
+ None => {
+ error!("Zero gradient stops found!");
+ PremultipliedColorF::BLACK
+ }
+ };
+
+ // A table of gradient entries, with two colors per entry, that specify the start and end color
+ // within the segment of the gradient space represented by that entry. To lookup a gradient result,
+ // first the entry index is calculated to determine which two colors to interpolate between, then
+ // the offset within that entry bucket is used to interpolate between the two colors in that entry.
+ // This layout preserves hard stops, as the end color for a given entry can differ from the start
+ // color for the following entry, despite them being adjacent. Colors are stored within in BGRA8
+ // format for texture upload. This table requires the gradient color stops to be normalized to the
+ // range [0, 1]. The first and last entries hold the first and last color stop colors respectively,
+ // while the entries in between hold the interpolated color stop values for the range [0, 1].
+ let mut entries: [GradientDataEntry; GRADIENT_DATA_SIZE] = unsafe { mem::uninitialized() };
+
+ if reverse_stops {
+ // Fill in the first entry (for reversed stops) with the first color stop
+ GradientGpuBlockBuilder::fill_colors(
+ GRADIENT_DATA_LAST_STOP,
+ GRADIENT_DATA_LAST_STOP + 1,
+ &cur_color,
+ &cur_color,
+ &mut entries,
+ );
+
+ // Fill in the center of the gradient table, generating a color ramp between each consecutive pair
+ // of gradient stops. Each iteration of a loop will fill the indices in [next_idx, cur_idx). The
+ // loop will then fill indices in [GRADIENT_DATA_TABLE_BEGIN, GRADIENT_DATA_TABLE_END).
+ let mut cur_idx = GRADIENT_DATA_TABLE_END;
+ for next in src_stops {
+ let next_color = next.color.premultiplied();
+ let next_idx = Self::get_index(1.0 - next.offset);
+
+ if next_idx < cur_idx {
+ GradientGpuBlockBuilder::fill_colors(
+ next_idx,
+ cur_idx,
+ &next_color,
+ &cur_color,
+ &mut entries,
+ );
+ cur_idx = next_idx;
+ }
+
+ cur_color = next_color;
+ }
+ if cur_idx != GRADIENT_DATA_TABLE_BEGIN {
+ error!("Gradient stops abruptly at {}, auto-completing to white", cur_idx);
+ GradientGpuBlockBuilder::fill_colors(
+ GRADIENT_DATA_TABLE_BEGIN,
+ cur_idx,
+ &PremultipliedColorF::WHITE,
+ &cur_color,
+ &mut entries,
+ );
+ }
+
+ // Fill in the last entry (for reversed stops) with the last color stop
+ GradientGpuBlockBuilder::fill_colors(
+ GRADIENT_DATA_FIRST_STOP,
+ GRADIENT_DATA_FIRST_STOP + 1,
+ &cur_color,
+ &cur_color,
+ &mut entries,
+ );
+ } else {
+ // Fill in the first entry with the first color stop
+ GradientGpuBlockBuilder::fill_colors(
+ GRADIENT_DATA_FIRST_STOP,
+ GRADIENT_DATA_FIRST_STOP + 1,
+ &cur_color,
+ &cur_color,
+ &mut entries,
+ );
+
+ // Fill in the center of the gradient table, generating a color ramp between each consecutive pair
+ // of gradient stops. Each iteration of a loop will fill the indices in [cur_idx, next_idx). The
+ // loop will then fill indices in [GRADIENT_DATA_TABLE_BEGIN, GRADIENT_DATA_TABLE_END).
+ let mut cur_idx = GRADIENT_DATA_TABLE_BEGIN;
+ for next in src_stops {
+ let next_color = next.color.premultiplied();
+ let next_idx = Self::get_index(next.offset);
+
+ if next_idx > cur_idx {
+ GradientGpuBlockBuilder::fill_colors(
+ cur_idx,
+ next_idx,
+ &cur_color,
+ &next_color,
+ &mut entries,
+ );
+ cur_idx = next_idx;
+ }
+
+ cur_color = next_color;
+ }
+ if cur_idx != GRADIENT_DATA_TABLE_END {
+ error!("Gradient stops abruptly at {}, auto-completing to white", cur_idx);
+ GradientGpuBlockBuilder::fill_colors(
+ cur_idx,
+ GRADIENT_DATA_TABLE_END,
+ &PremultipliedColorF::WHITE,
+ &cur_color,
+ &mut entries,
+ );
+ }
+
+ // Fill in the last entry with the last color stop
+ GradientGpuBlockBuilder::fill_colors(
+ GRADIENT_DATA_LAST_STOP,
+ GRADIENT_DATA_LAST_STOP + 1,
+ &cur_color,
+ &cur_color,
+ &mut entries,
+ );
+ }
+
+ for entry in entries.iter() {
+ request.push(entry.start_color);
+ request.push(entry.end_color);
+ }
+ }
+}
+
+#[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::(), 72, "LinearGradient size changed");
+ assert_eq!(mem::size_of::(), 168, "LinearGradientTemplate size changed");
+ assert_eq!(mem::size_of::(), 96, "LinearGradientKey size changed");
+
+ assert_eq!(mem::size_of::(), 72, "RadialGradient size changed");
+ assert_eq!(mem::size_of::(), 168, "RadialGradientTemplate size changed");
+ assert_eq!(mem::size_of::(), 104, "RadialGradientKey size changed");
+}
diff --git a/webrender/src/prim_store/mod.rs b/webrender/src/prim_store/mod.rs
index 1d4c352414..a9d0116a42 100644
--- a/webrender/src/prim_store/mod.rs
+++ b/webrender/src/prim_store/mod.rs
@@ -3,8 +3,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use api::{AlphaType, BorderRadius, ClipMode, ColorF, PictureRect, ColorU, LayoutVector2D};
-use api::{DeviceIntRect, DeviceIntSize, DevicePixelScale, ExtendMode, DeviceRect, LayoutSideOffsetsAu};
-use api::{FilterOp, GradientStop, ImageKey, ImageRendering, TileOffset, RepeatMode};
+use api::{DeviceIntRect, DeviceIntSize, DevicePixelScale, DeviceRect, LayoutSideOffsetsAu};
+use api::{FilterOp, ImageKey, ImageRendering, TileOffset, RepeatMode};
use api::{LayoutPoint, LayoutRect, LayoutSideOffsets, LayoutSize};
use api::{PremultipliedColorF, PropertyBinding, Shadow, YuvColorSpace, YuvFormat};
use api::{DeviceIntSideOffsets, WorldPixel, BoxShadowClipMode, NormalBorder, WorldRect, LayoutToWorldScale};
@@ -28,6 +28,7 @@ use intern;
use internal_types::FastHashMap;
use picture::{PictureCompositeMode, PicturePrimitive, PictureUpdateState};
use picture::{ClusterRange, PrimitiveList, SurfaceIndex, TileDescriptor};
+use prim_store::gradient::{LinearGradientDataHandle, RadialGradientDataHandle};
use prim_store::text_run::{TextRunDataHandle, TextRunPrimitive};
#[cfg(debug_assertions)]
use render_backend::{FrameId};
@@ -38,7 +39,7 @@ use renderer::{MAX_VERTEX_TEXTURE_WIDTH};
use resource_cache::{ImageProperties, ImageRequest, ResourceCache};
use scene::SceneProperties;
use segment::SegmentBuilder;
-use std::{cmp, fmt, hash, mem, ops, u32, usize};
+use std::{cmp, fmt, hash, ops, u32, usize};
#[cfg(debug_assertions)]
use std::sync::atomic::{AtomicUsize, Ordering};
use storage;
@@ -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 gradient;
pub mod text_run;
/// Counter for unique primitive IDs for debug tracing.
@@ -391,43 +393,6 @@ pub enum PrimitiveKeyKind {
image_rendering: ImageRendering,
alpha_type: AlphaType,
},
- LinearGradient {
- extend_mode: ExtendMode,
- start_point: PointKey,
- end_point: PointKey,
- stretch_size: SizeKey,
- tile_spacing: SizeKey,
- stops: Vec,
- reverse_stops: bool,
- nine_patch: Option>,
- },
- RadialGradient {
- extend_mode: ExtendMode,
- center: PointKey,
- params: RadialGradientParams,
- stretch_size: SizeKey,
- stops: Vec,
- tile_spacing: SizeKey,
- nine_patch: Option>,
- },
-}
-
-/// A hashable gradient stop that can be used in primitive keys.
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-#[derive(Debug, Clone, PartialEq)]
-pub struct GradientStopKey {
- pub offset: f32,
- pub color: ColorU,
-}
-
-impl Eq for GradientStopKey {}
-
-impl hash::Hash for GradientStopKey {
- fn hash(&self, state: &mut H) {
- self.offset.to_bits().hash(state);
- self.color.hash(state);
- }
}
#[cfg_attr(feature = "capture", derive(Serialize))]
@@ -568,26 +533,6 @@ impl SizeKey {
}
}
-/// Hashable radial gradient parameters, for use during prim interning.
-#[cfg_attr(feature = "capture", derive(Serialize))]
-#[cfg_attr(feature = "replay", derive(Deserialize))]
-#[derive(Debug, Clone, PartialEq)]
-pub struct RadialGradientParams {
- pub start_radius: f32,
- pub end_radius: f32,
- pub ratio_xy: f32,
-}
-
-impl Eq for RadialGradientParams {}
-
-impl hash::Hash for RadialGradientParams {
- fn hash(&self, state: &mut H) {
- self.start_radius.to_bits().hash(state);
- self.end_radius.to_bits().hash(state);
- self.ratio_xy.to_bits().hash(state);
- }
-}
-
/// A hashable point for using as a key during primitive interning.
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
@@ -727,18 +672,6 @@ impl AsInstanceKind for PrimitiveKey {
image_instance_index,
}
}
- PrimitiveKeyKind::LinearGradient { .. } => {
- PrimitiveInstanceKind::LinearGradient {
- data_handle,
- visible_tiles_range: GradientTileRange::empty(),
- }
- }
- PrimitiveKeyKind::RadialGradient { .. } => {
- PrimitiveInstanceKind::RadialGradient {
- data_handle,
- visible_tiles_range: GradientTileRange::empty(),
- }
- }
PrimitiveKeyKind::Unused => {
// Should never be hit as this method should not be
// called for old style primitives.
@@ -793,28 +726,6 @@ pub enum PrimitiveTemplateKind {
sub_rect: Option,
alpha_type: AlphaType,
},
- LinearGradient {
- extend_mode: ExtendMode,
- start_point: LayoutPoint,
- end_point: LayoutPoint,
- stretch_size: LayoutSize,
- tile_spacing: LayoutSize,
- stops_opacity: PrimitiveOpacity,
- stops: Vec,
- brush_segments: Vec,
- reverse_stops: bool,
- stops_handle: GpuCacheHandle,
- },
- RadialGradient {
- extend_mode: ExtendMode,
- center: LayoutPoint,
- params: RadialGradientParams,
- stretch_size: LayoutSize,
- tile_spacing: LayoutSize,
- brush_segments: Vec,
- stops: Vec,
- stops_handle: GpuCacheHandle,
- },
Clear,
Unused,
}
@@ -903,89 +814,6 @@ impl PrimitiveKeyKind {
color: color.into(),
}
}
- PrimitiveKeyKind::LinearGradient {
- extend_mode,
- tile_spacing,
- start_point,
- end_point,
- stretch_size,
- stops,
- reverse_stops,
- nine_patch,
- ..
- } => {
- let mut min_alpha: f32 = 1.0;
-
- // Convert the stops to more convenient representation
- // for the current gradient builder.
- let stops = stops.iter().map(|stop| {
- let color: ColorF = stop.color.into();
- min_alpha = min_alpha.min(color.a);
-
- GradientStop {
- offset: stop.offset,
- color,
- }
- }).collect();
-
- let mut brush_segments = Vec::new();
-
- if let Some(ref nine_patch) = nine_patch {
- brush_segments = nine_patch.create_segments(size);
- }
-
- // Save opacity of the stops for use in
- // selecting which pass this gradient
- // should be drawn in.
- let stops_opacity = PrimitiveOpacity::from_alpha(min_alpha);
-
- PrimitiveTemplateKind::LinearGradient {
- extend_mode,
- start_point: start_point.into(),
- end_point: end_point.into(),
- stretch_size: stretch_size.into(),
- tile_spacing: tile_spacing.into(),
- stops_opacity,
- stops,
- brush_segments,
- reverse_stops,
- stops_handle: GpuCacheHandle::new(),
- }
- }
- PrimitiveKeyKind::RadialGradient {
- extend_mode,
- params,
- stretch_size,
- tile_spacing,
- nine_patch,
- center,
- stops,
- ..
- } => {
- let mut brush_segments = Vec::new();
-
- if let Some(ref nine_patch) = nine_patch {
- brush_segments = nine_patch.create_segments(size);
- }
-
- let stops = stops.iter().map(|stop| {
- GradientStop {
- offset: stop.offset,
- color: stop.color.into(),
- }
- }).collect();
-
- PrimitiveTemplateKind::RadialGradient {
- center: center.into(),
- extend_mode,
- params,
- stretch_size: stretch_size.into(),
- tile_spacing: tile_spacing.into(),
- brush_segments,
- stops_handle: GpuCacheHandle::new(),
- stops,
- }
- }
}
}
}
@@ -1123,40 +951,6 @@ impl PrimitiveTemplateKind {
0.0,
]);
}
- PrimitiveTemplateKind::LinearGradient { stretch_size, start_point, end_point, extend_mode, .. } => {
- request.push([
- start_point.x,
- start_point.y,
- end_point.x,
- end_point.y,
- ]);
- request.push([
- pack_as_float(extend_mode as u32),
- stretch_size.width,
- stretch_size.height,
- 0.0,
- ]);
- }
- PrimitiveTemplateKind::RadialGradient {
- center,
- ref params,
- extend_mode,
- stretch_size,
- ..
- } => {
- request.push([
- center.x,
- center.y,
- params.start_radius,
- params.end_radius,
- ]);
- request.push([
- params.ratio_xy,
- pack_as_float(extend_mode as u32),
- stretch_size.width,
- stretch_size.height,
- ]);
- }
PrimitiveTemplateKind::Unused => {}
}
}
@@ -1184,16 +978,6 @@ impl PrimitiveTemplateKind {
);
}
}
- PrimitiveTemplateKind::LinearGradient { ref brush_segments, .. } |
- PrimitiveTemplateKind::RadialGradient { 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::Image { .. } |
@@ -1238,48 +1022,6 @@ impl PrimitiveTemplate {
// Shouldn't matter, since the segment opacity is used instead
PrimitiveOpacity::translucent()
}
- PrimitiveTemplateKind::LinearGradient {
- stretch_size,
- tile_spacing,
- stops_opacity,
- ref mut stops_handle,
- reverse_stops,
- ref stops,
- ..
- } => {
- if let Some(mut request) = frame_state.gpu_cache.request(stops_handle) {
- GradientGpuBlockBuilder::build(
- reverse_stops,
- &mut request,
- stops,
- );
- }
-
- // If the coverage of the gradient extends to or beyond
- // the primitive rect, then the opacity can be determined
- // by the colors of the stops. If we have tiling / spacing
- // then we just assume the gradient is translucent for now.
- // (In the future we could consider segmenting in some cases).
- let stride = stretch_size + tile_spacing;
- if stride.width >= self.common.prim_size.width &&
- stride.height >= self.common.prim_size.height {
- stops_opacity
- } else {
- PrimitiveOpacity::translucent()
- }
- }
- PrimitiveTemplateKind::RadialGradient { ref mut stops_handle, ref stops, .. } => {
- if let Some(mut request) = frame_state.gpu_cache.request(stops_handle) {
- GradientGpuBlockBuilder::build(
- false,
- &mut request,
- stops,
- );
- }
-
- //TODO: can we make it opaque in some cases?
- PrimitiveOpacity::translucent()
- }
PrimitiveTemplateKind::ImageBorder { request, .. } => {
let image_properties = frame_state
.resource_cache
@@ -1708,214 +1450,6 @@ pub enum ImageSource {
},
}
-// The gradient entry index for the first color stop
-pub const GRADIENT_DATA_FIRST_STOP: usize = 0;
-// The gradient entry index for the last color stop
-pub const GRADIENT_DATA_LAST_STOP: usize = GRADIENT_DATA_SIZE - 1;
-
-// The start of the gradient data table
-pub const GRADIENT_DATA_TABLE_BEGIN: usize = GRADIENT_DATA_FIRST_STOP + 1;
-// The exclusive bound of the gradient data table
-pub const GRADIENT_DATA_TABLE_END: usize = GRADIENT_DATA_LAST_STOP;
-// The number of entries in the gradient data table.
-pub const GRADIENT_DATA_TABLE_SIZE: usize = 128;
-
-// The number of entries in a gradient data: GRADIENT_DATA_TABLE_SIZE + first stop entry + last stop entry
-pub const GRADIENT_DATA_SIZE: usize = GRADIENT_DATA_TABLE_SIZE + 2;
-
-#[derive(Debug)]
-#[repr(C)]
-// An entry in a gradient data table representing a segment of the gradient color space.
-pub struct GradientDataEntry {
- pub start_color: PremultipliedColorF,
- pub end_color: PremultipliedColorF,
-}
-
-// TODO(gw): Tidy this up to be a free function / module?
-struct GradientGpuBlockBuilder {}
-
-impl GradientGpuBlockBuilder {
- /// Generate a color ramp filling the indices in [start_idx, end_idx) and interpolating
- /// from start_color to end_color.
- fn fill_colors(
- start_idx: usize,
- end_idx: usize,
- start_color: &PremultipliedColorF,
- end_color: &PremultipliedColorF,
- entries: &mut [GradientDataEntry; GRADIENT_DATA_SIZE],
- ) {
- // Calculate the color difference for individual steps in the ramp.
- let inv_steps = 1.0 / (end_idx - start_idx) as f32;
- let step_r = (end_color.r - start_color.r) * inv_steps;
- let step_g = (end_color.g - start_color.g) * inv_steps;
- let step_b = (end_color.b - start_color.b) * inv_steps;
- let step_a = (end_color.a - start_color.a) * inv_steps;
-
- let mut cur_color = *start_color;
-
- // Walk the ramp writing start and end colors for each entry.
- for index in start_idx .. end_idx {
- let entry = &mut entries[index];
- entry.start_color = cur_color;
- cur_color.r += step_r;
- cur_color.g += step_g;
- cur_color.b += step_b;
- cur_color.a += step_a;
- entry.end_color = cur_color;
- }
- }
-
- /// Compute an index into the gradient entry table based on a gradient stop offset. This
- /// function maps offsets from [0, 1] to indices in [GRADIENT_DATA_TABLE_BEGIN, GRADIENT_DATA_TABLE_END].
- #[inline]
- fn get_index(offset: f32) -> usize {
- (offset.max(0.0).min(1.0) * GRADIENT_DATA_TABLE_SIZE as f32 +
- GRADIENT_DATA_TABLE_BEGIN as f32)
- .round() as usize
- }
-
- // Build the gradient data from the supplied stops, reversing them if necessary.
- fn build(
- reverse_stops: bool,
- request: &mut GpuDataRequest,
- src_stops: &[GradientStop],
- ) {
- // Preconditions (should be ensured by DisplayListBuilder):
- // * we have at least two stops
- // * first stop has offset 0.0
- // * last stop has offset 1.0
- let mut src_stops = src_stops.into_iter();
- let mut cur_color = match src_stops.next() {
- Some(stop) => {
- debug_assert_eq!(stop.offset, 0.0);
- stop.color.premultiplied()
- }
- None => {
- error!("Zero gradient stops found!");
- PremultipliedColorF::BLACK
- }
- };
-
- // A table of gradient entries, with two colors per entry, that specify the start and end color
- // within the segment of the gradient space represented by that entry. To lookup a gradient result,
- // first the entry index is calculated to determine which two colors to interpolate between, then
- // the offset within that entry bucket is used to interpolate between the two colors in that entry.
- // This layout preserves hard stops, as the end color for a given entry can differ from the start
- // color for the following entry, despite them being adjacent. Colors are stored within in BGRA8
- // format for texture upload. This table requires the gradient color stops to be normalized to the
- // range [0, 1]. The first and last entries hold the first and last color stop colors respectively,
- // while the entries in between hold the interpolated color stop values for the range [0, 1].
- let mut entries: [GradientDataEntry; GRADIENT_DATA_SIZE] = unsafe { mem::uninitialized() };
-
- if reverse_stops {
- // Fill in the first entry (for reversed stops) with the first color stop
- GradientGpuBlockBuilder::fill_colors(
- GRADIENT_DATA_LAST_STOP,
- GRADIENT_DATA_LAST_STOP + 1,
- &cur_color,
- &cur_color,
- &mut entries,
- );
-
- // Fill in the center of the gradient table, generating a color ramp between each consecutive pair
- // of gradient stops. Each iteration of a loop will fill the indices in [next_idx, cur_idx). The
- // loop will then fill indices in [GRADIENT_DATA_TABLE_BEGIN, GRADIENT_DATA_TABLE_END).
- let mut cur_idx = GRADIENT_DATA_TABLE_END;
- for next in src_stops {
- let next_color = next.color.premultiplied();
- let next_idx = Self::get_index(1.0 - next.offset);
-
- if next_idx < cur_idx {
- GradientGpuBlockBuilder::fill_colors(
- next_idx,
- cur_idx,
- &next_color,
- &cur_color,
- &mut entries,
- );
- cur_idx = next_idx;
- }
-
- cur_color = next_color;
- }
- if cur_idx != GRADIENT_DATA_TABLE_BEGIN {
- error!("Gradient stops abruptly at {}, auto-completing to white", cur_idx);
- GradientGpuBlockBuilder::fill_colors(
- GRADIENT_DATA_TABLE_BEGIN,
- cur_idx,
- &PremultipliedColorF::WHITE,
- &cur_color,
- &mut entries,
- );
- }
-
- // Fill in the last entry (for reversed stops) with the last color stop
- GradientGpuBlockBuilder::fill_colors(
- GRADIENT_DATA_FIRST_STOP,
- GRADIENT_DATA_FIRST_STOP + 1,
- &cur_color,
- &cur_color,
- &mut entries,
- );
- } else {
- // Fill in the first entry with the first color stop
- GradientGpuBlockBuilder::fill_colors(
- GRADIENT_DATA_FIRST_STOP,
- GRADIENT_DATA_FIRST_STOP + 1,
- &cur_color,
- &cur_color,
- &mut entries,
- );
-
- // Fill in the center of the gradient table, generating a color ramp between each consecutive pair
- // of gradient stops. Each iteration of a loop will fill the indices in [cur_idx, next_idx). The
- // loop will then fill indices in [GRADIENT_DATA_TABLE_BEGIN, GRADIENT_DATA_TABLE_END).
- let mut cur_idx = GRADIENT_DATA_TABLE_BEGIN;
- for next in src_stops {
- let next_color = next.color.premultiplied();
- let next_idx = Self::get_index(next.offset);
-
- if next_idx > cur_idx {
- GradientGpuBlockBuilder::fill_colors(
- cur_idx,
- next_idx,
- &cur_color,
- &next_color,
- &mut entries,
- );
- cur_idx = next_idx;
- }
-
- cur_color = next_color;
- }
- if cur_idx != GRADIENT_DATA_TABLE_END {
- error!("Gradient stops abruptly at {}, auto-completing to white", cur_idx);
- GradientGpuBlockBuilder::fill_colors(
- cur_idx,
- GRADIENT_DATA_TABLE_END,
- &PremultipliedColorF::WHITE,
- &cur_color,
- &mut entries,
- );
- }
-
- // Fill in the last entry with the last color stop
- GradientGpuBlockBuilder::fill_colors(
- GRADIENT_DATA_LAST_STOP,
- GRADIENT_DATA_LAST_STOP + 1,
- &cur_color,
- &cur_color,
- &mut entries,
- );
- }
-
- for entry in entries.iter() {
- request.push(entry.start_color);
- request.push(entry.end_color);
- }
- }
-}
-
#[derive(Debug)]
#[repr(C)]
struct ClipRect {
@@ -2153,8 +1687,6 @@ impl IsVisible for PrimitiveKeyKind {
PrimitiveKeyKind::ImageBorder { .. } |
PrimitiveKeyKind::YuvImage { .. } |
PrimitiveKeyKind::Image { .. } |
- PrimitiveKeyKind::LinearGradient { .. } |
- PrimitiveKeyKind::RadialGradient { .. } |
PrimitiveKeyKind::Clear |
PrimitiveKeyKind::Unused => {
true
@@ -2207,8 +1739,6 @@ impl CreateShadow for PrimitiveKeyKind {
}
PrimitiveKeyKind::ImageBorder { .. } |
PrimitiveKeyKind::YuvImage { .. } |
- PrimitiveKeyKind::LinearGradient { .. } |
- PrimitiveKeyKind::RadialGradient { .. } |
PrimitiveKeyKind::Unused |
PrimitiveKeyKind::Clear => {
panic!("bug: this prim is not supported in shadow contexts");
@@ -2298,12 +1828,12 @@ pub enum PrimitiveInstanceKind {
},
LinearGradient {
/// Handle to the common interned data for this primitive.
- data_handle: PrimitiveDataHandle,
+ data_handle: LinearGradientDataHandle,
visible_tiles_range: GradientTileRange,
},
RadialGradient {
/// Handle to the common interned data for this primitive.
- data_handle: PrimitiveDataHandle,
+ data_handle: RadialGradientDataHandle,
visible_tiles_range: GradientTileRange,
},
/// Clear out a rect, used for special effects.
@@ -2401,8 +1931,12 @@ impl PrimitiveInstance {
PrimitiveInstanceKind::ImageBorder { data_handle, .. } |
PrimitiveInstanceKind::Rectangle { data_handle, .. } |
PrimitiveInstanceKind::YuvImage { data_handle, .. } |
- PrimitiveInstanceKind::Image { data_handle, .. } |
- PrimitiveInstanceKind::LinearGradient { data_handle, .. } |
+ PrimitiveInstanceKind::Image { data_handle, .. } => {
+ data_handle.uid()
+ }
+ PrimitiveInstanceKind::LinearGradient { data_handle, .. } => {
+ data_handle.uid()
+ }
PrimitiveInstanceKind::RadialGradient { data_handle, .. } => {
data_handle.uid()
}
@@ -3496,43 +3030,33 @@ impl PrimitiveStore {
write_segment(prim_data, image_instance.segment_instance_index, frame_state, scratch);
}
PrimitiveInstanceKind::LinearGradient { data_handle, ref mut visible_tiles_range, .. } => {
- let prim_data = &mut resources.prim_data_store[*data_handle];
+ let prim_data = &mut resources.linear_grad_data_store[*data_handle];
// Update the template this instane references, which may refresh the GPU
// cache with any shared template data.
- prim_data.update(
- pic_context.surface_index,
- frame_state,
- );
-
- let (extend_mode, stretch_size, start_point, end_point, tile_spacing) = match prim_data.kind {
- PrimitiveTemplateKind::LinearGradient { ref extend_mode, ref stretch_size, ref start_point, ref end_point, ref tile_spacing, .. } => {
- (extend_mode, stretch_size, start_point, end_point, tile_spacing)
- }
- _ => unreachable!()
- };
+ prim_data.update(frame_state);
- if *tile_spacing != LayoutSize::zero() {
+ if prim_data.tile_spacing != LayoutSize::zero() {
*visible_tiles_range = decompose_repeated_primitive(
&prim_instance.combined_local_clip_rect,
&prim_local_rect,
- &stretch_size,
- &tile_spacing,
+ &prim_data.stretch_size,
+ &prim_data.tile_spacing,
prim_context,
frame_state,
&pic_context.dirty_world_rect,
&mut scratch.gradient_tiles,
&mut |_, mut request| {
request.push([
- start_point.x,
- start_point.y,
- end_point.x,
- end_point.y,
+ prim_data.start_point.x,
+ prim_data.start_point.y,
+ prim_data.end_point.x,
+ prim_data.end_point.y,
]);
request.push([
- pack_as_float(*extend_mode as u32),
- stretch_size.width,
- stretch_size.height,
+ pack_as_float(prim_data.extend_mode as u32),
+ prim_data.stretch_size.width,
+ prim_data.stretch_size.height,
0.0,
]);
}
@@ -3547,44 +3071,34 @@ impl PrimitiveStore {
// for gradient primitives.
}
PrimitiveInstanceKind::RadialGradient { data_handle, ref mut visible_tiles_range, .. } => {
- let prim_data = &mut resources.prim_data_store[*data_handle];
+ let prim_data = &mut resources.radial_grad_data_store[*data_handle];
// Update the template this instane references, which may refresh the GPU
// cache with any shared template data.
- prim_data.update(
- pic_context.surface_index,
- frame_state,
- );
-
- let (params, extend_mode, stretch_size, tile_spacing, center) = match prim_data.kind {
- PrimitiveTemplateKind::RadialGradient { ref params, ref extend_mode, stretch_size, ref tile_spacing, center, .. } => {
- (params, extend_mode, stretch_size, tile_spacing, center)
- }
- _ => unreachable!()
- };
+ prim_data.update(frame_state);
- if *tile_spacing != LayoutSize::zero() {
+ if prim_data.tile_spacing != LayoutSize::zero() {
*visible_tiles_range = decompose_repeated_primitive(
&prim_instance.combined_local_clip_rect,
&prim_local_rect,
- &stretch_size,
- &tile_spacing,
+ &prim_data.stretch_size,
+ &prim_data.tile_spacing,
prim_context,
frame_state,
&pic_context.dirty_world_rect,
&mut scratch.gradient_tiles,
&mut |_, mut request| {
request.push([
- center.x,
- center.y,
- params.start_radius,
- params.end_radius,
+ prim_data.center.x,
+ prim_data.center.y,
+ prim_data.params.start_radius,
+ prim_data.params.end_radius,
]);
request.push([
- params.ratio_xy,
- pack_as_float(*extend_mode as u32),
- stretch_size.width,
- stretch_size.height,
+ prim_data.params.ratio_xy,
+ pack_as_float(prim_data.extend_mode as u32),
+ prim_data.stretch_size.width,
+ prim_data.stretch_size.height,
]);
},
);
@@ -4030,40 +3544,26 @@ impl PrimitiveInstance {
}
}
PrimitiveInstanceKind::LinearGradient { data_handle, .. } => {
- let prim_data = &resources.prim_data_store[data_handle];
+ let prim_data = &resources.linear_grad_data_store[data_handle];
// 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::LinearGradient { ref brush_segments, .. } => {
- if brush_segments.is_empty() {
- return false;
- }
-
- brush_segments.as_slice()
- }
- _ => {
- unreachable!();
- }
+ if prim_data.brush_segments.is_empty() {
+ return false;
}
+
+ prim_data.brush_segments.as_slice()
}
PrimitiveInstanceKind::RadialGradient { data_handle, .. } => {
- let prim_data = &resources.prim_data_store[data_handle];
+ let prim_data = &resources.radial_grad_data_store[data_handle];
// 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::RadialGradient { ref brush_segments, .. } => {
- if brush_segments.is_empty() {
- return false;
- }
-
- brush_segments.as_slice()
- }
- _ => {
- unreachable!();
- }
+ if prim_data.brush_segments.is_empty() {
+ return false;
}
+
+ prim_data.brush_segments.as_slice()
}
};
@@ -4317,6 +3817,7 @@ fn update_opacity_binding(
#[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
@@ -4325,8 +3826,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::(), 168, "PrimitiveTemplate size changed");
- assert_eq!(mem::size_of::(), 112, "PrimitiveTemplateKind size changed");
- assert_eq!(mem::size_of::(), 128, "PrimitiveKey size changed");
+ assert_eq!(mem::size_of::(), 144, "PrimitiveTemplate size changed");
+ assert_eq!(mem::size_of::(), 88, "PrimitiveTemplateKind size changed");
+ assert_eq!(mem::size_of::(), 124, "PrimitiveKey size changed");
assert_eq!(mem::size_of::(), 96, "PrimitiveKeyKind size changed");
}
diff --git a/webrender/src/profiler.rs b/webrender/src/profiler.rs
index 9148c2e10f..e8ad4e2cbd 100644
--- a/webrender/src/profiler.rs
+++ b/webrender/src/profiler.rs
@@ -404,6 +404,8 @@ pub struct IpcProfileCounters {
#[derive(Clone)]
pub struct InternProfileCounters {
pub prims: ResourceProfileCounter,
+ pub linear_gradients: ResourceProfileCounter,
+ pub radial_gradients: ResourceProfileCounter,
pub text_runs: ResourceProfileCounter,
pub clips: ResourceProfileCounter,
}
@@ -448,6 +450,8 @@ impl BackendProfileCounters {
},
intern: InternProfileCounters {
prims: ResourceProfileCounter::new("Interned primitives"),
+ linear_gradients: ResourceProfileCounter::new("Interned linear gradients"),
+ radial_gradients: ResourceProfileCounter::new("Interned radial gradients"),
text_runs: ResourceProfileCounter::new("Interned text runs"),
clips: ResourceProfileCounter::new("Interned clips"),
},
@@ -1099,6 +1103,8 @@ impl Profiler {
&[
&backend_profile.intern.clips,
&backend_profile.intern.prims,
+ &backend_profile.intern.linear_gradients,
+ &backend_profile.intern.radial_gradients,
&backend_profile.intern.text_runs,
],
debug_renderer,
diff --git a/webrender/src/render_backend.rs b/webrender/src/render_backend.rs
index 179b44181b..1e1a2645a9 100644
--- a/webrender/src/render_backend.rs
+++ b/webrender/src/render_backend.rs
@@ -32,6 +32,7 @@ use hit_test::{HitTest, HitTester};
use internal_types::{DebugOutput, FastHashMap, FastHashSet, RenderedDocument, ResultMsg};
use prim_store::{PrimitiveDataStore, PrimitiveScratchBuffer, PrimitiveInstance};
use prim_store::{PrimitiveInstanceKind, PrimTemplateCommonData};
+use prim_store::gradient::{LinearGradientDataStore, RadialGradientDataStore};
use prim_store::text_run::TextRunDataStore;
use profiler::{BackendProfileCounters, IpcProfileCounters, ResourceProfileCounters};
use record::ApiRecordingReceiver;
@@ -203,6 +204,8 @@ pub struct FrameResources {
/// Currently active / available primitives. Kept in sync with the
/// primitive interner in the scene builder, per document.
pub prim_data_store: PrimitiveDataStore,
+ pub linear_grad_data_store: LinearGradientDataStore,
+ pub radial_grad_data_store: RadialGradientDataStore,
pub text_run_data_store: TextRunDataStore,
}
@@ -219,12 +222,18 @@ impl FrameResources {
PrimitiveInstanceKind::Rectangle { data_handle, .. } |
PrimitiveInstanceKind::YuvImage { data_handle, .. } |
PrimitiveInstanceKind::Image { data_handle, .. } |
- PrimitiveInstanceKind::LinearGradient { data_handle, .. } |
- PrimitiveInstanceKind::RadialGradient { data_handle, .. } |
PrimitiveInstanceKind::Clear { data_handle, .. } => {
let prim_data = &self.prim_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::RadialGradient { data_handle, .. } =>{
+ let prim_data = &self.radial_grad_data_store[data_handle];
+ &prim_data.common
+ }
PrimitiveInstanceKind::TextRun { data_handle, .. } => {
let prim_data = &self.text_run_data_store[data_handle];
&prim_data.common
@@ -1211,6 +1220,14 @@ impl RenderBackend {
updates.prim_updates,
&mut profile_counters.intern.prims,
);
+ doc.resources.linear_grad_data_store.apply_updates(
+ updates.linear_grad_updates,
+ &mut profile_counters.intern.linear_gradients,
+ );
+ doc.resources.radial_grad_data_store.apply_updates(
+ updates.radial_grad_updates,
+ &mut profile_counters.intern.radial_gradients,
+ );
doc.resources.text_run_data_store.apply_updates(
updates.text_run_updates,
&mut profile_counters.intern.text_runs,
diff --git a/webrender/src/scene_builder.rs b/webrender/src/scene_builder.rs
index c75ffa88e5..8cb77b3a1e 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::gradient::{
+ LinearGradient, LinearGradientDataInterner, LinearGradientDataUpdateList,
+ RadialGradient, RadialGradientDataInterner, RadialGradientDataUpdateList
+};
use prim_store::text_run::{TextRunDataInterner, TextRun, TextRunDataUpdateList};
use resource_cache::FontInstanceMap;
use render_backend::DocumentView;
@@ -31,6 +35,8 @@ use std::time::Duration;
pub struct DocumentResourceUpdates {
pub clip_updates: ClipDataUpdateList,
pub prim_updates: PrimitiveDataUpdateList,
+ pub linear_grad_updates: LinearGradientDataUpdateList,
+ pub radial_grad_updates: RadialGradientDataUpdateList,
pub text_run_updates: TextRunDataUpdateList,
}
@@ -167,6 +173,8 @@ pub enum SceneSwapResult {
pub struct DocumentResources {
pub clip_interner: ClipDataInterner,
pub prim_interner: PrimitiveDataInterner,
+ pub linear_grad_interner: LinearGradientDataInterner,
+ pub radial_grad_interner: RadialGradientDataInterner,
pub text_run_interner: TextRunDataInterner,
}
@@ -182,6 +190,18 @@ impl InternerMut for DocumentResources {
}
}
+impl InternerMut for DocumentResources {
+ fn interner_mut(&mut self) -> &mut LinearGradientDataInterner {
+ &mut self.linear_grad_interner
+ }
+}
+
+impl InternerMut for DocumentResources {
+ fn interner_mut(&mut self) -> &mut RadialGradientDataInterner {
+ &mut self.radial_grad_interner
+ }
+}
+
impl InternerMut for DocumentResources {
fn interner_mut(&mut self) -> &mut TextRunDataInterner {
&mut self.text_run_interner
@@ -347,6 +367,8 @@ impl SceneBuilder {
&PrimitiveStoreStats::empty(),
);
+ // TODO(djg): Can we do better than this? Use a #[derive] to
+ // write the code for us, or unify updates into one enum/list?
let clip_updates = item
.doc_resources
.clip_interner
@@ -357,6 +379,16 @@ impl SceneBuilder {
.prim_interner
.end_frame_and_get_pending_updates();
+ let linear_grad_updates = item
+ .doc_resources
+ .linear_grad_interner
+ .end_frame_and_get_pending_updates();
+
+ let radial_grad_updates = item
+ .doc_resources
+ .radial_grad_interner
+ .end_frame_and_get_pending_updates();
+
let text_run_updates = item
.doc_resources
.text_run_interner
@@ -366,6 +398,8 @@ impl SceneBuilder {
DocumentResourceUpdates {
clip_updates,
prim_updates,
+ linear_grad_updates,
+ radial_grad_updates,
text_run_updates,
}
);
@@ -475,6 +509,16 @@ impl SceneBuilder {
.prim_interner
.end_frame_and_get_pending_updates();
+ let linear_grad_updates = doc
+ .resources
+ .linear_grad_interner
+ .end_frame_and_get_pending_updates();
+
+ let radial_grad_updates = doc
+ .resources
+ .radial_grad_interner
+ .end_frame_and_get_pending_updates();
+
let text_run_updates = doc
.resources
.text_run_interner
@@ -484,6 +528,8 @@ impl SceneBuilder {
DocumentResourceUpdates {
clip_updates,
prim_updates,
+ linear_grad_updates,
+ radial_grad_updates,
text_run_updates,
}
);