summaryrefslogtreecommitdiffstats
path: root/gfx/wr/webrender/src/quad.rs
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/wr/webrender/src/quad.rs')
-rw-r--r--gfx/wr/webrender/src/quad.rs695
1 files changed, 695 insertions, 0 deletions
diff --git a/gfx/wr/webrender/src/quad.rs b/gfx/wr/webrender/src/quad.rs
new file mode 100644
index 0000000000..5455611f3f
--- /dev/null
+++ b/gfx/wr/webrender/src/quad.rs
@@ -0,0 +1,695 @@
+/* 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::{units::*, PremultipliedColorF, ClipMode};
+use euclid::point2;
+
+use crate::batch::{BatchKey, BatchKind, BatchTextures};
+use crate::clip::{ClipChainInstance, ClipIntern, ClipItemKind, ClipStore};
+use crate::command_buffer::{CommandBufferIndex, PrimitiveCommand, QuadFlags};
+use crate::frame_builder::{FrameBuildingContext, FrameBuildingState, PictureContext, PictureState};
+use crate::gpu_types::{PrimitiveInstanceData, QuadInstance, QuadSegment, TransformPaletteId, ZBufferId};
+use crate::intern::DataStore;
+use crate::internal_types::TextureSource;
+use crate::pattern::{Pattern, PatternKind, PatternShaderInput};
+use crate::prim_store::{PrimitiveInstanceIndex, PrimitiveScratchBuffer};
+use crate::render_task::{MaskSubPass, RenderTask, RenderTaskAddress, RenderTaskKind, SubPass};
+use crate::render_task_graph::{RenderTaskGraph, RenderTaskId};
+use crate::renderer::{BlendMode, GpuBufferAddress, GpuBufferBuilder, GpuBufferBuilderF};
+use crate::segment::EdgeAaSegmentMask;
+use crate::space::SpaceMapper;
+use crate::spatial_tree::{SpatialNodeIndex, SpatialTree};
+use crate::util::MaxRect;
+
+const MIN_AA_SEGMENTS_SIZE: f32 = 4.0;
+const MIN_QUAD_SPLIT_SIZE: f32 = 256.0;
+
+/// Describes how clipping affects the rendering of a quad primitive.
+///
+/// As a general rule, parts of the quad that require masking are prerendered in an
+/// intermediate target and the mask is applied using multiplicative blending to
+/// the intermediate result before compositing it into the destination target.
+///
+/// Each segment can opt in or out of masking independently.
+#[derive(Debug, Copy, Clone)]
+enum QuadRenderStrategy {
+ /// The quad is not affected by any mask and is drawn directly in the destination
+ /// target.
+ Direct,
+ /// The quad is drawn entirely in an intermediate target and a mask is applied
+ /// before compositing in the destination target.
+ Indirect,
+ /// A rounded rectangle clip is applied to the quad primitive via a nine-patch.
+ /// The segments of the nine-patch that require a mask are rendered and masked in
+ /// an intermediate target, while other segments are drawn directly in the destination
+ /// target.
+ NinePatch {
+ radius: LayoutVector2D,
+ clip_rect: LayoutRect,
+ },
+ /// Split the primitive into coarse tiles so that each tile independently
+ /// has the opportunity to be drawn directly in the destination target or
+ /// via an intermediate target if it is affected by a mask.
+ Tiled {
+ x_tiles: u16,
+ y_tiles: u16,
+ }
+}
+
+pub fn push_quad(
+ pattern: &Pattern,
+ local_rect: &LayoutRect,
+ prim_instance_index: PrimitiveInstanceIndex,
+ prim_spatial_node_index: SpatialNodeIndex,
+ clip_chain: &ClipChainInstance,
+ device_pixel_scale: DevicePixelScale,
+
+ frame_context: &FrameBuildingContext,
+ pic_context: &PictureContext,
+ targets: &[CommandBufferIndex],
+ interned_clips: &DataStore<ClipIntern>,
+
+ frame_state: &mut FrameBuildingState,
+ pic_state: &mut PictureState,
+ scratch: &mut PrimitiveScratchBuffer,
+
+) {
+ let map_prim_to_surface = frame_context.spatial_tree.get_relative_transform(
+ prim_spatial_node_index,
+ pic_context.raster_spatial_node_index,
+ );
+ let prim_is_2d_scale_translation = map_prim_to_surface.is_2d_scale_translation();
+ let prim_is_2d_axis_aligned = map_prim_to_surface.is_2d_axis_aligned();
+
+ let strategy = get_prim_render_strategy(
+ prim_spatial_node_index,
+ clip_chain,
+ frame_state.clip_store,
+ interned_clips,
+ prim_is_2d_scale_translation,
+ frame_context.spatial_tree,
+ );
+
+ let mut quad_flags = QuadFlags::empty();
+
+ // Only use AA edge instances if the primitive is large enough to require it
+ let prim_size = local_rect.size();
+ if prim_size.width > MIN_AA_SEGMENTS_SIZE && prim_size.height > MIN_AA_SEGMENTS_SIZE {
+ quad_flags |= QuadFlags::USE_AA_SEGMENTS;
+ }
+
+ if pattern.is_opaque {
+ quad_flags |= QuadFlags::IS_OPAQUE;
+ }
+ let needs_scissor = !prim_is_2d_scale_translation;
+ if !needs_scissor {
+ quad_flags |= QuadFlags::APPLY_DEVICE_CLIP;
+ }
+
+ // TODO(gw): For now, we don't select per-edge AA at all if the primitive
+ // has a 2d transform, which matches existing behavior. However,
+ // as a follow up, we can now easily check if we have a 2d-aligned
+ // primitive on a subpixel boundary, and enable AA along those edge(s).
+ let aa_flags = if prim_is_2d_axis_aligned {
+ EdgeAaSegmentMask::empty()
+ } else {
+ EdgeAaSegmentMask::all()
+ };
+
+ let transform_id = frame_state.transforms.get_id(
+ prim_spatial_node_index,
+ pic_context.raster_spatial_node_index,
+ frame_context.spatial_tree,
+ );
+
+ // TODO(gw): Perhaps rather than writing untyped data here (we at least do validate
+ // the written block count) to gpu-buffer, we could add a trait for
+ // writing typed data?
+ let main_prim_address = write_prim_blocks(
+ &mut frame_state.frame_gpu_data.f32,
+ *local_rect,
+ clip_chain.local_clip_rect,
+ pattern.base_color,
+ &[],
+ );
+
+ if let QuadRenderStrategy::Direct = strategy {
+ frame_state.push_prim(
+ &PrimitiveCommand::quad(
+ pattern.kind,
+ pattern.shader_input,
+ prim_instance_index,
+ main_prim_address,
+ transform_id,
+ quad_flags,
+ aa_flags,
+ ),
+ prim_spatial_node_index,
+ targets,
+ );
+ return;
+ }
+
+ let surface = &frame_state.surfaces[pic_context.surface_index.0];
+ let Some(clipped_surface_rect) = surface.get_surface_rect(
+ &clip_chain.pic_coverage_rect, frame_context.spatial_tree
+ ) else {
+ return;
+ };
+
+ match strategy {
+ QuadRenderStrategy::Direct => {}
+ QuadRenderStrategy::Indirect => {
+ let segment = add_segment(
+ pattern,
+ &clipped_surface_rect,
+ true,
+ clip_chain,
+ prim_spatial_node_index,
+ pic_context.raster_spatial_node_index,
+ main_prim_address,
+ transform_id,
+ aa_flags,
+ quad_flags,
+ device_pixel_scale,
+ needs_scissor,
+ frame_state,
+ );
+
+ add_composite_prim(
+ pattern,
+ prim_instance_index,
+ segment.rect,
+ quad_flags,
+ frame_state,
+ targets,
+ &[segment],
+ );
+ }
+ QuadRenderStrategy::Tiled { x_tiles, y_tiles } => {
+ let unclipped_surface_rect = surface
+ .map_to_device_rect(&clip_chain.pic_coverage_rect, frame_context.spatial_tree);
+
+ scratch.quad_segments.clear();
+
+ let mut x_coords = vec![clipped_surface_rect.min.x];
+ let mut y_coords = vec![clipped_surface_rect.min.y];
+
+ let dx = (clipped_surface_rect.max.x - clipped_surface_rect.min.x) as f32 / x_tiles as f32;
+ let dy = (clipped_surface_rect.max.y - clipped_surface_rect.min.y) as f32 / y_tiles as f32;
+
+ for x in 1 .. (x_tiles as i32) {
+ x_coords.push((clipped_surface_rect.min.x as f32 + x as f32 * dx).round() as i32);
+ }
+ for y in 1 .. (y_tiles as i32) {
+ y_coords.push((clipped_surface_rect.min.y as f32 + y as f32 * dy).round() as i32);
+ }
+
+ x_coords.push(clipped_surface_rect.max.x);
+ y_coords.push(clipped_surface_rect.max.y);
+
+ for y in 0 .. y_coords.len()-1 {
+ let y0 = y_coords[y];
+ let y1 = y_coords[y+1];
+
+ if y1 <= y0 {
+ continue;
+ }
+
+ for x in 0 .. x_coords.len()-1 {
+ let x0 = x_coords[x];
+ let x1 = x_coords[x+1];
+
+ if x1 <= x0 {
+ continue;
+ }
+
+ let create_task = true;
+ let rect = DeviceIntRect {
+ min: point2(x0, y0),
+ max: point2(x1, y1),
+ };
+
+ let segment = add_segment(
+ pattern,
+ &rect,
+ create_task,
+ clip_chain,
+ prim_spatial_node_index,
+ pic_context.raster_spatial_node_index,
+ main_prim_address,
+ transform_id,
+ aa_flags,
+ quad_flags,
+ device_pixel_scale,
+ needs_scissor,
+ frame_state,
+ );
+ scratch.quad_segments.push(segment);
+ }
+ }
+
+ add_composite_prim(
+ pattern,
+ prim_instance_index,
+ unclipped_surface_rect.cast_unit(),
+ quad_flags,
+ frame_state,
+ targets,
+ &scratch.quad_segments,
+ );
+ }
+ QuadRenderStrategy::NinePatch { clip_rect, radius } => {
+ let unclipped_surface_rect = surface
+ .map_to_device_rect(&clip_chain.pic_coverage_rect, frame_context.spatial_tree);
+
+ let local_corner_0 = LayoutRect::new(
+ clip_rect.min,
+ clip_rect.min + radius,
+ );
+
+ let local_corner_1 = LayoutRect::new(
+ clip_rect.max - radius,
+ clip_rect.max,
+ );
+
+ let pic_corner_0 = pic_state.map_local_to_pic.map(&local_corner_0).unwrap();
+ let pic_corner_1 = pic_state.map_local_to_pic.map(&local_corner_1).unwrap();
+
+ let surface_rect_0 = surface.map_to_device_rect(
+ &pic_corner_0,
+ frame_context.spatial_tree,
+ ).round_out().to_i32();
+
+ let surface_rect_1 = surface.map_to_device_rect(
+ &pic_corner_1,
+ frame_context.spatial_tree,
+ ).round_out().to_i32();
+
+ let p0 = surface_rect_0.min;
+ let p1 = surface_rect_0.max;
+ let p2 = surface_rect_1.min;
+ let p3 = surface_rect_1.max;
+
+ let mut x_coords = [p0.x, p1.x, p2.x, p3.x];
+ let mut y_coords = [p0.y, p1.y, p2.y, p3.y];
+
+ x_coords.sort_by(|a, b| a.partial_cmp(b).unwrap());
+ y_coords.sort_by(|a, b| a.partial_cmp(b).unwrap());
+
+ scratch.quad_segments.clear();
+
+ for y in 0 .. y_coords.len()-1 {
+ let y0 = y_coords[y];
+ let y1 = y_coords[y+1];
+
+ if y1 <= y0 {
+ continue;
+ }
+
+ for x in 0 .. x_coords.len()-1 {
+ let x0 = x_coords[x];
+ let x1 = x_coords[x+1];
+
+ if x1 <= x0 {
+ continue;
+ }
+
+ // Only create render tasks for the corners.
+ let create_task = x != 1 && y != 1;
+
+ let rect = DeviceIntRect::new(point2(x0, y0), point2(x1, y1));
+
+ let rect = match rect.intersection(&clipped_surface_rect) {
+ Some(rect) => rect,
+ None => {
+ continue;
+ }
+ };
+
+ let segment = add_segment(
+ pattern,
+ &rect,
+ create_task,
+ clip_chain,
+ prim_spatial_node_index,
+ pic_context.raster_spatial_node_index,
+ main_prim_address,
+ transform_id,
+ aa_flags,
+ quad_flags,
+ device_pixel_scale,
+ false,
+ frame_state,
+ );
+ scratch.quad_segments.push(segment);
+ }
+ }
+
+ add_composite_prim(
+ pattern,
+ prim_instance_index,
+ unclipped_surface_rect.cast_unit(),
+ quad_flags,
+ frame_state,
+ targets,
+ &scratch.quad_segments,
+ );
+ }
+ }
+}
+
+fn get_prim_render_strategy(
+ prim_spatial_node_index: SpatialNodeIndex,
+ clip_chain: &ClipChainInstance,
+ clip_store: &ClipStore,
+ interned_clips: &DataStore<ClipIntern>,
+ can_use_nine_patch: bool,
+ spatial_tree: &SpatialTree,
+) -> QuadRenderStrategy {
+ if !clip_chain.needs_mask {
+ return QuadRenderStrategy::Direct
+ }
+
+ fn tile_count_for_size(size: f32) -> u16 {
+ (size / MIN_QUAD_SPLIT_SIZE).min(4.0).max(1.0).ceil() as u16
+ }
+
+ let prim_coverage_size = clip_chain.pic_coverage_rect.size();
+ let x_tiles = tile_count_for_size(prim_coverage_size.width);
+ let y_tiles = tile_count_for_size(prim_coverage_size.height);
+ let try_split_prim = x_tiles > 1 || y_tiles > 1;
+
+ if !try_split_prim {
+ return QuadRenderStrategy::Indirect;
+ }
+
+ if can_use_nine_patch && clip_chain.clips_range.count == 1 {
+ let clip_instance = clip_store.get_instance_from_range(&clip_chain.clips_range, 0);
+ let clip_node = &interned_clips[clip_instance.handle];
+
+ if let ClipItemKind::RoundedRectangle { ref radius, mode: ClipMode::Clip, rect, .. } = clip_node.item.kind {
+ let max_corner_width = radius.top_left.width
+ .max(radius.bottom_left.width)
+ .max(radius.top_right.width)
+ .max(radius.bottom_right.width);
+ let max_corner_height = radius.top_left.height
+ .max(radius.bottom_left.height)
+ .max(radius.top_right.height)
+ .max(radius.bottom_right.height);
+
+ if max_corner_width <= 0.5 * rect.size().width &&
+ max_corner_height <= 0.5 * rect.size().height {
+
+ let clip_prim_coords_match = spatial_tree.is_matching_coord_system(
+ prim_spatial_node_index,
+ clip_node.item.spatial_node_index,
+ );
+
+ if clip_prim_coords_match {
+ let map_clip_to_prim = SpaceMapper::new_with_target(
+ prim_spatial_node_index,
+ clip_node.item.spatial_node_index,
+ LayoutRect::max_rect(),
+ spatial_tree,
+ );
+
+ if let Some(rect) = map_clip_to_prim.map(&rect) {
+ return QuadRenderStrategy::NinePatch {
+ radius: LayoutVector2D::new(max_corner_width, max_corner_height),
+ clip_rect: rect,
+ };
+ }
+ }
+ }
+ }
+ }
+
+ QuadRenderStrategy::Tiled {
+ x_tiles,
+ y_tiles,
+ }
+}
+
+fn add_segment(
+ pattern: &Pattern,
+ rect: &DeviceIntRect,
+ create_task: bool,
+ clip_chain: &ClipChainInstance,
+ prim_spatial_node_index: SpatialNodeIndex,
+ raster_spatial_node_index: SpatialNodeIndex,
+ prim_address_f: GpuBufferAddress,
+ transform_id: TransformPaletteId,
+ aa_flags: EdgeAaSegmentMask,
+ quad_flags: QuadFlags,
+ device_pixel_scale: DevicePixelScale,
+ needs_scissor_rect: bool,
+ frame_state: &mut FrameBuildingState,
+) -> QuadSegment {
+ let task_size = rect.size();
+ let rect = rect.to_f32();
+ let content_origin = rect.min;
+
+ let task_id = if create_task {
+ let task_id = frame_state.rg_builder.add().init(RenderTask::new_dynamic(
+ task_size,
+ RenderTaskKind::new_prim(
+ pattern.kind,
+ pattern.shader_input,
+ prim_spatial_node_index,
+ raster_spatial_node_index,
+ device_pixel_scale,
+ content_origin,
+ prim_address_f,
+ transform_id,
+ aa_flags,
+ quad_flags,
+ clip_chain.clips_range,
+ needs_scissor_rect,
+ ),
+ ));
+
+ let masks = MaskSubPass {
+ clip_node_range: clip_chain.clips_range,
+ prim_spatial_node_index,
+ prim_address_f,
+ };
+
+ let task = frame_state.rg_builder.get_task_mut(task_id);
+ task.add_sub_pass(SubPass::Masks { masks });
+
+ frame_state
+ .surface_builder
+ .add_child_render_task(task_id, frame_state.rg_builder);
+
+ task_id
+ } else {
+ RenderTaskId::INVALID
+ };
+
+ QuadSegment { rect: rect.cast_unit(), task_id }
+}
+
+fn add_composite_prim(
+ pattern: &Pattern,
+ prim_instance_index: PrimitiveInstanceIndex,
+ rect: LayoutRect,
+ quad_flags: QuadFlags,
+ frame_state: &mut FrameBuildingState,
+ targets: &[CommandBufferIndex],
+ segments: &[QuadSegment],
+) {
+ let composite_prim_address = write_prim_blocks(
+ &mut frame_state.frame_gpu_data.f32,
+ rect,
+ rect,
+ pattern.base_color,
+ segments,
+ );
+
+ frame_state.set_segments(segments, targets);
+
+ let mut composite_quad_flags =
+ QuadFlags::IGNORE_DEVICE_PIXEL_SCALE | QuadFlags::APPLY_DEVICE_CLIP;
+ if quad_flags.contains(QuadFlags::IS_OPAQUE) {
+ composite_quad_flags |= QuadFlags::IS_OPAQUE;
+ }
+
+ frame_state.push_cmd(
+ &PrimitiveCommand::quad(
+ PatternKind::ColorOrTexture,
+ pattern.shader_input,
+ prim_instance_index,
+ composite_prim_address,
+ TransformPaletteId::IDENTITY,
+ composite_quad_flags,
+ // TODO(gw): No AA on composite, unless we use it to apply 2d clips
+ EdgeAaSegmentMask::empty(),
+ ),
+ targets,
+ );
+}
+
+pub fn write_prim_blocks(
+ builder: &mut GpuBufferBuilderF,
+ prim_rect: LayoutRect,
+ clip_rect: LayoutRect,
+ color: PremultipliedColorF,
+ segments: &[QuadSegment],
+) -> GpuBufferAddress {
+ let mut writer = builder.write_blocks(3 + segments.len() * 2);
+
+ writer.push_one(prim_rect);
+ writer.push_one(clip_rect);
+ writer.push_one(color);
+
+ for segment in segments {
+ writer.push_one(segment.rect);
+ match segment.task_id {
+ RenderTaskId::INVALID => {
+ writer.push_one([0.0; 4]);
+ }
+ task_id => {
+ writer.push_render_task(task_id);
+ }
+ }
+ }
+
+ writer.finish()
+}
+
+pub fn add_to_batch<F>(
+ kind: PatternKind,
+ pattern_input: PatternShaderInput,
+ render_task_address: RenderTaskAddress,
+ transform_id: TransformPaletteId,
+ prim_address_f: GpuBufferAddress,
+ quad_flags: QuadFlags,
+ edge_flags: EdgeAaSegmentMask,
+ segment_index: u8,
+ task_id: RenderTaskId,
+ z_id: ZBufferId,
+ render_tasks: &RenderTaskGraph,
+ gpu_buffer_builder: &mut GpuBufferBuilder,
+ mut f: F,
+) where F: FnMut(BatchKey, PrimitiveInstanceData) {
+
+ // See the corresponfing #defines in ps_quad.glsl
+ #[repr(u8)]
+ enum PartIndex {
+ Center = 0,
+ Left = 1,
+ Top = 2,
+ Right = 3,
+ Bottom = 4,
+ All = 5,
+ }
+
+ // See QuadHeader in ps_quad.glsl
+ let mut writer = gpu_buffer_builder.i32.write_blocks(1);
+ writer.push_one([
+ transform_id.0 as i32,
+ z_id.0,
+ pattern_input.0,
+ pattern_input.1,
+ ]);
+ let prim_address_i = writer.finish();
+
+ let texture = match task_id {
+ RenderTaskId::INVALID => {
+ TextureSource::Invalid
+ }
+ _ => {
+ let texture = render_tasks
+ .resolve_texture(task_id)
+ .expect("bug: valid task id must be resolvable");
+
+ texture
+ }
+ };
+
+ let textures = BatchTextures::prim_textured(
+ texture,
+ TextureSource::Invalid,
+ );
+
+ let default_blend_mode = if quad_flags.contains(QuadFlags::IS_OPAQUE) && task_id == RenderTaskId::INVALID {
+ BlendMode::None
+ } else {
+ BlendMode::PremultipliedAlpha
+ };
+
+ let edge_flags_bits = edge_flags.bits();
+
+ let prim_batch_key = BatchKey {
+ blend_mode: default_blend_mode,
+ kind: BatchKind::Quad(kind),
+ textures,
+ };
+
+ let aa_batch_key = BatchKey {
+ blend_mode: BlendMode::PremultipliedAlpha,
+ kind: BatchKind::Quad(kind),
+ textures,
+ };
+
+ let mut instance = QuadInstance {
+ render_task_address,
+ prim_address_i,
+ prim_address_f,
+ z_id,
+ transform_id,
+ edge_flags: edge_flags_bits,
+ quad_flags: quad_flags.bits(),
+ part_index: PartIndex::All as u8,
+ segment_index,
+ };
+
+ if edge_flags.is_empty() {
+ // No antialisaing.
+ f(prim_batch_key, instance.into());
+ } else if quad_flags.contains(QuadFlags::USE_AA_SEGMENTS) {
+ // Add instances for the antialisaing. This gives the center part
+ // an opportunity to stay in the opaque pass.
+ if edge_flags.contains(EdgeAaSegmentMask::LEFT) {
+ let instance = QuadInstance {
+ part_index: PartIndex::Left as u8,
+ ..instance
+ };
+ f(aa_batch_key, instance.into());
+ }
+ if edge_flags.contains(EdgeAaSegmentMask::RIGHT) {
+ let instance = QuadInstance {
+ part_index: PartIndex::Top as u8,
+ ..instance
+ };
+ f(aa_batch_key, instance.into());
+ }
+ if edge_flags.contains(EdgeAaSegmentMask::TOP) {
+ let instance = QuadInstance {
+ part_index: PartIndex::Right as u8,
+ ..instance
+ };
+ f(aa_batch_key, instance.into());
+ }
+ if edge_flags.contains(EdgeAaSegmentMask::BOTTOM) {
+ let instance = QuadInstance {
+ part_index: PartIndex::Bottom as u8,
+ ..instance
+ };
+ f(aa_batch_key, instance.into());
+ }
+
+ instance = QuadInstance {
+ part_index: PartIndex::Center as u8,
+ ..instance
+ };
+
+ f(prim_batch_key, instance.into());
+ } else {
+ // Render the anti-aliased quad with a single primitive.
+ f(aa_batch_key, instance.into());
+ }
+}
+