summaryrefslogtreecommitdiffstats
path: root/gfx/wr/webrender/src/render_task.rs
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/wr/webrender/src/render_task.rs')
-rw-r--r--gfx/wr/webrender/src/render_task.rs1560
1 files changed, 1560 insertions, 0 deletions
diff --git a/gfx/wr/webrender/src/render_task.rs b/gfx/wr/webrender/src/render_task.rs
new file mode 100644
index 0000000000..51fa71f943
--- /dev/null
+++ b/gfx/wr/webrender/src/render_task.rs
@@ -0,0 +1,1560 @@
+/* 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::{CompositeOperator, FilterPrimitive, FilterPrimitiveInput, FilterPrimitiveKind};
+use api::{LineStyle, LineOrientation, ClipMode, MixBlendMode, ColorF, ColorSpace};
+use api::MAX_RENDER_TASK_SIZE;
+use api::units::*;
+use crate::clip::{ClipDataStore, ClipItemKind, ClipStore, ClipNodeRange};
+use crate::command_buffer::{CommandBufferIndex, QuadFlags};
+use crate::spatial_tree::SpatialNodeIndex;
+use crate::filterdata::SFilterData;
+use crate::frame_builder::{FrameBuilderConfig};
+use crate::gpu_cache::{GpuCache, GpuCacheAddress, GpuCacheHandle};
+use crate::gpu_types::{BorderInstance, ImageSource, UvRectKind, TransformPaletteId};
+use crate::internal_types::{CacheTextureId, FastHashMap, TextureSource, Swizzle};
+use crate::picture::ResolvedSurfaceTexture;
+use crate::prim_store::ClipData;
+use crate::prim_store::gradient::{
+ FastLinearGradientTask, RadialGradientTask,
+ ConicGradientTask, LinearGradientTask,
+};
+use crate::resource_cache::{ResourceCache, ImageRequest};
+use std::{usize, f32, i32, u32};
+use crate::renderer::{GpuBufferAddress, GpuBufferBuilder};
+use crate::render_target::{ResolveOp, RenderTargetKind};
+use crate::render_task_graph::{PassId, RenderTaskId, RenderTaskGraphBuilder};
+use crate::render_task_cache::{RenderTaskCacheEntryHandle, RenderTaskCacheKey, RenderTaskCacheKeyKind, RenderTaskParent};
+use crate::segment::EdgeAaSegmentMask;
+use crate::surface::SurfaceBuilder;
+use smallvec::SmallVec;
+
+const FLOATS_PER_RENDER_TASK_INFO: usize = 8;
+pub const MAX_BLUR_STD_DEVIATION: f32 = 4.0;
+pub const MIN_DOWNSCALING_RT_SIZE: i32 = 8;
+
+fn render_task_sanity_check(size: &DeviceIntSize) {
+ if size.width > MAX_RENDER_TASK_SIZE ||
+ size.height > MAX_RENDER_TASK_SIZE {
+ error!("Attempting to create a render task of size {}x{}", size.width, size.height);
+ panic!();
+ }
+}
+
+#[derive(Debug, Copy, Clone, PartialEq)]
+#[repr(C)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct RenderTaskAddress(pub u16);
+
+impl Into<RenderTaskAddress> for RenderTaskId {
+ fn into(self) -> RenderTaskAddress {
+ RenderTaskAddress(self.index as u16)
+ }
+}
+
+/// A render task location that targets a persistent output buffer which
+/// will be retained over multiple frames.
+#[derive(Clone, Debug, Eq, PartialEq, Hash)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub enum StaticRenderTaskSurface {
+ /// The output of the `RenderTask` will be persisted beyond this frame, and
+ /// thus should be drawn into the `TextureCache`.
+ TextureCache {
+ /// Which texture in the texture cache should be drawn into.
+ texture: CacheTextureId,
+ /// What format this texture cache surface is
+ target_kind: RenderTargetKind,
+ },
+ /// Only used as a source for render tasks, can be any texture including an
+ /// external one.
+ ReadOnly {
+ source: TextureSource,
+ },
+ /// This render task will be drawn to a picture cache texture that is
+ /// persisted between both frames and scenes, if the content remains valid.
+ PictureCache {
+ /// Describes either a WR texture or a native OS compositor target
+ surface: ResolvedSurfaceTexture,
+ },
+}
+
+/// Identifies the output buffer location for a given `RenderTask`.
+#[derive(Clone, Debug)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub enum RenderTaskLocation {
+ // Towards the beginning of the frame, most task locations are typically not
+ // known yet, in which case they are set to one of the following variants:
+
+ /// A dynamic task that has not yet been allocated a texture and rect.
+ Unallocated {
+ /// Requested size of this render task
+ size: DeviceIntSize,
+ },
+ /// Will be replaced by a Static location after the texture cache update.
+ CacheRequest {
+ size: DeviceIntSize,
+ },
+ /// Same allocation as an existing task deeper in the dependency graph
+ Existing {
+ parent_task_id: RenderTaskId,
+ /// Requested size of this render task
+ size: DeviceIntSize,
+ },
+
+ // Before batching begins, we expect that locations have been resolved to
+ // one of the following variants:
+
+ /// The `RenderTask` should be drawn to a target provided by the atlas
+ /// allocator. This is the most common case.
+ Dynamic {
+ /// Texture that this task was allocated to render on
+ texture_id: CacheTextureId,
+ /// Rectangle in the texture this task occupies
+ rect: DeviceIntRect,
+ },
+ /// A task that is output to a persistent / retained target.
+ Static {
+ /// Target to draw to
+ surface: StaticRenderTaskSurface,
+ /// Rectangle in the texture this task occupies
+ rect: DeviceIntRect,
+ },
+}
+
+impl RenderTaskLocation {
+ /// Returns true if this is a dynamic location.
+ pub fn is_dynamic(&self) -> bool {
+ match *self {
+ RenderTaskLocation::Dynamic { .. } => true,
+ _ => false,
+ }
+ }
+
+ pub fn size(&self) -> DeviceIntSize {
+ match self {
+ RenderTaskLocation::Unallocated { size } => *size,
+ RenderTaskLocation::Dynamic { rect, .. } => rect.size(),
+ RenderTaskLocation::Static { rect, .. } => rect.size(),
+ RenderTaskLocation::CacheRequest { size } => *size,
+ RenderTaskLocation::Existing { size, .. } => *size,
+ }
+ }
+}
+
+#[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct CachedTask {
+ pub target_kind: RenderTargetKind,
+}
+
+#[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct CacheMaskTask {
+ pub actual_rect: DeviceRect,
+ pub root_spatial_node_index: SpatialNodeIndex,
+ pub clip_node_range: ClipNodeRange,
+ pub device_pixel_scale: DevicePixelScale,
+ pub clear_to_one: bool,
+}
+
+#[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct ClipRegionTask {
+ pub local_pos: LayoutPoint,
+ pub device_pixel_scale: DevicePixelScale,
+ pub clip_data: ClipData,
+ pub clear_to_one: bool,
+}
+
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct PrimTask {
+ pub device_pixel_scale: DevicePixelScale,
+ pub content_origin: DevicePoint,
+ pub prim_address: GpuBufferAddress,
+ pub prim_spatial_node_index: SpatialNodeIndex,
+ pub transform_id: TransformPaletteId,
+ pub edge_flags: EdgeAaSegmentMask,
+ pub quad_flags: QuadFlags,
+ pub clip_node_range: ClipNodeRange,
+}
+
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct TileCompositeTask {
+ pub clear_color: ColorF,
+ pub scissor_rect: DeviceIntRect,
+ pub valid_rect: DeviceIntRect,
+ pub task_id: Option<RenderTaskId>,
+ pub sub_rect_offset: DeviceIntVector2D,
+}
+
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct PictureTask {
+ pub can_merge: bool,
+ pub content_origin: DevicePoint,
+ pub surface_spatial_node_index: SpatialNodeIndex,
+ pub raster_spatial_node_index: SpatialNodeIndex,
+ pub device_pixel_scale: DevicePixelScale,
+ pub clear_color: Option<ColorF>,
+ pub scissor_rect: Option<DeviceIntRect>,
+ pub valid_rect: Option<DeviceIntRect>,
+ pub cmd_buffer_index: CommandBufferIndex,
+ pub resolve_op: Option<ResolveOp>,
+
+ pub can_use_shared_surface: bool,
+}
+
+impl PictureTask {
+ /// Copy an existing picture task, but set a new command buffer for it to build in to.
+ /// Used for pictures that are split between render tasks (e.g. pre/post a backdrop
+ /// filter). Subsequent picture tasks never have a clear color as they are by definition
+ /// going to write to an existing target
+ pub fn duplicate(
+ &self,
+ cmd_buffer_index: CommandBufferIndex,
+ ) -> Self {
+ assert_eq!(self.resolve_op, None);
+
+ PictureTask {
+ clear_color: None,
+ cmd_buffer_index,
+ resolve_op: None,
+ can_use_shared_surface: false,
+ ..*self
+ }
+ }
+}
+
+#[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct BlurTask {
+ pub blur_std_deviation: f32,
+ pub target_kind: RenderTargetKind,
+ pub blur_region: DeviceIntSize,
+}
+
+impl BlurTask {
+ // In order to do the blur down-scaling passes without introducing errors, we need the
+ // source of each down-scale pass to be a multuple of two. If need be, this inflates
+ // the source size so that each down-scale pass will sample correctly.
+ pub fn adjusted_blur_source_size(original_size: DeviceSize, mut std_dev: DeviceSize) -> DeviceIntSize {
+ let mut adjusted_size = original_size;
+ let mut scale_factor = 1.0;
+ while std_dev.width > MAX_BLUR_STD_DEVIATION && std_dev.height > MAX_BLUR_STD_DEVIATION {
+ if adjusted_size.width < MIN_DOWNSCALING_RT_SIZE as f32 ||
+ adjusted_size.height < MIN_DOWNSCALING_RT_SIZE as f32 {
+ break;
+ }
+ std_dev = std_dev * 0.5;
+ scale_factor *= 2.0;
+ adjusted_size = (original_size.to_f32() / scale_factor).ceil();
+ }
+
+ (adjusted_size * scale_factor).round().to_i32()
+ }
+}
+
+#[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct ScalingTask {
+ pub target_kind: RenderTargetKind,
+ pub padding: DeviceIntSideOffsets,
+}
+
+#[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct BorderTask {
+ pub instances: Vec<BorderInstance>,
+}
+
+#[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct BlitTask {
+ pub source: RenderTaskId,
+}
+
+#[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct LineDecorationTask {
+ pub wavy_line_thickness: f32,
+ pub style: LineStyle,
+ pub orientation: LineOrientation,
+ pub local_size: LayoutSize,
+}
+
+#[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub enum SvgFilterInfo {
+ Blend(MixBlendMode),
+ Flood(ColorF),
+ LinearToSrgb,
+ SrgbToLinear,
+ Opacity(f32),
+ ColorMatrix(Box<[f32; 20]>),
+ DropShadow(ColorF),
+ Offset(DeviceVector2D),
+ ComponentTransfer(SFilterData),
+ Composite(CompositeOperator),
+ // TODO: This is used as a hack to ensure that a blur task's input is always in the blur's previous pass.
+ Identity,
+}
+
+#[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct SvgFilterTask {
+ pub info: SvgFilterInfo,
+ pub extra_gpu_cache_handle: Option<GpuCacheHandle>,
+}
+
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct ReadbackTask {
+ // The offset of the rect that needs to be read back, in the
+ // device space of the surface that will be read back from.
+ // If this is None, there is no readback surface available
+ // and this is a dummy (empty) readback.
+ pub readback_origin: Option<DevicePoint>,
+}
+
+#[derive(Debug)]
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct RenderTaskData {
+ pub data: [f32; FLOATS_PER_RENDER_TASK_INFO],
+}
+
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub enum RenderTaskKind {
+ Image(ImageRequest),
+ Cached(CachedTask),
+ Picture(PictureTask),
+ CacheMask(CacheMaskTask),
+ ClipRegion(ClipRegionTask),
+ VerticalBlur(BlurTask),
+ HorizontalBlur(BlurTask),
+ Readback(ReadbackTask),
+ Scaling(ScalingTask),
+ Blit(BlitTask),
+ Border(BorderTask),
+ LineDecoration(LineDecorationTask),
+ FastLinearGradient(FastLinearGradientTask),
+ LinearGradient(LinearGradientTask),
+ RadialGradient(RadialGradientTask),
+ ConicGradient(ConicGradientTask),
+ SvgFilter(SvgFilterTask),
+ TileComposite(TileCompositeTask),
+ Prim(PrimTask),
+ #[cfg(test)]
+ Test(RenderTargetKind),
+}
+
+impl RenderTaskKind {
+ pub fn is_a_rendering_operation(&self) -> bool {
+ match self {
+ &RenderTaskKind::Image(..) => false,
+ &RenderTaskKind::Cached(..) => false,
+ _ => true,
+ }
+ }
+
+ /// Whether this task can be allocated on a shared render target surface
+ pub fn can_use_shared_surface(&self) -> bool {
+ match self {
+ &RenderTaskKind::Picture(ref info) => info.can_use_shared_surface,
+ _ => true,
+ }
+ }
+
+ pub fn should_advance_pass(&self) -> bool {
+ match self {
+ &RenderTaskKind::Image(..) => false,
+ &RenderTaskKind::Cached(..) => false,
+ _ => true,
+ }
+ }
+
+ pub fn as_str(&self) -> &'static str {
+ match *self {
+ RenderTaskKind::Image(..) => "Image",
+ RenderTaskKind::Cached(..) => "Cached",
+ RenderTaskKind::Picture(..) => "Picture",
+ RenderTaskKind::CacheMask(..) => "CacheMask",
+ RenderTaskKind::ClipRegion(..) => "ClipRegion",
+ RenderTaskKind::VerticalBlur(..) => "VerticalBlur",
+ RenderTaskKind::HorizontalBlur(..) => "HorizontalBlur",
+ RenderTaskKind::Readback(..) => "Readback",
+ RenderTaskKind::Scaling(..) => "Scaling",
+ RenderTaskKind::Blit(..) => "Blit",
+ RenderTaskKind::Border(..) => "Border",
+ RenderTaskKind::LineDecoration(..) => "LineDecoration",
+ RenderTaskKind::FastLinearGradient(..) => "FastLinearGradient",
+ RenderTaskKind::LinearGradient(..) => "LinearGradient",
+ RenderTaskKind::RadialGradient(..) => "RadialGradient",
+ RenderTaskKind::ConicGradient(..) => "ConicGradient",
+ RenderTaskKind::SvgFilter(..) => "SvgFilter",
+ RenderTaskKind::TileComposite(..) => "TileComposite",
+ RenderTaskKind::Prim(..) => "Prim",
+ #[cfg(test)]
+ RenderTaskKind::Test(..) => "Test",
+ }
+ }
+
+ pub fn target_kind(&self) -> RenderTargetKind {
+ match *self {
+ RenderTaskKind::Image(..) |
+ RenderTaskKind::LineDecoration(..) |
+ RenderTaskKind::Readback(..) |
+ RenderTaskKind::Border(..) |
+ RenderTaskKind::FastLinearGradient(..) |
+ RenderTaskKind::LinearGradient(..) |
+ RenderTaskKind::RadialGradient(..) |
+ RenderTaskKind::ConicGradient(..) |
+ RenderTaskKind::Picture(..) |
+ RenderTaskKind::Blit(..) |
+ RenderTaskKind::TileComposite(..) |
+ RenderTaskKind::Prim(..) |
+ RenderTaskKind::SvgFilter(..) => {
+ RenderTargetKind::Color
+ }
+
+ RenderTaskKind::ClipRegion(..) |
+ RenderTaskKind::CacheMask(..) => {
+ RenderTargetKind::Alpha
+ }
+
+ RenderTaskKind::VerticalBlur(ref task_info) |
+ RenderTaskKind::HorizontalBlur(ref task_info) => {
+ task_info.target_kind
+ }
+
+ RenderTaskKind::Scaling(ref task_info) => {
+ task_info.target_kind
+ }
+
+ RenderTaskKind::Cached(ref task_info) => {
+ task_info.target_kind
+ }
+
+ #[cfg(test)]
+ RenderTaskKind::Test(kind) => kind,
+ }
+ }
+
+ pub fn new_tile_composite(
+ sub_rect_offset: DeviceIntVector2D,
+ scissor_rect: DeviceIntRect,
+ valid_rect: DeviceIntRect,
+ clear_color: ColorF,
+ ) -> Self {
+ RenderTaskKind::TileComposite(TileCompositeTask {
+ task_id: None,
+ sub_rect_offset,
+ scissor_rect,
+ valid_rect,
+ clear_color,
+ })
+ }
+
+ pub fn new_picture(
+ size: DeviceIntSize,
+ needs_scissor_rect: bool,
+ content_origin: DevicePoint,
+ surface_spatial_node_index: SpatialNodeIndex,
+ raster_spatial_node_index: SpatialNodeIndex,
+ device_pixel_scale: DevicePixelScale,
+ scissor_rect: Option<DeviceIntRect>,
+ valid_rect: Option<DeviceIntRect>,
+ clear_color: Option<ColorF>,
+ cmd_buffer_index: CommandBufferIndex,
+ can_use_shared_surface: bool,
+ ) -> Self {
+ render_task_sanity_check(&size);
+
+ RenderTaskKind::Picture(PictureTask {
+ content_origin,
+ can_merge: !needs_scissor_rect,
+ surface_spatial_node_index,
+ raster_spatial_node_index,
+ device_pixel_scale,
+ scissor_rect,
+ valid_rect,
+ clear_color,
+ cmd_buffer_index,
+ resolve_op: None,
+ can_use_shared_surface,
+ })
+ }
+
+ pub fn new_prim(
+ prim_spatial_node_index: SpatialNodeIndex,
+ device_pixel_scale: DevicePixelScale,
+ content_origin: DevicePoint,
+ prim_address: GpuBufferAddress,
+ transform_id: TransformPaletteId,
+ edge_flags: EdgeAaSegmentMask,
+ quad_flags: QuadFlags,
+ clip_node_range: ClipNodeRange,
+ ) -> Self {
+ RenderTaskKind::Prim(PrimTask {
+ prim_spatial_node_index,
+ device_pixel_scale,
+ content_origin,
+ prim_address,
+ transform_id,
+ edge_flags,
+ quad_flags,
+ clip_node_range,
+ })
+ }
+
+ pub fn new_readback(
+ readback_origin: Option<DevicePoint>,
+ ) -> Self {
+ RenderTaskKind::Readback(
+ ReadbackTask {
+ readback_origin,
+ }
+ )
+ }
+
+ pub fn new_line_decoration(
+ style: LineStyle,
+ orientation: LineOrientation,
+ wavy_line_thickness: f32,
+ local_size: LayoutSize,
+ ) -> Self {
+ RenderTaskKind::LineDecoration(LineDecorationTask {
+ style,
+ orientation,
+ wavy_line_thickness,
+ local_size,
+ })
+ }
+
+ pub fn new_border_segment(
+ instances: Vec<BorderInstance>,
+ ) -> Self {
+ RenderTaskKind::Border(BorderTask {
+ instances,
+ })
+ }
+
+ pub fn new_rounded_rect_mask(
+ local_pos: LayoutPoint,
+ clip_data: ClipData,
+ device_pixel_scale: DevicePixelScale,
+ fb_config: &FrameBuilderConfig,
+ ) -> Self {
+ RenderTaskKind::ClipRegion(ClipRegionTask {
+ local_pos,
+ device_pixel_scale,
+ clip_data,
+ clear_to_one: fb_config.gpu_supports_fast_clears,
+ })
+ }
+
+ pub fn new_mask(
+ outer_rect: DeviceRect,
+ clip_node_range: ClipNodeRange,
+ root_spatial_node_index: SpatialNodeIndex,
+ clip_store: &mut ClipStore,
+ gpu_cache: &mut GpuCache,
+ gpu_buffer_builder: &mut GpuBufferBuilder,
+ resource_cache: &mut ResourceCache,
+ rg_builder: &mut RenderTaskGraphBuilder,
+ clip_data_store: &mut ClipDataStore,
+ device_pixel_scale: DevicePixelScale,
+ fb_config: &FrameBuilderConfig,
+ surface_builder: &mut SurfaceBuilder,
+ ) -> RenderTaskId {
+ // Step through the clip sources that make up this mask. If we find
+ // any box-shadow clip sources, request that image from the render
+ // task cache. This allows the blurred box-shadow rect to be cached
+ // in the texture cache across frames.
+ // TODO(gw): Consider moving this logic outside this function, especially
+ // as we add more clip sources that depend on render tasks.
+ // TODO(gw): If this ever shows up in a profile, we could pre-calculate
+ // whether a ClipSources contains any box-shadows and skip
+ // this iteration for the majority of cases.
+ let task_size = outer_rect.size().to_i32();
+
+ // If we have a potentially tiled clip mask, clear the mask area first. Otherwise,
+ // the first (primary) clip mask will overwrite all the clip mask pixels with
+ // blending disabled to set to the initial value.
+
+ let clip_task_id = rg_builder.add().init(
+ RenderTask::new_dynamic(
+ task_size,
+ RenderTaskKind::CacheMask(CacheMaskTask {
+ actual_rect: outer_rect,
+ clip_node_range,
+ root_spatial_node_index,
+ device_pixel_scale,
+ clear_to_one: fb_config.gpu_supports_fast_clears,
+ }),
+ )
+ );
+
+ for i in 0 .. clip_node_range.count {
+ let clip_instance = clip_store.get_instance_from_range(&clip_node_range, i);
+ let clip_node = &mut clip_data_store[clip_instance.handle];
+ match clip_node.item.kind {
+ ClipItemKind::BoxShadow { ref mut source } => {
+ let (cache_size, cache_key) = source.cache_key
+ .as_ref()
+ .expect("bug: no cache key set")
+ .clone();
+ let blur_radius_dp = cache_key.blur_radius_dp as f32;
+ let device_pixel_scale = DevicePixelScale::new(cache_key.device_pixel_scale.to_f32_px());
+
+ // Request a cacheable render task with a blurred, minimal
+ // sized box-shadow rect.
+ source.render_task = Some(resource_cache.request_render_task(
+ RenderTaskCacheKey {
+ size: cache_size,
+ kind: RenderTaskCacheKeyKind::BoxShadow(cache_key),
+ },
+ gpu_cache,
+ gpu_buffer_builder,
+ rg_builder,
+ None,
+ false,
+ RenderTaskParent::RenderTask(clip_task_id),
+ surface_builder,
+ |rg_builder, _| {
+ let clip_data = ClipData::rounded_rect(
+ source.minimal_shadow_rect.size(),
+ &source.shadow_radius,
+ ClipMode::Clip,
+ );
+
+ // Draw the rounded rect.
+ let mask_task_id = rg_builder.add().init(RenderTask::new_dynamic(
+ cache_size,
+ RenderTaskKind::new_rounded_rect_mask(
+ source.minimal_shadow_rect.min,
+ clip_data,
+ device_pixel_scale,
+ fb_config,
+ ),
+ ));
+
+ // Blur it
+ RenderTask::new_blur(
+ DeviceSize::new(blur_radius_dp, blur_radius_dp),
+ mask_task_id,
+ rg_builder,
+ RenderTargetKind::Alpha,
+ None,
+ cache_size,
+ )
+ }
+ ));
+ }
+ ClipItemKind::Rectangle { .. } |
+ ClipItemKind::RoundedRectangle { .. } |
+ ClipItemKind::Image { .. } => {}
+ }
+ }
+
+ clip_task_id
+ }
+
+ // Write (up to) 8 floats of data specific to the type
+ // of render task that is provided to the GPU shaders
+ // via a vertex texture.
+ pub fn write_task_data(
+ &self,
+ target_rect: DeviceIntRect,
+ ) -> RenderTaskData {
+ // NOTE: The ordering and layout of these structures are
+ // required to match both the GPU structures declared
+ // in prim_shared.glsl, and also the uses in submit_batch()
+ // in renderer.rs.
+ // TODO(gw): Maybe there's a way to make this stuff a bit
+ // more type-safe. Although, it will always need
+ // to be kept in sync with the GLSL code anyway.
+
+ let data = match self {
+ RenderTaskKind::Picture(ref task) => {
+ // Note: has to match `PICTURE_TYPE_*` in shaders
+ [
+ task.device_pixel_scale.0,
+ task.content_origin.x,
+ task.content_origin.y,
+ 0.0,
+ ]
+ }
+ RenderTaskKind::Prim(ref task) => {
+ [
+ // NOTE: This must match the render task data format for Picture tasks currently
+ task.device_pixel_scale.0,
+ task.content_origin.x,
+ task.content_origin.y,
+ 0.0,
+ ]
+ }
+ RenderTaskKind::CacheMask(ref task) => {
+ [
+ task.device_pixel_scale.0,
+ task.actual_rect.min.x,
+ task.actual_rect.min.y,
+ 0.0,
+ ]
+ }
+ RenderTaskKind::ClipRegion(ref task) => {
+ [
+ task.device_pixel_scale.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ ]
+ }
+ RenderTaskKind::VerticalBlur(ref task) |
+ RenderTaskKind::HorizontalBlur(ref task) => {
+ [
+ task.blur_std_deviation,
+ task.blur_region.width as f32,
+ task.blur_region.height as f32,
+ 0.0,
+ ]
+ }
+ RenderTaskKind::Image(..) |
+ RenderTaskKind::Cached(..) |
+ RenderTaskKind::Readback(..) |
+ RenderTaskKind::Scaling(..) |
+ RenderTaskKind::Border(..) |
+ RenderTaskKind::LineDecoration(..) |
+ RenderTaskKind::FastLinearGradient(..) |
+ RenderTaskKind::LinearGradient(..) |
+ RenderTaskKind::RadialGradient(..) |
+ RenderTaskKind::ConicGradient(..) |
+ RenderTaskKind::TileComposite(..) |
+ RenderTaskKind::Blit(..) => {
+ [0.0; 4]
+ }
+
+ RenderTaskKind::SvgFilter(ref task) => {
+ match task.info {
+ SvgFilterInfo::Opacity(opacity) => [opacity, 0.0, 0.0, 0.0],
+ SvgFilterInfo::Offset(offset) => [offset.x, offset.y, 0.0, 0.0],
+ _ => [0.0; 4]
+ }
+ }
+
+ #[cfg(test)]
+ RenderTaskKind::Test(..) => {
+ [0.0; 4]
+ }
+ };
+
+ RenderTaskData {
+ data: [
+ target_rect.min.x as f32,
+ target_rect.min.y as f32,
+ target_rect.max.x as f32,
+ target_rect.max.y as f32,
+ data[0],
+ data[1],
+ data[2],
+ data[3],
+ ]
+ }
+ }
+
+ pub fn write_gpu_blocks(
+ &mut self,
+ gpu_cache: &mut GpuCache,
+ ) {
+ if let RenderTaskKind::SvgFilter(ref mut filter_task) = self {
+ match filter_task.info {
+ SvgFilterInfo::ColorMatrix(ref matrix) => {
+ let handle = filter_task.extra_gpu_cache_handle.get_or_insert_with(GpuCacheHandle::new);
+ if let Some(mut request) = gpu_cache.request(handle) {
+ for i in 0..5 {
+ request.push([matrix[i*4], matrix[i*4+1], matrix[i*4+2], matrix[i*4+3]]);
+ }
+ }
+ }
+ SvgFilterInfo::DropShadow(color) |
+ SvgFilterInfo::Flood(color) => {
+ let handle = filter_task.extra_gpu_cache_handle.get_or_insert_with(GpuCacheHandle::new);
+ if let Some(mut request) = gpu_cache.request(handle) {
+ request.push(color.to_array());
+ }
+ }
+ SvgFilterInfo::ComponentTransfer(ref data) => {
+ let handle = filter_task.extra_gpu_cache_handle.get_or_insert_with(GpuCacheHandle::new);
+ if let Some(request) = gpu_cache.request(handle) {
+ data.update(request);
+ }
+ }
+ SvgFilterInfo::Composite(ref operator) => {
+ if let CompositeOperator::Arithmetic(k_vals) = operator {
+ let handle = filter_task.extra_gpu_cache_handle.get_or_insert_with(GpuCacheHandle::new);
+ if let Some(mut request) = gpu_cache.request(handle) {
+ request.push(*k_vals);
+ }
+ }
+ }
+ _ => {},
+ }
+ }
+ }
+}
+
+/// In order to avoid duplicating the down-scaling and blur passes when a picture has several blurs,
+/// we use a local (primitive-level) cache of the render tasks generated for a single shadowed primitive
+/// in a single frame.
+pub type BlurTaskCache = FastHashMap<BlurTaskKey, RenderTaskId>;
+
+/// Since we only use it within a single primitive, the key only needs to contain the down-scaling level
+/// and the blur std deviation.
+#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
+pub enum BlurTaskKey {
+ DownScale(u32),
+ Blur { downscale_level: u32, stddev_x: u32, stddev_y: u32 },
+}
+
+impl BlurTaskKey {
+ fn downscale_and_blur(downscale_level: u32, blur_stddev: DeviceSize) -> Self {
+ // Quantise the std deviations and store it as integers to work around
+ // Eq and Hash's f32 allergy.
+ // The blur radius is rounded before RenderTask::new_blur so we don't need
+ // a lot of precision.
+ const QUANTIZATION_FACTOR: f32 = 1024.0;
+ let stddev_x = (blur_stddev.width * QUANTIZATION_FACTOR) as u32;
+ let stddev_y = (blur_stddev.height * QUANTIZATION_FACTOR) as u32;
+ BlurTaskKey::Blur { downscale_level, stddev_x, stddev_y }
+ }
+}
+
+// The majority of render tasks have 0, 1 or 2 dependencies, except for pictures that
+// typically have dozens to hundreds of dependencies. SmallVec with 2 inline elements
+// avoids many tiny heap allocations in pages with a lot of text shadows and other
+// types of render tasks.
+pub type TaskDependencies = SmallVec<[RenderTaskId;2]>;
+
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct RenderTask {
+ pub location: RenderTaskLocation,
+ pub children: TaskDependencies,
+ pub kind: RenderTaskKind,
+
+ // TODO(gw): These fields and perhaps others can become private once the
+ // frame_graph / render_task source files are unified / cleaned up.
+ pub free_after: PassId,
+ pub render_on: PassId,
+
+ /// The gpu cache handle for the render task's destination rect.
+ ///
+ /// Will be set to None if the render task is cached, in which case the texture cache
+ /// manages the handle.
+ pub uv_rect_handle: GpuCacheHandle,
+ pub cache_handle: Option<RenderTaskCacheEntryHandle>,
+ uv_rect_kind: UvRectKind,
+}
+
+impl RenderTask {
+ pub fn new(
+ location: RenderTaskLocation,
+ kind: RenderTaskKind,
+ ) -> Self {
+ render_task_sanity_check(&location.size());
+
+ RenderTask {
+ location,
+ children: TaskDependencies::new(),
+ kind,
+ free_after: PassId::MAX,
+ render_on: PassId::MIN,
+ uv_rect_handle: GpuCacheHandle::new(),
+ uv_rect_kind: UvRectKind::Rect,
+ cache_handle: None,
+ }
+ }
+
+ pub fn new_dynamic(
+ size: DeviceIntSize,
+ kind: RenderTaskKind,
+ ) -> Self {
+ RenderTask::new(
+ RenderTaskLocation::Unallocated { size },
+ kind,
+ )
+ }
+
+ pub fn with_uv_rect_kind(mut self, uv_rect_kind: UvRectKind) -> Self {
+ self.uv_rect_kind = uv_rect_kind;
+ self
+ }
+
+ pub fn new_image(
+ size: DeviceIntSize,
+ request: ImageRequest,
+ ) -> Self {
+ // Note: this is a special constructor for image render tasks that does not
+ // do the render task size sanity check. This is because with SWGL we purposefully
+ // avoid tiling large images. There is no upload with SWGL so whatever was
+ // successfully allocated earlier will be what shaders read, regardless of the size
+ // and copying into tiles would only slow things down.
+ // As a result we can run into very large images being added to the frame graph
+ // (this is covered by a few reftests on the CI).
+
+ RenderTask {
+ location: RenderTaskLocation::CacheRequest { size, },
+ children: TaskDependencies::new(),
+ kind: RenderTaskKind::Image(request),
+ free_after: PassId::MAX,
+ render_on: PassId::MIN,
+ uv_rect_handle: GpuCacheHandle::new(),
+ uv_rect_kind: UvRectKind::Rect,
+ cache_handle: None,
+ }
+ }
+
+
+ #[cfg(test)]
+ pub fn new_test(
+ location: RenderTaskLocation,
+ target: RenderTargetKind,
+ ) -> Self {
+ RenderTask {
+ location,
+ children: TaskDependencies::new(),
+ kind: RenderTaskKind::Test(target),
+ free_after: PassId::MAX,
+ render_on: PassId::MIN,
+ uv_rect_handle: GpuCacheHandle::new(),
+ uv_rect_kind: UvRectKind::Rect,
+ cache_handle: None,
+ }
+ }
+
+ pub fn new_blit(
+ size: DeviceIntSize,
+ source: RenderTaskId,
+ rg_builder: &mut RenderTaskGraphBuilder,
+ ) -> RenderTaskId {
+ // If this blit uses a render task as a source,
+ // ensure it's added as a child task. This will
+ // ensure it gets allocated in the correct pass
+ // and made available as an input when this task
+ // executes.
+
+ let blit_task_id = rg_builder.add().init(RenderTask::new_dynamic(
+ size,
+ RenderTaskKind::Blit(BlitTask { source }),
+ ));
+
+ rg_builder.add_dependency(blit_task_id, source);
+
+ blit_task_id
+ }
+
+ // Construct a render task to apply a blur to a primitive.
+ // The render task chain that is constructed looks like:
+ //
+ // PrimitiveCacheTask: Draw the primitives.
+ // ^
+ // |
+ // DownscalingTask(s): Each downscaling task reduces the size of render target to
+ // ^ half. Also reduce the std deviation to half until the std
+ // | deviation less than 4.0.
+ // |
+ // |
+ // VerticalBlurTask: Apply the separable vertical blur to the primitive.
+ // ^
+ // |
+ // HorizontalBlurTask: Apply the separable horizontal blur to the vertical blur.
+ // |
+ // +---- This is stored as the input task to the primitive shader.
+ //
+ pub fn new_blur(
+ blur_std_deviation: DeviceSize,
+ src_task_id: RenderTaskId,
+ rg_builder: &mut RenderTaskGraphBuilder,
+ target_kind: RenderTargetKind,
+ mut blur_cache: Option<&mut BlurTaskCache>,
+ blur_region: DeviceIntSize,
+ ) -> RenderTaskId {
+ // Adjust large std deviation value.
+ let mut adjusted_blur_std_deviation = blur_std_deviation;
+ let (blur_target_size, uv_rect_kind) = {
+ let src_task = rg_builder.get_task(src_task_id);
+ (src_task.location.size(), src_task.uv_rect_kind())
+ };
+ let mut adjusted_blur_target_size = blur_target_size;
+ let mut downscaling_src_task_id = src_task_id;
+ let mut scale_factor = 1.0;
+ let mut n_downscales = 1;
+ while adjusted_blur_std_deviation.width > MAX_BLUR_STD_DEVIATION &&
+ adjusted_blur_std_deviation.height > MAX_BLUR_STD_DEVIATION {
+ if adjusted_blur_target_size.width < MIN_DOWNSCALING_RT_SIZE ||
+ adjusted_blur_target_size.height < MIN_DOWNSCALING_RT_SIZE {
+ break;
+ }
+ adjusted_blur_std_deviation = adjusted_blur_std_deviation * 0.5;
+ scale_factor *= 2.0;
+ adjusted_blur_target_size = (blur_target_size.to_f32() / scale_factor).to_i32();
+
+ let cached_task = match blur_cache {
+ Some(ref mut cache) => cache.get(&BlurTaskKey::DownScale(n_downscales)).cloned(),
+ None => None,
+ };
+
+ downscaling_src_task_id = cached_task.unwrap_or_else(|| {
+ RenderTask::new_scaling(
+ downscaling_src_task_id,
+ rg_builder,
+ target_kind,
+ adjusted_blur_target_size,
+ )
+ });
+
+ if let Some(ref mut cache) = blur_cache {
+ cache.insert(BlurTaskKey::DownScale(n_downscales), downscaling_src_task_id);
+ }
+
+ n_downscales += 1;
+ }
+
+
+ let blur_key = BlurTaskKey::downscale_and_blur(n_downscales, adjusted_blur_std_deviation);
+
+ let cached_task = match blur_cache {
+ Some(ref mut cache) => cache.get(&blur_key).cloned(),
+ None => None,
+ };
+
+ let blur_region = blur_region / (scale_factor as i32);
+
+ let blur_task_id = cached_task.unwrap_or_else(|| {
+ let blur_task_v = rg_builder.add().init(RenderTask::new_dynamic(
+ adjusted_blur_target_size,
+ RenderTaskKind::VerticalBlur(BlurTask {
+ blur_std_deviation: adjusted_blur_std_deviation.height,
+ target_kind,
+ blur_region,
+ }),
+ ).with_uv_rect_kind(uv_rect_kind));
+ rg_builder.add_dependency(blur_task_v, downscaling_src_task_id);
+
+ let task_id = rg_builder.add().init(RenderTask::new_dynamic(
+ adjusted_blur_target_size,
+ RenderTaskKind::HorizontalBlur(BlurTask {
+ blur_std_deviation: adjusted_blur_std_deviation.width,
+ target_kind,
+ blur_region,
+ }),
+ ).with_uv_rect_kind(uv_rect_kind));
+ rg_builder.add_dependency(task_id, blur_task_v);
+
+ task_id
+ });
+
+ if let Some(ref mut cache) = blur_cache {
+ cache.insert(blur_key, blur_task_id);
+ }
+
+ blur_task_id
+ }
+
+ pub fn new_scaling(
+ src_task_id: RenderTaskId,
+ rg_builder: &mut RenderTaskGraphBuilder,
+ target_kind: RenderTargetKind,
+ size: DeviceIntSize,
+ ) -> RenderTaskId {
+ Self::new_scaling_with_padding(
+ src_task_id,
+ rg_builder,
+ target_kind,
+ size,
+ DeviceIntSideOffsets::zero(),
+ )
+ }
+
+ pub fn new_scaling_with_padding(
+ source: RenderTaskId,
+ rg_builder: &mut RenderTaskGraphBuilder,
+ target_kind: RenderTargetKind,
+ padded_size: DeviceIntSize,
+ padding: DeviceIntSideOffsets,
+ ) -> RenderTaskId {
+ let uv_rect_kind = rg_builder.get_task(source).uv_rect_kind();
+
+ let task_id = rg_builder.add().init(
+ RenderTask::new_dynamic(
+ padded_size,
+ RenderTaskKind::Scaling(ScalingTask {
+ target_kind,
+ padding,
+ }),
+ ).with_uv_rect_kind(uv_rect_kind)
+ );
+
+ rg_builder.add_dependency(task_id, source);
+
+ task_id
+ }
+
+ pub fn new_svg_filter(
+ filter_primitives: &[FilterPrimitive],
+ filter_datas: &[SFilterData],
+ rg_builder: &mut RenderTaskGraphBuilder,
+ content_size: DeviceIntSize,
+ uv_rect_kind: UvRectKind,
+ original_task_id: RenderTaskId,
+ device_pixel_scale: DevicePixelScale,
+ ) -> RenderTaskId {
+
+ if filter_primitives.is_empty() {
+ return original_task_id;
+ }
+
+ // Resolves the input to a filter primitive
+ let get_task_input = |
+ input: &FilterPrimitiveInput,
+ filter_primitives: &[FilterPrimitive],
+ rg_builder: &mut RenderTaskGraphBuilder,
+ cur_index: usize,
+ outputs: &[RenderTaskId],
+ original: RenderTaskId,
+ color_space: ColorSpace,
+ | {
+ // TODO(cbrewster): Not sure we can assume that the original input is sRGB.
+ let (mut task_id, input_color_space) = match input.to_index(cur_index) {
+ Some(index) => (outputs[index], filter_primitives[index].color_space),
+ None => (original, ColorSpace::Srgb),
+ };
+
+ match (input_color_space, color_space) {
+ (ColorSpace::Srgb, ColorSpace::LinearRgb) => {
+ task_id = RenderTask::new_svg_filter_primitive(
+ smallvec![task_id],
+ content_size,
+ uv_rect_kind,
+ SvgFilterInfo::SrgbToLinear,
+ rg_builder,
+ );
+ },
+ (ColorSpace::LinearRgb, ColorSpace::Srgb) => {
+ task_id = RenderTask::new_svg_filter_primitive(
+ smallvec![task_id],
+ content_size,
+ uv_rect_kind,
+ SvgFilterInfo::LinearToSrgb,
+ rg_builder,
+ );
+ },
+ _ => {},
+ }
+
+ task_id
+ };
+
+ let mut outputs = vec![];
+ let mut cur_filter_data = 0;
+ for (cur_index, primitive) in filter_primitives.iter().enumerate() {
+ let render_task_id = match primitive.kind {
+ FilterPrimitiveKind::Identity(ref identity) => {
+ // Identity does not create a task, it provides its input's render task
+ get_task_input(
+ &identity.input,
+ filter_primitives,
+ rg_builder,
+ cur_index,
+ &outputs,
+ original_task_id,
+ primitive.color_space
+ )
+ }
+ FilterPrimitiveKind::Blend(ref blend) => {
+ let input_1_task_id = get_task_input(
+ &blend.input1,
+ filter_primitives,
+ rg_builder,
+ cur_index,
+ &outputs,
+ original_task_id,
+ primitive.color_space
+ );
+ let input_2_task_id = get_task_input(
+ &blend.input2,
+ filter_primitives,
+ rg_builder,
+ cur_index,
+ &outputs,
+ original_task_id,
+ primitive.color_space
+ );
+
+ RenderTask::new_svg_filter_primitive(
+ smallvec![input_1_task_id, input_2_task_id],
+ content_size,
+ uv_rect_kind,
+ SvgFilterInfo::Blend(blend.mode),
+ rg_builder,
+ )
+ },
+ FilterPrimitiveKind::Flood(ref flood) => {
+ RenderTask::new_svg_filter_primitive(
+ smallvec![],
+ content_size,
+ uv_rect_kind,
+ SvgFilterInfo::Flood(flood.color),
+ rg_builder,
+ )
+ }
+ FilterPrimitiveKind::Blur(ref blur) => {
+ let width_std_deviation = blur.width * device_pixel_scale.0;
+ let height_std_deviation = blur.height * device_pixel_scale.0;
+ let input_task_id = get_task_input(
+ &blur.input,
+ filter_primitives,
+ rg_builder,
+ cur_index,
+ &outputs,
+ original_task_id,
+ primitive.color_space
+ );
+
+ RenderTask::new_blur(
+ DeviceSize::new(width_std_deviation, height_std_deviation),
+ // TODO: This is a hack to ensure that a blur task's input is always
+ // in the blur's previous pass.
+ RenderTask::new_svg_filter_primitive(
+ smallvec![input_task_id],
+ content_size,
+ uv_rect_kind,
+ SvgFilterInfo::Identity,
+ rg_builder,
+ ),
+ rg_builder,
+ RenderTargetKind::Color,
+ None,
+ content_size,
+ )
+ }
+ FilterPrimitiveKind::Opacity(ref opacity) => {
+ let input_task_id = get_task_input(
+ &opacity.input,
+ filter_primitives,
+ rg_builder,
+ cur_index,
+ &outputs,
+ original_task_id,
+ primitive.color_space
+ );
+
+ RenderTask::new_svg_filter_primitive(
+ smallvec![input_task_id],
+ content_size,
+ uv_rect_kind,
+ SvgFilterInfo::Opacity(opacity.opacity),
+ rg_builder,
+ )
+ }
+ FilterPrimitiveKind::ColorMatrix(ref color_matrix) => {
+ let input_task_id = get_task_input(
+ &color_matrix.input,
+ filter_primitives,
+ rg_builder,
+ cur_index,
+ &outputs,
+ original_task_id,
+ primitive.color_space
+ );
+
+ RenderTask::new_svg_filter_primitive(
+ smallvec![input_task_id],
+ content_size,
+ uv_rect_kind,
+ SvgFilterInfo::ColorMatrix(Box::new(color_matrix.matrix)),
+ rg_builder,
+ )
+ }
+ FilterPrimitiveKind::DropShadow(ref drop_shadow) => {
+ let input_task_id = get_task_input(
+ &drop_shadow.input,
+ filter_primitives,
+ rg_builder,
+ cur_index,
+ &outputs,
+ original_task_id,
+ primitive.color_space
+ );
+
+ let blur_std_deviation = drop_shadow.shadow.blur_radius * device_pixel_scale.0;
+ let offset = drop_shadow.shadow.offset * LayoutToWorldScale::new(1.0) * device_pixel_scale;
+
+ let offset_task_id = RenderTask::new_svg_filter_primitive(
+ smallvec![input_task_id],
+ content_size,
+ uv_rect_kind,
+ SvgFilterInfo::Offset(offset),
+ rg_builder,
+ );
+
+ let blur_task_id = RenderTask::new_blur(
+ DeviceSize::new(blur_std_deviation, blur_std_deviation),
+ offset_task_id,
+ rg_builder,
+ RenderTargetKind::Color,
+ None,
+ content_size,
+ );
+
+ RenderTask::new_svg_filter_primitive(
+ smallvec![input_task_id, blur_task_id],
+ content_size,
+ uv_rect_kind,
+ SvgFilterInfo::DropShadow(drop_shadow.shadow.color),
+ rg_builder,
+ )
+ }
+ FilterPrimitiveKind::ComponentTransfer(ref component_transfer) => {
+ let input_task_id = get_task_input(
+ &component_transfer.input,
+ filter_primitives,
+ rg_builder,
+ cur_index,
+ &outputs,
+ original_task_id,
+ primitive.color_space
+ );
+
+ let filter_data = &filter_datas[cur_filter_data];
+ cur_filter_data += 1;
+ if filter_data.is_identity() {
+ input_task_id
+ } else {
+ RenderTask::new_svg_filter_primitive(
+ smallvec![input_task_id],
+ content_size,
+ uv_rect_kind,
+ SvgFilterInfo::ComponentTransfer(filter_data.clone()),
+ rg_builder,
+ )
+ }
+ }
+ FilterPrimitiveKind::Offset(ref info) => {
+ let input_task_id = get_task_input(
+ &info.input,
+ filter_primitives,
+ rg_builder,
+ cur_index,
+ &outputs,
+ original_task_id,
+ primitive.color_space
+ );
+
+ let offset = info.offset * LayoutToWorldScale::new(1.0) * device_pixel_scale;
+ RenderTask::new_svg_filter_primitive(
+ smallvec![input_task_id],
+ content_size,
+ uv_rect_kind,
+ SvgFilterInfo::Offset(offset),
+ rg_builder,
+ )
+ }
+ FilterPrimitiveKind::Composite(info) => {
+ let input_1_task_id = get_task_input(
+ &info.input1,
+ filter_primitives,
+ rg_builder,
+ cur_index,
+ &outputs,
+ original_task_id,
+ primitive.color_space
+ );
+ let input_2_task_id = get_task_input(
+ &info.input2,
+ filter_primitives,
+ rg_builder,
+ cur_index,
+ &outputs,
+ original_task_id,
+ primitive.color_space
+ );
+
+ RenderTask::new_svg_filter_primitive(
+ smallvec![input_1_task_id, input_2_task_id],
+ content_size,
+ uv_rect_kind,
+ SvgFilterInfo::Composite(info.operator),
+ rg_builder,
+ )
+ }
+ };
+ outputs.push(render_task_id);
+ }
+
+ // The output of a filter is the output of the last primitive in the chain.
+ let mut render_task_id = *outputs.last().unwrap();
+
+ // Convert to sRGB if needed
+ if filter_primitives.last().unwrap().color_space == ColorSpace::LinearRgb {
+ render_task_id = RenderTask::new_svg_filter_primitive(
+ smallvec![render_task_id],
+ content_size,
+ uv_rect_kind,
+ SvgFilterInfo::LinearToSrgb,
+ rg_builder,
+ );
+ }
+
+ render_task_id
+ }
+
+ pub fn new_svg_filter_primitive(
+ tasks: TaskDependencies,
+ target_size: DeviceIntSize,
+ uv_rect_kind: UvRectKind,
+ info: SvgFilterInfo,
+ rg_builder: &mut RenderTaskGraphBuilder,
+ ) -> RenderTaskId {
+ let task_id = rg_builder.add().init(RenderTask::new_dynamic(
+ target_size,
+ RenderTaskKind::SvgFilter(SvgFilterTask {
+ extra_gpu_cache_handle: None,
+ info,
+ }),
+ ).with_uv_rect_kind(uv_rect_kind));
+
+ for child_id in tasks {
+ rg_builder.add_dependency(task_id, child_id);
+ }
+
+ task_id
+ }
+
+ pub fn uv_rect_kind(&self) -> UvRectKind {
+ self.uv_rect_kind
+ }
+
+ pub fn get_texture_address(&self, gpu_cache: &GpuCache) -> GpuCacheAddress {
+ gpu_cache.get_address(&self.uv_rect_handle)
+ }
+
+ pub fn get_target_texture(&self) -> CacheTextureId {
+ match self.location {
+ RenderTaskLocation::Dynamic { texture_id, .. } => {
+ assert_ne!(texture_id, CacheTextureId::INVALID);
+ texture_id
+ }
+ RenderTaskLocation::Existing { .. } |
+ RenderTaskLocation::CacheRequest { .. } |
+ RenderTaskLocation::Unallocated { .. } |
+ RenderTaskLocation::Static { .. } => {
+ unreachable!();
+ }
+ }
+ }
+
+ pub fn get_texture_source(&self) -> TextureSource {
+ match self.location {
+ RenderTaskLocation::Dynamic { texture_id, .. } => {
+ assert_ne!(texture_id, CacheTextureId::INVALID);
+ TextureSource::TextureCache(texture_id, Swizzle::default())
+ }
+ RenderTaskLocation::Static { surface: StaticRenderTaskSurface::ReadOnly { source }, .. } => {
+ source
+ }
+ RenderTaskLocation::Static { surface: StaticRenderTaskSurface::TextureCache { texture, .. }, .. } => {
+ TextureSource::TextureCache(texture, Swizzle::default())
+ }
+ RenderTaskLocation::Existing { .. } |
+ RenderTaskLocation::Static { .. } |
+ RenderTaskLocation::CacheRequest { .. } |
+ RenderTaskLocation::Unallocated { .. } => {
+ unreachable!();
+ }
+ }
+ }
+
+ pub fn get_target_rect(&self) -> DeviceIntRect {
+ match self.location {
+ // Previously, we only added render tasks after the entire
+ // primitive chain was determined visible. This meant that
+ // we could assert any render task in the list was also
+ // allocated (assigned to passes). Now, we add render
+ // tasks earlier, and the picture they belong to may be
+ // culled out later, so we can't assert that the task
+ // has been allocated.
+ // Render tasks that are created but not assigned to
+ // passes consume a row in the render task texture, but
+ // don't allocate any space in render targets nor
+ // draw any pixels.
+ // TODO(gw): Consider some kind of tag or other method
+ // to mark a task as unused explicitly. This
+ // would allow us to restore this debug check.
+ RenderTaskLocation::Dynamic { rect, .. } => rect,
+ RenderTaskLocation::Static { rect, .. } => rect,
+ RenderTaskLocation::Existing { .. } |
+ RenderTaskLocation::CacheRequest { .. } |
+ RenderTaskLocation::Unallocated { .. } => {
+ panic!("bug: get_target_rect called before allocating");
+ }
+ }
+ }
+
+ pub fn target_kind(&self) -> RenderTargetKind {
+ self.kind.target_kind()
+ }
+
+ pub fn write_gpu_blocks(
+ &mut self,
+ target_rect: DeviceIntRect,
+ gpu_cache: &mut GpuCache,
+ ) {
+ profile_scope!("write_gpu_blocks");
+
+ self.kind.write_gpu_blocks(gpu_cache);
+
+ if self.cache_handle.is_some() {
+ // The uv rect handle of cached render tasks is requested and set by the
+ // render task cache.
+ return;
+ }
+
+ if let Some(mut request) = gpu_cache.request(&mut self.uv_rect_handle) {
+ let p0 = target_rect.min.to_f32();
+ let p1 = target_rect.max.to_f32();
+ let image_source = ImageSource {
+ p0,
+ p1,
+ user_data: [0.0; 4],
+ uv_rect_kind: self.uv_rect_kind,
+ };
+ image_source.write_gpu_blocks(&mut request);
+ }
+ }
+
+ /// Called by the render task cache.
+ ///
+ /// Tells the render task that it is cached (which means its gpu cache
+ /// handle is managed by the texture cache).
+ pub fn mark_cached(&mut self, handle: RenderTaskCacheEntryHandle) {
+ self.cache_handle = Some(handle);
+ }
+}