/* This Source Code Form is subject to the terms of the Mozilla Public *License,v.2.0.IfacopyoftheMPLwasnotdistributedwiththis
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
use api::{LineStyle, LineOrientation, ColorF, FilterOpGraphPictureBufferId}; use api::{MAX_RENDER_TASK_SIZE, SVGFE_GRAPH_MAX}; use api::units::*; use std::time::Duration; usecrate::box_shadow::BLUR_SAMPLE_SCALE; usecrate::render_task_graph::SubTaskRange; usecrate::clip::ClipNodeRange; usecrate::command_buffer::{CommandBufferIndex, QuadFlags}; usecrate::pattern::{PatternKind, PatternShaderInput}; usecrate::profiler::{add_text_marker}; usecrate::spatial_tree::SpatialNodeIndex; usecrate::frame_builder::FrameBuilderConfig; usecrate::gpu_types::{BorderInstance, UvRectKind, BlurEdgeMode, ClipSpace}; usecrate::internal_types::{CacheTextureId, FastHashMap, TextureSource, Swizzle}; usecrate::svg_filter::{FilterGraphNode, FilterGraphOp, FilterGraphPictureReference, SVGFE_CONVOLVE_VALUES_LIMIT}; usecrate::picture::ResolvedSurfaceTexture; usecrate::tile_cache::MAX_SURFACE_SIZE; usecrate::transform::GpuTransformId; usecrate::prim_store::ClipData; usecrate::resource_cache::ImageRequest; use std::{usize, f32, i32, u32}; usecrate::renderer::{GpuBufferAddress, GpuBufferBuilder, GpuBufferBuilderF}; usecrate::render_backend::DataStores; usecrate::render_target::{ResolveOp, RenderTargetKind}; usecrate::render_task_graph::{PassId, RenderTaskId, RenderTaskGraphBuilder}; usecrate::render_task_cache::RenderTaskCacheEntryHandle; usecrate::segment::EdgeMask; use smallvec::SmallVec;
impl Into<RenderTaskAddress> for RenderTaskId { fn into(self) -> RenderTaskAddress {
RenderTaskAddress(self.index as i32)
}
}
/// 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))] pubenum 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))] pubenum 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. pubfn is_dynamic(&self) -> bool { match *self {
RenderTaskLocation::Dynamic { .. } => true,
_ => false,
}
}
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 pubfn duplicate(
&self,
cmd_buffer_index: CommandBufferIndex,
) -> Self {
assert_eq!(self.resolve_op, None);
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. pubfn adjusted_blur_source_size(original_size: DeviceSize, mut std_dev: DeviceSize) -> DeviceSize { letmut adjusted_size = original_size; letmut 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();
}
#[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pubstruct 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>,
}
// 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. pubfn 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 = matchself {
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
DevicePixelScale::identity().0,
task.content_origin.x,
task.content_origin.y, 0.0,
]
}
RenderTaskKind::Empty(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(_) |
RenderTaskKind::HorizontalBlur(_) => { // TODO(gw): Make this match Picture tasks so that we can draw // sub-passes on them to apply box-shadow masks.
[ 0.0, 0.0, 0.0, 0.0,
]
}
RenderTaskKind::Image(..) |
RenderTaskKind::Cached(..) |
RenderTaskKind::Readback(..) |
RenderTaskKind::Scaling(..) |
RenderTaskKind::Border(..) |
RenderTaskKind::LineDecoration(..) |
RenderTaskKind::TileComposite(..) |
RenderTaskKind::Blit(..) => {
[0.0; 4]
}
RenderTaskKind::SVGFENode(_task) => { // we don't currently use this for SVGFE filters. // see SVGFEFilterInstance instead
[0.0; 4]
}
/// 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. pubtype 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)] pubenum 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. pubtype TaskDependencies = SmallVec<[RenderTaskId;2]>;
// 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: GpuBufferAddress, pub cache_handle: Option<RenderTaskCacheEntryHandle>, pub uv_rect_kind: UvRectKind,
}
pubfn new_image(
size: DeviceIntSize,
request: ImageRequest,
is_composited: bool,
) -> 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).
pubfn new_blit(
size: DeviceIntSize,
source: RenderTaskId,
source_rect: DeviceIntRect,
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.
/// Creates render tasks from PictureCompositeMode::SVGFEGraph. /// /// The interesting parts of the handling of SVG filters are: /// * scene_building.rs : wrap_prim_with_filters /// * picture.rs : get_coverage_svgfe /// * render_task.rs : new_svg_filter_graph (you are here) /// * render_target.rs : add_svg_filter_node_instances pubfn new_svg_filter_graph(
filter_nodes: &[(FilterGraphNode, FilterGraphOp)],
rg_builder: &mut RenderTaskGraphBuilder,
gpu_buffer: &mut GpuBufferBuilderF,
data_stores: &mut DataStores,
_uv_rect_kind: UvRectKind,
original_task_id: RenderTaskId,
source_subregion: LayoutRect,
target_subregion: LayoutRect,
prim_subregion: LayoutRect,
subregion_to_device_scale_x: f32,
subregion_to_device_scale_y: f32,
subregion_to_device_offset_x: f32,
subregion_to_device_offset_y: f32,
) -> RenderTaskId { const BUFFER_LIMIT: usize = SVGFE_GRAPH_MAX; letmut task_by_buffer_id: [RenderTaskId; BUFFER_LIMIT] = [RenderTaskId::INVALID; BUFFER_LIMIT]; letmut subregion_by_buffer_id: [LayoutRect; BUFFER_LIMIT] = [LayoutRect::zero(); BUFFER_LIMIT]; // If nothing replaces this value (all node subregions are empty), we // can just return the original picture letmut output_task_id = original_task_id;
// By this point we assume the following about the graph: // * BUFFER_LIMIT here should be >= BUFFER_LIMIT in the scene_building.rs code. // * input buffer id < output buffer id // * output buffer id between 0 and BUFFER_LIMIT // * the number of filter_datas matches the number of kept nodes with op // SVGFEComponentTransfer. // // These assumptions are verified with asserts in this function as // appropriate.
// Make a UvRectKind::Quad that represents a task for a node, which may // have an inflate border, must be a Quad because the surface_rects // compositing shader expects it to be one, we don't actually use this // internally as we use subregions, see calculate_uv_rect_kind for how // this works, it projects from clipped rect to unclipped rect, where // our clipped rect is simply task_size minus the inflate, and unclipped // is our full task_size fn uv_rect_kind_for_task_size(clipped: DeviceRect, unclipped: DeviceRect) -> UvRectKind { let scale_x = 1.0 / clipped.width(); let scale_y = 1.0 / clipped.height();
UvRectKind::Quad{
top_left: DeviceHomogeneousVector::new(
(unclipped.min.x - clipped.min.x) * scale_x,
(unclipped.min.y - clipped.min.y) * scale_y, 0.0, 1.0),
top_right: DeviceHomogeneousVector::new(
(unclipped.max.x - clipped.min.x) * scale_x,
(unclipped.min.y - clipped.min.y) * scale_y, 0.0, 1.0),
bottom_left: DeviceHomogeneousVector::new(
(unclipped.min.x - clipped.min.x) * scale_x,
(unclipped.max.y - clipped.min.y) * scale_y, 0.0, 1.0),
bottom_right: DeviceHomogeneousVector::new(
(unclipped.max.x - clipped.min.x) * scale_x,
(unclipped.max.y - clipped.min.y) * scale_y, 0.0, 1.0),
}
}
// Iterate the filter nodes and create tasks letmut made_dependency_on_source = false; for (filter_index, (filter_node, op)) in filter_nodes.iter().enumerate() { let node = &filter_node; let is_output = filter_index == filter_nodes.len() - 1;
// Note that this is never set on the final output by design. if !node.kept_by_optimizer { continue;
}
// Process the inputs and figure out their new subregion, because // the SourceGraphic subregion is smaller than it was in scene build // now that it reflects the invalidation rect // // Also look up the child tasks while we are here. letmut used_subregion = LayoutRect::zero(); letmut combined_input_subregion = LayoutRect::zero(); let node_inputs: Vec<(FilterGraphPictureReference, RenderTaskId)> = node.inputs.iter().map(|input| { let (subregion, task) = match input.buffer_id {
FilterOpGraphPictureBufferId::BufferId(id) => {
(subregion_by_buffer_id[id as usize], task_by_buffer_id[id as usize])
}
FilterOpGraphPictureBufferId::None => { // Task must resolve so we use the SourceGraphic as // a placeholder for these, they don't actually // contribute anything to the output
(LayoutRect::zero(), original_task_id)
}
}; // Convert offset to device coordinates. let offset = LayoutVector2D::new(
(input.offset.x * subregion_to_device_scale_x).round(),
(input.offset.y * subregion_to_device_scale_y).round(),
); // To figure out the portion of the node subregion used by this // source image we need to apply the target padding. Note that // this does not affect the subregion of the input, as that // can't be modified as it is used for placement (offset). let target_padding = input.target_padding
.scale(subregion_to_device_scale_x, subregion_to_device_scale_y)
.round(); let target_subregion =
LayoutRect::new(
LayoutPoint::new(
subregion.min.x + target_padding.min.x,
subregion.min.y + target_padding.min.y,
),
LayoutPoint::new(
subregion.max.x + target_padding.max.x,
subregion.max.y + target_padding.max.y,
),
);
used_subregion = used_subregion.union(&target_subregion);
combined_input_subregion = combined_input_subregion.union(&subregion);
(FilterGraphPictureReference{
buffer_id: input.buffer_id, // Apply offset to the placement of the input subregion.
subregion: subregion.translate(offset),
offset: LayoutVector2D::zero(),
inflate: input.inflate, // Nothing past this point uses the padding.
source_padding: LayoutRect::zero(),
target_padding: LayoutRect::zero(),
}, task)
}).collect();
// Convert subregion from PicturePixels to DevicePixels and round. let full_subregion = node.subregion
.scale(subregion_to_device_scale_x, subregion_to_device_scale_y)
.translate(LayoutVector2D::new(subregion_to_device_offset_x, subregion_to_device_offset_y))
.round();
// Clip the used subregion we calculated from the inputs to fit // within the node's specified subregion, but we want to keep a copy // of the combined input subregion for sizing tasks that involve // blurs as their intermediate stages will have to be downscaled if // very large, and we want that to be at the same alignment as the // node output itself.
used_subregion = used_subregion
.intersection(&full_subregion)
.unwrap_or(LayoutRect::zero())
.round();
// Certain filters need to override the used_subregion directly. match op {
FilterGraphOp::SVGFEBlendColor => {},
FilterGraphOp::SVGFEBlendColorBurn => {},
FilterGraphOp::SVGFEBlendColorDodge => {},
FilterGraphOp::SVGFEBlendDarken => {},
FilterGraphOp::SVGFEBlendDifference => {},
FilterGraphOp::SVGFEBlendExclusion => {},
FilterGraphOp::SVGFEBlendHardLight => {},
FilterGraphOp::SVGFEBlendHue => {},
FilterGraphOp::SVGFEBlendLighten => {},
FilterGraphOp::SVGFEBlendLuminosity => {},
FilterGraphOp::SVGFEBlendMultiply => {},
FilterGraphOp::SVGFEBlendNormal => {},
FilterGraphOp::SVGFEBlendOverlay => {},
FilterGraphOp::SVGFEBlendSaturation => {},
FilterGraphOp::SVGFEBlendScreen => {},
FilterGraphOp::SVGFEBlendSoftLight => {},
FilterGraphOp::SVGFEColorMatrix{values} => { if values[19] > 0.0 { // Manipulating alpha offset can easily create new // pixels outside of input subregions
used_subregion = full_subregion;
}
},
FilterGraphOp::SVGFEComponentTransfer => unreachable!(),
FilterGraphOp::SVGFEComponentTransferInterned{handle: _, creates_pixels} => { // Check if the value of alpha[0] is modified, if so // the whole subregion is used because it will be // creating new pixels outside of input subregions if creates_pixels {
used_subregion = full_subregion;
}
},
FilterGraphOp::SVGFECompositeArithmetic { k1, k2, k3, k4 } => { // Optimize certain cases of Arithmetic operator // // See logic for SVG_FECOMPOSITE_OPERATOR_ARITHMETIC // in FilterSupport.cpp for more information. // // Any other case uses the union of input subregions if k4 > 0.0 { // Can produce pixels anywhere in the subregion.
used_subregion = full_subregion;
} elseif k1 > 0.0 && k2 == 0.0 && k3 == 0.0 { // Can produce pixels where both exist.
used_subregion = full_subregion
.intersection(&node_inputs[0].0.subregion)
.unwrap_or(LayoutRect::zero())
.intersection(&node_inputs[1].0.subregion)
.unwrap_or(LayoutRect::zero());
} elseif k2 > 0.0 && k3 == 0.0 { // Can produce pixels where source exists.
used_subregion = full_subregion
.intersection(&node_inputs[0].0.subregion)
.unwrap_or(LayoutRect::zero());
} elseif k2 == 0.0 && k3 > 0.0 { // Can produce pixels where background exists.
used_subregion = full_subregion
.intersection(&node_inputs[1].0.subregion)
.unwrap_or(LayoutRect::zero());
}
},
FilterGraphOp::SVGFECompositeATop => { // Can only produce pixels where background exists.
used_subregion = full_subregion
.intersection(&node_inputs[1].0.subregion)
.unwrap_or(LayoutRect::zero());
},
FilterGraphOp::SVGFECompositeIn => { // Can only produce pixels where both exist.
used_subregion = used_subregion
.intersection(&node_inputs[0].0.subregion)
.unwrap_or(LayoutRect::zero())
.intersection(&node_inputs[1].0.subregion)
.unwrap_or(LayoutRect::zero());
},
FilterGraphOp::SVGFECompositeLighter => {},
FilterGraphOp::SVGFECompositeOut => { // Can only produce pixels where source exists.
used_subregion = full_subregion
.intersection(&node_inputs[0].0.subregion)
.unwrap_or(LayoutRect::zero());
},
FilterGraphOp::SVGFECompositeOver => {},
FilterGraphOp::SVGFECompositeXOR => {},
FilterGraphOp::SVGFEConvolveMatrixEdgeModeDuplicate{..} => {},
FilterGraphOp::SVGFEConvolveMatrixEdgeModeNone{..} => {},
FilterGraphOp::SVGFEConvolveMatrixEdgeModeWrap{..} => {},
FilterGraphOp::SVGFEDiffuseLightingDistant{..} => {},
FilterGraphOp::SVGFEDiffuseLightingPoint{..} => {},
FilterGraphOp::SVGFEDiffuseLightingSpot{..} => {},
FilterGraphOp::SVGFEDisplacementMap{..} => {},
FilterGraphOp::SVGFEDropShadow{..} => {},
FilterGraphOp::SVGFEFlood { color } => { // Subregion needs to be set to the full node // subregion for fills (unless the fill is a no-op), // we know at this point that it has no inputs, so the // used_region is empty unless we set it here. if color.a > 0.0 {
used_subregion = full_subregion;
}
},
FilterGraphOp::SVGFEIdentity => {},
FilterGraphOp::SVGFEImage { sampling_filter: _sampling_filter, matrix: _matrix } => { // TODO: calculate the actual subregion
used_subregion = full_subregion;
},
FilterGraphOp::SVGFEGaussianBlur{..} => {},
FilterGraphOp::SVGFEMorphologyDilate{..} => {},
FilterGraphOp::SVGFEMorphologyErode{..} => {},
FilterGraphOp::SVGFEOpacity{valuebinding: _valuebinding, value} => { // If fully transparent, we can ignore this node if value <= 0.0 {
used_subregion = LayoutRect::zero();
}
},
FilterGraphOp::SVGFESourceAlpha |
FilterGraphOp::SVGFESourceGraphic => {
used_subregion = source_subregion
.intersection(&full_subregion)
.unwrap_or(LayoutRect::zero());
},
FilterGraphOp::SVGFESpecularLightingDistant{..} => {},
FilterGraphOp::SVGFESpecularLightingPoint{..} => {},
FilterGraphOp::SVGFESpecularLightingSpot{..} => {},
FilterGraphOp::SVGFETile => { if !used_subregion.is_empty() { // This fills the entire target, at least if there are // any input pixels to work with.
used_subregion = full_subregion;
}
},
FilterGraphOp::SVGFEToAlpha => {},
FilterGraphOp::SVGFETurbulenceWithFractalNoiseWithNoStitching{..} |
FilterGraphOp::SVGFETurbulenceWithFractalNoiseWithStitching{..} |
FilterGraphOp::SVGFETurbulenceWithTurbulenceNoiseWithNoStitching{..} |
FilterGraphOp::SVGFETurbulenceWithTurbulenceNoiseWithStitching{..} => { // Turbulence produces pixel values throughout the // node subregion.
used_subregion = full_subregion;
},
}
// SVG spec requires that a later node sampling pixels outside // this node's subregion will receive a transparent black color // for those samples, we achieve this by adding a 1 pixel inflate // around the target rect, which works fine with the // edgemode=duplicate behavior of the texture fetch in the shader, // all of the out of bounds reads are transparent black. // // If this is the output node, we don't apply the inflate, knowing // that the pixels outside of the invalidation rect will not be used // so it is okay if they duplicate outside the view. letmut node_inflate = node.inflate; if is_output { // Use the provided target subregion (invalidation rect)
used_subregion = target_subregion;
node_inflate = 0;
}
// We can't render tasks larger than a certain size, if this node // is too large (particularly with blur padding), we need to render // at a reduced resolution, later nodes can still be full resolution // but for example blurs are not significantly harmed by reduced // resolution in most cases. letmut device_to_render_scale = 1.0; letmut render_to_device_scale = 1.0; letmut subregion = used_subregion; let padded_subregion = match op {
FilterGraphOp::SVGFEGaussianBlur{std_deviation_x, std_deviation_y} |
FilterGraphOp::SVGFEDropShadow{std_deviation_x, std_deviation_y, ..} => {
used_subregion
.inflate(
std_deviation_x.ceil() * BLUR_SAMPLE_SCALE,
std_deviation_y.ceil() * BLUR_SAMPLE_SCALE)
}
_ => used_subregion,
}.union(&combined_input_subregion); while
padded_subregion.scale(device_to_render_scale, device_to_render_scale).round().width() + node_inflate as f32 * 2.0 > MAX_SURFACE_SIZE as f32 ||
padded_subregion.scale(device_to_render_scale, device_to_render_scale).round().height() + node_inflate as f32 * 2.0 > MAX_SURFACE_SIZE as f32 {
device_to_render_scale *= 0.5;
render_to_device_scale *= 2.0; // If the rendering was scaled, we need to snap used_subregion // to the correct granularity or we'd have misaligned sampling // when this is used as an input later.
subregion = used_subregion
.scale(device_to_render_scale, device_to_render_scale)
.round()
.scale(render_to_device_scale, render_to_device_scale);
}
// This is the rect we will be actually producing as a render task, // it is sometimes the case that subregion is empty, but we // must make a task or else the earlier tasks would not be properly // linked into the frametree, causing a leak. let node_task_rect: DeviceRect =
subregion
.scale(device_to_render_scale, device_to_render_scale)
.round()
.inflate(node_inflate as f32, node_inflate as f32)
.cast_unit(); let node_task_size = node_task_rect.to_i32().size(); let node_task_size = if node_task_size.width < 1 || node_task_size.height < 1 {
DeviceIntSize::new(1, 1)
} else {
node_task_size
};
// Make the uv_rect_kind for this node's task to use, this matters // only on the final node because we don't use it internally let node_uv_rect_kind = uv_rect_kind_for_task_size(
subregion
.scale(device_to_render_scale, device_to_render_scale)
.round()
.inflate(node_inflate as f32, node_inflate as f32)
.cast_unit(),
prim_subregion
.scale(device_to_render_scale, device_to_render_scale)
.round()
.inflate(node_inflate as f32, node_inflate as f32)
.cast_unit(),
);
// Create task for this node let task_id; match op {
FilterGraphOp::SVGFEGaussianBlur { std_deviation_x, std_deviation_y } => { // Note: wrap_prim_with_filters copies the SourceGraphic to // a node to apply the transparent border around the image, // we rely on that behavior here as the Blur filter is a // different shader without awareness of the subregion // rules in the SVG spec.
// Find the input task id
assert!(node_inputs.len() == 1); let blur_input = &node_inputs[0].0; let source_task_id = node_inputs[0].1;
// We have to make a copy of the input that is padded with // transparent black for the area outside the subregion, so // that the blur task does not duplicate at the edges let adjusted_blur_std_deviation = DeviceSize::new(
std_deviation_x.clamp(0.0, (i32::MAX / 2) as f32) * device_to_render_scale,
std_deviation_y.clamp(0.0, (i32::MAX / 2) as f32) * device_to_render_scale,
); let blur_subregion = blur_input.subregion
.scale(device_to_render_scale, device_to_render_scale)
.inflate(
adjusted_blur_std_deviation.width * BLUR_SAMPLE_SCALE,
adjusted_blur_std_deviation.height * BLUR_SAMPLE_SCALE)
.round_out(); let blur_task_size = blur_subregion
.size()
.cast_unit()
.max(DeviceSize::new(1.0, 1.0)); // Adjust task size to prevent potential sampling errors let adjusted_blur_task_size =
BlurTask::adjusted_blur_source_size(
blur_task_size,
adjusted_blur_std_deviation,
).max(DeviceSize::new(1.0, 1.0)); // Now change the subregion to match the revised task size, // keeping it centered should keep animated radius smooth. let corner = LayoutPoint::new(
blur_subregion.min.x.floor() + ((
blur_task_size.width -
adjusted_blur_task_size.width) * 0.5).floor(),
blur_subregion.min.y.floor() + ((
blur_task_size.height -
adjusted_blur_task_size.height) * 0.5).floor(),
); // Recalculate the blur_subregion to match, and if render // scale is used, undo that so it is in the same subregion // coordinate system as the node let blur_subregion =
LayoutRect::new(
corner,
LayoutPoint::new(
corner.x + adjusted_blur_task_size.width,
corner.y + adjusted_blur_task_size.height,
),
)
.scale(render_to_device_scale, render_to_device_scale);
let input_subregion_task_id = rg_builder.add().init(RenderTask::new_dynamic(
adjusted_blur_task_size.to_i32(),
RenderTaskKind::SVGFENode(
SVGFEFilterTask{
node: FilterGraphNode{
kept_by_optimizer: true,
linear: false,
inflate: 0,
inputs: [blur_input.clone()].to_vec(),
subregion: blur_subregion,
},
op: FilterGraphOp::SVGFEIdentity,
content_origin: DevicePoint::zero(),
extra_gpu_data: None,
}
),
).with_uv_rect_kind(UvRectKind::Rect)); // Adding the dependencies sets the inputs for this task
rg_builder.add_dependency(input_subregion_task_id, source_task_id);
// TODO: We should do this blur in the correct // colorspace, linear=true is the default in SVG and // new_blur does not currently support it. If the nodes // that consume the result only use the alpha channel, it // does not matter, but when they use the RGB it matters. let blur_task_id =
RenderTask::new_blur(
adjusted_blur_std_deviation,
input_subregion_task_id,
rg_builder,
RenderTargetKind::Color,
None,
adjusted_blur_task_size.to_i32(),
BlurEdgeMode::Duplicate,
);
task_id = rg_builder.add().init(RenderTask::new_dynamic(
node_task_size,
RenderTaskKind::SVGFENode(
SVGFEFilterTask{
node: FilterGraphNode{
kept_by_optimizer: true,
linear: node.linear,
inflate: node_inflate,
inputs: [
FilterGraphPictureReference{
buffer_id: blur_input.buffer_id,
subregion: blur_subregion,
inflate: 0,
offset: LayoutVector2D::zero(),
source_padding: LayoutRect::zero(),
target_padding: LayoutRect::zero(),
}].to_vec(),
subregion,
},
op: FilterGraphOp::SVGFEIdentity,
content_origin: node_task_rect.min,
extra_gpu_data: None,
}
),
).with_uv_rect_kind(node_uv_rect_kind)); // Adding the dependencies sets the inputs for this task
rg_builder.add_dependency(task_id, blur_task_id);
}
FilterGraphOp::SVGFEDropShadow { color, dx, dy, std_deviation_x, std_deviation_y } => { // Note: wrap_prim_with_filters copies the SourceGraphic to // a node to apply the transparent border around the image, // we rely on that behavior here as the Blur filter is a // different shader without awareness of the subregion // rules in the SVG spec.
// Find the input task id
assert!(node_inputs.len() == 1); let blur_input = &node_inputs[0].0; let source_task_id = node_inputs[0].1;
// We have to make a copy of the input that is padded with // transparent black for the area outside the subregion, so // that the blur task does not duplicate at the edges let adjusted_blur_std_deviation = DeviceSize::new(
std_deviation_x.clamp(0.0, (i32::MAX / 2) as f32) * device_to_render_scale,
std_deviation_y.clamp(0.0, (i32::MAX / 2) as f32) * device_to_render_scale,
); let blur_subregion = blur_input.subregion
.scale(device_to_render_scale, device_to_render_scale)
.inflate(
adjusted_blur_std_deviation.width * BLUR_SAMPLE_SCALE,
adjusted_blur_std_deviation.height * BLUR_SAMPLE_SCALE)
.round_out(); let blur_task_size = blur_subregion
.size()
.cast_unit()
.max(DeviceSize::new(1.0, 1.0)); // Adjust task size to prevent potential sampling errors let adjusted_blur_task_size =
BlurTask::adjusted_blur_source_size(
blur_task_size,
adjusted_blur_std_deviation,
).max(DeviceSize::new(1.0, 1.0)); // Now change the subregion to match the revised task size, // keeping it centered should keep animated radius smooth. let corner = LayoutPoint::new(
blur_subregion.min.x.floor() + ((
blur_task_size.width -
adjusted_blur_task_size.width) * 0.5).floor(),
blur_subregion.min.y.floor() + ((
blur_task_size.height -
adjusted_blur_task_size.height) * 0.5).floor(),
); // Recalculate the blur_subregion to match, and if render // scale is used, undo that so it is in the same subregion // coordinate system as the node let blur_subregion =
LayoutRect::new(
corner,
LayoutPoint::new(
corner.x + adjusted_blur_task_size.width,
corner.y + adjusted_blur_task_size.height,
),
)
.scale(render_to_device_scale, render_to_device_scale);
// The shadow compositing only cares about alpha channel // which is always linear, so we can blur this in sRGB or // linear color space and the result is the same as we will // be replacing the rgb completely. let blur_task_id =
RenderTask::new_blur(
adjusted_blur_std_deviation,
input_subregion_task_id,
rg_builder,
RenderTargetKind::Color,
None,
adjusted_blur_task_size.to_i32(),
BlurEdgeMode::Duplicate,
);
// Now we make the compositing task, for this we need to put // the blurred shadow image at the correct subregion offset let blur_subregion_translated = blur_subregion
.translate(LayoutVector2D::new(dx, dy));
task_id = rg_builder.add().init(RenderTask::new_dynamic(
node_task_size,
RenderTaskKind::SVGFENode(
SVGFEFilterTask{
node: FilterGraphNode{
kept_by_optimizer: true,
linear: node.linear,
inflate: node_inflate,
inputs: [ // Original picture
*blur_input, // Shadow picture
FilterGraphPictureReference{
buffer_id: blur_input.buffer_id,
subregion: blur_subregion_translated,
inflate: 0,
offset: LayoutVector2D::zero(),
source_padding: LayoutRect::zero(),
target_padding: LayoutRect::zero(),
}].to_vec(),
subregion,
},
op: FilterGraphOp::SVGFEDropShadow{
color, // These parameters don't matter here
dx: 0.0, dy: 0.0,
std_deviation_x: 0.0, std_deviation_y: 0.0,
},
content_origin: node_task_rect.min,
extra_gpu_data: None,
}
),
).with_uv_rect_kind(node_uv_rect_kind)); // Adding the dependencies sets the inputs for this task
rg_builder.add_dependency(task_id, source_task_id);
rg_builder.add_dependency(task_id, blur_task_id);
}
FilterGraphOp::SVGFESourceAlpha |
FilterGraphOp::SVGFESourceGraphic => { // These copy from the original task, we have to synthesize // a fake input binding to make the shader do the copy. In // the case of SourceAlpha the shader will zero the RGB but // we don't have to care about that distinction here.
task_id = rg_builder.add().init(RenderTask::new_dynamic(
node_task_size,
RenderTaskKind::SVGFENode(
SVGFEFilterTask{
node: FilterGraphNode{
kept_by_optimizer: true,
linear: node.linear,
inflate: node_inflate,
inputs: [
FilterGraphPictureReference{
buffer_id: FilterOpGraphPictureBufferId::None, // This is what makes the mapping // actually work.
subregion: source_subregion.cast_unit(),
offset: LayoutVector2D::zero(),
inflate: 0,
source_padding: LayoutRect::zero(),
target_padding: LayoutRect::zero(),
}
].to_vec(),
subregion: source_subregion.cast_unit(),
},
op: op.clone(),
content_origin: source_subregion.min.cast_unit(),
extra_gpu_data: None,
}
),
).with_uv_rect_kind(node_uv_rect_kind));
rg_builder.add_dependency(task_id, original_task_id);
made_dependency_on_source = true;
}
FilterGraphOp::SVGFEComponentTransferInterned { handle, creates_pixels: _ } => { // FIXME: Doing this in prepare_interned_prim_for_render // doesn't seem to be enough, where should it be done? let filter_data = &mut data_stores.filter_data[handle];
filter_data.write_gpu_blocks(gpu_buffer); // ComponentTransfer has a gpu buffer address that we need to // pass along
task_id = rg_builder.add().init(RenderTask::new_dynamic(
node_task_size,
RenderTaskKind::SVGFENode(
SVGFEFilterTask {
node: FilterGraphNode{
kept_by_optimizer: true,
linear: node.linear,
inputs: node_inputs.iter().map(|input| {input.0}).collect(),
subregion,
inflate: node_inflate,
},
op: op.clone(),
content_origin: node_task_rect.min,
extra_gpu_data: Some(filter_data.gpu_buffer_address),
}
),
).with_uv_rect_kind(node_uv_rect_kind));
// Add the dependencies for inputs of this node, which will // be used by add_svg_filter_node_instances later for (_input, input_task) in &node_inputs { if *input_task == original_task_id {
made_dependency_on_source = true;
} if *input_task != RenderTaskId::INVALID {
rg_builder.add_dependency(task_id, *input_task);
}
}
}
_ => { // This is the usual case - zero, one or two inputs that // reference earlier node results.
task_id = rg_builder.add().init(RenderTask::new_dynamic(
node_task_size,
RenderTaskKind::SVGFENode(
SVGFEFilterTask{
node: FilterGraphNode{
kept_by_optimizer: true,
linear: node.linear,
inputs: node_inputs.iter().map(|input| {input.0}).collect(),
subregion,
inflate: node_inflate,
},
op: op.clone(),
content_origin: node_task_rect.min,
extra_gpu_data: None,
}
),
).with_uv_rect_kind(node_uv_rect_kind));
// Add the dependencies for inputs of this node, which will // be used by add_svg_filter_node_instances later for (_input, input_task) in &node_inputs { if *input_task == original_task_id {
made_dependency_on_source = true;
} if *input_task != RenderTaskId::INVALID {
rg_builder.add_dependency(task_id, *input_task);
}
}
}
}
// We track the tasks we created by output buffer id to make it easy // to look them up quickly, since nodes can only depend on previous // nodes in the same list
task_by_buffer_id[filter_index] = task_id;
subregion_by_buffer_id[filter_index] = subregion;
// The final task we create is the output picture.
output_task_id = task_id;
}
// If no tasks referenced the SourceGraphic, we actually have to create // a fake dependency so that it does not leak. if !made_dependency_on_source && output_task_id != original_task_id {
rg_builder.add_dependency(output_task_id, original_task_id);
}
pubfn get_target_rect(&self) -> DeviceIntRect { matchself.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");
}
}
}
/// 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). pubfn mark_cached(&mutself, handle: RenderTaskCacheEntryHandle) { self.cache_handle = Some(handle);
}
}
/// A rendering operation applied on top of a render task. #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pubenum SubTask {
RectangleClip(RectangleClipSubTask),
ImageClip(ImageClipSubTask),
}
/// A (rounded) rectangle clip applied to a render task using the multiply /// blend mode on top of the content being clipped. #[derive(Debug)] #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pubstruct RectangleClipSubTask { /// Quad primitive address for this clip. pub quad_address: GpuBufferAddress, /// Location of the (rounded) rectangle parameters. pub clip_address: GpuBufferAddress, /// The coordinate space of the clip. /// - If primitive space, then the clip's quad primitive is positioned using /// the transform of the primitive being clipped. clip_transform_id is used /// by the shader to map from that space to the clip's local space. /// This is used when the clip and raster space are indifferent coordinate /// systems. /// - If raster space, then the clip's quad primitive is positioned directly /// using rectangles in raster space (no transforms). The clip parameters /// are still potentially in a different space so clip_transform_id is used /// to map from raster to clip space (this transform is assumed to be a /// scale-offset). pub clip_space: ClipSpace, /// Transform applied to the quad primitive of the clip. pub quad_transform_id: GpuTransformId, /// Transform from the quad primitive's space to the clip's local space. pub clip_transform_id: GpuTransformId, pub quad_flags: QuadFlags, pub needs_scissor_rect: bool, pub rounded_rect_fast_path: bool,
}
/// An clip applied to a render task using the multiply blend mode on top of /// the content being clipped. #[derive(Debug)] #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] pubstruct ImageClipSubTask { /// Quad primitive address for this clip. pub quad_address: GpuBufferAddress, /// Transform from the clip's local space to raster space (to position the /// clip's quad primitive). pub quad_transform_id: GpuTransformId, pub src_task: RenderTaskId, pub quad_flags: QuadFlags, pub needs_scissor_rect: bool,
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.