tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 7182a1824edfb2471e0167378aabc4673f3542c1
parent b3959a19bfa025823ed9151b1938a9cef0478a5d
Author: Nicolas Silva <nical@fastmail.com>
Date:   Mon, 20 Oct 2025 07:03:53 +0000

Bug 1994764 - Add support for tile repetitions with quad gradients. r=gfx-reviewers,lsalzman

This implements tile repetitions by decomposing the primitive on the CPU. It's not ideal when there are a lot of repetitions but the non-quad code paths for gradients also use that approach so it's probably fine in practice.

Differential Revision: https://phabricator.services.mozilla.com/D268909

Diffstat:
Mgfx/wr/webrender/src/prepare.rs | 34++++++++++++++++------------------
Mgfx/wr/webrender/src/quad.rs | 236++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
2 files changed, 219 insertions(+), 51 deletions(-)

diff --git a/gfx/wr/webrender/src/prepare.rs b/gfx/wr/webrender/src/prepare.rs @@ -241,10 +241,6 @@ fn prepare_prim_for_render( let prim_instance = &mut prim_instances[prim_instance_index]; if !is_passthrough { - fn may_need_repetition(stretch_size: LayoutSize, prim_rect: LayoutRect) -> bool { - stretch_size.width < prim_rect.width() || - stretch_size.height < prim_rect.height() - } // Bug 1887841: At the moment the quad shader does not support repetitions. // Bug 1888349: Some primitives have brush segments that aren't handled by // the quad infrastructure yet. @@ -252,20 +248,17 @@ fn prepare_prim_for_render( PrimitiveInstanceKind::Rectangle { .. } => false, PrimitiveInstanceKind::LinearGradient { data_handle, .. } => { let prim_data = &data_stores.linear_grad[*data_handle]; - !prim_data.brush_segments.is_empty() || - may_need_repetition(prim_data.stretch_size, prim_data.common.prim_rect) + !prim_data.brush_segments.is_empty() || !frame_context.fb_config.precise_linear_gradients } PrimitiveInstanceKind::RadialGradient { data_handle, .. } => { let prim_data = &data_stores.radial_grad[*data_handle]; - !prim_data.brush_segments.is_empty() || - may_need_repetition(prim_data.stretch_size, prim_data.common.prim_rect) + !prim_data.brush_segments.is_empty() } // TODO(bug 1899546) Enable quad conic gradients with SWGL. PrimitiveInstanceKind::ConicGradient { data_handle, .. } if !frame_context.fb_config.is_software => { let prim_data = &data_stores.conic_grad[*data_handle]; - !prim_data.brush_segments.is_empty() || - may_need_repetition(prim_data.stretch_size, prim_data.common.prim_rect) + !prim_data.brush_segments.is_empty() } _ => true, }; @@ -765,11 +758,12 @@ fn prepare_interned_prim_for_render( PrimitiveInstanceKind::LinearGradient { data_handle, ref mut visible_tiles_range, use_legacy_path: cached, .. } => { profile_scope!("LinearGradient"); let prim_data = &mut data_stores.linear_grad[*data_handle]; - if !*cached { - quad::prepare_quad( + quad::prepare_repeatable_quad( prim_data, &prim_data.common.prim_rect, + prim_data.stretch_size, + prim_data.tile_spacing, prim_instance_index, prim_spatial_node_index, &prim_instance.vis.clip_chain, @@ -876,14 +870,16 @@ fn prepare_interned_prim_for_render( } } } - PrimitiveInstanceKind::RadialGradient { data_handle, ref mut visible_tiles_range, use_legacy_path: cached, .. } => { + PrimitiveInstanceKind::RadialGradient { data_handle, ref mut visible_tiles_range, use_legacy_path, .. } => { profile_scope!("RadialGradient"); let prim_data = &mut data_stores.radial_grad[*data_handle]; - if !*cached { - quad::prepare_quad( + if !*use_legacy_path { + quad::prepare_repeatable_quad( prim_data, &prim_data.common.prim_rect, + prim_data.stretch_size, + prim_data.tile_spacing, prim_instance_index, prim_spatial_node_index, &prim_instance.vis.clip_chain, @@ -927,14 +923,16 @@ fn prepare_interned_prim_for_render( } } } - PrimitiveInstanceKind::ConicGradient { data_handle, ref mut visible_tiles_range, use_legacy_path: cached, .. } => { + PrimitiveInstanceKind::ConicGradient { data_handle, ref mut visible_tiles_range, use_legacy_path, .. } => { profile_scope!("ConicGradient"); let prim_data = &mut data_stores.conic_grad[*data_handle]; - if !*cached { - quad::prepare_quad( + if !*use_legacy_path { + quad::prepare_repeatable_quad( prim_data, &prim_data.common.prim_rect, + prim_data.stretch_size, + prim_data.tile_spacing, prim_instance_index, prim_spatial_node_index, &prim_instance.vis.clip_chain, diff --git a/gfx/wr/webrender/src/quad.rs b/gfx/wr/webrender/src/quad.rs @@ -19,9 +19,10 @@ use crate::render_task_graph::{RenderTaskGraph, RenderTaskGraphBuilder, RenderTa use crate::renderer::{BlendMode, GpuBufferAddress, GpuBufferBuilder, GpuBufferBuilderF}; use crate::segment::EdgeAaSegmentMask; use crate::space::SpaceMapper; -use crate::spatial_tree::{SpatialNodeIndex, SpatialTree}; +use crate::spatial_tree::{CoordinateSpaceMapping, SpatialNodeIndex, SpatialTree}; use crate::surface::SurfaceBuilder; use crate::util::{extract_inner_rect_k, MaxRect, ScaleOffset}; +use crate::visibility::compute_conservative_visible_rect; const MIN_AA_SEGMENTS_SIZE: f32 = 4.0; const MIN_QUAD_SPLIT_SIZE: f32 = 256.0; @@ -76,50 +77,220 @@ pub fn prepare_quad( pic_state: &mut PictureState, scratch: &mut PrimitiveScratchBuffer, ) { - let map_prim_to_raster = frame_context.spatial_tree.get_relative_transform( + let pattern_ctx = PatternBuilderContext { + scene_properties: frame_context.scene_properties, + spatial_tree: frame_context.spatial_tree, + fb_config: frame_context.fb_config, + }; + + let shared_pattern = if pattern_builder.use_shared_pattern() { + Some(pattern_builder.build( + None, + &pattern_ctx, + &mut PatternBuilderState { + frame_gpu_data: frame_state.frame_gpu_data, + rg_builder: frame_state.rg_builder, + clip_store: frame_state.clip_store, + }, + )) + } else { + None + }; + + // TODO: It would be worth hoisting this out of prepare_quad and + // prepare_repeatable_quad. + let map_prim_to_raster = pattern_ctx.spatial_tree.get_relative_transform( prim_spatial_node_index, pic_context.raster_spatial_node_index, ); - let ctx = PatternBuilderContext { + let can_use_nine_patch = map_prim_to_raster.is_2d_scale_translation() + && pattern_builder.can_use_nine_patch(); + + let strategy = get_prim_render_strategy( + prim_spatial_node_index, + clip_chain, + frame_state.clip_store, + interned_clips, + can_use_nine_patch, + pattern_ctx.spatial_tree, + ); + + prepare_quad_impl( + strategy, + pattern_builder, + shared_pattern.as_ref(), + local_rect, + prim_instance_index, + prim_spatial_node_index, + clip_chain, + device_pixel_scale, + + &map_prim_to_raster, + &pattern_ctx, + pic_context, + targets, + interned_clips, + + frame_state, + pic_state, + scratch, + ) +} + +pub fn prepare_repeatable_quad( + pattern_builder: &dyn PatternBuilder, + local_rect: &LayoutRect, + stretch_size: LayoutSize, + tile_spacing: LayoutSize, + 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 pattern_ctx = PatternBuilderContext { scene_properties: frame_context.scene_properties, spatial_tree: frame_context.spatial_tree, fb_config: frame_context.fb_config, }; - let mut state = PatternBuilderState { - frame_gpu_data: frame_state.frame_gpu_data, - rg_builder: frame_state.rg_builder, - clip_store: frame_state.clip_store, - }; - let shared_pattern = if pattern_builder.use_shared_pattern() { Some(pattern_builder.build( None, - &ctx, - &mut state, + &pattern_ctx, + &mut PatternBuilderState { + frame_gpu_data: frame_state.frame_gpu_data, + rg_builder: frame_state.rg_builder, + clip_store: frame_state.clip_store, + }, )) } else { None }; - let prim_is_2d_scale_translation = map_prim_to_raster.is_2d_scale_translation(); - let prim_is_2d_axis_aligned = map_prim_to_raster.is_2d_axis_aligned(); + let map_prim_to_raster = pattern_ctx.spatial_tree.get_relative_transform( + prim_spatial_node_index, + pic_context.raster_spatial_node_index, + ); - // TODO(gw): Can't support 9-patch for box-shadows for now as should_create_task - // assumes pattern is solid. This is a temporary hack until as once that's - // fixed we can select 9-patch for box-shadows - let can_use_nine_patch = prim_is_2d_scale_translation && pattern_builder.can_use_nine_patch(); + let can_use_nine_patch = map_prim_to_raster.is_2d_scale_translation() + && pattern_builder.can_use_nine_patch(); + // This could move back into preapre_quad_impl if it took the tile's + // coverage rect into account rather than the whole primitive's, but + // for now it does the latter so we might as well not do the work + // multiple times. let strategy = get_prim_render_strategy( prim_spatial_node_index, clip_chain, - state.clip_store, + frame_state.clip_store, interned_clips, can_use_nine_patch, + pattern_ctx.spatial_tree, + ); + + let needs_repetition = stretch_size.width < local_rect.width() + || stretch_size.height < local_rect.height(); + + if !needs_repetition { + // Most common path. + prepare_quad_impl( + strategy, + pattern_builder, + shared_pattern.as_ref(), + local_rect, + prim_instance_index, + prim_spatial_node_index, + clip_chain, + device_pixel_scale, + &map_prim_to_raster, + &pattern_ctx, + pic_context, + targets, + interned_clips, + frame_state, + pic_state, + scratch, + ); + + return; + } + + // TODO: In some cases it would be a lot more efficient to bake the repeated + // pattern into a texture and use a repeating image shader instead of duplicating + // the primitive, especially with a high number of repetitions. + + let visible_rect = compute_conservative_visible_rect( + clip_chain, + frame_state.current_dirty_region().combined, + frame_state.current_dirty_region().visibility_spatial_node, + prim_spatial_node_index, frame_context.spatial_tree, ); + let stride = stretch_size + tile_spacing; + let repetitions = crate::image_tiling::repetitions(&local_rect, &visible_rect, stride); + for tile in repetitions { + let tile_rect = LayoutRect::from_origin_and_size(tile.origin, stretch_size); + prepare_quad_impl( + strategy, + pattern_builder, + shared_pattern.as_ref(), + &tile_rect, + prim_instance_index, + prim_spatial_node_index, + clip_chain, + device_pixel_scale, + &map_prim_to_raster, + &pattern_ctx, + pic_context, + targets, + interned_clips, + frame_state, + pic_state, + scratch, + ); + } +} + +fn prepare_quad_impl( + strategy: QuadRenderStrategy, + pattern_builder: &dyn PatternBuilder, + shared_pattern: Option<&Pattern>, + local_rect: &LayoutRect, + prim_instance_index: PrimitiveInstanceIndex, + prim_spatial_node_index: SpatialNodeIndex, + clip_chain: &ClipChainInstance, + device_pixel_scale: DevicePixelScale, + + map_prim_to_raster: &CoordinateSpaceMapping<LayoutPixel, LayoutPixel>, + ctx: &PatternBuilderContext, + pic_context: &PictureContext, + targets: &[CommandBufferIndex], + interned_clips: &DataStore<ClipIntern>, + + frame_state: &mut FrameBuildingState, + pic_state: &mut PictureState, + scratch: &mut PrimitiveScratchBuffer, +) { + let mut state = PatternBuilderState { + frame_gpu_data: frame_state.frame_gpu_data, + rg_builder: frame_state.rg_builder, + clip_store: frame_state.clip_store, + }; + + let prim_is_2d_scale_translation = map_prim_to_raster.is_2d_scale_translation(); + let prim_is_2d_axis_aligned = map_prim_to_raster.is_2d_axis_aligned(); + let mut quad_flags = QuadFlags::empty(); // Only use AA edge instances if the primitive is large enough to require it @@ -146,11 +317,11 @@ pub fn prepare_quad( let transform_id = frame_state.transforms.get_id( prim_spatial_node_index, pic_context.raster_spatial_node_index, - frame_context.spatial_tree, + ctx.spatial_tree, ); if let QuadRenderStrategy::Direct = strategy { - let pattern = shared_pattern.unwrap_or_else(|| { + let pattern = shared_pattern.cloned().unwrap_or_else(|| { pattern_builder.build( None, &ctx, @@ -202,7 +373,7 @@ pub fn prepare_quad( let surface = &mut 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 + &clip_chain.pic_coverage_rect, ctx.spatial_tree ) else { return; }; @@ -210,7 +381,7 @@ pub fn prepare_quad( match strategy { QuadRenderStrategy::Direct => {} QuadRenderStrategy::Indirect => { - let pattern = shared_pattern.unwrap_or_else(|| { + let pattern = shared_pattern.cloned().unwrap_or_else(|| { pattern_builder.build( None, &ctx, @@ -272,18 +443,18 @@ pub fn prepare_quad( // - in layout space for the render task, // - in device space for the instances that draw into the destination picture. let clip_coverage_rect = surface - .map_to_device_rect(&clip_chain.pic_coverage_rect, frame_context.spatial_tree); + .map_to_device_rect(&clip_chain.pic_coverage_rect, ctx.spatial_tree); let clipped_surface_rect = clipped_surface_rect.to_f32(); surface.map_local_to_picture.set_target_spatial_node( prim_spatial_node_index, - frame_context.spatial_tree, + ctx.spatial_tree, ); let Some(pic_rect) = surface.map_local_to_picture.map(local_rect) else { return }; let unclipped_surface_rect = surface.map_to_device_rect( - &pic_rect, frame_context.spatial_tree + &pic_rect, ctx.spatial_tree ).round_out(); // Set up the tile classifier for the params of this quad @@ -303,7 +474,7 @@ pub fn prepare_quad( prim_spatial_node_index, clip_node.item.spatial_node_index, pic_context.visibility_spatial_node_index, - frame_context.spatial_tree, + ctx.spatial_tree, ); // For now, we only handle axis-aligned mappings @@ -464,7 +635,7 @@ pub fn prepare_quad( if is_direct { scratch.quad_direct_segments.push(QuadSegment { rect: rect.cast_unit(), task_id: RenderTaskId::INVALID }); } else { - let pattern = match shared_pattern { + let pattern = match shared_pattern.cloned() { Some(ref shared_pattern) => shared_pattern.clone(), None => { pattern_builder.build( @@ -519,7 +690,7 @@ pub fn prepare_quad( let device_prim_rect: DeviceRect = local_to_device.map_rect(&local_rect); let pattern = match shared_pattern { - Some(ref shared_pattern) => shared_pattern.clone(), + Some(shared_pattern) => shared_pattern.clone(), None => { pattern_builder.build( Some(device_prim_rect), @@ -561,7 +732,7 @@ pub fn prepare_quad( // - in layout space for the render task, // - in device space for the instances that draw into the destination picture. let clip_coverage_rect = surface - .map_to_device_rect(&clip_chain.pic_coverage_rect, frame_context.spatial_tree); + .map_to_device_rect(&clip_chain.pic_coverage_rect, ctx.spatial_tree); let local_to_device = map_prim_to_raster.as_2d_scale_offset() .expect("bug: nine-patch segments should be axis-aligned only") @@ -584,12 +755,12 @@ pub fn prepare_quad( let surface_rect_0 = surface.map_to_device_rect( &pic_corner_0, - frame_context.spatial_tree, + ctx.spatial_tree, ).round_out().to_i32(); let surface_rect_1 = surface.map_to_device_rect( &pic_corner_1, - frame_context.spatial_tree, + ctx.spatial_tree, ).round_out().to_i32(); let p0 = surface_rect_0.min; @@ -650,7 +821,6 @@ pub fn prepare_quad( if should_create_task(mode, x, y) { let pattern = shared_pattern - .as_ref() .expect("bug: nine-patch expects shared pattern, for now"); if pattern.is_opaque { @@ -668,7 +838,7 @@ pub fn prepare_quad( ); let task_id = add_render_task_with_mask( - &pattern, + pattern, device_rect.size(), device_rect.min.to_f32(), clip_chain.clips_range,