Source code

Revision control

Copy as Markdown

Other Tools

/* 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, PremultipliedColorF, PropertyBinding, PropertyBindingId, SnapshotInfo};
use api::units::*;
use crate::prim_store::image::AdjustedImageSource;
use crate::{render_task_graph::RenderTaskGraphBuilder, renderer::GpuBufferBuilderF};
use crate::box_shadow::BLUR_SAMPLE_SCALE;
use crate::frame_builder::{FrameBuildingContext, FrameBuildingState};
use crate::gpu_types::{BlurEdgeMode, BrushSegmentGpuData, ImageBrushPrimitiveData, UvRectKind};
use crate::intern::ItemUid;
use crate::render_backend::DataStores;
use crate::render_task_graph::RenderTaskId;
use crate::render_target::RenderTargetKind;
use crate::render_task::{BlurTask, RenderTask, BlurTaskCache};
use crate::render_task::RenderTaskKind;
use crate::renderer::{BlendMode, GpuBufferAddress, GpuBufferBuilder};
use crate::space::SpaceMapper;
use crate::spatial_tree::SpatialTree;
use crate::surface::{SurfaceDescriptor, SurfaceInfo, calculate_screen_uv};
use crate::surface::SurfaceIndex;
use crate::svg_filter::{get_coverage_source_svgfe, FilterGraphNodeKey, FilterGraphOpKey};
use crate::util::MaxRect;
use smallvec::SmallVec;
use crate::internal_types::Filter;
use crate::profiler;
use core::time::Duration;
use euclid::Scale;
use api::MixBlendMode;
use crate::filterdata::FilterDataHandle;
use crate::tile_cache::SliceId;
use crate::svg_filter::{FilterGraphNode, FilterGraphOp, get_coverage_target_svgfe};
use crate::picture::BlitReason;
use crate::prim_store::VectorKey;
#[cfg(feature = "capture")]
use serde::Serialize;
/// Specifies how this Picture should be composited
/// onto the target it belongs to.
#[allow(dead_code)]
#[derive(Debug, Clone)]
#[cfg_attr(feature = "capture", derive(Serialize))]
pub enum PictureCompositeMode {
/// Apply CSS mix-blend-mode effect.
MixBlend(MixBlendMode),
/// Apply a CSS filter (except component transfer).
Filter(Filter),
/// Apply a component transfer filter.
ComponentTransferFilter(FilterDataHandle),
/// Draw to intermediate surface, copy straight across. This
/// is used for CSS isolation, and plane splitting.
Blit(BlitReason),
/// Used to cache a picture as a series of tiles.
TileCache {
slice_id: SliceId,
},
/// Apply an SVG filter graph
SVGFEGraph(Vec<(FilterGraphNode, FilterGraphOp)>),
/// A surface that is used as an input to another primitive
IntermediateSurface,
}
impl PictureCompositeMode {
pub fn get_rect(
&self,
surface: &SurfaceInfo,
sub_rect: Option<LayoutRect>,
) -> LayoutRect {
let surface_rect = match sub_rect {
Some(sub_rect) => sub_rect,
None => surface.clipped_local_rect.cast_unit(),
};
match self {
PictureCompositeMode::Filter(Filter::Blur { width, height, should_inflate, .. }) => {
if *should_inflate {
let (width_factor, height_factor) = surface.clamp_blur_radius(*width, *height);
surface_rect.inflate(
width_factor.ceil() * BLUR_SAMPLE_SCALE,
height_factor.ceil() * BLUR_SAMPLE_SCALE,
)
} else {
surface_rect
}
}
PictureCompositeMode::Filter(Filter::DropShadows(ref shadows)) => {
let mut max_blur_radius = 0.0;
for shadow in shadows {
max_blur_radius = f32::max(max_blur_radius, shadow.blur_radius);
}
let (max_blur_radius_x, max_blur_radius_y) = surface.clamp_blur_radius(
max_blur_radius,
max_blur_radius,
);
let blur_inflation_x = max_blur_radius_x * BLUR_SAMPLE_SCALE;
let blur_inflation_y = max_blur_radius_y * BLUR_SAMPLE_SCALE;
surface_rect.inflate(blur_inflation_x, blur_inflation_y)
}
PictureCompositeMode::SVGFEGraph(ref filters) => {
// Return prim_subregion for use in get_local_prim_rect, which
// is the polygon size.
// This must match surface_rects.unclipped_local.
get_coverage_target_svgfe(filters, surface_rect.cast_unit())
}
_ => {
surface_rect
}
}
}
pub fn get_coverage(
&self,
surface: &SurfaceInfo,
sub_rect: Option<LayoutRect>,
) -> LayoutRect {
let surface_rect = match sub_rect {
Some(sub_rect) => sub_rect,
None => surface.clipped_local_rect.cast_unit(),
};
match self {
PictureCompositeMode::Filter(Filter::Blur { width, height, should_inflate, .. }) => {
if *should_inflate {
let (width_factor, height_factor) = surface.clamp_blur_radius(*width, *height);
surface_rect.inflate(
width_factor.ceil() * BLUR_SAMPLE_SCALE,
height_factor.ceil() * BLUR_SAMPLE_SCALE,
)
} else {
surface_rect
}
}
PictureCompositeMode::Filter(Filter::DropShadows(ref shadows)) => {
let mut rect = surface_rect;
for shadow in shadows {
let (blur_radius_x, blur_radius_y) = surface.clamp_blur_radius(
shadow.blur_radius,
shadow.blur_radius,
);
let blur_inflation_x = blur_radius_x * BLUR_SAMPLE_SCALE;
let blur_inflation_y = blur_radius_y * BLUR_SAMPLE_SCALE;
let shadow_rect = surface_rect
.translate(shadow.offset)
.inflate(blur_inflation_x, blur_inflation_y);
rect = rect.union(&shadow_rect);
}
rect
}
PictureCompositeMode::SVGFEGraph(ref filters) => {
// surface_rect may be for source or target, so invalidate based
// on both interpretations
let target_subregion = get_coverage_source_svgfe(filters, surface_rect.cast());
let source_subregion = get_coverage_target_svgfe(filters, surface_rect.cast());
target_subregion.union(&source_subregion)
}
_ => {
surface_rect
}
}
}
pub fn write_gpu_blocks(
&self,
surface: &SurfaceInfo,
gpu_buffers: &mut GpuBufferBuilder,
data_stores: &mut DataStores,
extra_gpu_data: &mut SmallVec<[GpuBufferAddress; 1]>,
) {
// TODO(gw): Almost all of the composite modes below use extra_gpu_data
// to store the same type of data. The exception is the filter
// with a ColorMatrix, which stores the color matrix here. It's
// probably worth tidying this code up to be a bit more consistent.
// Perhaps store the color matrix after the common data, even though
// it's not used by that shader.
match *self {
PictureCompositeMode::TileCache { .. } => {}
PictureCompositeMode::Filter(Filter::Blur { .. }) => {}
PictureCompositeMode::Filter(Filter::DropShadows(ref shadows)) => {
extra_gpu_data.resize(shadows.len(), GpuBufferAddress::INVALID);
for (shadow, extra_handle) in shadows.iter().zip(extra_gpu_data.iter_mut()) {
let mut writer = gpu_buffers.f32.write_blocks(5);
let prim_rect = surface.clipped_local_rect.cast_unit();
// Basic brush primitive header is (see end of prepare_prim_for_render_inner in prim_store.rs)
// [brush specific data]
// [segment_rect, segment data]
let (blur_inflation_x, blur_inflation_y) = surface.clamp_blur_radius(
shadow.blur_radius,
shadow.blur_radius,
);
let shadow_rect = prim_rect.inflate(
blur_inflation_x * BLUR_SAMPLE_SCALE,
blur_inflation_y * BLUR_SAMPLE_SCALE,
).translate(shadow.offset);
// ImageBrush colors
writer.push(&ImageBrushPrimitiveData {
color: shadow.color.premultiplied(),
background_color: PremultipliedColorF::WHITE,
stretch_size: shadow_rect.size(),
});
writer.push(&BrushSegmentGpuData {
local_rect: shadow_rect,
extra_data: [0.0; 4],
});
*extra_handle = writer.finish();
}
}
PictureCompositeMode::Filter(ref filter) => {
match *filter {
Filter::ColorMatrix(ref m) => {
if extra_gpu_data.is_empty() {
extra_gpu_data.push(GpuBufferAddress::INVALID);
}
let mut writer = gpu_buffers.f32.write_blocks(5);
for i in 0..5 {
writer.push_one([m[i*4], m[i*4+1], m[i*4+2], m[i*4+3]]);
}
extra_gpu_data[0] = writer.finish();
}
Filter::Flood(ref color) => {
if extra_gpu_data.is_empty() {
extra_gpu_data.push(GpuBufferAddress::INVALID);
}
let mut writer = gpu_buffers.f32.write_blocks(1);
writer.push_one(color.to_array());
extra_gpu_data[0] = writer.finish();
}
_ => {}
}
}
PictureCompositeMode::ComponentTransferFilter(handle) => {
let filter_data = &mut data_stores.filter_data[handle];
filter_data.write_gpu_blocks(&mut gpu_buffers.f32);
}
PictureCompositeMode::MixBlend(..) |
PictureCompositeMode::Blit(_) |
PictureCompositeMode::IntermediateSurface => {}
PictureCompositeMode::SVGFEGraph(ref filters) => {
// Update interned filter data
for (_node, op) in filters {
match op {
FilterGraphOp::SVGFEComponentTransferInterned { handle, creates_pixels: _ } => {
let filter_data = &mut data_stores.filter_data[*handle];
filter_data.write_gpu_blocks(&mut gpu_buffers.f32);
}
_ => {}
}
}
}
}
}
/// Returns a static str describing the type of PictureCompositeMode (and
/// filter type if applicable)
pub fn kind(&self) -> &'static str {
match *self {
PictureCompositeMode::Blit(..) => "Blit",
PictureCompositeMode::ComponentTransferFilter(..) => "ComponentTransferFilter",
PictureCompositeMode::IntermediateSurface => "IntermediateSurface",
PictureCompositeMode::MixBlend(..) => "MixBlend",
PictureCompositeMode::SVGFEGraph(..) => "SVGFEGraph",
PictureCompositeMode::TileCache{..} => "TileCache",
PictureCompositeMode::Filter(Filter::Blur{..}) => "Filter::Blur",
PictureCompositeMode::Filter(Filter::Brightness(..)) => "Filter::Brightness",
PictureCompositeMode::Filter(Filter::ColorMatrix(..)) => "Filter::ColorMatrix",
PictureCompositeMode::Filter(Filter::ComponentTransfer) => "Filter::ComponentTransfer",
PictureCompositeMode::Filter(Filter::Contrast(..)) => "Filter::Contrast",
PictureCompositeMode::Filter(Filter::DropShadows(..)) => "Filter::DropShadows",
PictureCompositeMode::Filter(Filter::Flood(..)) => "Filter::Flood",
PictureCompositeMode::Filter(Filter::Grayscale(..)) => "Filter::Grayscale",
PictureCompositeMode::Filter(Filter::HueRotate(..)) => "Filter::HueRotate",
PictureCompositeMode::Filter(Filter::Identity) => "Filter::Identity",
PictureCompositeMode::Filter(Filter::Invert(..)) => "Filter::Invert",
PictureCompositeMode::Filter(Filter::LinearToSrgb) => "Filter::LinearToSrgb",
PictureCompositeMode::Filter(Filter::Opacity(..)) => "Filter::Opacity",
PictureCompositeMode::Filter(Filter::Saturate(..)) => "Filter::Saturate",
PictureCompositeMode::Filter(Filter::Sepia(..)) => "Filter::Sepia",
PictureCompositeMode::Filter(Filter::SrgbToLinear) => "Filter::SrgbToLinear",
PictureCompositeMode::Filter(Filter::SVGGraphNode(..)) => "Filter::SVGGraphNode",
}
}
}
pub fn prepare_composite_mode(
composite_mode: &PictureCompositeMode,
surface_index: SurfaceIndex,
parent_surface_index: SurfaceIndex,
surface_rects: &SurfaceAllocInfo,
snapshot: &Option<SnapshotInfo>,
can_use_shared_surface: bool,
frame_context: &FrameBuildingContext,
frame_state: &mut FrameBuildingState,
data_stores: &mut DataStores,
extra_gpu_data: &mut SmallVec<[GpuBufferAddress; 1]>,
) -> (SurfaceDescriptor, [Option<RenderTaskId>; 2]) {
let surface = &frame_state.surfaces[surface_index.0];
let surface_spatial_node_index = surface.surface_spatial_node_index;
let raster_spatial_node_index = surface.raster_spatial_node_index;
let device_pixel_scale = surface.device_pixel_scale;
let primary_render_task_id;
let surface_descriptor;
let mut secondary_render_task_id = None;
match *composite_mode {
PictureCompositeMode::TileCache { .. } => {
unreachable!("handled above");
}
PictureCompositeMode::Filter(Filter::Blur { width, height, edge_mode, .. }) => {
let (width, height) = surface.clamp_blur_radius(width, height);
let width_std_deviation = width * surface.local_scale.0 * device_pixel_scale.0;
let height_std_deviation = height * surface.local_scale.1 * device_pixel_scale.0;
let blur_std_deviation = DeviceSize::new(
width_std_deviation,
height_std_deviation,
);
let original_size = surface_rects.clipped.size();
let adjusted_size = BlurTask::adjusted_blur_source_size(
original_size,
blur_std_deviation,
);
let clear_color = if adjusted_size == original_size {
None
} else {
Some(ColorF::TRANSPARENT)
};
let cmd_buffer_index = frame_state.cmd_buffers.create_cmd_buffer();
let adjusted_size = adjusted_size.to_i32();
let uv_rect_kind = calculate_uv_rect_kind(
DeviceRect::from_origin_and_size(surface_rects.clipped.min, adjusted_size.to_f32()),
surface_rects.unclipped,
);
let picture_task_id = frame_state.rg_builder.add().init(
RenderTask::new_dynamic(
adjusted_size,
RenderTaskKind::new_picture(
adjusted_size,
surface_rects.needs_scissor_rect,
surface_rects.clipped.min,
surface_spatial_node_index,
raster_spatial_node_index,
device_pixel_scale,
None,
None,
clear_color,
cmd_buffer_index,
can_use_shared_surface,
Some(original_size.round().to_i32()),
)
).with_uv_rect_kind(uv_rect_kind)
);
let blur_render_task_id = request_render_task(
frame_state,
snapshot,
&surface_rects,
false,
&mut|rg_builder, _| {
RenderTask::new_blur(
blur_std_deviation,
picture_task_id,
rg_builder,
RenderTargetKind::Color,
None,
original_size.to_i32(),
edge_mode,
)
}
);
primary_render_task_id = blur_render_task_id;
surface_descriptor = SurfaceDescriptor::new_chained(
picture_task_id,
blur_render_task_id,
surface_rects.clipped_local,
);
}
PictureCompositeMode::Filter(Filter::DropShadows(ref shadows)) => {
let surface = &frame_state.surfaces[surface_index.0];
let device_rect = surface_rects.clipped;
let cmd_buffer_index = frame_state.cmd_buffers.create_cmd_buffer();
let picture_task_id = frame_state.rg_builder.add().init(
RenderTask::new_dynamic(
surface_rects.task_size,
RenderTaskKind::new_picture(
surface_rects.task_size,
surface_rects.needs_scissor_rect,
device_rect.min,
surface_spatial_node_index,
raster_spatial_node_index,
device_pixel_scale,
None,
None,
None,
cmd_buffer_index,
can_use_shared_surface,
None,
),
).with_uv_rect_kind(surface_rects.uv_rect_kind)
);
let mut blur_tasks = BlurTaskCache::default();
extra_gpu_data.resize(shadows.len(), GpuBufferAddress::INVALID);
let mut blur_render_task_id = picture_task_id;
for shadow in shadows {
let (blur_radius_x, blur_radius_y) = surface.clamp_blur_radius(
shadow.blur_radius,
shadow.blur_radius,
);
blur_render_task_id = RenderTask::new_blur(
DeviceSize::new(
blur_radius_x * surface.local_scale.0 * device_pixel_scale.0,
blur_radius_y * surface.local_scale.1 * device_pixel_scale.0,
),
picture_task_id,
frame_state.rg_builder,
RenderTargetKind::Color,
Some(&mut blur_tasks),
device_rect.size().to_i32(),
BlurEdgeMode::Duplicate,
);
}
frame_state.surface_builder.add_picture_render_task(picture_task_id);
primary_render_task_id = blur_render_task_id;
secondary_render_task_id = Some(picture_task_id);
surface_descriptor = SurfaceDescriptor::new_chained(
picture_task_id,
blur_render_task_id,
surface_rects.clipped_local,
);
}
PictureCompositeMode::MixBlend(mode) if BlendMode::from_mix_blend_mode(
mode,
frame_context.fb_config.gpu_supports_advanced_blend,
frame_context.fb_config.advanced_blend_is_coherent,
frame_context.fb_config.dual_source_blending_is_supported,
).is_none() => {
let parent_surface = &frame_state.surfaces[parent_surface_index.0];
let map_pic_to_parent = SpaceMapper::new_with_target(
parent_surface.surface_spatial_node_index,
surface_spatial_node_index,
parent_surface.clipping_rect,
frame_context.spatial_tree,
);
let pic_rect = surface.clipped_local_rect;
let pic_in_raster_space = map_pic_to_parent
.map(&pic_rect)
.expect("bug: unable to map mix-blend content into parent");
let backdrop_rect = pic_in_raster_space;
let parent_surface_rect = parent_surface.clipping_rect;
let readback_task_id = match backdrop_rect.intersection(&parent_surface_rect) {
Some(available_rect) => {
let backdrop_rect = parent_surface.map_to_device_rect(
&backdrop_rect,
frame_context.spatial_tree,
);
let available_rect = parent_surface.map_to_device_rect(
&available_rect,
frame_context.spatial_tree,
).round_out();
let backdrop_uv = calculate_uv_rect_kind(
available_rect,
backdrop_rect,
);
frame_state.rg_builder.add().init(
RenderTask::new_dynamic(
available_rect.size().to_i32(),
RenderTaskKind::new_readback(Some(available_rect.min)),
).with_uv_rect_kind(backdrop_uv)
)
}
None => {
frame_state.rg_builder.add().init(
RenderTask::new_dynamic(
DeviceIntSize::new(16, 16),
RenderTaskKind::new_readback(None),
)
)
}
};
frame_state.surface_builder.add_child_render_task(
readback_task_id,
frame_state.rg_builder,
);
secondary_render_task_id = Some(readback_task_id);
let task_size = surface_rects.clipped.size().to_i32();
let cmd_buffer_index = frame_state.cmd_buffers.create_cmd_buffer();
let is_opaque = false;
let render_task_id = request_render_task(
frame_state,
&snapshot,
&surface_rects,
is_opaque,
&mut|rg_builder, _| {
rg_builder.add().init(
RenderTask::new_dynamic(
task_size,
RenderTaskKind::new_picture(
task_size,
surface_rects.needs_scissor_rect,
surface_rects.clipped.min,
surface_spatial_node_index,
raster_spatial_node_index,
device_pixel_scale,
None,
None,
None,
cmd_buffer_index,
can_use_shared_surface,
None,
)
).with_uv_rect_kind(surface_rects.uv_rect_kind)
)
}
);
primary_render_task_id = render_task_id;
surface_descriptor = SurfaceDescriptor::new_simple(
render_task_id,
surface_rects.clipped_local,
);
}
PictureCompositeMode::Filter(..) => {
let cmd_buffer_index = frame_state.cmd_buffers.create_cmd_buffer();
let is_opaque = false;
let render_task_id = request_render_task(
frame_state,
snapshot,
&surface_rects,
is_opaque,
&mut|rg_builder, _| {
rg_builder.add().init(
RenderTask::new_dynamic(
surface_rects.task_size,
RenderTaskKind::new_picture(
surface_rects.task_size,
surface_rects.needs_scissor_rect,
surface_rects.clipped.min,
surface_spatial_node_index,
raster_spatial_node_index,
device_pixel_scale,
None,
None,
None,
cmd_buffer_index,
can_use_shared_surface,
None,
)
).with_uv_rect_kind(surface_rects.uv_rect_kind)
)
},
);
primary_render_task_id = render_task_id;
surface_descriptor = SurfaceDescriptor::new_simple(
render_task_id,
surface_rects.clipped_local,
);
}
PictureCompositeMode::ComponentTransferFilter(..) => {
let cmd_buffer_index = frame_state.cmd_buffers.create_cmd_buffer();
let is_opaque = false;
let render_task_id = request_render_task(
frame_state,
snapshot,
&surface_rects,
is_opaque,
&mut|rg_builder, _| {
rg_builder.add().init(
RenderTask::new_dynamic(
surface_rects.task_size,
RenderTaskKind::new_picture(
surface_rects.task_size,
surface_rects.needs_scissor_rect,
surface_rects.clipped.min,
surface_spatial_node_index,
raster_spatial_node_index,
device_pixel_scale,
None,
None,
None,
cmd_buffer_index,
can_use_shared_surface,
None,
)
).with_uv_rect_kind(surface_rects.uv_rect_kind)
)
}
);
primary_render_task_id = render_task_id;
surface_descriptor = SurfaceDescriptor::new_simple(
render_task_id,
surface_rects.clipped_local,
);
}
PictureCompositeMode::MixBlend(..) |
PictureCompositeMode::Blit(_) => {
let cmd_buffer_index = frame_state.cmd_buffers.create_cmd_buffer();
let is_opaque = false;
let render_task_id = request_render_task(
frame_state,
snapshot,
&surface_rects,
is_opaque,
&mut|rg_builder, _| {
rg_builder.add().init(
RenderTask::new_dynamic(
surface_rects.task_size,
RenderTaskKind::new_picture(
surface_rects.task_size,
surface_rects.needs_scissor_rect,
surface_rects.clipped.min,
surface_spatial_node_index,
raster_spatial_node_index,
device_pixel_scale,
None,
None,
None,
cmd_buffer_index,
can_use_shared_surface,
None,
)
).with_uv_rect_kind(surface_rects.uv_rect_kind)
)
}
);
primary_render_task_id = render_task_id;
surface_descriptor = SurfaceDescriptor::new_simple(
render_task_id,
surface_rects.clipped_local,
);
}
PictureCompositeMode::IntermediateSurface => {
let cmd_buffer_index = frame_state.cmd_buffers.create_cmd_buffer();
let is_opaque = false;
let render_task_id = request_render_task(
frame_state,
snapshot,
&surface_rects,
is_opaque,
&mut|rg_builder, _| {
rg_builder.add().init(
RenderTask::new_dynamic(
surface_rects.task_size,
RenderTaskKind::new_picture(
surface_rects.task_size,
surface_rects.needs_scissor_rect,
surface_rects.clipped.min,
surface_spatial_node_index,
raster_spatial_node_index,
device_pixel_scale,
None,
None,
None,
cmd_buffer_index,
can_use_shared_surface,
None,
)
).with_uv_rect_kind(surface_rects.uv_rect_kind)
)
}
);
primary_render_task_id = render_task_id;
surface_descriptor = SurfaceDescriptor::new_simple(
render_task_id,
surface_rects.clipped_local,
);
}
PictureCompositeMode::SVGFEGraph(ref filters) => {
let cmd_buffer_index = frame_state.cmd_buffers.create_cmd_buffer();
let prim_subregion = surface_rects.unclipped;
let target_subregion = surface_rects.clipped;
let source_subregion = surface_rects.source;
let source_task_size = source_subregion.round_out().size().to_i32();
let source_task_size = if source_task_size.width > 0 && source_task_size.height > 0 {
source_task_size
} else {
DeviceIntSize::new(1,1)
};
let picture_task_id = frame_state.rg_builder.add().init(
RenderTask::new_dynamic(
source_task_size,
RenderTaskKind::new_picture(
source_task_size,
surface_rects.needs_scissor_rect,
source_subregion.min,
surface_spatial_node_index,
raster_spatial_node_index,
device_pixel_scale,
None,
None,
None,
cmd_buffer_index,
can_use_shared_surface,
None,
)
)
);
let subregion_to_device_scale_x = surface_rects.clipped_notsnapped.width() / surface_rects.clipped_local.width();
let subregion_to_device_scale_y = surface_rects.clipped_notsnapped.height() / surface_rects.clipped_local.height();
let subregion_to_device_offset_x = surface_rects.clipped_notsnapped.min.x - (surface_rects.clipped_local.min.x * subregion_to_device_scale_x).floor();
let subregion_to_device_offset_y = surface_rects.clipped_notsnapped.min.y - (surface_rects.clipped_local.min.y * subregion_to_device_scale_y).floor();
let filter_task_id = request_render_task(
frame_state,
snapshot,
&surface_rects,
false,
&mut|rg_builder, gpu_buffer| {
RenderTask::new_svg_filter_graph(
filters,
rg_builder,
gpu_buffer,
data_stores,
surface_rects.uv_rect_kind,
picture_task_id,
source_subregion.cast_unit(),
target_subregion.cast_unit(),
prim_subregion.cast_unit(),
subregion_to_device_scale_x,
subregion_to_device_scale_y,
subregion_to_device_offset_x,
subregion_to_device_offset_y,
)
}
);
primary_render_task_id = filter_task_id;
surface_descriptor = SurfaceDescriptor::new_chained(
picture_task_id,
filter_task_id,
surface_rects.clipped_local,
);
}
}
(
surface_descriptor,
[
Some(primary_render_task_id),
secondary_render_task_id,
]
)
}
fn request_render_task(
frame_state: &mut FrameBuildingState,
snapshot: &Option<SnapshotInfo>,
surface_rects: &SurfaceAllocInfo,
is_opaque: bool,
f: &mut dyn FnMut(&mut RenderTaskGraphBuilder, &mut GpuBufferBuilderF) -> RenderTaskId,
) -> RenderTaskId {
let task_id = match snapshot {
Some(info) => {
let adjustment = AdjustedImageSource::from_rects(
&info.area,
&surface_rects.clipped_local.cast_unit()
);
let task_id = frame_state.resource_cache.render_as_image(
info.key.as_image(),
surface_rects.task_size,
frame_state.rg_builder,
&mut frame_state.frame_gpu_data.f32,
is_opaque,
&adjustment,
f
);
// TODO(bug 1929809): adding the dependency in the other branch causes
// a panic in reftests/blend/backdrop-filter-blend-container.yaml.
// Presumably if we use backdrop filters with snapshotting it will
// trigger the panic as well.
frame_state.surface_builder.add_child_render_task(
task_id,
frame_state.rg_builder,
);
frame_state.image_dependencies.insert(info.key.as_image(), task_id);
task_id
}
None => {
f(
frame_state.rg_builder,
&mut frame_state.frame_gpu_data.f32,
)
}
};
task_id
}
/// Information from `get_surface_rects` about the allocated size, UV sampling
/// parameters etc for an off-screen surface
#[derive(Debug)]
pub struct SurfaceAllocInfo {
pub task_size: DeviceIntSize,
pub needs_scissor_rect: bool,
pub clipped: DeviceRect,
pub unclipped: DeviceRect,
// Only used for SVGFEGraph currently, this is the source pixels needed to
// render the pixels in clipped.
pub source: DeviceRect,
// Only used for SVGFEGraph, this is the same as clipped before rounding.
pub clipped_notsnapped: DeviceRect,
pub clipped_local: PictureRect,
pub uv_rect_kind: UvRectKind,
}
pub fn get_surface_rects(
surface_index: SurfaceIndex,
composite_mode: &PictureCompositeMode,
parent_surface_index: SurfaceIndex,
surfaces: &mut [SurfaceInfo],
spatial_tree: &SpatialTree,
max_surface_size: f32,
force_scissor_rect: bool,
) -> Option<SurfaceAllocInfo> {
let parent_surface = &surfaces[parent_surface_index.0];
let local_to_parent = SpaceMapper::new_with_target(
parent_surface.surface_spatial_node_index,
surfaces[surface_index.0].surface_spatial_node_index,
parent_surface.clipping_rect,
spatial_tree,
);
let local_clip_rect = local_to_parent
.unmap(&parent_surface.clipping_rect)
.unwrap_or(PictureRect::max_rect())
.cast_unit();
let surface = &mut surfaces[surface_index.0];
let (clipped_local, unclipped_local, source_local) = match composite_mode {
PictureCompositeMode::SVGFEGraph(ref filters) => {
let prim_subregion = composite_mode.get_rect(surface, None);
let visible_subregion: LayoutRect =
prim_subregion.cast_unit()
.intersection(&local_clip_rect)
.unwrap_or(PictureRect::zero())
.cast_unit();
if visible_subregion.is_empty() {
return None;
}
let source_potential_subregion = get_coverage_source_svgfe(
filters, visible_subregion.cast_unit());
let source_subregion =
source_potential_subregion
.intersection(&surface.unclipped_local_rect.cast_unit())
.unwrap_or(LayoutRect::zero());
let coverage_subregion = source_subregion.union(&visible_subregion);
(coverage_subregion.cast_unit(), prim_subregion.cast_unit(), source_subregion.cast_unit())
}
PictureCompositeMode::Filter(Filter::DropShadows(ref shadows)) => {
let local_prim_rect = surface.clipped_local_rect;
let mut required_local_rect = local_prim_rect
.intersection(&local_clip_rect)
.unwrap_or(PictureRect::zero());
for shadow in shadows {
let (blur_radius_x, blur_radius_y) = surface.clamp_blur_radius(
shadow.blur_radius,
shadow.blur_radius,
);
let blur_inflation_x = blur_radius_x * BLUR_SAMPLE_SCALE;
let blur_inflation_y = blur_radius_y * BLUR_SAMPLE_SCALE;
let local_shadow_rect = local_prim_rect
.translate(shadow.offset.cast_unit())
.inflate(blur_inflation_x, blur_inflation_y);
if let Some(clipped_shadow_rect) = local_clip_rect.intersection(&local_shadow_rect) {
let required_shadow_rect = clipped_shadow_rect.inflate(blur_inflation_x, blur_inflation_y);
let local_clipped_shadow_rect = required_shadow_rect.translate(-shadow.offset.cast_unit());
required_local_rect = required_local_rect.union(&local_clipped_shadow_rect);
}
}
let unclipped = composite_mode.get_rect(surface, None);
let clipped = required_local_rect;
let clipped = match clipped.intersection(&unclipped.cast_unit()) {
Some(rect) => rect,
None => return None,
};
(clipped, unclipped, clipped)
}
_ => {
let surface_origin = surface.clipped_local_rect.min.to_vector().cast_unit();
let normalized_prim_rect = composite_mode
.get_rect(surface, None)
.translate(-surface_origin);
let normalized_clip_rect = local_clip_rect
.cast_unit()
.translate(-surface_origin);
let norm_clipped_rect = match normalized_prim_rect.intersection(&normalized_clip_rect) {
Some(rect) => rect,
None => return None,
};
let norm_clipped_rect = composite_mode.get_rect(surface, Some(norm_clipped_rect));
let norm_clipped_rect = match norm_clipped_rect.intersection(&normalized_prim_rect) {
Some(rect) => rect,
None => return None,
};
let unclipped = normalized_prim_rect.translate(surface_origin);
let clipped = norm_clipped_rect.translate(surface_origin);
(clipped.cast_unit(), unclipped.cast_unit(), clipped.cast_unit())
}
};
let (mut clipped, mut unclipped, mut source) = if surface.raster_spatial_node_index != surface.surface_spatial_node_index {
assert_eq!(surface.device_pixel_scale.0, 1.0);
let local_to_world = SpaceMapper::new_with_target(
spatial_tree.root_reference_frame_index(),
surface.surface_spatial_node_index,
WorldRect::max_rect(),
spatial_tree,
);
let clipped = local_to_world.map(&clipped_local.cast_unit()).unwrap() * surface.device_pixel_scale;
let unclipped = local_to_world.map(&unclipped_local).unwrap() * surface.device_pixel_scale;
let source = local_to_world.map(&source_local.cast_unit()).unwrap() * surface.device_pixel_scale;
(clipped, unclipped, source)
} else {
let clipped = clipped_local.cast_unit() * surface.device_pixel_scale;
let unclipped = unclipped_local.cast_unit() * surface.device_pixel_scale;
let source = source_local.cast_unit() * surface.device_pixel_scale;
(clipped, unclipped, source)
};
let mut clipped_snapped = clipped.round_out();
let mut source_snapped = source.round_out();
let max_dimension =
clipped_snapped.width().max(
clipped_snapped.height().max(
source_snapped.width().max(
source_snapped.height()
))).ceil();
if max_dimension > max_surface_size {
let max_dimension =
clipped_local.width().max(
clipped_local.height().max(
source_local.width().max(
source_local.height()
))).ceil();
surface.raster_spatial_node_index = surface.surface_spatial_node_index;
surface.device_pixel_scale = Scale::new(max_surface_size / max_dimension);
surface.local_scale = (1.0, 1.0);
let add_markers = profiler::thread_is_being_profiled();
if add_markers {
let new_clipped = (clipped_local.cast_unit() * surface.device_pixel_scale).round();
let new_source = (source_local.cast_unit() * surface.device_pixel_scale).round();
profiler::add_text_marker("SurfaceSizeLimited",
format!("Surface for {:?} reduced from raster {:?} (source {:?}) to local {:?} (source {:?})",
composite_mode.kind(),
clipped.size(), source.size(),
new_clipped, new_source).as_str(),
Duration::from_secs_f32(new_clipped.width() * new_clipped.height() / 1000000000.0));
}
clipped = clipped_local.cast_unit() * surface.device_pixel_scale;
unclipped = unclipped_local.cast_unit() * surface.device_pixel_scale;
source = source_local.cast_unit() * surface.device_pixel_scale;
clipped_snapped = clipped.round();
source_snapped = source.round();
}
let task_size = clipped_snapped.size().to_i32();
let task_size = task_size.min(DeviceIntSize::new(max_surface_size as i32, max_surface_size as i32));
debug_assert!(
task_size.width <= max_surface_size as i32 &&
task_size.height <= max_surface_size as i32,
"task_size {:?} for {:?} must be within max_surface_size {}",
task_size,
composite_mode.kind(),
max_surface_size);
let uv_rect_kind = calculate_uv_rect_kind(
clipped_snapped,
unclipped,
);
if task_size.width == 0 || task_size.height == 0 {
return None;
}
let needs_scissor_rect = force_scissor_rect || !clipped_local.contains_box(&surface.unclipped_local_rect);
Some(SurfaceAllocInfo {
task_size,
needs_scissor_rect,
clipped: clipped_snapped,
unclipped,
source: source_snapped,
clipped_notsnapped: clipped,
clipped_local,
uv_rect_kind,
})
}
pub fn calculate_uv_rect_kind(
clipped: DeviceRect,
unclipped: DeviceRect,
) -> UvRectKind {
let top_left = calculate_screen_uv(
unclipped.top_left().cast_unit(),
clipped,
);
let top_right = calculate_screen_uv(
unclipped.top_right().cast_unit(),
clipped,
);
let bottom_left = calculate_screen_uv(
unclipped.bottom_left().cast_unit(),
clipped,
);
let bottom_right = calculate_screen_uv(
unclipped.bottom_right().cast_unit(),
clipped,
);
UvRectKind::Quad {
top_left,
top_right,
bottom_left,
bottom_right,
}
}
/// Represents a hashable description of how a picture primitive
/// will be composited into its parent.
#[cfg_attr(feature = "capture", derive(Serialize))]
#[cfg_attr(feature = "replay", derive(Deserialize))]
#[derive(Debug, Clone, MallocSizeOf, PartialEq, Hash, Eq)]
pub enum PictureCompositeKey {
// No visual compositing effect
Identity,
// FilterOp
Blur(Au, Au, bool, BlurEdgeMode),
Brightness(Au),
Contrast(Au),
Grayscale(Au),
HueRotate(Au),
Invert(Au),
Opacity(Au),
OpacityBinding(PropertyBindingId, Au),
Saturate(Au),
Sepia(Au),
DropShadows(Vec<(VectorKey, Au, ColorU)>),
ColorMatrix([Au; 20]),
SrgbToLinear,
LinearToSrgb,
ComponentTransfer(ItemUid),
Flood(ColorU),
SVGFEGraph(Vec<(FilterGraphNodeKey, FilterGraphOpKey)>),
// MixBlendMode
Multiply,
Screen,
Overlay,
Darken,
Lighten,
ColorDodge,
ColorBurn,
HardLight,
SoftLight,
Difference,
Exclusion,
Hue,
Saturation,
Color,
Luminosity,
PlusLighter,
}
impl From<Option<PictureCompositeMode>> for PictureCompositeKey {
fn from(mode: Option<PictureCompositeMode>) -> Self {
match mode {
Some(PictureCompositeMode::MixBlend(mode)) => {
match mode {
MixBlendMode::Normal => PictureCompositeKey::Identity,
MixBlendMode::Multiply => PictureCompositeKey::Multiply,
MixBlendMode::Screen => PictureCompositeKey::Screen,
MixBlendMode::Overlay => PictureCompositeKey::Overlay,
MixBlendMode::Darken => PictureCompositeKey::Darken,
MixBlendMode::Lighten => PictureCompositeKey::Lighten,
MixBlendMode::ColorDodge => PictureCompositeKey::ColorDodge,
MixBlendMode::ColorBurn => PictureCompositeKey::ColorBurn,
MixBlendMode::HardLight => PictureCompositeKey::HardLight,
MixBlendMode::SoftLight => PictureCompositeKey::SoftLight,
MixBlendMode::Difference => PictureCompositeKey::Difference,
MixBlendMode::Exclusion => PictureCompositeKey::Exclusion,
MixBlendMode::Hue => PictureCompositeKey::Hue,
MixBlendMode::Saturation => PictureCompositeKey::Saturation,
MixBlendMode::Color => PictureCompositeKey::Color,
MixBlendMode::Luminosity => PictureCompositeKey::Luminosity,
MixBlendMode::PlusLighter => PictureCompositeKey::PlusLighter,
}
}
Some(PictureCompositeMode::Filter(op)) => {
match op {
Filter::Blur { width, height, should_inflate, edge_mode } => {
PictureCompositeKey::Blur(
Au::from_f32_px(width),
Au::from_f32_px(height),
should_inflate,
edge_mode,
)
}
Filter::Brightness(value) => PictureCompositeKey::Brightness(Au::from_f32_px(value)),
Filter::Contrast(value) => PictureCompositeKey::Contrast(Au::from_f32_px(value)),
Filter::Grayscale(value) => PictureCompositeKey::Grayscale(Au::from_f32_px(value)),
Filter::HueRotate(value) => PictureCompositeKey::HueRotate(Au::from_f32_px(value)),
Filter::Invert(value) => PictureCompositeKey::Invert(Au::from_f32_px(value)),
Filter::Saturate(value) => PictureCompositeKey::Saturate(Au::from_f32_px(value)),
Filter::Sepia(value) => PictureCompositeKey::Sepia(Au::from_f32_px(value)),
Filter::SrgbToLinear => PictureCompositeKey::SrgbToLinear,
Filter::LinearToSrgb => PictureCompositeKey::LinearToSrgb,
Filter::Identity => PictureCompositeKey::Identity,
Filter::DropShadows(ref shadows) => {
PictureCompositeKey::DropShadows(
shadows.iter().map(|shadow| {
(shadow.offset.into(), Au::from_f32_px(shadow.blur_radius), shadow.color.into())
}).collect()
)
}
Filter::Opacity(binding, _) => {
match binding {
PropertyBinding::Value(value) => {
PictureCompositeKey::Opacity(Au::from_f32_px(value))
}
PropertyBinding::Binding(key, default) => {
PictureCompositeKey::OpacityBinding(key.id, Au::from_f32_px(default))
}
}
}
Filter::ColorMatrix(values) => {
let mut quantized_values: [Au; 20] = [Au(0); 20];
for (value, result) in values.iter().zip(quantized_values.iter_mut()) {
*result = Au::from_f32_px(*value);
}
PictureCompositeKey::ColorMatrix(quantized_values)
}
Filter::ComponentTransfer => unreachable!(),
Filter::Flood(color) => PictureCompositeKey::Flood(color.into()),
Filter::SVGGraphNode(_node, _op) => unreachable!(),
}
}
Some(PictureCompositeMode::ComponentTransferFilter(handle)) => {
PictureCompositeKey::ComponentTransfer(handle.uid())
}
Some(PictureCompositeMode::SVGFEGraph(filter_nodes)) => {
PictureCompositeKey::SVGFEGraph(
filter_nodes.into_iter().map(|(node, op)| {
(node.into(), op.into())
}).collect())
}
Some(PictureCompositeMode::Blit(_)) |
Some(PictureCompositeMode::TileCache { .. }) |
Some(PictureCompositeMode::IntermediateSurface) |
None => {
PictureCompositeKey::Identity
}
}
}
}