summaryrefslogtreecommitdiffstats
path: root/gfx/wr/webrender/src/prim_store/gradient
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/wr/webrender/src/prim_store/gradient')
-rw-r--r--gfx/wr/webrender/src/prim_store/gradient/conic.rs399
-rw-r--r--gfx/wr/webrender/src/prim_store/gradient/linear.rs750
-rw-r--r--gfx/wr/webrender/src/prim_store/gradient/mod.rs392
-rw-r--r--gfx/wr/webrender/src/prim_store/gradient/radial.rs531
4 files changed, 2072 insertions, 0 deletions
diff --git a/gfx/wr/webrender/src/prim_store/gradient/conic.rs b/gfx/wr/webrender/src/prim_store/gradient/conic.rs
new file mode 100644
index 0000000000..d9c3f5d350
--- /dev/null
+++ b/gfx/wr/webrender/src/prim_store/gradient/conic.rs
@@ -0,0 +1,399 @@
+/* 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/. */
+
+//! Conic gradients
+//!
+//! Specification: https://drafts.csswg.org/css-images-4/#conic-gradients
+//!
+//! Conic gradients are rendered via cached render tasks and composited with the image brush.
+
+use euclid::vec2;
+use api::{ExtendMode, GradientStop, PremultipliedColorF};
+use api::units::*;
+use crate::scene_building::IsVisible;
+use crate::frame_builder::FrameBuildingState;
+use crate::intern::{Internable, InternDebug, Handle as InternHandle};
+use crate::internal_types::LayoutPrimitiveInfo;
+use crate::prim_store::{BrushSegment, GradientTileRange};
+use crate::prim_store::{PrimitiveInstanceKind, PrimitiveOpacity, FloatKey};
+use crate::prim_store::{PrimKeyCommonData, PrimTemplateCommonData, PrimitiveStore};
+use crate::prim_store::{NinePatchDescriptor, PointKey, SizeKey, InternablePrimitive};
+use crate::render_task::{RenderTask, RenderTaskKind};
+use crate::render_task_graph::RenderTaskId;
+use crate::render_task_cache::{RenderTaskCacheKeyKind, RenderTaskCacheKey, RenderTaskParent};
+use crate::renderer::GpuBufferAddress;
+use crate::picture::{SurfaceIndex};
+
+use std::{hash, ops::{Deref, DerefMut}};
+use super::{stops_and_min_alpha, GradientStopKey, GradientGpuBlockBuilder};
+
+/// Hashable conic gradient parameters, for use during prim interning.
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Debug, Clone, MallocSizeOf, PartialEq)]
+pub struct ConicGradientParams {
+ pub angle: f32, // in radians
+ pub start_offset: f32,
+ pub end_offset: f32,
+}
+
+impl Eq for ConicGradientParams {}
+
+impl hash::Hash for ConicGradientParams {
+ fn hash<H: hash::Hasher>(&self, state: &mut H) {
+ self.angle.to_bits().hash(state);
+ self.start_offset.to_bits().hash(state);
+ self.end_offset.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, MallocSizeOf)]
+pub struct ConicGradientKey {
+ pub common: PrimKeyCommonData,
+ pub extend_mode: ExtendMode,
+ pub center: PointKey,
+ pub params: ConicGradientParams,
+ pub stretch_size: SizeKey,
+ pub stops: Vec<GradientStopKey>,
+ pub tile_spacing: SizeKey,
+ pub nine_patch: Option<Box<NinePatchDescriptor>>,
+}
+
+impl ConicGradientKey {
+ pub fn new(
+ info: &LayoutPrimitiveInfo,
+ conic_grad: ConicGradient,
+ ) -> Self {
+ ConicGradientKey {
+ common: info.into(),
+ extend_mode: conic_grad.extend_mode,
+ center: conic_grad.center,
+ params: conic_grad.params,
+ stretch_size: conic_grad.stretch_size,
+ stops: conic_grad.stops,
+ tile_spacing: conic_grad.tile_spacing,
+ nine_patch: conic_grad.nine_patch,
+ }
+ }
+}
+
+impl InternDebug for ConicGradientKey {}
+
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(MallocSizeOf)]
+pub struct ConicGradientTemplate {
+ pub common: PrimTemplateCommonData,
+ pub extend_mode: ExtendMode,
+ pub center: DevicePoint,
+ pub params: ConicGradientParams,
+ pub task_size: DeviceIntSize,
+ pub scale: DeviceVector2D,
+ pub stretch_size: LayoutSize,
+ pub tile_spacing: LayoutSize,
+ pub brush_segments: Vec<BrushSegment>,
+ pub stops_opacity: PrimitiveOpacity,
+ pub stops: Vec<GradientStop>,
+ pub src_color: Option<RenderTaskId>,
+}
+
+impl Deref for ConicGradientTemplate {
+ type Target = PrimTemplateCommonData;
+ fn deref(&self) -> &Self::Target {
+ &self.common
+ }
+}
+
+impl DerefMut for ConicGradientTemplate {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.common
+ }
+}
+
+impl From<ConicGradientKey> for ConicGradientTemplate {
+ fn from(item: ConicGradientKey) -> 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_rect.size());
+ }
+
+ let (stops, min_alpha) = stops_and_min_alpha(&item.stops);
+
+ // 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);
+
+ let mut stretch_size: LayoutSize = item.stretch_size.into();
+ stretch_size.width = stretch_size.width.min(common.prim_rect.width());
+ stretch_size.height = stretch_size.height.min(common.prim_rect.height());
+
+ fn approx_eq(a: f32, b: f32) -> bool { (a - b).abs() < 0.01 }
+
+ // Attempt to detect some of the common configurations with hard gradient stops. Allow
+ // those a higher maximum resolution to avoid the worst cases of aliasing artifacts with
+ // large conic gradients. A better solution would be to go back to rendering very large
+ // conic gradients via a brush shader instead of caching all of them (unclear whether
+ // it is important enough to warrant the better solution).
+ let mut has_hard_stops = false;
+ let mut prev_stop = None;
+ let offset_range = item.params.end_offset - item.params.start_offset;
+ for stop in &stops {
+ if offset_range <= 0.0 {
+ break;
+ }
+ if let Some(prev_offset) = prev_stop {
+ // Check whether two consecutive stops are very close (hard stops).
+ if stop.offset < prev_offset + 0.005 / offset_range {
+ // a is the angle of the stop normalized into 0-1 space and repeating in the 0-0.25 range.
+ // If close to 0.0 or 0.25 it means the stop is vertical or horizontal. For those, the lower
+ // resolution isn't a big issue.
+ let a = item.params.angle / (2.0 * std::f32::consts::PI)
+ + item.params.start_offset
+ + stop.offset / offset_range;
+ let a = a.rem_euclid(0.25);
+
+ if !approx_eq(a, 0.0) && !approx_eq(a, 0.25) {
+ has_hard_stops = true;
+ break;
+ }
+ }
+ }
+ prev_stop = Some(stop.offset);
+ }
+
+ let max_size = if has_hard_stops {
+ 2048.0
+ } else {
+ 1024.0
+ };
+
+ // Avoid rendering enormous gradients. Radial gradients are mostly made of soft transitions,
+ // so it is unlikely that rendering at a higher resolution that 1024 would produce noticeable
+ // differences, especially with 8 bits per channel.
+ let mut task_size: DeviceSize = stretch_size.cast_unit();
+ let mut scale = vec2(1.0, 1.0);
+ if task_size.width > max_size {
+ scale.x = task_size.width / max_size;
+ task_size.width = max_size;
+ }
+ if task_size.height > max_size {
+ scale.y = task_size.height / max_size;
+ task_size.height = max_size;
+ }
+
+ ConicGradientTemplate {
+ common,
+ center: DevicePoint::new(item.center.x, item.center.y),
+ extend_mode: item.extend_mode,
+ params: item.params,
+ stretch_size,
+ task_size: task_size.ceil().to_i32(),
+ scale,
+ tile_spacing: item.tile_spacing.into(),
+ brush_segments,
+ stops_opacity,
+ stops,
+ src_color: None,
+ }
+ }
+}
+
+impl ConicGradientTemplate {
+ /// 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,
+ parent_surface: SurfaceIndex,
+ ) {
+ if let Some(mut request) =
+ frame_state.gpu_cache.request(&mut self.common.gpu_cache_handle) {
+ // write_prim_gpu_blocks
+ request.push(PremultipliedColorF::WHITE);
+ request.push(PremultipliedColorF::WHITE);
+ request.push([
+ self.stretch_size.width,
+ self.stretch_size.height,
+ 0.0,
+ 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,
+ );
+ }
+ }
+
+ let cache_key = ConicGradientCacheKey {
+ size: self.task_size,
+ center: PointKey { x: self.center.x, y: self.center.y },
+ scale: PointKey { x: self.scale.x, y: self.scale.y },
+ start_offset: FloatKey(self.params.start_offset),
+ end_offset: FloatKey(self.params.end_offset),
+ angle: FloatKey(self.params.angle),
+ extend_mode: self.extend_mode,
+ stops: self.stops.iter().map(|stop| (*stop).into()).collect(),
+ };
+
+ let task_id = frame_state.resource_cache.request_render_task(
+ RenderTaskCacheKey {
+ size: self.task_size,
+ kind: RenderTaskCacheKeyKind::ConicGradient(cache_key),
+ },
+ frame_state.gpu_cache,
+ frame_state.frame_gpu_data,
+ frame_state.rg_builder,
+ None,
+ false,
+ RenderTaskParent::Surface(parent_surface),
+ &mut frame_state.surface_builder,
+ |rg_builder, gpu_buffer_builder| {
+ let stops = GradientGpuBlockBuilder::build(
+ false,
+ gpu_buffer_builder,
+ &self.stops,
+ );
+
+ rg_builder.add().init(RenderTask::new_dynamic(
+ self.task_size,
+ RenderTaskKind::ConicGradient(ConicGradientTask {
+ extend_mode: self.extend_mode,
+ scale: self.scale,
+ center: self.center,
+ params: self.params.clone(),
+ stops,
+ }),
+ ))
+ }
+ );
+
+ self.src_color = Some(task_id);
+
+ // Tile spacing is always handled by decomposing into separate draw calls so the
+ // primitive opacity is equivalent to stops opacity. This might change to being
+ // set to non-opaque in the presence of tile spacing if/when tile spacing is handled
+ // in the same way as with the image primitive.
+ self.opacity = self.stops_opacity;
+ }
+}
+
+pub type ConicGradientDataHandle = InternHandle<ConicGradient>;
+
+#[derive(Debug, MallocSizeOf)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct ConicGradient {
+ pub extend_mode: ExtendMode,
+ pub center: PointKey,
+ pub params: ConicGradientParams,
+ pub stretch_size: SizeKey,
+ pub stops: Vec<GradientStopKey>,
+ pub tile_spacing: SizeKey,
+ pub nine_patch: Option<Box<NinePatchDescriptor>>,
+}
+
+impl Internable for ConicGradient {
+ type Key = ConicGradientKey;
+ type StoreData = ConicGradientTemplate;
+ type InternData = ();
+ const PROFILE_COUNTER: usize = crate::profiler::INTERNED_CONIC_GRADIENTS;
+}
+
+impl InternablePrimitive for ConicGradient {
+ fn into_key(
+ self,
+ info: &LayoutPrimitiveInfo,
+ ) -> ConicGradientKey {
+ ConicGradientKey::new(info, self)
+ }
+
+ fn make_instance_kind(
+ _key: ConicGradientKey,
+ data_handle: ConicGradientDataHandle,
+ _prim_store: &mut PrimitiveStore,
+ _reference_frame_relative_offset: LayoutVector2D,
+ ) -> PrimitiveInstanceKind {
+ PrimitiveInstanceKind::ConicGradient {
+ data_handle,
+ visible_tiles_range: GradientTileRange::empty(),
+ }
+ }
+}
+
+impl IsVisible for ConicGradient {
+ fn is_visible(&self) -> bool {
+ true
+ }
+}
+
+#[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct ConicGradientTask {
+ pub extend_mode: ExtendMode,
+ pub center: DevicePoint,
+ pub scale: DeviceVector2D,
+ pub params: ConicGradientParams,
+ pub stops: GpuBufferAddress,
+}
+
+impl ConicGradientTask {
+ pub fn to_instance(&self, target_rect: &DeviceIntRect) -> ConicGradientInstance {
+ ConicGradientInstance {
+ task_rect: target_rect.to_f32(),
+ center: self.center,
+ scale: self.scale,
+ start_offset: self.params.start_offset,
+ end_offset: self.params.end_offset,
+ angle: self.params.angle,
+ extend_mode: self.extend_mode as i32,
+ gradient_stops_address: self.stops.as_int(),
+ }
+ }
+}
+
+/// The per-instance shader input of a radial gradient render task.
+///
+/// Must match the RADIAL_GRADIENT instance description in renderer/vertex.rs.
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[repr(C)]
+#[derive(Clone, Debug)]
+pub struct ConicGradientInstance {
+ pub task_rect: DeviceRect,
+ pub center: DevicePoint,
+ pub scale: DeviceVector2D,
+ pub start_offset: f32,
+ pub end_offset: f32,
+ pub angle: f32,
+ pub extend_mode: i32,
+ pub gradient_stops_address: i32,
+}
+
+#[derive(Clone, Debug, Hash, PartialEq, Eq)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct ConicGradientCacheKey {
+ pub size: DeviceIntSize,
+ pub center: PointKey,
+ pub scale: PointKey,
+ pub start_offset: FloatKey,
+ pub end_offset: FloatKey,
+ pub angle: FloatKey,
+ pub extend_mode: ExtendMode,
+ pub stops: Vec<GradientStopKey>,
+}
+
diff --git a/gfx/wr/webrender/src/prim_store/gradient/linear.rs b/gfx/wr/webrender/src/prim_store/gradient/linear.rs
new file mode 100644
index 0000000000..85da4b670a
--- /dev/null
+++ b/gfx/wr/webrender/src/prim_store/gradient/linear.rs
@@ -0,0 +1,750 @@
+/* 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/. */
+
+//! Linear gradients
+//!
+//! Specification: https://drafts.csswg.org/css-images-4/#linear-gradients
+//!
+//! Linear gradients are rendered via cached render tasks and composited with the image brush.
+
+use euclid::approxeq::ApproxEq;
+use euclid::{point2, vec2, size2};
+use api::{ExtendMode, GradientStop, LineOrientation, PremultipliedColorF, ColorF, ColorU};
+use api::units::*;
+use crate::scene_building::IsVisible;
+use crate::frame_builder::FrameBuildingState;
+use crate::intern::{Internable, InternDebug, Handle as InternHandle};
+use crate::internal_types::LayoutPrimitiveInfo;
+use crate::image_tiling::simplify_repeated_primitive;
+use crate::prim_store::{BrushSegment, GradientTileRange};
+use crate::prim_store::{PrimitiveInstanceKind, PrimitiveOpacity};
+use crate::prim_store::{PrimKeyCommonData, PrimTemplateCommonData, PrimitiveStore};
+use crate::prim_store::{NinePatchDescriptor, PointKey, SizeKey, InternablePrimitive};
+use crate::render_task::{RenderTask, RenderTaskKind};
+use crate::render_task_graph::RenderTaskId;
+use crate::render_task_cache::{RenderTaskCacheKeyKind, RenderTaskCacheKey, RenderTaskParent};
+use crate::renderer::GpuBufferAddress;
+use crate::segment::EdgeAaSegmentMask;
+use crate::picture::{SurfaceIndex};
+use crate::util::pack_as_float;
+use super::{stops_and_min_alpha, GradientStopKey, GradientGpuBlockBuilder, apply_gradient_local_clip};
+use std::ops::{Deref, DerefMut};
+use std::mem::swap;
+
+pub const MAX_CACHED_SIZE: f32 = 1024.0;
+
+/// Identifying key for a linear gradient.
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Debug, Clone, Eq, PartialEq, Hash, MallocSizeOf)]
+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<GradientStopKey>,
+ pub reverse_stops: bool,
+ pub cached: bool,
+ pub nine_patch: Option<Box<NinePatchDescriptor>>,
+ pub edge_aa_mask: EdgeAaSegmentMask,
+}
+
+impl LinearGradientKey {
+ pub fn new(
+ info: &LayoutPrimitiveInfo,
+ linear_grad: LinearGradient,
+ ) -> Self {
+ LinearGradientKey {
+ common: info.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,
+ cached: linear_grad.cached,
+ nine_patch: linear_grad.nine_patch,
+ edge_aa_mask: linear_grad.edge_aa_mask,
+ }
+ }
+}
+
+impl InternDebug for LinearGradientKey {}
+
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Debug, MallocSizeOf)]
+pub struct LinearGradientTemplate {
+ pub common: PrimTemplateCommonData,
+ pub extend_mode: ExtendMode,
+ pub start_point: DevicePoint,
+ pub end_point: DevicePoint,
+ pub task_size: DeviceIntSize,
+ pub scale: DeviceVector2D,
+ pub stretch_size: LayoutSize,
+ pub tile_spacing: LayoutSize,
+ pub stops_opacity: PrimitiveOpacity,
+ pub stops: Vec<GradientStop>,
+ pub brush_segments: Vec<BrushSegment>,
+ pub reverse_stops: bool,
+ pub is_fast_path: bool,
+ pub cached: bool,
+ pub src_color: Option<RenderTaskId>,
+}
+
+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
+ }
+}
+
+/// Perform a few optimizations to the gradient that are relevant to scene building.
+///
+/// Returns true if the gradient was decomposed into fast-path primitives, indicating
+/// that we shouldn't emit a regular gradient primitive after this returns.
+pub fn optimize_linear_gradient(
+ prim_rect: &mut LayoutRect,
+ tile_size: &mut LayoutSize,
+ mut tile_spacing: LayoutSize,
+ clip_rect: &LayoutRect,
+ start: &mut LayoutPoint,
+ end: &mut LayoutPoint,
+ extend_mode: ExtendMode,
+ stops: &mut [GradientStopKey],
+ // Callback called for each fast-path segment (rect, start end, stops).
+ callback: &mut dyn FnMut(&LayoutRect, LayoutPoint, LayoutPoint, &[GradientStopKey], EdgeAaSegmentMask)
+) -> bool {
+ // First sanitize the gradient parameters. See if we can remove repetitions,
+ // tighten the primitive bounds, etc.
+
+ simplify_repeated_primitive(&tile_size, &mut tile_spacing, prim_rect);
+
+ let vertical = start.x.approx_eq(&end.x);
+ let horizontal = start.y.approx_eq(&end.y);
+
+ let mut horizontally_tiled = prim_rect.width() > tile_size.width;
+ let mut vertically_tiled = prim_rect.height() > tile_size.height;
+
+ // Check whether the tiling is equivalent to stretching on either axis.
+ // Stretching the gradient is more efficient than repeating it.
+ if vertically_tiled && horizontal && tile_spacing.height == 0.0 {
+ tile_size.height = prim_rect.height();
+ vertically_tiled = false;
+ }
+
+ if horizontally_tiled && vertical && tile_spacing.width == 0.0 {
+ tile_size.width = prim_rect.width();
+ horizontally_tiled = false;
+ }
+
+ let offset = apply_gradient_local_clip(
+ prim_rect,
+ &tile_size,
+ &tile_spacing,
+ &clip_rect
+ );
+
+ // The size of gradient render tasks depends on the tile_size. No need to generate
+ // large stretch sizes that will be clipped to the bounds of the primitive.
+ tile_size.width = tile_size.width.min(prim_rect.width());
+ tile_size.height = tile_size.height.min(prim_rect.height());
+
+ *start += offset;
+ *end += offset;
+
+ // Next, in the case of axis-aligned gradients, see if it is worth
+ // decomposing the gradient into multiple gradients with only two
+ // gradient stops per segment to get a faster shader.
+
+ if extend_mode != ExtendMode::Clamp || stops.is_empty() {
+ return false;
+ }
+
+ if !vertical && !horizontal {
+ return false;
+ }
+
+ if vertical && horizontal {
+ return false;
+ }
+
+ if !tile_spacing.is_empty() || vertically_tiled || horizontally_tiled {
+ return false;
+ }
+
+ // If the gradient is small, no need to bother with decomposing it.
+ if (horizontal && tile_size.width < 256.0)
+ || (vertical && tile_size.height < 256.0) {
+
+ return false;
+ }
+
+ // Flip x and y if need be so that we only deal with the horizontal case.
+
+ // From now on don't return false. We are going modifying the caller's
+ // variables and not bother to restore them. If the control flow changes,
+ // Make sure to to restore &mut parameters to sensible values before
+ // returning false.
+
+ let adjust_rect = &mut |rect: &mut LayoutRect| {
+ if vertical {
+ swap(&mut rect.min.x, &mut rect.min.y);
+ swap(&mut rect.max.x, &mut rect.max.y);
+ }
+ };
+
+ let adjust_size = &mut |size: &mut LayoutSize| {
+ if vertical { swap(&mut size.width, &mut size.height); }
+ };
+
+ let adjust_point = &mut |p: &mut LayoutPoint| {
+ if vertical { swap(&mut p.x, &mut p.y); }
+ };
+
+ let clip_rect = match clip_rect.intersection(prim_rect) {
+ Some(clip) => clip,
+ None => {
+ return false;
+ }
+ };
+
+ adjust_rect(prim_rect);
+ adjust_point(start);
+ adjust_point(end);
+ adjust_size(tile_size);
+
+ let length = (end.x - start.x).abs();
+
+ // Decompose the gradient into simple segments. This lets us:
+ // - separate opaque from semi-transparent segments,
+ // - compress long segments into small render tasks,
+ // - make sure hard stops stay so even if the primitive is large.
+
+ let reverse_stops = start.x > end.x;
+
+ // Handle reverse stops so we can assume stops are arranged in increasing x.
+ if reverse_stops {
+ stops.reverse();
+ swap(start, end);
+ }
+
+ // Use fake gradient stop to emulate the potential constant color sections
+ // before and after the gradient endpoints.
+ let mut prev = *stops.first().unwrap();
+ let mut last = *stops.last().unwrap();
+
+ // Set the offsets of the fake stops to position them at the edges of the primitive.
+ prev.offset = -start.x / length;
+ last.offset = (tile_size.width - start.x) / length;
+ if reverse_stops {
+ prev.offset = 1.0 - prev.offset;
+ last.offset = 1.0 - last.offset;
+ }
+
+ let (side_edges, first_edge, last_edge) = if vertical {
+ (
+ EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::RIGHT,
+ EdgeAaSegmentMask::TOP,
+ EdgeAaSegmentMask::BOTTOM
+ )
+ } else {
+ (
+ EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::BOTTOM,
+ EdgeAaSegmentMask::LEFT,
+ EdgeAaSegmentMask::RIGHT
+ )
+ };
+
+ let mut is_first = true;
+ let last_offset = last.offset;
+ for stop in stops.iter().chain((&[last]).iter()) {
+ let prev_stop = prev;
+ prev = *stop;
+
+ if prev_stop.color.a == 0 && stop.color.a == 0 {
+ continue;
+ }
+
+
+ let prev_offset = if reverse_stops { 1.0 - prev_stop.offset } else { prev_stop.offset };
+ let offset = if reverse_stops { 1.0 - stop.offset } else { stop.offset };
+
+ // In layout space, relative to the primitive.
+ let segment_start = start.x + prev_offset * length;
+ let segment_end = start.x + offset * length;
+ let segment_length = segment_end - segment_start;
+
+ if segment_length <= 0.0 {
+ continue;
+ }
+
+ let mut segment_rect = *prim_rect;
+ segment_rect.min.x += segment_start;
+ segment_rect.max.x = segment_rect.min.x + segment_length;
+
+ let mut start = point2(0.0, 0.0);
+ let mut end = point2(segment_length, 0.0);
+
+ adjust_point(&mut start);
+ adjust_point(&mut end);
+ adjust_rect(&mut segment_rect);
+
+ let origin_before_clip = segment_rect.min;
+ segment_rect = match segment_rect.intersection(&clip_rect) {
+ Some(rect) => rect,
+ None => {
+ continue;
+ }
+ };
+ let offset = segment_rect.min - origin_before_clip;
+
+ // Account for the clipping since start and end are relative to the origin.
+ start -= offset;
+ end -= offset;
+
+ let mut edge_flags = side_edges;
+ if is_first {
+ edge_flags |= first_edge;
+ is_first = false;
+ }
+ if stop.offset == last_offset {
+ edge_flags |= last_edge;
+ }
+
+ callback(
+ &segment_rect,
+ start,
+ end,
+ &[
+ GradientStopKey { offset: 0.0, .. prev_stop },
+ GradientStopKey { offset: 1.0, .. *stop },
+ ],
+ edge_flags,
+ );
+ }
+
+ true
+}
+
+impl From<LinearGradientKey> for LinearGradientTemplate {
+ fn from(item: LinearGradientKey) -> Self {
+
+ let mut common = PrimTemplateCommonData::with_key_common(item.common);
+ common.edge_aa_mask = item.edge_aa_mask;
+
+ let (mut stops, min_alpha) = stops_and_min_alpha(&item.stops);
+
+ let mut brush_segments = Vec::new();
+
+ if let Some(ref nine_patch) = item.nine_patch {
+ brush_segments = nine_patch.create_segments(common.prim_rect.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);
+
+ let start_point = DevicePoint::new(item.start_point.x, item.start_point.y);
+ let end_point = DevicePoint::new(item.end_point.x, item.end_point.y);
+ let tile_spacing: LayoutSize = item.tile_spacing.into();
+ let stretch_size: LayoutSize = item.stretch_size.into();
+ let mut task_size: DeviceSize = stretch_size.cast_unit();
+
+ let horizontal = start_point.y.approx_eq(&end_point.y);
+ let vertical = start_point.x.approx_eq(&end_point.x);
+
+ if horizontal {
+ // Completely horizontal, we can stretch the gradient vertically.
+ task_size.height = 1.0;
+ }
+
+ if vertical {
+ // Completely vertical, we can stretch the gradient horizontally.
+ task_size.width = 1.0;
+ }
+
+ // See if we can render the gradient using a special fast-path shader.
+ // The fast path path only works with two gradient stops.
+ let mut is_fast_path = false;
+ if item.cached && stops.len() == 2 && brush_segments.is_empty() {
+ if horizontal
+ && stretch_size.width >= common.prim_rect.width()
+ && start_point.x.approx_eq(&0.0)
+ && end_point.x.approx_eq(&stretch_size.width) {
+ is_fast_path = true;
+ task_size.width = task_size.width.min(256.0);
+ }
+ if vertical
+ && stretch_size.height >= common.prim_rect.height()
+ && start_point.y.approx_eq(&0.0)
+ && end_point.y.approx_eq(&stretch_size.height) {
+ is_fast_path = true;
+ task_size.height = task_size.height.min(256.0);
+ }
+
+ if stops[0].color == stops[1].color {
+ is_fast_path = true;
+ task_size = size2(1.0, 1.0);
+ }
+
+ if is_fast_path && item.reverse_stops {
+ // The fast path doesn't use the gradient gpu blocks builder so handle
+ // reversed stops here.
+ stops.swap(0, 1);
+ }
+ }
+
+ // Avoid rendering enormous gradients. Linear gradients are mostly made of soft transitions,
+ // so it is unlikely that rendering at a higher resolution than 1024 would produce noticeable
+ // differences, especially with 8 bits per channel.
+
+ let mut scale = vec2(1.0, 1.0);
+
+ if task_size.width > MAX_CACHED_SIZE {
+ scale.x = task_size.width / MAX_CACHED_SIZE;
+ task_size.width = MAX_CACHED_SIZE;
+ }
+
+ if task_size.height > MAX_CACHED_SIZE {
+ scale.y = task_size.height / MAX_CACHED_SIZE;
+ task_size.height = MAX_CACHED_SIZE;
+ }
+
+ LinearGradientTemplate {
+ common,
+ extend_mode: item.extend_mode,
+ start_point,
+ end_point,
+ task_size: task_size.ceil().to_i32(),
+ scale,
+ stretch_size,
+ tile_spacing,
+ stops_opacity,
+ stops,
+ brush_segments,
+ reverse_stops: item.reverse_stops,
+ is_fast_path,
+ cached: item.cached,
+ src_color: None,
+ }
+ }
+}
+
+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,
+ parent_surface: SurfaceIndex,
+ ) {
+ if let Some(mut request) = frame_state.gpu_cache.request(
+ &mut self.common.gpu_cache_handle
+ ) {
+
+ // Write_prim_gpu_blocks
+ if self.cached {
+ // We are using the image brush.
+ request.push(PremultipliedColorF::WHITE);
+ request.push(PremultipliedColorF::WHITE);
+ request.push([
+ self.stretch_size.width,
+ self.stretch_size.height,
+ 0.0,
+ 0.0,
+ ]);
+ } else {
+ // We are using the gradient brush.
+ 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,
+ );
+ }
+ }
+
+ // Tile spacing is always handled by decomposing into separate draw calls so the
+ // primitive opacity is equivalent to stops opacity. This might change to being
+ // set to non-opaque in the presence of tile spacing if/when tile spacing is handled
+ // in the same way as with the image primitive.
+ self.opacity = self.stops_opacity;
+
+ if !self.cached {
+ return;
+ }
+
+ let task_id = if self.is_fast_path {
+ let orientation = if self.task_size.width > self.task_size.height {
+ LineOrientation::Horizontal
+ } else {
+ LineOrientation::Vertical
+ };
+
+ let gradient = FastLinearGradientTask {
+ color0: self.stops[0].color.into(),
+ color1: self.stops[1].color.into(),
+ orientation,
+ };
+
+ frame_state.resource_cache.request_render_task(
+ RenderTaskCacheKey {
+ size: self.task_size,
+ kind: RenderTaskCacheKeyKind::FastLinearGradient(gradient),
+ },
+ frame_state.gpu_cache,
+ frame_state.frame_gpu_data,
+ frame_state.rg_builder,
+ None,
+ false,
+ RenderTaskParent::Surface(parent_surface),
+ &mut frame_state.surface_builder,
+ |rg_builder, _| {
+ rg_builder.add().init(RenderTask::new_dynamic(
+ self.task_size,
+ RenderTaskKind::FastLinearGradient(gradient),
+ ))
+ }
+ )
+ } else {
+ let cache_key = LinearGradientCacheKey {
+ size: self.task_size,
+ start: PointKey { x: self.start_point.x, y: self.start_point.y },
+ end: PointKey { x: self.end_point.x, y: self.end_point.y },
+ scale: PointKey { x: self.scale.x, y: self.scale.y },
+ extend_mode: self.extend_mode,
+ stops: self.stops.iter().map(|stop| (*stop).into()).collect(),
+ reversed_stops: self.reverse_stops,
+ };
+
+ frame_state.resource_cache.request_render_task(
+ RenderTaskCacheKey {
+ size: self.task_size,
+ kind: RenderTaskCacheKeyKind::LinearGradient(cache_key),
+ },
+ frame_state.gpu_cache,
+ frame_state.frame_gpu_data,
+ frame_state.rg_builder,
+ None,
+ false,
+ RenderTaskParent::Surface(parent_surface),
+ &mut frame_state.surface_builder,
+ |rg_builder, gpu_buffer_builder| {
+ let stops = Some(GradientGpuBlockBuilder::build(
+ self.reverse_stops,
+ gpu_buffer_builder,
+ &self.stops,
+ ));
+
+ rg_builder.add().init(RenderTask::new_dynamic(
+ self.task_size,
+ RenderTaskKind::LinearGradient(LinearGradientTask {
+ start: self.start_point,
+ end: self.end_point,
+ scale: self.scale,
+ extend_mode: self.extend_mode,
+ stops: stops.unwrap(),
+ }),
+ ))
+ }
+ )
+ };
+
+ self.src_color = Some(task_id);
+ }
+}
+
+pub type LinearGradientDataHandle = InternHandle<LinearGradient>;
+
+#[derive(Debug, MallocSizeOf)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+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<GradientStopKey>,
+ pub reverse_stops: bool,
+ pub nine_patch: Option<Box<NinePatchDescriptor>>,
+ pub cached: bool,
+ pub edge_aa_mask: EdgeAaSegmentMask,
+}
+
+impl Internable for LinearGradient {
+ type Key = LinearGradientKey;
+ type StoreData = LinearGradientTemplate;
+ type InternData = ();
+ const PROFILE_COUNTER: usize = crate::profiler::INTERNED_LINEAR_GRADIENTS;
+}
+
+impl InternablePrimitive for LinearGradient {
+ fn into_key(
+ self,
+ info: &LayoutPrimitiveInfo,
+ ) -> LinearGradientKey {
+ LinearGradientKey::new(info, self)
+ }
+
+ fn make_instance_kind(
+ key: LinearGradientKey,
+ data_handle: LinearGradientDataHandle,
+ _prim_store: &mut PrimitiveStore,
+ _reference_frame_relative_offset: LayoutVector2D,
+ ) -> PrimitiveInstanceKind {
+ if key.cached {
+ PrimitiveInstanceKind::CachedLinearGradient {
+ data_handle,
+ visible_tiles_range: GradientTileRange::empty(),
+ }
+ } else {
+ PrimitiveInstanceKind::LinearGradient {
+ data_handle,
+ visible_tiles_range: GradientTileRange::empty(),
+ }
+ }
+ }
+}
+
+impl IsVisible for LinearGradient {
+ fn is_visible(&self) -> bool {
+ true
+ }
+}
+
+#[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+pub struct LinearGradientPrimitive {
+ pub cache_segments: Vec<CachedGradientSegment>,
+ pub visible_tiles_range: GradientTileRange,
+}
+
+#[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+pub struct CachedGradientSegment {
+ pub render_task: RenderTaskId,
+ pub local_rect: LayoutRect,
+}
+
+
+#[derive(Copy, Clone, Debug, Hash, MallocSizeOf, PartialEq, Eq)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct FastLinearGradientTask {
+ pub color0: ColorU,
+ pub color1: ColorU,
+ pub orientation: LineOrientation,
+}
+
+impl FastLinearGradientTask {
+ pub fn to_instance(&self, target_rect: &DeviceIntRect) -> FastLinearGradientInstance {
+ FastLinearGradientInstance {
+ task_rect: target_rect.to_f32(),
+ color0: ColorF::from(self.color0).premultiplied(),
+ color1: ColorF::from(self.color1).premultiplied(),
+ axis_select: match self.orientation {
+ LineOrientation::Horizontal => 0.0,
+ LineOrientation::Vertical => 1.0,
+ },
+ }
+ }
+}
+
+pub type FastLinearGradientCacheKey = FastLinearGradientTask;
+
+/// The per-instance shader input of a fast-path linear gradient render task.
+///
+/// Must match the FAST_LINEAR_GRADIENT instance description in renderer/vertex.rs.
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[repr(C)]
+#[derive(Clone, Debug)]
+pub struct FastLinearGradientInstance {
+ pub task_rect: DeviceRect,
+ pub color0: PremultipliedColorF,
+ pub color1: PremultipliedColorF,
+ pub axis_select: f32,
+}
+
+#[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct LinearGradientTask {
+ pub start: DevicePoint,
+ pub end: DevicePoint,
+ pub scale: DeviceVector2D,
+ pub extend_mode: ExtendMode,
+ pub stops: GpuBufferAddress,
+}
+
+impl LinearGradientTask {
+ pub fn to_instance(&self, target_rect: &DeviceIntRect) -> LinearGradientInstance {
+ LinearGradientInstance {
+ task_rect: target_rect.to_f32(),
+ start: self.start,
+ end: self.end,
+ scale: self.scale,
+ extend_mode: self.extend_mode as i32,
+ gradient_stops_address: self.stops.as_int(),
+ }
+ }
+}
+
+/// The per-instance shader input of a linear gradient render task.
+///
+/// Must match the LINEAR_GRADIENT instance description in renderer/vertex.rs.
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[repr(C)]
+#[derive(Clone, Debug)]
+pub struct LinearGradientInstance {
+ pub task_rect: DeviceRect,
+ pub start: DevicePoint,
+ pub end: DevicePoint,
+ pub scale: DeviceVector2D,
+ pub extend_mode: i32,
+ pub gradient_stops_address: i32,
+}
+
+#[derive(Clone, Debug, Hash, PartialEq, Eq)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct LinearGradientCacheKey {
+ pub size: DeviceIntSize,
+ pub start: PointKey,
+ pub end: PointKey,
+ pub scale: PointKey,
+ pub extend_mode: ExtendMode,
+ pub stops: Vec<GradientStopKey>,
+ pub reversed_stops: bool,
+}
diff --git a/gfx/wr/webrender/src/prim_store/gradient/mod.rs b/gfx/wr/webrender/src/prim_store/gradient/mod.rs
new file mode 100644
index 0000000000..d0b922c579
--- /dev/null
+++ b/gfx/wr/webrender/src/prim_store/gradient/mod.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::{ColorF, ColorU, GradientStop, PremultipliedColorF};
+use api::units::{LayoutRect, LayoutSize, LayoutVector2D};
+use crate::renderer::{GpuBufferAddress, GpuBufferBuilder};
+use std::hash;
+
+mod linear;
+mod radial;
+mod conic;
+
+pub use linear::MAX_CACHED_SIZE as LINEAR_MAX_CACHED_SIZE;
+
+pub use linear::*;
+pub use radial::*;
+pub use conic::*;
+
+/// 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, Copy, Clone, MallocSizeOf, PartialEq)]
+pub struct GradientStopKey {
+ pub offset: f32,
+ pub color: ColorU,
+}
+
+impl GradientStopKey {
+ pub fn empty() -> Self {
+ GradientStopKey {
+ offset: 0.0,
+ color: ColorU::new(0, 0, 0, 0),
+ }
+ }
+}
+
+impl Into<GradientStopKey> for GradientStop {
+ fn into(self) -> GradientStopKey {
+ GradientStopKey {
+ offset: self.offset,
+ color: self.color.into(),
+ }
+ }
+}
+
+// Convert `stop_keys` into a vector of `GradientStop`s, which is a more
+// convenient representation for the current gradient builder. Compute the
+// minimum stop alpha along the way.
+fn stops_and_min_alpha(stop_keys: &[GradientStopKey]) -> (Vec<GradientStop>, f32) {
+ let mut min_alpha: f32 = 1.0;
+ let stops = stop_keys.iter().map(|stop_key| {
+ let color: ColorF = stop_key.color.into();
+ min_alpha = min_alpha.min(color.a);
+
+ GradientStop {
+ offset: stop_key.offset,
+ color,
+ }
+ }).collect();
+
+ (stops, min_alpha)
+}
+
+impl Eq for GradientStopKey {}
+
+impl hash::Hash for GradientStopKey {
+ fn hash<H: hash::Hasher>(&self, state: &mut H) {
+ self.offset.to_bits().hash(state);
+ self.color.hash(state);
+ }
+}
+
+// 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;
+
+/// An entry in a gradient data table representing a segment of the gradient
+/// color space.
+#[derive(Debug, Copy, Clone)]
+#[repr(C)]
+struct GradientDataEntry {
+ start_color: PremultipliedColorF,
+ end_step: PremultipliedColorF,
+}
+
+impl GradientDataEntry {
+ fn white() -> Self {
+ Self {
+ start_color: PremultipliedColorF::WHITE,
+ end_step: PremultipliedColorF::TRANSPARENT,
+ }
+ }
+}
+
+// TODO(gw): Tidy this up to be a free function / module?
+pub 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],
+ prev_step: &PremultipliedColorF,
+ ) -> PremultipliedColorF {
+ // Calculate the color difference for individual steps in the ramp.
+ let inv_steps = 1.0 / (end_idx - start_idx) as f32;
+ let mut step = PremultipliedColorF {
+ r: (end_color.r - start_color.r) * inv_steps,
+ g: (end_color.g - start_color.g) * inv_steps,
+ b: (end_color.b - start_color.b) * inv_steps,
+ a: (end_color.a - start_color.a) * inv_steps,
+ };
+ // As a subtle form of compression, we ensure that the step values for
+ // each stop range are the same if and only if they belong to the same
+ // stop range. However, if two different stop ranges have the same step,
+ // we need to modify the steps so they compare unequally between ranges.
+ // This allows to quickly compare if two adjacent stops belong to the
+ // same range by comparing their steps.
+ if step == *prev_step {
+ // Modify the step alpha value as if by nextafter(). The difference
+ // here should be so small as to be unnoticeable, but yet allow it
+ // to compare differently.
+ step.a = f32::from_bits(if step.a == 0.0 { 1 } else { step.a.to_bits() + 1 });
+ }
+
+ 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_step = step;
+ }
+
+ step
+ }
+
+ /// 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.
+ pub fn build(
+ reverse_stops: bool,
+ gpu_buffer_builder: &mut GpuBufferBuilder,
+ src_stops: &[GradientStop],
+ ) -> GpuBufferAddress {
+ // 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 is motivated by the fact that if one naively tries to store a single color per entry
+ // and interpolate directly between entries, then hard stops will become softened because the end
+ // color of an entry actually differs from the start color of the next entry, even though they fall
+ // at the same edge offset in the gradient space. Instead, the two-color-per-entry layout preserves
+ // hard stops, as the end color for a given entry can differ from the start color for the following
+ // entry.
+ // Colors are stored in RGBA32F format (in the GPU cache). 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].
+ // As a further optimization, rather than directly storing the end color, the difference of the end
+ // color from the start color is stored instead, so that an entry can be evaluated more cheaply
+ // with start+diff*offset instead of mix(start,end,offset). Further, the color difference in two
+ // adjacent entries will always be the same if they were generated from the same set of stops/run.
+ // To allow fast searching of the table, if two adjacent entries generated from different sets of
+ // stops (a boundary) have the same difference, the floating-point bits of the stop will be nudged
+ // so that they compare differently without perceptibly altering the interpolation result. This way,
+ // one can quickly scan the table and recover runs just by comparing the color differences of the
+ // current and next entry.
+ // For example, a table with 2 inside entries (startR,startG,startB):(diffR,diffG,diffB) might look
+ // like so:
+ // first | 0.0 | 0.5 | last
+ // (0,0,0):(0,0,0) | (1,0,0):(-1,1,0) | (0,0,1):(0,1,-1) | (1,1,1):(0,0,0)
+ // ^ solid black ^ red to green ^ blue to green ^ solid white
+ let mut entries = [GradientDataEntry::white(); GRADIENT_DATA_SIZE];
+ let mut prev_step = cur_color;
+ if reverse_stops {
+ // Fill in the first entry (for reversed stops) with the first color stop
+ prev_step = GradientGpuBlockBuilder::fill_colors(
+ GRADIENT_DATA_LAST_STOP,
+ GRADIENT_DATA_LAST_STOP + 1,
+ &cur_color,
+ &cur_color,
+ &mut entries,
+ &prev_step,
+ );
+
+ // 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 {
+ prev_step = GradientGpuBlockBuilder::fill_colors(
+ next_idx,
+ cur_idx,
+ &next_color,
+ &cur_color,
+ &mut entries,
+ &prev_step,
+ );
+ 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);
+ }
+
+ // 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,
+ &prev_step,
+ );
+ } else {
+ // Fill in the first entry with the first color stop
+ prev_step = GradientGpuBlockBuilder::fill_colors(
+ GRADIENT_DATA_FIRST_STOP,
+ GRADIENT_DATA_FIRST_STOP + 1,
+ &cur_color,
+ &cur_color,
+ &mut entries,
+ &prev_step,
+ );
+
+ // 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 {
+ prev_step = GradientGpuBlockBuilder::fill_colors(
+ cur_idx,
+ next_idx,
+ &cur_color,
+ &next_color,
+ &mut entries,
+ &prev_step,
+ );
+ 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);
+ }
+
+ // 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,
+ &prev_step,
+ );
+ }
+
+ let mut writer = gpu_buffer_builder.write_blocks(2 * entries.len());
+
+ for entry in entries {
+ writer.push_one(entry.start_color);
+ writer.push_one(entry.end_step);
+ }
+
+ writer.finish()
+ }
+}
+
+// If the gradient is not tiled we know that any content outside of the clip will not
+// be shown. Applying the clip early reduces how much of the gradient we
+// render and cache. We do this optimization separately on each axis.
+// Returns the offset between the new and old primitive rect origin, to apply to the
+// gradient parameters that are relative to the primitive origin.
+pub fn apply_gradient_local_clip(
+ prim_rect: &mut LayoutRect,
+ stretch_size: &LayoutSize,
+ tile_spacing: &LayoutSize,
+ clip_rect: &LayoutRect,
+) -> LayoutVector2D {
+ let w = prim_rect.max.x.min(clip_rect.max.x) - prim_rect.min.x;
+ let h = prim_rect.max.y.min(clip_rect.max.y) - prim_rect.min.y;
+ let is_tiled_x = w > stretch_size.width + tile_spacing.width;
+ let is_tiled_y = h > stretch_size.height + tile_spacing.height;
+
+ let mut offset = LayoutVector2D::new(0.0, 0.0);
+
+ if !is_tiled_x {
+ let diff = (clip_rect.min.x - prim_rect.min.x).min(prim_rect.width());
+ if diff > 0.0 {
+ prim_rect.min.x += diff;
+ offset.x = -diff;
+ }
+
+ let diff = prim_rect.max.x - clip_rect.max.x;
+ if diff > 0.0 {
+ prim_rect.max.x -= diff;
+ }
+ }
+
+ if !is_tiled_y {
+ let diff = (clip_rect.min.y - prim_rect.min.y).min(prim_rect.height());
+ if diff > 0.0 {
+ prim_rect.min.y += diff;
+ offset.y = -diff;
+ }
+
+ let diff = prim_rect.max.y - clip_rect.max.y;
+ if diff > 0.0 {
+ prim_rect.max.y -= diff;
+ }
+ }
+
+ offset
+}
+
+#[test]
+#[cfg(target_pointer_width = "64")]
+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::<LinearGradient>(), 72, "LinearGradient size changed");
+ assert_eq!(mem::size_of::<LinearGradientTemplate>(), 144, "LinearGradientTemplate size changed");
+ assert_eq!(mem::size_of::<LinearGradientKey>(), 88, "LinearGradientKey size changed");
+
+ assert_eq!(mem::size_of::<RadialGradient>(), 72, "RadialGradient size changed");
+ assert_eq!(mem::size_of::<RadialGradientTemplate>(), 144, "RadialGradientTemplate size changed");
+ assert_eq!(mem::size_of::<RadialGradientKey>(), 96, "RadialGradientKey size changed");
+
+ assert_eq!(mem::size_of::<ConicGradient>(), 72, "ConicGradient size changed");
+ assert_eq!(mem::size_of::<ConicGradientTemplate>(), 144, "ConicGradientTemplate size changed");
+ assert_eq!(mem::size_of::<ConicGradientKey>(), 96, "ConicGradientKey size changed");
+}
diff --git a/gfx/wr/webrender/src/prim_store/gradient/radial.rs b/gfx/wr/webrender/src/prim_store/gradient/radial.rs
new file mode 100644
index 0000000000..f3f20f9a55
--- /dev/null
+++ b/gfx/wr/webrender/src/prim_store/gradient/radial.rs
@@ -0,0 +1,531 @@
+/* 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/. */
+
+//! Radial gradients
+//!
+//! Specification: https://drafts.csswg.org/css-images-4/#radial-gradients
+//!
+//! Radial gradients are rendered via cached render tasks and composited with the image brush.
+
+use euclid::{vec2, size2};
+use api::{ExtendMode, GradientStop, PremultipliedColorF, ColorU};
+use api::units::*;
+use crate::scene_building::IsVisible;
+use crate::frame_builder::FrameBuildingState;
+use crate::intern::{Internable, InternDebug, Handle as InternHandle};
+use crate::internal_types::LayoutPrimitiveInfo;
+use crate::prim_store::{BrushSegment, GradientTileRange, InternablePrimitive};
+use crate::prim_store::{PrimitiveInstanceKind, PrimitiveOpacity};
+use crate::prim_store::{PrimKeyCommonData, PrimTemplateCommonData, PrimitiveStore};
+use crate::prim_store::{NinePatchDescriptor, PointKey, SizeKey, FloatKey};
+use crate::render_task::{RenderTask, RenderTaskKind};
+use crate::render_task_graph::RenderTaskId;
+use crate::render_task_cache::{RenderTaskCacheKeyKind, RenderTaskCacheKey, RenderTaskParent};
+use crate::renderer::GpuBufferAddress;
+use crate::picture::{SurfaceIndex};
+
+use std::{hash, ops::{Deref, DerefMut}};
+use super::{
+ stops_and_min_alpha, GradientStopKey, GradientGpuBlockBuilder,
+ apply_gradient_local_clip,
+};
+
+/// Hashable radial gradient parameters, for use during prim interning.
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Debug, Clone, MallocSizeOf, 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<H: hash::Hasher>(&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 radial gradient.
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Debug, Clone, Eq, PartialEq, Hash, MallocSizeOf)]
+pub struct RadialGradientKey {
+ pub common: PrimKeyCommonData,
+ pub extend_mode: ExtendMode,
+ pub center: PointKey,
+ pub params: RadialGradientParams,
+ pub stretch_size: SizeKey,
+ pub stops: Vec<GradientStopKey>,
+ pub tile_spacing: SizeKey,
+ pub nine_patch: Option<Box<NinePatchDescriptor>>,
+}
+
+impl RadialGradientKey {
+ pub fn new(
+ info: &LayoutPrimitiveInfo,
+ radial_grad: RadialGradient,
+ ) -> Self {
+ RadialGradientKey {
+ common: info.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 InternDebug for RadialGradientKey {}
+
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(MallocSizeOf)]
+#[derive(Debug)]
+pub struct RadialGradientTemplate {
+ pub common: PrimTemplateCommonData,
+ pub extend_mode: ExtendMode,
+ pub params: RadialGradientParams,
+ pub center: DevicePoint,
+ pub task_size: DeviceIntSize,
+ pub scale: DeviceVector2D,
+ pub stretch_size: LayoutSize,
+ pub tile_spacing: LayoutSize,
+ pub brush_segments: Vec<BrushSegment>,
+ pub stops_opacity: PrimitiveOpacity,
+ pub stops: Vec<GradientStop>,
+ pub src_color: Option<RenderTaskId>,
+}
+
+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<RadialGradientKey> 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_rect.size());
+ }
+
+ let (stops, min_alpha) = stops_and_min_alpha(&item.stops);
+
+ // 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);
+
+ let mut stretch_size: LayoutSize = item.stretch_size.into();
+ stretch_size.width = stretch_size.width.min(common.prim_rect.width());
+ stretch_size.height = stretch_size.height.min(common.prim_rect.height());
+
+ // Avoid rendering enormous gradients. Radial gradients are mostly made of soft transitions,
+ // so it is unlikely that rendering at a higher resolution that 1024 would produce noticeable
+ // differences, especially with 8 bits per channel.
+ const MAX_SIZE: f32 = 1024.0;
+ let mut task_size: DeviceSize = stretch_size.cast_unit();
+ let mut scale = vec2(1.0, 1.0);
+ if task_size.width > MAX_SIZE {
+ scale.x = task_size.width/ MAX_SIZE;
+ task_size.width = MAX_SIZE;
+ }
+ if task_size.height > MAX_SIZE {
+ scale.y = task_size.height /MAX_SIZE;
+ task_size.height = MAX_SIZE;
+ }
+
+ RadialGradientTemplate {
+ common,
+ center: DevicePoint::new(item.center.x, item.center.y),
+ extend_mode: item.extend_mode,
+ params: item.params,
+ stretch_size,
+ task_size: task_size.ceil().to_i32(),
+ scale,
+ tile_spacing: item.tile_spacing.into(),
+ brush_segments,
+ stops_opacity,
+ stops,
+ src_color: None,
+ }
+ }
+}
+
+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,
+ parent_surface: SurfaceIndex,
+ ) {
+ if let Some(mut request) =
+ frame_state.gpu_cache.request(&mut self.common.gpu_cache_handle) {
+ // write_prim_gpu_blocks
+ request.push(PremultipliedColorF::WHITE);
+ request.push(PremultipliedColorF::WHITE);
+ request.push([
+ self.stretch_size.width,
+ self.stretch_size.height,
+ 0.0,
+ 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,
+ );
+ }
+ }
+
+ let task_size = self.task_size;
+ let cache_key = RadialGradientCacheKey {
+ size: task_size,
+ center: PointKey { x: self.center.x, y: self.center.y },
+ scale: PointKey { x: self.scale.x, y: self.scale.y },
+ start_radius: FloatKey(self.params.start_radius),
+ end_radius: FloatKey(self.params.end_radius),
+ ratio_xy: FloatKey(self.params.ratio_xy),
+ extend_mode: self.extend_mode,
+ stops: self.stops.iter().map(|stop| (*stop).into()).collect(),
+ };
+
+ let task_id = frame_state.resource_cache.request_render_task(
+ RenderTaskCacheKey {
+ size: task_size,
+ kind: RenderTaskCacheKeyKind::RadialGradient(cache_key),
+ },
+ frame_state.gpu_cache,
+ frame_state.frame_gpu_data,
+ frame_state.rg_builder,
+ None,
+ false,
+ RenderTaskParent::Surface(parent_surface),
+ &mut frame_state.surface_builder,
+ |rg_builder, gpu_buffer_builder| {
+ let stops = GradientGpuBlockBuilder::build(
+ false,
+ gpu_buffer_builder,
+ &self.stops,
+ );
+
+ rg_builder.add().init(RenderTask::new_dynamic(
+ task_size,
+ RenderTaskKind::RadialGradient(RadialGradientTask {
+ extend_mode: self.extend_mode,
+ center: self.center,
+ scale: self.scale,
+ params: self.params.clone(),
+ stops,
+ }),
+ ))
+ }
+ );
+
+ self.src_color = Some(task_id);
+
+ // Tile spacing is always handled by decomposing into separate draw calls so the
+ // primitive opacity is equivalent to stops opacity. This might change to being
+ // set to non-opaque in the presence of tile spacing if/when tile spacing is handled
+ // in the same way as with the image primitive.
+ self.opacity = self.stops_opacity;
+ }
+}
+
+pub type RadialGradientDataHandle = InternHandle<RadialGradient>;
+
+#[derive(Debug, MallocSizeOf)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct RadialGradient {
+ pub extend_mode: ExtendMode,
+ pub center: PointKey,
+ pub params: RadialGradientParams,
+ pub stretch_size: SizeKey,
+ pub stops: Vec<GradientStopKey>,
+ pub tile_spacing: SizeKey,
+ pub nine_patch: Option<Box<NinePatchDescriptor>>,
+}
+
+impl Internable for RadialGradient {
+ type Key = RadialGradientKey;
+ type StoreData = RadialGradientTemplate;
+ type InternData = ();
+ const PROFILE_COUNTER: usize = crate::profiler::INTERNED_RADIAL_GRADIENTS;
+}
+
+impl InternablePrimitive for RadialGradient {
+ fn into_key(
+ self,
+ info: &LayoutPrimitiveInfo,
+ ) -> RadialGradientKey {
+ RadialGradientKey::new(info, self)
+ }
+
+ fn make_instance_kind(
+ _key: RadialGradientKey,
+ data_handle: RadialGradientDataHandle,
+ _prim_store: &mut PrimitiveStore,
+ _reference_frame_relative_offset: LayoutVector2D,
+ ) -> PrimitiveInstanceKind {
+ PrimitiveInstanceKind::RadialGradient {
+ data_handle,
+ visible_tiles_range: GradientTileRange::empty(),
+ }
+ }
+}
+
+impl IsVisible for RadialGradient {
+ fn is_visible(&self) -> bool {
+ true
+ }
+}
+
+#[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct RadialGradientTask {
+ pub extend_mode: ExtendMode,
+ pub center: DevicePoint,
+ pub scale: DeviceVector2D,
+ pub params: RadialGradientParams,
+ pub stops: GpuBufferAddress,
+}
+
+impl RadialGradientTask {
+ pub fn to_instance(&self, target_rect: &DeviceIntRect) -> RadialGradientInstance {
+ RadialGradientInstance {
+ task_rect: target_rect.to_f32(),
+ center: self.center,
+ scale: self.scale,
+ start_radius: self.params.start_radius,
+ end_radius: self.params.end_radius,
+ ratio_xy: self.params.ratio_xy,
+ extend_mode: self.extend_mode as i32,
+ gradient_stops_address: self.stops.as_int(),
+ }
+ }
+}
+
+/// The per-instance shader input of a radial gradient render task.
+///
+/// Must match the RADIAL_GRADIENT instance description in renderer/vertex.rs.
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[repr(C)]
+#[derive(Clone, Debug)]
+pub struct RadialGradientInstance {
+ pub task_rect: DeviceRect,
+ pub center: DevicePoint,
+ pub scale: DeviceVector2D,
+ pub start_radius: f32,
+ pub end_radius: f32,
+ pub ratio_xy: f32,
+ pub extend_mode: i32,
+ pub gradient_stops_address: i32,
+}
+
+#[derive(Clone, Debug, Hash, PartialEq, Eq)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct RadialGradientCacheKey {
+ pub size: DeviceIntSize,
+ pub center: PointKey,
+ pub scale: PointKey,
+ pub start_radius: FloatKey,
+ pub end_radius: FloatKey,
+ pub ratio_xy: FloatKey,
+ pub extend_mode: ExtendMode,
+ pub stops: Vec<GradientStopKey>,
+}
+
+/// Avoid invoking the radial gradient shader on large areas where the color is
+/// constant.
+///
+/// If the extend mode is set to clamp, the "interesting" part
+/// of the gradient is only in the bounds of the gradient's ellipse, and the rest
+/// is the color of the last gradient stop.
+///
+/// Sometimes we run into radial gradient with a small radius compared to the
+/// primitive bounds, which means a large area of the primitive is a constant color
+/// This function tries to detect that, potentially shrink the gradient primitive to only
+/// the useful part and if needed insert solid color primitives around the gradient where
+/// parts of it have been removed.
+pub fn optimize_radial_gradient(
+ prim_rect: &mut LayoutRect,
+ stretch_size: &mut LayoutSize,
+ center: &mut LayoutPoint,
+ tile_spacing: &mut LayoutSize,
+ clip_rect: &LayoutRect,
+ radius: LayoutSize,
+ end_offset: f32,
+ extend_mode: ExtendMode,
+ stops: &[GradientStopKey],
+ solid_parts: &mut dyn FnMut(&LayoutRect, ColorU),
+) {
+ let offset = apply_gradient_local_clip(
+ prim_rect,
+ stretch_size,
+ tile_spacing,
+ clip_rect
+ );
+
+ *center += offset;
+
+ if extend_mode != ExtendMode::Clamp || stops.is_empty() {
+ return;
+ }
+
+ // Bounding box of the "interesting" part of the gradient.
+ let min = prim_rect.min + center.to_vector() - radius.to_vector() * end_offset;
+ let max = prim_rect.min + center.to_vector() + radius.to_vector() * end_offset;
+
+ // The (non-repeated) gradient primitive rect.
+ let gradient_rect = LayoutRect::from_origin_and_size(
+ prim_rect.min,
+ *stretch_size,
+ );
+
+ // How much internal margin between the primitive bounds and the gradient's
+ // bounding rect (areas that are a constant color).
+ let mut l = (min.x - gradient_rect.min.x).max(0.0).floor();
+ let mut t = (min.y - gradient_rect.min.y).max(0.0).floor();
+ let mut r = (gradient_rect.max.x - max.x).max(0.0).floor();
+ let mut b = (gradient_rect.max.y - max.y).max(0.0).floor();
+
+ let is_tiled = prim_rect.width() > stretch_size.width + tile_spacing.width
+ || prim_rect.height() > stretch_size.height + tile_spacing.height;
+
+ let bg_color = stops.last().unwrap().color;
+
+ if bg_color.a != 0 && is_tiled {
+ // If the primitive has repetitions, it's not enough to insert solid rects around it,
+ // so bail out.
+ return;
+ }
+
+ // If the background is fully transparent, shrinking the primitive bounds as much as possible
+ // is always a win. If the background is not transparent, we have to insert solid rectangles
+ // around the shrunk parts.
+ // If the background is transparent and the primitive is tiled, the optimization may introduce
+ // tile spacing which forces the tiling to be manually decomposed.
+ // Either way, don't bother optimizing unless it saves a significant amount of pixels.
+ if bg_color.a != 0 || (is_tiled && tile_spacing.is_empty()) {
+ let threshold = 128.0;
+ if l < threshold { l = 0.0 }
+ if t < threshold { t = 0.0 }
+ if r < threshold { r = 0.0 }
+ if b < threshold { b = 0.0 }
+ }
+
+ if l + t + r + b == 0.0 {
+ // No adjustment to make;
+ return;
+ }
+
+ // Insert solid rectangles around the gradient, in the places where the primitive will be
+ // shrunk.
+ if bg_color.a != 0 {
+ if l != 0.0 && t != 0.0 {
+ let solid_rect = LayoutRect::from_origin_and_size(
+ gradient_rect.min,
+ size2(l, t),
+ );
+ solid_parts(&solid_rect, bg_color);
+ }
+
+ if l != 0.0 && b != 0.0 {
+ let solid_rect = LayoutRect::from_origin_and_size(
+ gradient_rect.bottom_left() - vec2(0.0, b),
+ size2(l, b),
+ );
+ solid_parts(&solid_rect, bg_color);
+ }
+
+ if t != 0.0 && r != 0.0 {
+ let solid_rect = LayoutRect::from_origin_and_size(
+ gradient_rect.top_right() - vec2(r, 0.0),
+ size2(r, t),
+ );
+ solid_parts(&solid_rect, bg_color);
+ }
+
+ if r != 0.0 && b != 0.0 {
+ let solid_rect = LayoutRect::from_origin_and_size(
+ gradient_rect.bottom_right() - vec2(r, b),
+ size2(r, b),
+ );
+ solid_parts(&solid_rect, bg_color);
+ }
+
+ if l != 0.0 {
+ let solid_rect = LayoutRect::from_origin_and_size(
+ gradient_rect.min + vec2(0.0, t),
+ size2(l, gradient_rect.height() - t - b),
+ );
+ solid_parts(&solid_rect, bg_color);
+ }
+
+ if r != 0.0 {
+ let solid_rect = LayoutRect::from_origin_and_size(
+ gradient_rect.top_right() + vec2(-r, t),
+ size2(r, gradient_rect.height() - t - b),
+ );
+ solid_parts(&solid_rect, bg_color);
+ }
+
+ if t != 0.0 {
+ let solid_rect = LayoutRect::from_origin_and_size(
+ gradient_rect.min + vec2(l, 0.0),
+ size2(gradient_rect.width() - l - r, t),
+ );
+ solid_parts(&solid_rect, bg_color);
+ }
+
+ if b != 0.0 {
+ let solid_rect = LayoutRect::from_origin_and_size(
+ gradient_rect.bottom_left() + vec2(l, -b),
+ size2(gradient_rect.width() - l - r, b),
+ );
+ solid_parts(&solid_rect, bg_color);
+ }
+ }
+
+ // Shrink the gradient primitive.
+
+ prim_rect.min.x += l;
+ prim_rect.min.y += t;
+
+ stretch_size.width -= l + r;
+ stretch_size.height -= b + t;
+
+ center.x -= l;
+ center.y -= t;
+
+ tile_spacing.width += l + r;
+ tile_spacing.height += t + b;
+}