From f25021ed81f3702a3a4b1b40d9c62725c1a251a6 Mon Sep 17 00:00:00 2001 From: Glenn Watson Date: Fri, 10 Aug 2018 10:44:54 +1000 Subject: [PATCH 01/11] Embed PrimitiveMetadata inside Primitive struct. This is another small step to moving primitives to be stored inside Picture structs, which will simplify some of the picture caching work. --- webrender/src/batch.rs | 49 ++- webrender/src/display_list_flattener.rs | 2 +- webrender/src/frame_builder.rs | 4 +- webrender/src/picture.rs | 3 + webrender/src/prim_store.rs | 407 ++++++++++++------------ 5 files changed, 222 insertions(+), 243 deletions(-) diff --git a/webrender/src/batch.rs b/webrender/src/batch.rs index 7c8adb319b..03f419501d 100644 --- a/webrender/src/batch.rs +++ b/webrender/src/batch.rs @@ -17,9 +17,9 @@ use internal_types::{FastHashMap, SavedTargetIndex, SourceTexture}; use picture::{PictureCompositeMode, PicturePrimitive, PictureSurface}; use plane_split::{BspSplitter, Clipper, Polygon, Splitter}; use prim_store::{BrushKind, BrushPrimitive, BrushSegmentTaskId, DeferredResolve}; -use prim_store::{EdgeAaSegmentMask, ImageSource, PrimitiveIndex, PrimitiveKind}; -use prim_store::{PrimitiveMetadata, PrimitiveRun, PrimitiveStore, VisibleGradientTile}; -use prim_store::{BorderSource}; +use prim_store::{EdgeAaSegmentMask, ImageSource, PrimitiveIndex}; +use prim_store::{PrimitiveMetadata, PrimitiveRun, VisibleGradientTile}; +use prim_store::{BorderSource, Primitive, PrimitiveDetails}; use render_task::{RenderTaskAddress, RenderTaskId, RenderTaskKind, RenderTaskTree}; use renderer::{BlendMode, ImageBufferKind, ShaderColorMode}; use renderer::BLOCKS_PER_UV_RECT; @@ -511,7 +511,7 @@ impl AlphaBatchBuilder { BlendMode::PremultipliedAlpha, BatchTextures::no_texture(), ); - let pic_metadata = &ctx.prim_store.cpu_metadata[prim_index.0]; + let pic_metadata = &ctx.prim_store.primitives[prim_index.0].metadata; let pic = ctx.prim_store.get_pic(prim_index); let batch = self.batch_list.get_suitable_batch(key, &pic_metadata.screen_rect.as_ref().expect("bug").clipped); @@ -553,7 +553,7 @@ impl AlphaBatchBuilder { ) { for i in 0 .. run.count { let prim_index = PrimitiveIndex(run.base_prim_index.0 + i); - let metadata = &ctx.prim_store.cpu_metadata[prim_index.0]; + let metadata = &ctx.prim_store.primitives[prim_index.0].metadata; if metadata.screen_rect.is_some() { self.add_prim_to_batch( @@ -591,7 +591,8 @@ impl AlphaBatchBuilder { content_origin: DeviceIntPoint, prim_headers: &mut PrimitiveHeaders, ) { - let prim_metadata = ctx.prim_store.get_metadata(prim_index); + let prim = &ctx.prim_store.primitives[prim_index.0]; + let prim_metadata = &prim.metadata; #[cfg(debug_assertions)] //TODO: why is this needed? debug_assert_eq!(prim_metadata.prepared_frame_id, render_tasks.frame_id()); @@ -612,9 +613,8 @@ impl AlphaBatchBuilder { // If the primitive is internally decomposed into multiple sub-primitives we may not // use some of the per-primitive data typically stored in PrimitiveMetadata and get // it from each sub-primitive instead. - let is_multiple_primitives = match prim_metadata.prim_kind { - PrimitiveKind::Brush => { - let brush = &ctx.prim_store.cpu_brushes[prim_metadata.cpu_prim_index.0]; + let is_multiple_primitives = match prim.details { + PrimitiveDetails::Brush(ref brush) => { match brush.kind { BrushKind::Image { ref visible_tiles, .. } => !visible_tiles.is_empty(), BrushKind::LinearGradient { ref visible_tiles, .. } => !visible_tiles.is_empty(), @@ -635,7 +635,7 @@ impl AlphaBatchBuilder { .clip_task_id .map_or(OPAQUE_TASK_ADDRESS, |id| render_tasks.get_task_address(id)); - let specified_blend_mode = ctx.prim_store.get_blend_mode(prim_metadata); + let specified_blend_mode = prim.get_blend_mode(); let non_segmented_blend_mode = if !prim_metadata.opacity.is_opaque || prim_metadata.clip_task_id.is_some() || @@ -659,10 +659,8 @@ impl AlphaBatchBuilder { println!("\t{:?}", prim_header); } - match prim_metadata.prim_kind { - PrimitiveKind::Brush => { - let brush = &ctx.prim_store.cpu_brushes[prim_metadata.cpu_prim_index.0]; - + match prim.details { + PrimitiveDetails::Brush(ref brush) => { match brush.kind { BrushKind::Picture(ref picture) => { // If this picture is participating in a 3D rendering context, @@ -1094,10 +1092,7 @@ impl AlphaBatchBuilder { } } } - PrimitiveKind::TextRun => { - let text_cpu = - &ctx.prim_store.cpu_text_runs[prim_metadata.cpu_prim_index.0]; - + PrimitiveDetails::TextRun(ref text_cpu) => { let subpx_dir = text_cpu.used_font.get_subpx_dir(); let glyph_fetch_buffer = &mut self.glyph_fetch_buffer; @@ -1570,23 +1565,15 @@ impl BrushPrimitive { } } -trait AlphaBatchHelpers { - fn get_blend_mode( - &self, - metadata: &PrimitiveMetadata, - ) -> BlendMode; -} - -impl AlphaBatchHelpers for PrimitiveStore { - fn get_blend_mode(&self, metadata: &PrimitiveMetadata) -> BlendMode { - match metadata.prim_kind { +impl Primitive { + fn get_blend_mode(&self) -> BlendMode { + match self.details { // Can only resolve the TextRun's blend mode once glyphs are fetched. - PrimitiveKind::TextRun => { + PrimitiveDetails::TextRun(..) => { BlendMode::PremultipliedAlpha } - PrimitiveKind::Brush => { - let brush = &self.cpu_brushes[metadata.cpu_prim_index.0]; + PrimitiveDetails::Brush(ref brush) => { match brush.kind { BrushKind::Clear => { BlendMode::PremultipliedDestOut diff --git a/webrender/src/display_list_flattener.rs b/webrender/src/display_list_flattener.rs index 580af2cfeb..813df42a72 100644 --- a/webrender/src/display_list_flattener.rs +++ b/webrender/src/display_list_flattener.rs @@ -937,7 +937,7 @@ impl<'a> DisplayListFlattener<'a> { // If there is no root picture, create one for the main framebuffer. if self.sc_stack.is_empty() { // Should be no pictures at all if the stack is empty... - debug_assert!(self.prim_store.cpu_metadata.is_empty()); + debug_assert!(self.prim_store.primitives.is_empty()); debug_assert_eq!(transform_style, TransformStyle::Flat); // This picture stores primitive runs for items on the diff --git a/webrender/src/frame_builder.rs b/webrender/src/frame_builder.rs index c2b116d07d..e54d785212 100644 --- a/webrender/src/frame_builder.rs +++ b/webrender/src/frame_builder.rs @@ -193,7 +193,7 @@ impl FrameBuilder { ) -> Option { profile_scope!("cull"); - if self.prim_store.cpu_metadata.is_empty() { + if self.prim_store.primitives.is_empty() { return None } @@ -276,7 +276,7 @@ impl FrameBuilder { static SCROLLBAR_PADDING: f32 = 8.0; for scrollbar_prim in &self.scrollbar_prims { - let metadata = &mut self.prim_store.cpu_metadata[scrollbar_prim.prim_index.0]; + let metadata = &mut self.prim_store.primitives[scrollbar_prim.prim_index.0].metadata; let scroll_frame = &clip_scroll_tree.spatial_nodes[scrollbar_prim.scroll_frame_index.0]; // Invalidate what's in the cache so it will get rebuilt. diff --git a/webrender/src/picture.rs b/webrender/src/picture.rs index 6711db84a7..36cec393ed 100644 --- a/webrender/src/picture.rs +++ b/webrender/src/picture.rs @@ -225,7 +225,10 @@ impl PicturePrimitive { pub fn update_local_rect( &mut self, prim_run_rect: PrimitiveRunLocalRect, + prim_runs: Vec, ) -> LayoutRect { + self.runs = prim_runs; + let local_content_rect = prim_run_rect.local_rect_in_actual_parent_space; self.real_local_rect = prim_run_rect.local_rect_in_original_parent_space; diff --git a/webrender/src/prim_store.rs b/webrender/src/prim_store.rs index f60deca0f9..45daaebfbd 100644 --- a/webrender/src/prim_store.rs +++ b/webrender/src/prim_store.rs @@ -149,12 +149,6 @@ pub struct SpecificPrimitiveIndex(pub usize); #[cfg_attr(feature = "replay", derive(Deserialize))] pub struct PrimitiveIndex(pub usize); -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub enum PrimitiveKind { - TextRun, - Brush, -} - impl GpuCacheHandle { pub fn as_int(&self, gpu_cache: &GpuCache) -> i32 { gpu_cache.get_address(self).as_int() @@ -181,8 +175,6 @@ pub struct ScreenRect { pub struct PrimitiveMetadata { pub opacity: PrimitiveOpacity, pub clip_chain_id: ClipChainId, - pub prim_kind: PrimitiveKind, - pub cpu_prim_index: SpecificPrimitiveIndex, pub gpu_location: GpuCacheHandle, pub clip_task_id: Option, @@ -1199,11 +1191,48 @@ impl PrimitiveContainer { } } +pub enum PrimitiveDetails { + Brush(BrushPrimitive), + TextRun(TextRunPrimitiveCpu), +} + +pub struct Primitive { + pub metadata: PrimitiveMetadata, + pub details: PrimitiveDetails, +} + +impl Primitive { + pub fn as_pic(&self) -> &PicturePrimitive { + match self.details { + PrimitiveDetails::Brush(ref brush) => { + match brush.kind { + BrushKind::Picture(ref pic) => pic, + _ => panic!("bug: not a picture!"), + } + } + PrimitiveDetails::TextRun(..) => { + panic!("bug: not a picture!"); + } + } + } + + pub fn as_pic_mut(&mut self) -> &mut PicturePrimitive { + match self.details { + PrimitiveDetails::Brush(ref mut brush) => { + match brush.kind { + BrushKind::Picture(ref mut pic) => pic, + _ => panic!("bug: not a picture!"), + } + } + PrimitiveDetails::TextRun(..) => { + panic!("bug: not a picture!"); + } + } + } +} + pub struct PrimitiveStore { - /// CPU side information only. - pub cpu_brushes: Vec, - pub cpu_text_runs: Vec, - pub cpu_metadata: Vec, + pub primitives: Vec, /// A primitive index to chase through debugging. pub chase_id: Option, @@ -1212,40 +1241,24 @@ pub struct PrimitiveStore { impl PrimitiveStore { pub fn new() -> PrimitiveStore { PrimitiveStore { - cpu_metadata: Vec::new(), - cpu_brushes: Vec::new(), - cpu_text_runs: Vec::new(), - + primitives: Vec::new(), chase_id: None, } } pub fn recycle(self) -> Self { PrimitiveStore { - cpu_metadata: recycle_vec(self.cpu_metadata), - cpu_brushes: recycle_vec(self.cpu_brushes), - cpu_text_runs: recycle_vec(self.cpu_text_runs), - + primitives: recycle_vec(self.primitives), chase_id: self.chase_id, } } pub fn get_pic(&self, index: PrimitiveIndex) -> &PicturePrimitive { - let metadata = &self.cpu_metadata[index.0]; - let brush = &self.cpu_brushes[metadata.cpu_prim_index.0]; - match brush.kind { - BrushKind::Picture(ref pic) => pic, - _ => panic!("bug: not a picture!"), - } + self.primitives[index.0].as_pic() } pub fn get_pic_mut(&mut self, index: PrimitiveIndex) -> &mut PicturePrimitive { - let metadata = &self.cpu_metadata[index.0]; - let brush = &mut self.cpu_brushes[metadata.cpu_prim_index.0]; - match brush.kind { - BrushKind::Picture(ref mut pic) => pic, - _ => panic!("bug: not a picture!"), - } + self.primitives[index.0].as_pic_mut() } pub fn add_primitive( @@ -1257,7 +1270,7 @@ impl PrimitiveStore { tag: Option, container: PrimitiveContainer, ) -> PrimitiveIndex { - let prim_index = self.cpu_metadata.len(); + let prim_index = self.primitives.len(); let base_metadata = PrimitiveMetadata { clip_chain_id, @@ -1270,13 +1283,11 @@ impl PrimitiveStore { screen_rect: None, tag, opacity: PrimitiveOpacity::translucent(), - prim_kind: PrimitiveKind::Brush, - cpu_prim_index: SpecificPrimitiveIndex(0), #[cfg(debug_assertions)] prepared_frame_id: FrameId(0), }; - let metadata = match container { + let prim = match container { PrimitiveContainer::Brush(brush) => { let opacity = match brush.kind { BrushKind::Clear => PrimitiveOpacity::translucent(), @@ -1291,29 +1302,28 @@ impl PrimitiveStore { let metadata = PrimitiveMetadata { opacity, - prim_kind: PrimitiveKind::Brush, - cpu_prim_index: SpecificPrimitiveIndex(self.cpu_brushes.len()), ..base_metadata }; - self.cpu_brushes.push(brush); - - metadata + Primitive { + metadata, + details: PrimitiveDetails::Brush(brush), + } } PrimitiveContainer::TextRun(text_cpu) => { let metadata = PrimitiveMetadata { opacity: PrimitiveOpacity::translucent(), - prim_kind: PrimitiveKind::TextRun, - cpu_prim_index: SpecificPrimitiveIndex(self.cpu_text_runs.len()), ..base_metadata }; - self.cpu_text_runs.push(text_cpu); - metadata + Primitive { + metadata, + details: PrimitiveDetails::TextRun(text_cpu), + } } }; - self.cpu_metadata.push(metadata); + self.primitives.push(prim); PrimitiveIndex(prim_index) } @@ -1337,15 +1347,14 @@ impl PrimitiveStore { return None; } - let prim_metadata = &self.cpu_metadata[run.base_prim_index.0]; + let prim = &self.primitives[run.base_prim_index.0]; // For now, we only support opacity collapse on solid rects and images. // This covers the most common types of opacity filters that can be // handled by this optimization. In the future, we can easily extend // this to other primitives, such as text runs and gradients. - match prim_metadata.prim_kind { - PrimitiveKind::Brush => { - let brush = &self.cpu_brushes[prim_metadata.cpu_prim_index.0]; + match prim.details { + PrimitiveDetails::Brush(ref brush) => { match brush.kind { BrushKind::Picture(ref pic) => { // If we encounter a picture that is a pass-through @@ -1367,7 +1376,7 @@ impl PrimitiveStore { BrushKind::Clear => {} } } - PrimitiveKind::TextRun => {} + PrimitiveDetails::TextRun(..) => {} } None @@ -1393,53 +1402,48 @@ impl PrimitiveStore { // See if this picture contains a single primitive that supports // opacity collapse. - if let Some(prim_index) = self.get_opacity_collapse_prim(pic_prim_index) { - let (prim_kind, cpu_prim_index) = { - let metadata = &self.cpu_metadata[prim_index.0]; - (metadata.prim_kind, metadata.cpu_prim_index) - }; - - match prim_kind { - PrimitiveKind::Brush => { - let brush = &mut self.cpu_brushes[cpu_prim_index.0]; - - // By this point, we know we should only have found a primitive - // that supports opacity collapse. - match brush.kind { - BrushKind::Solid { ref mut opacity_binding, .. } | - BrushKind::Image { ref mut opacity_binding, .. } => { - opacity_binding.push(binding); - } - BrushKind::Clear { .. } | - BrushKind::Picture { .. } | - BrushKind::YuvImage { .. } | - BrushKind::Border { .. } | - BrushKind::LinearGradient { .. } | - BrushKind::RadialGradient { .. } => { - unreachable!("bug: invalid prim type for opacity collapse"); + match self.get_opacity_collapse_prim(pic_prim_index) { + Some(prim_index) => { + let prim = &mut self.primitives[prim_index.0]; + match prim.details { + PrimitiveDetails::Brush(ref mut brush) => { + // By this point, we know we should only have found a primitive + // that supports opacity collapse. + match brush.kind { + BrushKind::Solid { ref mut opacity_binding, .. } | + BrushKind::Image { ref mut opacity_binding, .. } => { + opacity_binding.push(binding); + } + BrushKind::Clear { .. } | + BrushKind::Picture { .. } | + BrushKind::YuvImage { .. } | + BrushKind::Border { .. } | + BrushKind::LinearGradient { .. } | + BrushKind::RadialGradient { .. } => { + unreachable!("bug: invalid prim type for opacity collapse"); + } } - }; - } - PrimitiveKind::TextRun => { - unreachable!("bug: invalid prim type for opacity collapse"); + } + PrimitiveDetails::TextRun(..) => { + unreachable!("bug: invalid prim type for opacity collapse"); + } } } - - // The opacity filter has been collapsed, so mark this picture - // as a pass though. This means it will no longer allocate an - // intermediate surface or incur an extra blend / blit. Instead, - // the collapsed primitive will be drawn directly into the - // parent picture. - self.get_pic_mut(pic_prim_index).composite_mode = None; + None => { + return; + } } - } - pub fn get_metadata(&self, index: PrimitiveIndex) -> &PrimitiveMetadata { - &self.cpu_metadata[index.0] + // The opacity filter has been collapsed, so mark this picture + // as a pass though. This means it will no longer allocate an + // intermediate surface or incur an extra blend / blit. Instead, + // the collapsed primitive will be drawn directly into the + // parent picture. + self.get_pic_mut(pic_prim_index).composite_mode = None; } pub fn prim_count(&self) -> usize { - self.cpu_metadata.len() + self.primitives.len() } fn build_prim_segments_if_needed( @@ -1449,13 +1453,12 @@ impl PrimitiveStore { frame_state: &mut FrameBuildingState, frame_context: &FrameBuildingContext, ) { - let metadata = &mut self.cpu_metadata[prim_index.0]; + let prim = &mut self.primitives[prim_index.0]; - if metadata.prim_kind != PrimitiveKind::Brush { - return; - } - - let brush = &mut self.cpu_brushes[metadata.cpu_prim_index.0]; + let brush = match prim.details { + PrimitiveDetails::Brush(ref mut brush) => brush, + PrimitiveDetails::TextRun(..) => return, + }; if let BrushKind::Border { ref mut source, .. } = brush.kind { if let BorderSource::Border { @@ -1481,7 +1484,7 @@ impl PrimitiveStore { cache_key.scale = scale_au; *task_info = BorderRenderTaskInfo::new( - &metadata.local_rect, + &prim.metadata.local_rect, border, widths, scale, @@ -1522,7 +1525,7 @@ impl PrimitiveStore { // The segments have changed, so force the GPU cache to // re-upload the primitive information. - frame_state.gpu_cache.invalidate(&mut metadata.gpu_location); + frame_state.gpu_cache.invalidate(&mut prim.metadata.gpu_location); } } } @@ -1539,15 +1542,15 @@ impl PrimitiveStore { frame_state: &mut FrameBuildingState, ) { let mut is_tiled = false; - let metadata = &mut self.cpu_metadata[prim_index.0]; + let prim = &mut self.primitives[prim_index.0]; + let metadata = &mut prim.metadata; #[cfg(debug_assertions)] { metadata.prepared_frame_id = frame_state.render_tasks.frame_id(); } - match metadata.prim_kind { - PrimitiveKind::TextRun => { - let text = &mut self.cpu_text_runs[metadata.cpu_prim_index.0]; + match prim.details { + PrimitiveDetails::TextRun(ref mut text) => { // The transform only makes sense for screen space rasterization let transform = prim_run_context.scroll_node.world_content_transform.to_transform(); text.prepare_for_render( @@ -1558,8 +1561,7 @@ impl PrimitiveStore { frame_state, ); } - PrimitiveKind::Brush => { - let brush = &mut self.cpu_brushes[metadata.cpu_prim_index.0]; + PrimitiveDetails::Brush(ref mut brush) => { match brush.kind { BrushKind::Image { @@ -1948,13 +1950,11 @@ impl PrimitiveStore { // Mark this GPU resource as required for this frame. if let Some(mut request) = frame_state.gpu_cache.request(&mut metadata.gpu_location) { - match metadata.prim_kind { - PrimitiveKind::TextRun => { - let text = &self.cpu_text_runs[metadata.cpu_prim_index.0]; + match prim.details { + PrimitiveDetails::TextRun(ref mut text) => { text.write_gpu_blocks(&mut request); } - PrimitiveKind::Brush => { - let brush = &self.cpu_brushes[metadata.cpu_prim_index.0]; + PrimitiveDetails::Brush(ref mut brush) => { brush.write_gpu_blocks(&mut request, metadata.local_rect); match brush.segment_desc { @@ -2150,19 +2150,15 @@ impl PrimitiveStore { ) -> bool { debug_assert!(frame_context.screen_rect.contains_rect(combined_outer_rect)); - let metadata = &self.cpu_metadata[prim_index.0]; - let brush = match metadata.prim_kind { - PrimitiveKind::Brush => { - &mut self.cpu_brushes[metadata.cpu_prim_index.0] - } - _ => { - return false; - } + let prim = &mut self.primitives[prim_index.0]; + let brush = match prim.details { + PrimitiveDetails::Brush(ref mut brush) => brush, + PrimitiveDetails::TextRun(..) => return false, }; PrimitiveStore::write_brush_segment_description( brush, - metadata, + &prim.metadata, clip_chain, frame_state, ); @@ -2214,10 +2210,10 @@ impl PrimitiveStore { } fn reset_clip_task(&mut self, prim_index: PrimitiveIndex) { - let metadata = &mut self.cpu_metadata[prim_index.0]; - metadata.clip_task_id = None; - if metadata.prim_kind == PrimitiveKind::Brush { - if let Some(ref mut desc) = self.cpu_brushes[metadata.cpu_prim_index.0].segment_desc { + let prim = &mut self.primitives[prim_index.0]; + prim.metadata.clip_task_id = None; + if let PrimitiveDetails::Brush(ref mut brush) = prim.details { + if let Some(ref mut desc) = brush.segment_desc { for segment in &mut desc.segments { segment.clip_task_id = BrushSegmentTaskId::Opaque; } @@ -2272,7 +2268,7 @@ impl PrimitiveStore { println!("\tcreated task {:?} with combined outer rect {:?}", clip_task_id, prim_screen_rect); } - self.cpu_metadata[prim_index.0].clip_task_id = Some(clip_task_id); + self.primitives[prim_index.0].metadata.clip_task_id = Some(clip_task_id); pic_state.tasks.push(clip_task_id); } @@ -2291,84 +2287,82 @@ impl PrimitiveStore { let mut may_need_clip_mask = true; let mut pic_state_for_children = PictureState::new(); - // Do some basic checks first, that can early out - // without even knowing the local rect. - let (prim_kind, cpu_prim_index, clip_chain_id) = { - let metadata = &self.cpu_metadata[prim_index.0]; + // If we have dependencies, we need to prepare them first, in order + // to know the actual rect of this primitive. + // For example, scrolling may affect the location of an item in + // local space, which may force us to render this item on a larger + // picture target, if being composited. + let pic_context_for_children = { + let prim = &mut self.primitives[prim_index.0]; - if !metadata.is_backface_visible && prim_run_context.transform.backface_is_visible { + // Do some basic checks first, that can early out + // without even knowing the local rect. + if !prim.metadata.is_backface_visible && prim_run_context.transform.backface_is_visible { if cfg!(debug_assertions) && Some(prim_index) == self.chase_id { println!("\tculled for not having visible back faces"); } return None; } - (metadata.prim_kind, metadata.cpu_prim_index, metadata.clip_chain_id) - }; - - // If we have dependencies, we need to prepare them first, in order - // to know the actual rect of this primitive. - // For example, scrolling may affect the location of an item in - // local space, which may force us to render this item on a larger - // picture target, if being composited. - let pic_context_for_children = match prim_kind { - PrimitiveKind::Brush => { - match self.cpu_brushes[cpu_prim_index.0].kind { - BrushKind::Picture(ref mut pic) => { - if !pic.resolve_scene_properties(frame_context.scene_properties) { - if cfg!(debug_assertions) && Some(prim_index) == self.chase_id { - println!("\tculled for carrying an invisible composite filter"); + match prim.details { + PrimitiveDetails::Brush(ref mut brush) => { + match brush.kind { + BrushKind::Picture(ref mut pic) => { + if !pic.resolve_scene_properties(frame_context.scene_properties) { + if cfg!(debug_assertions) && Some(prim_index) == self.chase_id { + println!("\tculled for carrying an invisible composite filter"); + } + return None; } - return None; - } - may_need_clip_mask = pic.composite_mode.is_some(); + may_need_clip_mask = pic.composite_mode.is_some(); - let inflation_factor = match pic.composite_mode { - Some(PictureCompositeMode::Filter(FilterOp::Blur(blur_radius))) => { - // The amount of extra space needed for primitives inside - // this picture to ensure the visibility check is correct. - BLUR_SAMPLE_SCALE * blur_radius - } - _ => { - 0.0 - } - }; - - let display_list = &frame_context - .pipelines - .get(&pic.pipeline_id) - .expect("No display list?") - .display_list; - - let inv_world_transform = prim_run_context - .scroll_node - .world_content_transform - .inverse(); - - // Mark whether this picture has a complex coordinate system. - pic_state_for_children.has_non_root_coord_system |= - prim_run_context.scroll_node.coordinate_system_id != CoordinateSystemId::root(); - - Some(PictureContext { - pipeline_id: pic.pipeline_id, - prim_runs: mem::replace(&mut pic.runs, Vec::new()), - original_reference_frame_index: Some(pic.reference_frame_index), - display_list, - inv_world_transform, - apply_local_clip_rect: pic.apply_local_clip_rect, - inflation_factor, - // TODO(lsalzman): allow overriding parent if intermediate surface is opaque - allow_subpixel_aa: pic_context.allow_subpixel_aa && pic.allow_subpixel_aa(), - }) - } - _ => { - None + let inflation_factor = match pic.composite_mode { + Some(PictureCompositeMode::Filter(FilterOp::Blur(blur_radius))) => { + // The amount of extra space needed for primitives inside + // this picture to ensure the visibility check is correct. + BLUR_SAMPLE_SCALE * blur_radius + } + _ => { + 0.0 + } + }; + + let display_list = &frame_context + .pipelines + .get(&pic.pipeline_id) + .expect("No display list?") + .display_list; + + let inv_world_transform = prim_run_context + .scroll_node + .world_content_transform + .inverse(); + + // Mark whether this picture has a complex coordinate system. + pic_state_for_children.has_non_root_coord_system |= + prim_run_context.scroll_node.coordinate_system_id != CoordinateSystemId::root(); + + Some(PictureContext { + pipeline_id: pic.pipeline_id, + prim_runs: mem::replace(&mut pic.runs, Vec::new()), + original_reference_frame_index: Some(pic.reference_frame_index), + display_list, + inv_world_transform, + apply_local_clip_rect: pic.apply_local_clip_rect, + inflation_factor, + // TODO(lsalzman): allow overriding parent if intermediate surface is opaque + allow_subpixel_aa: pic_context.allow_subpixel_aa && pic.allow_subpixel_aa(), + }) + } + _ => { + None + } } } - } - _ => { - None + PrimitiveDetails::TextRun(..) => { + None + } } }; @@ -2381,25 +2375,20 @@ impl PrimitiveStore { ); // Restore the dependencies (borrow check dance) - let new_local_rect = match self.cpu_brushes[cpu_prim_index.0].kind { - BrushKind::Picture(ref mut pic) => { - pic.runs = pic_context_for_children.prim_runs; - pic.update_local_rect(result) - } - _ => unreachable!(), - }; - - let metadata = &mut self.cpu_metadata[prim_index.0]; - - if new_local_rect != metadata.local_rect { - metadata.local_rect = new_local_rect; - frame_state.gpu_cache.invalidate(&mut metadata.gpu_location); + let prim = &mut self.primitives[prim_index.0]; + let new_local_rect = prim + .as_pic_mut() + .update_local_rect(result, pic_context_for_children.prim_runs); + + if new_local_rect != prim.metadata.local_rect { + prim.metadata.local_rect = new_local_rect; + frame_state.gpu_cache.invalidate(&mut prim.metadata.gpu_location); pic_state.local_rect_changed = true; } } - let (local_rect, local_clip_rect) = { - let metadata = &mut self.cpu_metadata[prim_index.0]; + let (local_rect, local_clip_rect, clip_chain_id) = { + let metadata = &mut self.primitives[prim_index.0].metadata; if metadata.local_rect.size.width <= 0.0 || metadata.local_rect.size.height <= 0.0 { if cfg!(debug_assertions) && Some(prim_index) == self.chase_id { @@ -2428,7 +2417,7 @@ impl PrimitiveStore { } }; - (local_rect, metadata.local_clip_rect) + (local_rect, metadata.local_clip_rect, metadata.clip_chain_id) }; let clip_chain = frame_state @@ -2447,7 +2436,7 @@ impl PrimitiveStore { let clip_chain = match clip_chain { Some(clip_chain) => clip_chain, None => { - self.cpu_metadata[prim_index.0].screen_rect = None; + self.primitives[prim_index.0].metadata.screen_rect = None; return None; } }; @@ -2499,7 +2488,7 @@ impl PrimitiveStore { }; let ccr = { - let metadata = &mut self.cpu_metadata[prim_index.0]; + let metadata = &mut self.primitives[prim_index.0].metadata; metadata.screen_rect = Some(ScreenRect { clipped: clipped_device_rect, @@ -2557,8 +2546,8 @@ impl PrimitiveStore { // TODO(gw): Make this simpler / more efficient by tidying // up the logic that early outs from prepare_prim_for_render. pub fn reset_prim_visibility(&mut self) { - for md in &mut self.cpu_metadata { - md.screen_rect = None; + for prim in &mut self.primitives { + prim.metadata.screen_rect = None; } } From ea8dc4e4951ba052078d319e79e87a614639c234 Mon Sep 17 00:00:00 2001 From: Glenn Watson Date: Fri, 10 Aug 2018 15:49:14 +1000 Subject: [PATCH 02/11] Move write_brush_segment_description to a free function. --- webrender/src/prim_store.rs | 314 ++++++++++++++++++------------------ 1 file changed, 157 insertions(+), 157 deletions(-) diff --git a/webrender/src/prim_store.rs b/webrender/src/prim_store.rs index 45daaebfbd..4fb81c1111 100644 --- a/webrender/src/prim_store.rs +++ b/webrender/src/prim_store.rs @@ -1982,162 +1982,6 @@ impl PrimitiveStore { } } - fn write_brush_segment_description( - brush: &mut BrushPrimitive, - metadata: &PrimitiveMetadata, - clip_chain: &ClipChainInstance, - frame_state: &mut FrameBuildingState, - ) { - match brush.segment_desc { - Some(ref segment_desc) => { - // If we already have a segment descriptor, only run through the - // clips list if we haven't already determined the mask kind. - if segment_desc.clip_mask_kind != BrushClipMaskKind::Unknown { - return; - } - } - None => { - // If no segment descriptor built yet, see if it is a brush - // type that wants to be segmented. - if !brush.kind.supports_segments(frame_state.resource_cache) { - return; - } - } - } - - // If the brush is small, we generally want to skip building segments - // and just draw it as a single primitive with clip mask. However, - // if the clips are purely rectangles that have no per-fragment - // clip masks, we will segment anyway. This allows us to completely - // skip allocating a clip mask in these cases. - let is_large = metadata.local_rect.size.area() > MIN_BRUSH_SPLIT_AREA; - - // TODO(gw): We should probably detect and store this on each - // ClipSources instance, to avoid having to iterate - // the clip sources here. - let mut rect_clips_only = true; - - let mut segment_builder = SegmentBuilder::new( - metadata.local_rect, - None, - metadata.local_clip_rect - ); - - // If this primitive is clipped by clips from a different coordinate system, then we - // need to apply a clip mask for the entire primitive. - let mut clip_mask_kind = if clip_chain.has_clips_from_other_coordinate_systems { - BrushClipMaskKind::Global - } else { - BrushClipMaskKind::Individual - }; - - // Segment the primitive on all the local-space clip sources that we can. - for i in 0 .. clip_chain.clips_range.count { - let (clip_node, flags) = frame_state.clip_store.get_node_from_range(&clip_chain.clips_range, i); - - if !flags.contains(ClipNodeFlags::SAME_COORD_SYSTEM) { - continue; - } - - // TODO(gw): We can easily extend the segment builder to support these clip sources in - // the future, but they are rarely used. - // We must do this check here in case we continue early below. - if clip_node.item.is_image_or_line_decoration_clip() { - clip_mask_kind = BrushClipMaskKind::Global; - } - - // If this clip item is positioned by another positioning node, its relative position - // could change during scrolling. This means that we would need to resegment. Instead - // of doing that, only segment with clips that have the same positioning node. - // TODO(mrobinson, #2858): It may make sense to include these nodes, resegmenting only - // when necessary while scrolling. - if !flags.contains(ClipNodeFlags::SAME_SPATIAL_NODE) { - // We don't need to generate a global clip mask for rectangle clips because we are - // in the same coordinate system and rectangular clips are handled by the local - // clip chain rectangle. - if !clip_node.item.is_rect() { - clip_mask_kind = BrushClipMaskKind::Global; - } - continue; - } - - let (local_clip_rect, radius, mode) = match clip_node.item { - ClipItem::RoundedRectangle(rect, radii, clip_mode) => { - rect_clips_only = false; - (rect, Some(radii), clip_mode) - } - ClipItem::Rectangle(rect, mode) => { - (rect, None, mode) - } - ClipItem::BoxShadow(ref info) => { - rect_clips_only = false; - - // For inset box shadows, we can clip out any - // pixels that are inside the shadow region - // and are beyond the inner rect, as they can't - // be affected by the blur radius. - let inner_clip_mode = match info.clip_mode { - BoxShadowClipMode::Outset => None, - BoxShadowClipMode::Inset => Some(ClipMode::ClipOut), - }; - - // Push a region into the segment builder where the - // box-shadow can have an effect on the result. This - // ensures clip-mask tasks get allocated for these - // pixel regions, even if no other clips affect them. - segment_builder.push_mask_region( - info.prim_shadow_rect, - info.prim_shadow_rect.inflate( - -0.5 * info.shadow_rect_alloc_size.width, - -0.5 * info.shadow_rect_alloc_size.height, - ), - inner_clip_mode, - ); - - continue; - } - ClipItem::LineDecoration(..) | ClipItem::Image(..) => { - rect_clips_only = false; - continue; - } - }; - - segment_builder.push_clip_rect(local_clip_rect, radius, mode); - } - - if is_large || rect_clips_only { - match brush.segment_desc { - Some(ref mut segment_desc) => { - segment_desc.clip_mask_kind = clip_mask_kind; - } - None => { - // TODO(gw): We can probably make the allocation - // patterns of this and the segment - // builder significantly better, by - // retaining it across primitives. - let mut segments = Vec::new(); - - segment_builder.build(|segment| { - segments.push( - BrushSegment::new( - segment.rect, - segment.has_mask, - segment.edge_flags, - [0.0; 4], - BrushFlags::empty(), - ), - ); - }); - - brush.segment_desc = Some(BrushSegmentDescriptor { - segments, - clip_mask_kind, - }); - } - } - } - } - fn update_clip_task_for_brush( &mut self, prim_run_context: &PrimitiveRunContext, @@ -2156,7 +2000,7 @@ impl PrimitiveStore { PrimitiveDetails::TextRun(..) => return false, }; - PrimitiveStore::write_brush_segment_description( + write_brush_segment_description( brush, &prim.metadata, clip_chain, @@ -2792,3 +2636,159 @@ impl<'a> GpuDataRequest<'a> { self.push(extra_data); } } + +fn write_brush_segment_description( + brush: &mut BrushPrimitive, + metadata: &PrimitiveMetadata, + clip_chain: &ClipChainInstance, + frame_state: &mut FrameBuildingState, +) { + match brush.segment_desc { + Some(ref segment_desc) => { + // If we already have a segment descriptor, only run through the + // clips list if we haven't already determined the mask kind. + if segment_desc.clip_mask_kind != BrushClipMaskKind::Unknown { + return; + } + } + None => { + // If no segment descriptor built yet, see if it is a brush + // type that wants to be segmented. + if !brush.kind.supports_segments(frame_state.resource_cache) { + return; + } + } + } + + // If the brush is small, we generally want to skip building segments + // and just draw it as a single primitive with clip mask. However, + // if the clips are purely rectangles that have no per-fragment + // clip masks, we will segment anyway. This allows us to completely + // skip allocating a clip mask in these cases. + let is_large = metadata.local_rect.size.area() > MIN_BRUSH_SPLIT_AREA; + + // TODO(gw): We should probably detect and store this on each + // ClipSources instance, to avoid having to iterate + // the clip sources here. + let mut rect_clips_only = true; + + let mut segment_builder = SegmentBuilder::new( + metadata.local_rect, + None, + metadata.local_clip_rect + ); + + // If this primitive is clipped by clips from a different coordinate system, then we + // need to apply a clip mask for the entire primitive. + let mut clip_mask_kind = if clip_chain.has_clips_from_other_coordinate_systems { + BrushClipMaskKind::Global + } else { + BrushClipMaskKind::Individual + }; + + // Segment the primitive on all the local-space clip sources that we can. + for i in 0 .. clip_chain.clips_range.count { + let (clip_node, flags) = frame_state.clip_store.get_node_from_range(&clip_chain.clips_range, i); + + if !flags.contains(ClipNodeFlags::SAME_COORD_SYSTEM) { + continue; + } + + // TODO(gw): We can easily extend the segment builder to support these clip sources in + // the future, but they are rarely used. + // We must do this check here in case we continue early below. + if clip_node.item.is_image_or_line_decoration_clip() { + clip_mask_kind = BrushClipMaskKind::Global; + } + + // If this clip item is positioned by another positioning node, its relative position + // could change during scrolling. This means that we would need to resegment. Instead + // of doing that, only segment with clips that have the same positioning node. + // TODO(mrobinson, #2858): It may make sense to include these nodes, resegmenting only + // when necessary while scrolling. + if !flags.contains(ClipNodeFlags::SAME_SPATIAL_NODE) { + // We don't need to generate a global clip mask for rectangle clips because we are + // in the same coordinate system and rectangular clips are handled by the local + // clip chain rectangle. + if !clip_node.item.is_rect() { + clip_mask_kind = BrushClipMaskKind::Global; + } + continue; + } + + let (local_clip_rect, radius, mode) = match clip_node.item { + ClipItem::RoundedRectangle(rect, radii, clip_mode) => { + rect_clips_only = false; + (rect, Some(radii), clip_mode) + } + ClipItem::Rectangle(rect, mode) => { + (rect, None, mode) + } + ClipItem::BoxShadow(ref info) => { + rect_clips_only = false; + + // For inset box shadows, we can clip out any + // pixels that are inside the shadow region + // and are beyond the inner rect, as they can't + // be affected by the blur radius. + let inner_clip_mode = match info.clip_mode { + BoxShadowClipMode::Outset => None, + BoxShadowClipMode::Inset => Some(ClipMode::ClipOut), + }; + + // Push a region into the segment builder where the + // box-shadow can have an effect on the result. This + // ensures clip-mask tasks get allocated for these + // pixel regions, even if no other clips affect them. + segment_builder.push_mask_region( + info.prim_shadow_rect, + info.prim_shadow_rect.inflate( + -0.5 * info.shadow_rect_alloc_size.width, + -0.5 * info.shadow_rect_alloc_size.height, + ), + inner_clip_mode, + ); + + continue; + } + ClipItem::LineDecoration(..) | ClipItem::Image(..) => { + rect_clips_only = false; + continue; + } + }; + + segment_builder.push_clip_rect(local_clip_rect, radius, mode); + } + + if is_large || rect_clips_only { + match brush.segment_desc { + Some(ref mut segment_desc) => { + segment_desc.clip_mask_kind = clip_mask_kind; + } + None => { + // TODO(gw): We can probably make the allocation + // patterns of this and the segment + // builder significantly better, by + // retaining it across primitives. + let mut segments = Vec::new(); + + segment_builder.build(|segment| { + segments.push( + BrushSegment::new( + segment.rect, + segment.has_mask, + segment.edge_flags, + [0.0; 4], + BrushFlags::empty(), + ), + ); + }); + + brush.segment_desc = Some(BrushSegmentDescriptor { + segments, + clip_mask_kind, + }); + } + } + } +} From 042c1860c0de804e1dfc3f64230f5e3e441cd3af Mon Sep 17 00:00:00 2001 From: Glenn Watson Date: Fri, 10 Aug 2018 15:53:30 +1000 Subject: [PATCH 03/11] Move update_clip_task_for_brush to method of Primitive. --- webrender/src/prim_store.rs | 148 ++++++++++++++++++------------------ 1 file changed, 74 insertions(+), 74 deletions(-) diff --git a/webrender/src/prim_store.rs b/webrender/src/prim_store.rs index 4fb81c1111..0ee6896ce7 100644 --- a/webrender/src/prim_store.rs +++ b/webrender/src/prim_store.rs @@ -1982,77 +1982,6 @@ impl PrimitiveStore { } } - fn update_clip_task_for_brush( - &mut self, - prim_run_context: &PrimitiveRunContext, - prim_index: PrimitiveIndex, - clip_chain: &ClipChainInstance, - combined_outer_rect: &DeviceIntRect, - pic_state: &mut PictureState, - frame_context: &FrameBuildingContext, - frame_state: &mut FrameBuildingState, - ) -> bool { - debug_assert!(frame_context.screen_rect.contains_rect(combined_outer_rect)); - - let prim = &mut self.primitives[prim_index.0]; - let brush = match prim.details { - PrimitiveDetails::Brush(ref mut brush) => brush, - PrimitiveDetails::TextRun(..) => return false, - }; - - write_brush_segment_description( - brush, - &prim.metadata, - clip_chain, - frame_state, - ); - - let segment_desc = match brush.segment_desc { - Some(ref mut description) => description, - None => return false, - }; - let clip_mask_kind = segment_desc.clip_mask_kind; - - for segment in &mut segment_desc.segments { - if !segment.may_need_clip_mask && clip_mask_kind != BrushClipMaskKind::Global { - segment.clip_task_id = BrushSegmentTaskId::Opaque; - continue; - } - - let intersected_rect = calculate_screen_bounding_rect( - &prim_run_context.scroll_node.world_content_transform, - &segment.local_rect, - frame_context.device_pixel_scale, - Some(&combined_outer_rect), - ); - - let bounds = match intersected_rect { - Some(bounds) => bounds, - None => { - segment.clip_task_id = BrushSegmentTaskId::Empty; - continue; - } - }; - - if clip_chain.clips_range.count > 0 { - let clip_task = RenderTask::new_mask( - bounds, - clip_chain.clips_range, - frame_state.clip_store, - frame_state.gpu_cache, - frame_state.resource_cache, - frame_state.render_tasks, - ); - - let clip_task_id = frame_state.render_tasks.add(clip_task); - pic_state.tasks.push(clip_task_id); - segment.clip_task_id = BrushSegmentTaskId::RenderTaskId(clip_task_id); - } - } - - true - } - fn reset_clip_task(&mut self, prim_index: PrimitiveIndex) { let prim = &mut self.primitives[prim_index.0]; prim.metadata.clip_task_id = None; @@ -2082,9 +2011,9 @@ impl PrimitiveStore { self.reset_clip_task(prim_index); // First try to render this primitive's mask using optimized brush rendering. - if self.update_clip_task_for_brush( + let prim = &mut self.primitives[prim_index.0]; + if prim.update_clip_task_for_brush( prim_run_context, - prim_index, &clip_chain, prim_screen_rect, pic_state, @@ -2112,7 +2041,7 @@ impl PrimitiveStore { println!("\tcreated task {:?} with combined outer rect {:?}", clip_task_id, prim_screen_rect); } - self.primitives[prim_index.0].metadata.clip_task_id = Some(clip_task_id); + prim.metadata.clip_task_id = Some(clip_task_id); pic_state.tasks.push(clip_task_id); } @@ -2792,3 +2721,74 @@ fn write_brush_segment_description( } } } + +impl Primitive { + fn update_clip_task_for_brush( + &mut self, + prim_run_context: &PrimitiveRunContext, + clip_chain: &ClipChainInstance, + combined_outer_rect: &DeviceIntRect, + pic_state: &mut PictureState, + frame_context: &FrameBuildingContext, + frame_state: &mut FrameBuildingState, + ) -> bool { + debug_assert!(frame_context.screen_rect.contains_rect(combined_outer_rect)); + + let brush = match self.details { + PrimitiveDetails::Brush(ref mut brush) => brush, + PrimitiveDetails::TextRun(..) => return false, + }; + + write_brush_segment_description( + brush, + &self.metadata, + clip_chain, + frame_state, + ); + + let segment_desc = match brush.segment_desc { + Some(ref mut description) => description, + None => return false, + }; + let clip_mask_kind = segment_desc.clip_mask_kind; + + for segment in &mut segment_desc.segments { + if !segment.may_need_clip_mask && clip_mask_kind != BrushClipMaskKind::Global { + segment.clip_task_id = BrushSegmentTaskId::Opaque; + continue; + } + + let intersected_rect = calculate_screen_bounding_rect( + &prim_run_context.scroll_node.world_content_transform, + &segment.local_rect, + frame_context.device_pixel_scale, + Some(&combined_outer_rect), + ); + + let bounds = match intersected_rect { + Some(bounds) => bounds, + None => { + segment.clip_task_id = BrushSegmentTaskId::Empty; + continue; + } + }; + + if clip_chain.clips_range.count > 0 { + let clip_task = RenderTask::new_mask( + bounds, + clip_chain.clips_range, + frame_state.clip_store, + frame_state.gpu_cache, + frame_state.resource_cache, + frame_state.render_tasks, + ); + + let clip_task_id = frame_state.render_tasks.add(clip_task); + pic_state.tasks.push(clip_task_id); + segment.clip_task_id = BrushSegmentTaskId::RenderTaskId(clip_task_id); + } + } + + true + } +} From 09fa8791ec917a426b80ae0efa12bd5c80ed0e96 Mon Sep 17 00:00:00 2001 From: Glenn Watson Date: Fri, 10 Aug 2018 16:02:41 +1000 Subject: [PATCH 04/11] Move reset_clip_mask to be method of Primitive. --- webrender/src/prim_store.rs | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/webrender/src/prim_store.rs b/webrender/src/prim_store.rs index 0ee6896ce7..144a786037 100644 --- a/webrender/src/prim_store.rs +++ b/webrender/src/prim_store.rs @@ -1982,18 +1982,6 @@ impl PrimitiveStore { } } - fn reset_clip_task(&mut self, prim_index: PrimitiveIndex) { - let prim = &mut self.primitives[prim_index.0]; - prim.metadata.clip_task_id = None; - if let PrimitiveDetails::Brush(ref mut brush) = prim.details { - if let Some(ref mut desc) = brush.segment_desc { - for segment in &mut desc.segments { - segment.clip_task_id = BrushSegmentTaskId::Opaque; - } - } - } - } - fn update_clip_task( &mut self, prim_index: PrimitiveIndex, @@ -2008,10 +1996,10 @@ impl PrimitiveStore { println!("\tupdating clip task with screen rect {:?}", prim_screen_rect); } // Reset clips from previous frames since we may clip differently each frame. - self.reset_clip_task(prim_index); + let prim = &mut self.primitives[prim_index.0]; + prim.reset_clip_task(); // First try to render this primitive's mask using optimized brush rendering. - let prim = &mut self.primitives[prim_index.0]; if prim.update_clip_task_for_brush( prim_run_context, &clip_chain, @@ -2791,4 +2779,15 @@ impl Primitive { true } + + fn reset_clip_task(&mut self) { + self.metadata.clip_task_id = None; + if let PrimitiveDetails::Brush(ref mut brush) = self.details { + if let Some(ref mut desc) = brush.segment_desc { + for segment in &mut desc.segments { + segment.clip_task_id = BrushSegmentTaskId::Opaque; + } + } + } + } } From 8ab87d934ca3dddad28201277abe03b3d5364bab Mon Sep 17 00:00:00 2001 From: Glenn Watson Date: Fri, 10 Aug 2018 16:08:13 +1000 Subject: [PATCH 05/11] Move prepare_prim_for_render_inner to method of Primitive --- webrender/src/prim_store.rs | 2175 ++++++++++++++++++----------------- 1 file changed, 1089 insertions(+), 1086 deletions(-) diff --git a/webrender/src/prim_store.rs b/webrender/src/prim_store.rs index 144a786037..ebd07d467b 100644 --- a/webrender/src/prim_store.rs +++ b/webrender/src/prim_store.rs @@ -1531,1261 +1531,1264 @@ impl PrimitiveStore { } } - fn prepare_prim_for_render_inner( + fn update_clip_task( &mut self, prim_index: PrimitiveIndex, prim_run_context: &PrimitiveRunContext, - pic_state_for_children: PictureState, - pic_context: &PictureContext, + prim_screen_rect: &DeviceIntRect, + clip_chain: &ClipChainInstance, pic_state: &mut PictureState, frame_context: &FrameBuildingContext, frame_state: &mut FrameBuildingState, - ) { - let mut is_tiled = false; - let prim = &mut self.primitives[prim_index.0]; - let metadata = &mut prim.metadata; - #[cfg(debug_assertions)] - { - metadata.prepared_frame_id = frame_state.render_tasks.frame_id(); + ) -> bool { + if cfg!(debug_assertions) && Some(prim_index) == self.chase_id { + println!("\tupdating clip task with screen rect {:?}", prim_screen_rect); } + // Reset clips from previous frames since we may clip differently each frame. + let prim = &mut self.primitives[prim_index.0]; + prim.reset_clip_task(); - match prim.details { - PrimitiveDetails::TextRun(ref mut text) => { - // The transform only makes sense for screen space rasterization - let transform = prim_run_context.scroll_node.world_content_transform.to_transform(); - text.prepare_for_render( - frame_context.device_pixel_scale, - &transform, - pic_context.allow_subpixel_aa, - pic_context.display_list, - frame_state, - ); + // First try to render this primitive's mask using optimized brush rendering. + if prim.update_clip_task_for_brush( + prim_run_context, + &clip_chain, + prim_screen_rect, + pic_state, + frame_context, + frame_state, + ) { + if cfg!(debug_assertions) && Some(prim_index) == self.chase_id { + println!("\tsegment tasks have been created for clipping"); } - PrimitiveDetails::Brush(ref mut brush) => { + return true; + } - match brush.kind { - BrushKind::Image { - request, - sub_rect, - stretch_size, - ref mut tile_spacing, - ref mut source, - ref mut opacity_binding, - ref mut visible_tiles, - .. - } => { - let image_properties = frame_state - .resource_cache - .get_image_properties(request.key); + if clip_chain.clips_range.count > 0 { + let clip_task = RenderTask::new_mask( + *prim_screen_rect, + clip_chain.clips_range, + frame_state.clip_store, + frame_state.gpu_cache, + frame_state.resource_cache, + frame_state.render_tasks, + ); + let clip_task_id = frame_state.render_tasks.add(clip_task); + if cfg!(debug_assertions) && Some(prim_index) == self.chase_id { + println!("\tcreated task {:?} with combined outer rect {:?}", + clip_task_id, prim_screen_rect); + } + prim.metadata.clip_task_id = Some(clip_task_id); + pic_state.tasks.push(clip_task_id); + } - // Set if we need to request the source image from the cache this frame. - if let Some(image_properties) = image_properties { - is_tiled = image_properties.tiling.is_some(); + true + } - // If the opacity changed, invalidate the GPU cache so that - // the new color for this primitive gets uploaded. - if opacity_binding.update(frame_context.scene_properties) { - frame_state.gpu_cache.invalidate(&mut metadata.gpu_location); - } + pub fn prepare_prim_for_render( + &mut self, + prim_index: PrimitiveIndex, + prim_run_context: &PrimitiveRunContext, + pic_context: &PictureContext, + pic_state: &mut PictureState, + frame_context: &FrameBuildingContext, + frame_state: &mut FrameBuildingState, + ) -> Option { + let mut may_need_clip_mask = true; + let mut pic_state_for_children = PictureState::new(); + let is_chased = Some(prim_index) == self.chase_id; - // Update opacity for this primitive to ensure the correct - // batching parameters are used. - metadata.opacity.is_opaque = - image_properties.descriptor.is_opaque && - opacity_binding.current == 1.0; + // If we have dependencies, we need to prepare them first, in order + // to know the actual rect of this primitive. + // For example, scrolling may affect the location of an item in + // local space, which may force us to render this item on a larger + // picture target, if being composited. + let pic_context_for_children = { + let prim = &mut self.primitives[prim_index.0]; - if *tile_spacing != LayoutSize::zero() && !is_tiled { - *source = ImageSource::Cache { - // Size in device-pixels we need to allocate in render task cache. - size: image_properties.descriptor.size.to_i32(), - handle: None, - }; - } + // Do some basic checks first, that can early out + // without even knowing the local rect. + if !prim.metadata.is_backface_visible && prim_run_context.transform.backface_is_visible { + if cfg!(debug_assertions) && Some(prim_index) == self.chase_id { + println!("\tculled for not having visible back faces"); + } + return None; + } - // Work out whether this image is a normal / simple type, or if - // we need to pre-render it to the render task cache. - if let Some(rect) = sub_rect { - // We don't properly support this right now. - debug_assert!(!is_tiled); - *source = ImageSource::Cache { - // Size in device-pixels we need to allocate in render task cache. - size: rect.size, - handle: None, - }; + match prim.details { + PrimitiveDetails::Brush(ref mut brush) => { + match brush.kind { + BrushKind::Picture(ref mut pic) => { + if !pic.resolve_scene_properties(frame_context.scene_properties) { + if cfg!(debug_assertions) && Some(prim_index) == self.chase_id { + println!("\tculled for carrying an invisible composite filter"); + } + return None; } - let mut request_source_image = false; + may_need_clip_mask = pic.composite_mode.is_some(); - // Every frame, for cached items, we need to request the render - // task cache item. The closure will be invoked on the first - // time through, and any time the render task output has been - // evicted from the texture cache. - match *source { - ImageSource::Cache { ref mut size, ref mut handle } => { - let padding = DeviceIntSideOffsets::new( - 0, - (tile_spacing.width * size.width as f32 / stretch_size.width) as i32, - (tile_spacing.height * size.height as f32 / stretch_size.height) as i32, - 0, - ); + let inflation_factor = match pic.composite_mode { + Some(PictureCompositeMode::Filter(FilterOp::Blur(blur_radius))) => { + // The amount of extra space needed for primitives inside + // this picture to ensure the visibility check is correct. + BLUR_SAMPLE_SCALE * blur_radius + } + _ => { + 0.0 + } + }; - let inner_size = *size; - size.width += padding.horizontal(); - size.height += padding.vertical(); + let display_list = &frame_context + .pipelines + .get(&pic.pipeline_id) + .expect("No display list?") + .display_list; - if padding != DeviceIntSideOffsets::zero() { - metadata.opacity.is_opaque = false; - } + let inv_world_transform = prim_run_context + .scroll_node + .world_content_transform + .inverse(); - let image_cache_key = ImageCacheKey { - request, - texel_rect: sub_rect, - }; + // Mark whether this picture has a complex coordinate system. + pic_state_for_children.has_non_root_coord_system |= + prim_run_context.scroll_node.coordinate_system_id != CoordinateSystemId::root(); - // Request a pre-rendered image task. - *handle = Some(frame_state.resource_cache.request_render_task( - RenderTaskCacheKey { - size: *size, - kind: RenderTaskCacheKeyKind::Image(image_cache_key), - }, - frame_state.gpu_cache, - frame_state.render_tasks, - None, - image_properties.descriptor.is_opaque, - |render_tasks| { - // We need to render the image cache this frame, - // so will need access to the source texture. - request_source_image = true; + Some(PictureContext { + pipeline_id: pic.pipeline_id, + prim_runs: mem::replace(&mut pic.runs, Vec::new()), + original_reference_frame_index: Some(pic.reference_frame_index), + display_list, + inv_world_transform, + apply_local_clip_rect: pic.apply_local_clip_rect, + inflation_factor, + // TODO(lsalzman): allow overriding parent if intermediate surface is opaque + allow_subpixel_aa: pic_context.allow_subpixel_aa && pic.allow_subpixel_aa(), + }) + } + _ => { + None + } + } + } + PrimitiveDetails::TextRun(..) => { + None + } + } + }; - // Create a task to blit from the texture cache to - // a normal transient render task surface. This will - // copy only the sub-rect, if specified. - let cache_to_target_task = RenderTask::new_blit_with_padding( - inner_size, - &padding, - BlitSource::Image { key: image_cache_key }, - ); - let cache_to_target_task_id = render_tasks.add(cache_to_target_task); + if let Some(pic_context_for_children) = pic_context_for_children { + let result = self.prepare_prim_runs( + &pic_context_for_children, + &mut pic_state_for_children, + frame_context, + frame_state, + ); - // Create a task to blit the rect from the child render - // task above back into the right spot in the persistent - // render target cache. - let target_to_cache_task = RenderTask::new_blit( - *size, - BlitSource::RenderTask { - task_id: cache_to_target_task_id, - }, - ); - let target_to_cache_task_id = render_tasks.add(target_to_cache_task); + // Restore the dependencies (borrow check dance) + let prim = &mut self.primitives[prim_index.0]; + let new_local_rect = prim + .as_pic_mut() + .update_local_rect(result, pic_context_for_children.prim_runs); - // Hook this into the render task tree at the right spot. - pic_state.tasks.push(target_to_cache_task_id); + if new_local_rect != prim.metadata.local_rect { + prim.metadata.local_rect = new_local_rect; + frame_state.gpu_cache.invalidate(&mut prim.metadata.gpu_location); + pic_state.local_rect_changed = true; + } + } - // Pass the image opacity, so that the cached render task - // item inherits the same opacity properties. - target_to_cache_task_id - } - )); - } - ImageSource::Default => { - // Normal images just reference the source texture each frame. - request_source_image = true; - } - } + let (local_rect, local_clip_rect, clip_chain_id) = { + let metadata = &mut self.primitives[prim_index.0].metadata; + if metadata.local_rect.size.width <= 0.0 || + metadata.local_rect.size.height <= 0.0 { + if cfg!(debug_assertions) && Some(prim_index) == self.chase_id { + println!("\tculled for zero local rectangle"); + } + return None; + } - if let Some(tile_size) = image_properties.tiling { + metadata.screen_rect = None; - let device_image_size = image_properties.descriptor.size; + // Inflate the local rect for this primitive by the inflation factor of + // the picture context. This ensures that even if the primitive itself + // is not visible, any effects from the blur radius will be correctly + // taken into account. + let local_rect = metadata.local_rect + .inflate(pic_context.inflation_factor, pic_context.inflation_factor) + .intersection(&metadata.local_clip_rect); + let local_rect = match local_rect { + Some(local_rect) => local_rect, + None => { + if cfg!(debug_assertions) && Some(prim_index) == self.chase_id { + println!("\tculled for being out of the local clip rectangle: {:?}", + metadata.local_clip_rect); + } + return None + } + }; - // Tighten the clip rect because decomposing the repeated image can - // produce primitives that are partially covering the original image - // rect and we want to clip these extra parts out. - let tight_clip_rect = metadata - .combined_local_clip_rect - .intersection(&metadata.local_rect).unwrap(); + (local_rect, metadata.local_clip_rect, metadata.clip_chain_id) + }; - let visible_rect = compute_conservative_visible_rect( - prim_run_context, - frame_context, - &metadata.screen_rect.unwrap().clipped, - &tight_clip_rect - ); + let clip_chain = frame_state + .clip_store + .build_clip_chain_instance( + clip_chain_id, + local_rect, + local_clip_rect, + prim_run_context.spatial_node_index, + &frame_context.clip_scroll_tree.spatial_nodes, + frame_state.gpu_cache, + frame_state.resource_cache, + frame_context.device_pixel_scale, + ); - let base_edge_flags = edge_flags_for_tile_spacing(tile_spacing); + let clip_chain = match clip_chain { + Some(clip_chain) => clip_chain, + None => { + self.primitives[prim_index.0].metadata.screen_rect = None; + return None; + } + }; - let stride = stretch_size + *tile_spacing; + if self.chase_id == Some(prim_index) { + println!("\teffective clip chain from {:?} {}", + clip_chain.clips_range, + if pic_context.apply_local_clip_rect { "(applied)" } else { "" }, + ); + } - visible_tiles.clear(); + pic_state.has_non_root_coord_system |= clip_chain.has_non_root_coord_system; - for_each_repetition( - &metadata.local_rect, - &visible_rect, - &stride, - &mut |origin, edge_flags| { - let edge_flags = base_edge_flags | edge_flags; + let unclipped_device_rect = match calculate_screen_bounding_rect( + &prim_run_context.scroll_node.world_content_transform, + &local_rect, + frame_context.device_pixel_scale, + None, //TODO: inflate `frame_context.screen_rect` appropriately + ) { + Some(rect) => rect, + None => { + if cfg!(debug_assertions) && Some(prim_index) == self.chase_id { + println!("\tculled for being behind the near plane of transform: {:?}", + prim_run_context.scroll_node.world_content_transform); + } + return None + } + }; - let image_rect = LayoutRect { - origin: *origin, - size: stretch_size, - }; + let clipped_device_rect = match calculate_screen_bounding_rect( + &prim_run_context.scroll_node.world_content_transform, + &clip_chain.local_bounding_rect, + frame_context.device_pixel_scale, + None, + ) { + Some(rect) => rect, + None => { + if cfg!(debug_assertions) && Some(prim_index) == self.chase_id { + println!("\tculled for being behind the near plane of transform: {:?}", + prim_run_context.scroll_node.world_content_transform); + } + return None + } + }; - for_each_tile( - &image_rect, - &visible_rect, - &device_image_size, - tile_size as u32, - &mut |tile_rect, tile_offset, tile_flags| { + let clipped_device_rect = match clipped_device_rect.intersection(&frame_context.screen_rect) { + Some(clipped_device_rect) => clipped_device_rect, + None => return None, + }; - frame_state.resource_cache.request_image( - request.with_tile(tile_offset), - frame_state.gpu_cache, - ); + let ccr = { + let metadata = &mut self.primitives[prim_index.0].metadata; - let mut handle = GpuCacheHandle::new(); - if let Some(mut request) = frame_state.gpu_cache.request(&mut handle) { - request.push(ColorF::new(1.0, 1.0, 1.0, opacity_binding.current).premultiplied()); - request.push(PremultipliedColorF::WHITE); - request.push([tile_rect.size.width, tile_rect.size.height, 0.0, 0.0]); - request.write_segment(*tile_rect, [0.0; 4]); - } + metadata.screen_rect = Some(ScreenRect { + clipped: clipped_device_rect, + unclipped: unclipped_device_rect, + }); - visible_tiles.push(VisibleImageTile { - tile_offset, - handle, - edge_flags: tile_flags & edge_flags, - local_rect: *tile_rect, - local_clip_rect: tight_clip_rect, - }); - } - ); - } - ); + metadata.combined_local_clip_rect = if pic_context.apply_local_clip_rect { + clip_chain.local_clip_rect + } else { + local_clip_rect + }; - if visible_tiles.is_empty() { - // At this point if we don't have tiles to show it means we could probably - // have done a better a job at culling during an earlier stage. - // Clearing the screen rect has the effect of "culling out" the primitive - // from the point of view of the batch builder, and ensures we don't hit - // assertions later on because we didn't request any image. - metadata.screen_rect = None; - } - } else if request_source_image { - frame_state.resource_cache.request_image( - request, - frame_state.gpu_cache, - ); - } - } - } - BrushKind::YuvImage { format, yuv_key, image_rendering, .. } => { - let channel_num = format.get_plane_num(); - debug_assert!(channel_num <= 3); - for channel in 0 .. channel_num { - frame_state.resource_cache.request_image( - ImageRequest { - key: yuv_key[channel], - rendering: image_rendering, - tile: None, - }, - frame_state.gpu_cache, - ); - } - } - BrushKind::Border { ref mut source, .. } => { - match *source { - BorderSource::Image(request) => { - let image_properties = frame_state - .resource_cache - .get_image_properties(request.key); + match metadata.combined_local_clip_rect.intersection(&local_rect) { + Some(ccr) => ccr, + None => return None, + } + }; - if let Some(image_properties) = image_properties { - // Update opacity for this primitive to ensure the correct - // batching parameters are used. - metadata.opacity.is_opaque = - image_properties.descriptor.is_opaque; + self.build_prim_segments_if_needed( + prim_index, + pic_state, + frame_state, + frame_context, + ); - frame_state.resource_cache.request_image( - request, - frame_state.gpu_cache, - ); - } - } - BorderSource::Border { .. } => { - // Handled earlier since we need to update the segment - // descriptor *before* update_clip_task() is called. - } - } - } - BrushKind::RadialGradient { - stops_range, - center, - start_radius, - end_radius, - ratio_xy, - extend_mode, - stretch_size, - tile_spacing, - ref mut stops_handle, - ref mut visible_tiles, - .. - } => { - build_gradient_stops_request( - stops_handle, - stops_range, - false, - frame_state, - pic_context, - ); - - if tile_spacing != LayoutSize::zero() { - is_tiled = true; - - decompose_repeated_primitive( - visible_tiles, - metadata, - &stretch_size, - &tile_spacing, - prim_run_context, - frame_context, - frame_state, - &mut |rect, mut request| { - request.push([ - center.x, - center.y, - start_radius, - end_radius, - ]); - request.push([ - ratio_xy, - pack_as_float(extend_mode as u32), - stretch_size.width, - stretch_size.height, - ]); - request.write_segment(*rect, [0.0; 4]); - }, - ); - } - } - BrushKind::LinearGradient { - stops_range, - reverse_stops, - start_point, - end_point, - extend_mode, - stretch_size, - tile_spacing, - ref mut stops_handle, - ref mut visible_tiles, - .. - } => { - - build_gradient_stops_request( - stops_handle, - stops_range, - reverse_stops, - frame_state, - pic_context, - ); - - if tile_spacing != LayoutSize::zero() { - is_tiled = true; - - decompose_repeated_primitive( - visible_tiles, - metadata, - &stretch_size, - &tile_spacing, - prim_run_context, - frame_context, - frame_state, - &mut |rect, mut request| { - request.push([ - start_point.x, - start_point.y, - end_point.x, - end_point.y, - ]); - request.push([ - pack_as_float(extend_mode as u32), - stretch_size.width, - stretch_size.height, - 0.0, - ]); - request.write_segment(*rect, [0.0; 4]); - } - ); - } - } - BrushKind::Picture(ref mut pic) => { - pic.prepare_for_render( - prim_index, - metadata, - prim_run_context, - pic_state_for_children, - pic_state, - frame_context, - frame_state, - ); - } - BrushKind::Solid { ref color, ref mut opacity_binding, .. } => { - // If the opacity changed, invalidate the GPU cache so that - // the new color for this primitive gets uploaded. Also update - // the opacity field that controls which batches this primitive - // will be added to. - if opacity_binding.update(frame_context.scene_properties) { - metadata.opacity = PrimitiveOpacity::from_alpha(opacity_binding.current * color.a); - frame_state.gpu_cache.invalidate(&mut metadata.gpu_location); - } - } - BrushKind::Clear => {} - } - } - } - - if is_tiled { - // we already requested each tile's gpu data. - return; - } - - // Mark this GPU resource as required for this frame. - if let Some(mut request) = frame_state.gpu_cache.request(&mut metadata.gpu_location) { - match prim.details { - PrimitiveDetails::TextRun(ref mut text) => { - text.write_gpu_blocks(&mut request); - } - PrimitiveDetails::Brush(ref mut brush) => { - brush.write_gpu_blocks(&mut request, metadata.local_rect); - - match brush.segment_desc { - Some(ref segment_desc) => { - for segment in &segment_desc.segments { - if cfg!(debug_assertions) && self.chase_id == Some(prim_index) { - println!("\t\t{:?}", segment); - } - // has to match VECS_PER_SEGMENT - request.write_segment( - segment.local_rect, - segment.extra_data, - ); - } - } - None => { - request.write_segment( - metadata.local_rect, - [0.0; 4], - ); - } - } - } - } + if may_need_clip_mask && !self.update_clip_task( + prim_index, + prim_run_context, + &clipped_device_rect, + &clip_chain, + pic_state, + frame_context, + frame_state, + ) { + return None; } - } - fn update_clip_task( - &mut self, - prim_index: PrimitiveIndex, - prim_run_context: &PrimitiveRunContext, - prim_screen_rect: &DeviceIntRect, - clip_chain: &ClipChainInstance, - pic_state: &mut PictureState, - frame_context: &FrameBuildingContext, - frame_state: &mut FrameBuildingState, - ) -> bool { if cfg!(debug_assertions) && Some(prim_index) == self.chase_id { - println!("\tupdating clip task with screen rect {:?}", prim_screen_rect); + println!("\tconsidered visible and ready with local rect {:?}", local_rect); } - // Reset clips from previous frames since we may clip differently each frame. - let prim = &mut self.primitives[prim_index.0]; - prim.reset_clip_task(); - // First try to render this primitive's mask using optimized brush rendering. - if prim.update_clip_task_for_brush( + let prim = &mut self.primitives[prim_index.0]; + prim.prepare_prim_for_render_inner( + prim_index, prim_run_context, - &clip_chain, - prim_screen_rect, + pic_state_for_children, + pic_context, pic_state, frame_context, frame_state, - ) { - if cfg!(debug_assertions) && Some(prim_index) == self.chase_id { - println!("\tsegment tasks have been created for clipping"); - } - return true; - } + is_chased, + ); - if clip_chain.clips_range.count > 0 { - let clip_task = RenderTask::new_mask( - *prim_screen_rect, - clip_chain.clips_range, - frame_state.clip_store, - frame_state.gpu_cache, - frame_state.resource_cache, - frame_state.render_tasks, - ); + Some(ccr) + } - let clip_task_id = frame_state.render_tasks.add(clip_task); - if cfg!(debug_assertions) && Some(prim_index) == self.chase_id { - println!("\tcreated task {:?} with combined outer rect {:?}", - clip_task_id, prim_screen_rect); - } - prim.metadata.clip_task_id = Some(clip_task_id); - pic_state.tasks.push(clip_task_id); + // TODO(gw): Make this simpler / more efficient by tidying + // up the logic that early outs from prepare_prim_for_render. + pub fn reset_prim_visibility(&mut self) { + for prim in &mut self.primitives { + prim.metadata.screen_rect = None; } - - true } - pub fn prepare_prim_for_render( + pub fn prepare_prim_runs( &mut self, - prim_index: PrimitiveIndex, - prim_run_context: &PrimitiveRunContext, pic_context: &PictureContext, pic_state: &mut PictureState, frame_context: &FrameBuildingContext, frame_state: &mut FrameBuildingState, - ) -> Option { - let mut may_need_clip_mask = true; - let mut pic_state_for_children = PictureState::new(); + ) -> PrimitiveRunLocalRect { + let mut result = PrimitiveRunLocalRect { + local_rect_in_actual_parent_space: LayoutRect::zero(), + local_rect_in_original_parent_space: LayoutRect::zero(), + }; - // If we have dependencies, we need to prepare them first, in order - // to know the actual rect of this primitive. - // For example, scrolling may affect the location of an item in - // local space, which may force us to render this item on a larger - // picture target, if being composited. - let pic_context_for_children = { - let prim = &mut self.primitives[prim_index.0]; + for run in &pic_context.prim_runs { + // TODO(gw): Perhaps we can restructure this to not need to create + // a new primitive context for every run (if the hash + // lookups ever show up in a profile). + let scroll_node = &frame_context + .clip_scroll_tree + .spatial_nodes[run.spatial_node_index.0]; - // Do some basic checks first, that can early out - // without even knowing the local rect. - if !prim.metadata.is_backface_visible && prim_run_context.transform.backface_is_visible { - if cfg!(debug_assertions) && Some(prim_index) == self.chase_id { - println!("\tculled for not having visible back faces"); + if run.is_chasing(self.chase_id) { + println!("\tpreparing a run of length {} in pipeline {:?}", + run.count, pic_context.pipeline_id); + println!("\trun {:?}", run.spatial_node_index); + println!("\ttransform {:?}", scroll_node.world_content_transform.to_transform()); + } + + // Mark whether this picture contains any complex coordinate + // systems, due to either the scroll node or the clip-chain. + pic_state.has_non_root_coord_system |= + scroll_node.coordinate_system_id != CoordinateSystemId::root(); + + if !scroll_node.invertible { + if run.is_chasing(self.chase_id) { + println!("\tculled for the scroll node transform being invertible"); } - return None; + continue; } - match prim.details { - PrimitiveDetails::Brush(ref mut brush) => { - match brush.kind { - BrushKind::Picture(ref mut pic) => { - if !pic.resolve_scene_properties(frame_context.scene_properties) { - if cfg!(debug_assertions) && Some(prim_index) == self.chase_id { - println!("\tculled for carrying an invisible composite filter"); - } - return None; - } + let parent_relative_transform = pic_context + .inv_world_transform + .map(|inv_parent| { + inv_parent.pre_mul(&scroll_node.world_content_transform) + }); - may_need_clip_mask = pic.composite_mode.is_some(); + let original_relative_transform = pic_context + .original_reference_frame_index + .and_then(|original_reference_frame_index| { + frame_context + .clip_scroll_tree + .spatial_nodes[original_reference_frame_index.0] + .world_content_transform + .inverse() + }) + .map(|inv_parent| { + inv_parent.pre_mul(&scroll_node.world_content_transform) + }); - let inflation_factor = match pic.composite_mode { - Some(PictureCompositeMode::Filter(FilterOp::Blur(blur_radius))) => { - // The amount of extra space needed for primitives inside - // this picture to ensure the visibility check is correct. - BLUR_SAMPLE_SCALE * blur_radius - } - _ => { - 0.0 - } - }; + let transform = frame_context + .transforms + .get_transform(run.spatial_node_index); - let display_list = &frame_context - .pipelines - .get(&pic.pipeline_id) - .expect("No display list?") - .display_list; + let child_prim_run_context = PrimitiveRunContext::new( + scroll_node, + run.spatial_node_index, + transform, + ); - let inv_world_transform = prim_run_context - .scroll_node - .world_content_transform - .inverse(); + for i in 0 .. run.count { + let prim_index = PrimitiveIndex(run.base_prim_index.0 + i); - // Mark whether this picture has a complex coordinate system. - pic_state_for_children.has_non_root_coord_system |= - prim_run_context.scroll_node.coordinate_system_id != CoordinateSystemId::root(); + if let Some(prim_local_rect) = self.prepare_prim_for_render( + prim_index, + &child_prim_run_context, + pic_context, + pic_state, + frame_context, + frame_state, + ) { + frame_state.profile_counters.visible_primitives.inc(); - Some(PictureContext { - pipeline_id: pic.pipeline_id, - prim_runs: mem::replace(&mut pic.runs, Vec::new()), - original_reference_frame_index: Some(pic.reference_frame_index), - display_list, - inv_world_transform, - apply_local_clip_rect: pic.apply_local_clip_rect, - inflation_factor, - // TODO(lsalzman): allow overriding parent if intermediate surface is opaque - allow_subpixel_aa: pic_context.allow_subpixel_aa && pic.allow_subpixel_aa(), - }) + if let Some(ref matrix) = parent_relative_transform { + match matrix.transform_rect(&prim_local_rect) { + Some(bounds) => { + result.local_rect_in_actual_parent_space = + result.local_rect_in_actual_parent_space.union(&bounds); + } + None => { + warn!("parent relative transform can't transform the primitive rect for {:?}", prim_index); + } } - _ => { - None + + } + if let Some(ref matrix) = original_relative_transform { + match matrix.transform_rect(&prim_local_rect) { + Some(bounds) => { + result.local_rect_in_original_parent_space = + result.local_rect_in_original_parent_space.union(&bounds); + } + None => { + warn!("original relative transform can't transform the primitive rect for {:?}", prim_index); + } } } } - PrimitiveDetails::TextRun(..) => { - None - } } - }; - - if let Some(pic_context_for_children) = pic_context_for_children { - let result = self.prepare_prim_runs( - &pic_context_for_children, - &mut pic_state_for_children, - frame_context, - frame_state, - ); + } - // Restore the dependencies (borrow check dance) - let prim = &mut self.primitives[prim_index.0]; - let new_local_rect = prim - .as_pic_mut() - .update_local_rect(result, pic_context_for_children.prim_runs); + result + } +} - if new_local_rect != prim.metadata.local_rect { - prim.metadata.local_rect = new_local_rect; - frame_state.gpu_cache.invalidate(&mut prim.metadata.gpu_location); - pic_state.local_rect_changed = true; - } - } +fn build_gradient_stops_request( + stops_handle: &mut GpuCacheHandle, + stops_range: ItemRange, + reverse_stops: bool, + frame_state: &mut FrameBuildingState, + pic_context: &PictureContext +) { + if let Some(mut request) = frame_state.gpu_cache.request(stops_handle) { + let gradient_builder = GradientGpuBlockBuilder::new( + stops_range, + pic_context.display_list, + ); + gradient_builder.build( + reverse_stops, + &mut request, + ); + } +} - let (local_rect, local_clip_rect, clip_chain_id) = { - let metadata = &mut self.primitives[prim_index.0].metadata; - if metadata.local_rect.size.width <= 0.0 || - metadata.local_rect.size.height <= 0.0 { - if cfg!(debug_assertions) && Some(prim_index) == self.chase_id { - println!("\tculled for zero local rectangle"); - } - return None; - } +fn decompose_repeated_primitive( + visible_tiles: &mut Vec, + metadata: &mut PrimitiveMetadata, + stretch_size: &LayoutSize, + tile_spacing: &LayoutSize, + prim_run_context: &PrimitiveRunContext, + frame_context: &FrameBuildingContext, + frame_state: &mut FrameBuildingState, + callback: &mut FnMut(&LayoutRect, GpuDataRequest), +) { + visible_tiles.clear(); - metadata.screen_rect = None; + // Tighten the clip rect because decomposing the repeated image can + // produce primitives that are partially covering the original image + // rect and we want to clip these extra parts out. + let tight_clip_rect = metadata + .combined_local_clip_rect + .intersection(&metadata.local_rect).unwrap(); - // Inflate the local rect for this primitive by the inflation factor of - // the picture context. This ensures that even if the primitive itself - // is not visible, any effects from the blur radius will be correctly - // taken into account. - let local_rect = metadata.local_rect - .inflate(pic_context.inflation_factor, pic_context.inflation_factor) - .intersection(&metadata.local_clip_rect); - let local_rect = match local_rect { - Some(local_rect) => local_rect, - None => { - if cfg!(debug_assertions) && Some(prim_index) == self.chase_id { - println!("\tculled for being out of the local clip rectangle: {:?}", - metadata.local_clip_rect); - } - return None - } - }; + let unclipped_device_rect = &metadata + .screen_rect + .unwrap() + .unclipped; - (local_rect, metadata.local_clip_rect, metadata.clip_chain_id) - }; + let visible_rect = compute_conservative_visible_rect( + prim_run_context, + frame_context, + unclipped_device_rect, + &tight_clip_rect + ); + let stride = *stretch_size + *tile_spacing; - let clip_chain = frame_state - .clip_store - .build_clip_chain_instance( - clip_chain_id, - local_rect, - local_clip_rect, - prim_run_context.spatial_node_index, - &frame_context.clip_scroll_tree.spatial_nodes, - frame_state.gpu_cache, - frame_state.resource_cache, - frame_context.device_pixel_scale, - ); + for_each_repetition( + &metadata.local_rect, + &visible_rect, + &stride, + &mut |origin, _| { - let clip_chain = match clip_chain { - Some(clip_chain) => clip_chain, - None => { - self.primitives[prim_index.0].metadata.screen_rect = None; - return None; + let mut handle = GpuCacheHandle::new(); + let rect = LayoutRect { + origin: *origin, + size: *stretch_size, + }; + if let Some(request) = frame_state.gpu_cache.request(&mut handle) { + callback(&rect, request); } - }; - if self.chase_id == Some(prim_index) { - println!("\teffective clip chain from {:?} {}", - clip_chain.clips_range, - if pic_context.apply_local_clip_rect { "(applied)" } else { "" }, - ); + visible_tiles.push(VisibleGradientTile { + local_rect: rect, + local_clip_rect: tight_clip_rect, + handle + }); } + ); - pic_state.has_non_root_coord_system |= clip_chain.has_non_root_coord_system; + if visible_tiles.is_empty() { + // At this point if we don't have tiles to show it means we could probably + // have done a better a job at culling during an earlier stage. + // Clearing the screen rect has the effect of "culling out" the primitive + // from the point of view of the batch builder, and ensures we don't hit + // assertions later on because we didn't request any image. + metadata.screen_rect = None; + } +} - let unclipped_device_rect = match calculate_screen_bounding_rect( - &prim_run_context.scroll_node.world_content_transform, - &local_rect, - frame_context.device_pixel_scale, - None, //TODO: inflate `frame_context.screen_rect` appropriately - ) { - Some(rect) => rect, - None => { - if cfg!(debug_assertions) && Some(prim_index) == self.chase_id { - println!("\tculled for being behind the near plane of transform: {:?}", - prim_run_context.scroll_node.world_content_transform); - } - return None - } - }; +fn compute_conservative_visible_rect( + prim_run_context: &PrimitiveRunContext, + frame_context: &FrameBuildingContext, + clipped_device_rect: &DeviceIntRect, + local_clip_rect: &LayoutRect, +) -> LayoutRect { + let world_screen_rect = clipped_device_rect + .to_f32() / frame_context.device_pixel_scale; - let clipped_device_rect = match calculate_screen_bounding_rect( - &prim_run_context.scroll_node.world_content_transform, - &clip_chain.local_bounding_rect, - frame_context.device_pixel_scale, - None, - ) { - Some(rect) => rect, - None => { - if cfg!(debug_assertions) && Some(prim_index) == self.chase_id { - println!("\tculled for being behind the near plane of transform: {:?}", - prim_run_context.scroll_node.world_content_transform); - } - return None - } - }; + if let Some(layer_screen_rect) = prim_run_context + .scroll_node + .world_content_transform + .unapply(&world_screen_rect) { - let clipped_device_rect = match clipped_device_rect.intersection(&frame_context.screen_rect) { - Some(clipped_device_rect) => clipped_device_rect, - None => return None, - }; + return local_clip_rect.intersection(&layer_screen_rect).unwrap_or(LayoutRect::zero()); + } - let ccr = { - let metadata = &mut self.primitives[prim_index.0].metadata; + *local_clip_rect +} - metadata.screen_rect = Some(ScreenRect { - clipped: clipped_device_rect, - unclipped: unclipped_device_rect, - }); +fn edge_flags_for_tile_spacing(tile_spacing: &LayoutSize) -> EdgeAaSegmentMask { + let mut flags = EdgeAaSegmentMask::empty(); - metadata.combined_local_clip_rect = if pic_context.apply_local_clip_rect { - clip_chain.local_clip_rect - } else { - local_clip_rect - }; + if tile_spacing.width > 0.0 { + flags |= EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::RIGHT; + } + if tile_spacing.height > 0.0 { + flags |= EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::BOTTOM; + } - match metadata.combined_local_clip_rect.intersection(&local_rect) { - Some(ccr) => ccr, - None => return None, - } - }; + flags +} - self.build_prim_segments_if_needed( - prim_index, - pic_state, - frame_state, - frame_context, - ); +impl<'a> GpuDataRequest<'a> { + // Write the GPU cache data for an individual segment. + fn write_segment( + &mut self, + local_rect: LayoutRect, + extra_data: [f32; 4], + ) { + let _ = VECS_PER_SEGMENT; + self.push(local_rect); + self.push(extra_data); + } +} - if may_need_clip_mask && !self.update_clip_task( - prim_index, - prim_run_context, - &clipped_device_rect, - &clip_chain, - pic_state, - frame_context, - frame_state, - ) { - return None; +fn write_brush_segment_description( + brush: &mut BrushPrimitive, + metadata: &PrimitiveMetadata, + clip_chain: &ClipChainInstance, + frame_state: &mut FrameBuildingState, +) { + match brush.segment_desc { + Some(ref segment_desc) => { + // If we already have a segment descriptor, only run through the + // clips list if we haven't already determined the mask kind. + if segment_desc.clip_mask_kind != BrushClipMaskKind::Unknown { + return; + } } - - if cfg!(debug_assertions) && Some(prim_index) == self.chase_id { - println!("\tconsidered visible and ready with local rect {:?}", local_rect); + None => { + // If no segment descriptor built yet, see if it is a brush + // type that wants to be segmented. + if !brush.kind.supports_segments(frame_state.resource_cache) { + return; + } } + } - self.prepare_prim_for_render_inner( - prim_index, - prim_run_context, - pic_state_for_children, - pic_context, - pic_state, - frame_context, - frame_state, - ); + // If the brush is small, we generally want to skip building segments + // and just draw it as a single primitive with clip mask. However, + // if the clips are purely rectangles that have no per-fragment + // clip masks, we will segment anyway. This allows us to completely + // skip allocating a clip mask in these cases. + let is_large = metadata.local_rect.size.area() > MIN_BRUSH_SPLIT_AREA; - Some(ccr) - } + // TODO(gw): We should probably detect and store this on each + // ClipSources instance, to avoid having to iterate + // the clip sources here. + let mut rect_clips_only = true; - // TODO(gw): Make this simpler / more efficient by tidying - // up the logic that early outs from prepare_prim_for_render. - pub fn reset_prim_visibility(&mut self) { - for prim in &mut self.primitives { - prim.metadata.screen_rect = None; + let mut segment_builder = SegmentBuilder::new( + metadata.local_rect, + None, + metadata.local_clip_rect + ); + + // If this primitive is clipped by clips from a different coordinate system, then we + // need to apply a clip mask for the entire primitive. + let mut clip_mask_kind = if clip_chain.has_clips_from_other_coordinate_systems { + BrushClipMaskKind::Global + } else { + BrushClipMaskKind::Individual + }; + + // Segment the primitive on all the local-space clip sources that we can. + for i in 0 .. clip_chain.clips_range.count { + let (clip_node, flags) = frame_state.clip_store.get_node_from_range(&clip_chain.clips_range, i); + + if !flags.contains(ClipNodeFlags::SAME_COORD_SYSTEM) { + continue; } - } - pub fn prepare_prim_runs( - &mut self, - pic_context: &PictureContext, - pic_state: &mut PictureState, - frame_context: &FrameBuildingContext, - frame_state: &mut FrameBuildingState, - ) -> PrimitiveRunLocalRect { - let mut result = PrimitiveRunLocalRect { - local_rect_in_actual_parent_space: LayoutRect::zero(), - local_rect_in_original_parent_space: LayoutRect::zero(), - }; + // TODO(gw): We can easily extend the segment builder to support these clip sources in + // the future, but they are rarely used. + // We must do this check here in case we continue early below. + if clip_node.item.is_image_or_line_decoration_clip() { + clip_mask_kind = BrushClipMaskKind::Global; + } - for run in &pic_context.prim_runs { - // TODO(gw): Perhaps we can restructure this to not need to create - // a new primitive context for every run (if the hash - // lookups ever show up in a profile). - let scroll_node = &frame_context - .clip_scroll_tree - .spatial_nodes[run.spatial_node_index.0]; + // If this clip item is positioned by another positioning node, its relative position + // could change during scrolling. This means that we would need to resegment. Instead + // of doing that, only segment with clips that have the same positioning node. + // TODO(mrobinson, #2858): It may make sense to include these nodes, resegmenting only + // when necessary while scrolling. + if !flags.contains(ClipNodeFlags::SAME_SPATIAL_NODE) { + // We don't need to generate a global clip mask for rectangle clips because we are + // in the same coordinate system and rectangular clips are handled by the local + // clip chain rectangle. + if !clip_node.item.is_rect() { + clip_mask_kind = BrushClipMaskKind::Global; + } + continue; + } - if run.is_chasing(self.chase_id) { - println!("\tpreparing a run of length {} in pipeline {:?}", - run.count, pic_context.pipeline_id); - println!("\trun {:?}", run.spatial_node_index); - println!("\ttransform {:?}", scroll_node.world_content_transform.to_transform()); + let (local_clip_rect, radius, mode) = match clip_node.item { + ClipItem::RoundedRectangle(rect, radii, clip_mode) => { + rect_clips_only = false; + (rect, Some(radii), clip_mode) + } + ClipItem::Rectangle(rect, mode) => { + (rect, None, mode) } + ClipItem::BoxShadow(ref info) => { + rect_clips_only = false; - // Mark whether this picture contains any complex coordinate - // systems, due to either the scroll node or the clip-chain. - pic_state.has_non_root_coord_system |= - scroll_node.coordinate_system_id != CoordinateSystemId::root(); + // For inset box shadows, we can clip out any + // pixels that are inside the shadow region + // and are beyond the inner rect, as they can't + // be affected by the blur radius. + let inner_clip_mode = match info.clip_mode { + BoxShadowClipMode::Outset => None, + BoxShadowClipMode::Inset => Some(ClipMode::ClipOut), + }; + + // Push a region into the segment builder where the + // box-shadow can have an effect on the result. This + // ensures clip-mask tasks get allocated for these + // pixel regions, even if no other clips affect them. + segment_builder.push_mask_region( + info.prim_shadow_rect, + info.prim_shadow_rect.inflate( + -0.5 * info.shadow_rect_alloc_size.width, + -0.5 * info.shadow_rect_alloc_size.height, + ), + inner_clip_mode, + ); - if !scroll_node.invertible { - if run.is_chasing(self.chase_id) { - println!("\tculled for the scroll node transform being invertible"); - } continue; } + ClipItem::LineDecoration(..) | ClipItem::Image(..) => { + rect_clips_only = false; + continue; + } + }; - let parent_relative_transform = pic_context - .inv_world_transform - .map(|inv_parent| { - inv_parent.pre_mul(&scroll_node.world_content_transform) - }); - - let original_relative_transform = pic_context - .original_reference_frame_index - .and_then(|original_reference_frame_index| { - frame_context - .clip_scroll_tree - .spatial_nodes[original_reference_frame_index.0] - .world_content_transform - .inverse() - }) - .map(|inv_parent| { - inv_parent.pre_mul(&scroll_node.world_content_transform) - }); - - let transform = frame_context - .transforms - .get_transform(run.spatial_node_index); - - let child_prim_run_context = PrimitiveRunContext::new( - scroll_node, - run.spatial_node_index, - transform, - ); - - for i in 0 .. run.count { - let prim_index = PrimitiveIndex(run.base_prim_index.0 + i); + segment_builder.push_clip_rect(local_clip_rect, radius, mode); + } - if let Some(prim_local_rect) = self.prepare_prim_for_render( - prim_index, - &child_prim_run_context, - pic_context, - pic_state, - frame_context, - frame_state, - ) { - frame_state.profile_counters.visible_primitives.inc(); + if is_large || rect_clips_only { + match brush.segment_desc { + Some(ref mut segment_desc) => { + segment_desc.clip_mask_kind = clip_mask_kind; + } + None => { + // TODO(gw): We can probably make the allocation + // patterns of this and the segment + // builder significantly better, by + // retaining it across primitives. + let mut segments = Vec::new(); - if let Some(ref matrix) = parent_relative_transform { - match matrix.transform_rect(&prim_local_rect) { - Some(bounds) => { - result.local_rect_in_actual_parent_space = - result.local_rect_in_actual_parent_space.union(&bounds); - } - None => { - warn!("parent relative transform can't transform the primitive rect for {:?}", prim_index); - } - } + segment_builder.build(|segment| { + segments.push( + BrushSegment::new( + segment.rect, + segment.has_mask, + segment.edge_flags, + [0.0; 4], + BrushFlags::empty(), + ), + ); + }); - } - if let Some(ref matrix) = original_relative_transform { - match matrix.transform_rect(&prim_local_rect) { - Some(bounds) => { - result.local_rect_in_original_parent_space = - result.local_rect_in_original_parent_space.union(&bounds); - } - None => { - warn!("original relative transform can't transform the primitive rect for {:?}", prim_index); - } - } - } - } + brush.segment_desc = Some(BrushSegmentDescriptor { + segments, + clip_mask_kind, + }); } } - - result } } -fn build_gradient_stops_request( - stops_handle: &mut GpuCacheHandle, - stops_range: ItemRange, - reverse_stops: bool, - frame_state: &mut FrameBuildingState, - pic_context: &PictureContext -) { - if let Some(mut request) = frame_state.gpu_cache.request(stops_handle) { - let gradient_builder = GradientGpuBlockBuilder::new( - stops_range, - pic_context.display_list, - ); - gradient_builder.build( - reverse_stops, - &mut request, - ); - } -} +impl Primitive { + fn update_clip_task_for_brush( + &mut self, + prim_run_context: &PrimitiveRunContext, + clip_chain: &ClipChainInstance, + combined_outer_rect: &DeviceIntRect, + pic_state: &mut PictureState, + frame_context: &FrameBuildingContext, + frame_state: &mut FrameBuildingState, + ) -> bool { + debug_assert!(frame_context.screen_rect.contains_rect(combined_outer_rect)); -fn decompose_repeated_primitive( - visible_tiles: &mut Vec, - metadata: &mut PrimitiveMetadata, - stretch_size: &LayoutSize, - tile_spacing: &LayoutSize, - prim_run_context: &PrimitiveRunContext, - frame_context: &FrameBuildingContext, - frame_state: &mut FrameBuildingState, - callback: &mut FnMut(&LayoutRect, GpuDataRequest), -) { - visible_tiles.clear(); + let brush = match self.details { + PrimitiveDetails::Brush(ref mut brush) => brush, + PrimitiveDetails::TextRun(..) => return false, + }; - // Tighten the clip rect because decomposing the repeated image can - // produce primitives that are partially covering the original image - // rect and we want to clip these extra parts out. - let tight_clip_rect = metadata - .combined_local_clip_rect - .intersection(&metadata.local_rect).unwrap(); + write_brush_segment_description( + brush, + &self.metadata, + clip_chain, + frame_state, + ); - let unclipped_device_rect = &metadata - .screen_rect - .unwrap() - .unclipped; + let segment_desc = match brush.segment_desc { + Some(ref mut description) => description, + None => return false, + }; + let clip_mask_kind = segment_desc.clip_mask_kind; - let visible_rect = compute_conservative_visible_rect( - prim_run_context, - frame_context, - unclipped_device_rect, - &tight_clip_rect - ); - let stride = *stretch_size + *tile_spacing; + for segment in &mut segment_desc.segments { + if !segment.may_need_clip_mask && clip_mask_kind != BrushClipMaskKind::Global { + segment.clip_task_id = BrushSegmentTaskId::Opaque; + continue; + } - for_each_repetition( - &metadata.local_rect, - &visible_rect, - &stride, - &mut |origin, _| { + let intersected_rect = calculate_screen_bounding_rect( + &prim_run_context.scroll_node.world_content_transform, + &segment.local_rect, + frame_context.device_pixel_scale, + Some(&combined_outer_rect), + ); - let mut handle = GpuCacheHandle::new(); - let rect = LayoutRect { - origin: *origin, - size: *stretch_size, + let bounds = match intersected_rect { + Some(bounds) => bounds, + None => { + segment.clip_task_id = BrushSegmentTaskId::Empty; + continue; + } }; - if let Some(request) = frame_state.gpu_cache.request(&mut handle) { - callback(&rect, request); - } - visible_tiles.push(VisibleGradientTile { - local_rect: rect, - local_clip_rect: tight_clip_rect, - handle - }); + if clip_chain.clips_range.count > 0 { + let clip_task = RenderTask::new_mask( + bounds, + clip_chain.clips_range, + frame_state.clip_store, + frame_state.gpu_cache, + frame_state.resource_cache, + frame_state.render_tasks, + ); + + let clip_task_id = frame_state.render_tasks.add(clip_task); + pic_state.tasks.push(clip_task_id); + segment.clip_task_id = BrushSegmentTaskId::RenderTaskId(clip_task_id); + } } - ); - if visible_tiles.is_empty() { - // At this point if we don't have tiles to show it means we could probably - // have done a better a job at culling during an earlier stage. - // Clearing the screen rect has the effect of "culling out" the primitive - // from the point of view of the batch builder, and ensures we don't hit - // assertions later on because we didn't request any image. - metadata.screen_rect = None; + true } -} -fn compute_conservative_visible_rect( - prim_run_context: &PrimitiveRunContext, - frame_context: &FrameBuildingContext, - clipped_device_rect: &DeviceIntRect, - local_clip_rect: &LayoutRect, -) -> LayoutRect { - let world_screen_rect = clipped_device_rect - .to_f32() / frame_context.device_pixel_scale; - - if let Some(layer_screen_rect) = prim_run_context - .scroll_node - .world_content_transform - .unapply(&world_screen_rect) { - - return local_clip_rect.intersection(&layer_screen_rect).unwrap_or(LayoutRect::zero()); + fn reset_clip_task(&mut self) { + self.metadata.clip_task_id = None; + if let PrimitiveDetails::Brush(ref mut brush) = self.details { + if let Some(ref mut desc) = brush.segment_desc { + for segment in &mut desc.segments { + segment.clip_task_id = BrushSegmentTaskId::Opaque; + } + } + } } - *local_clip_rect -} - -fn edge_flags_for_tile_spacing(tile_spacing: &LayoutSize) -> EdgeAaSegmentMask { - let mut flags = EdgeAaSegmentMask::empty(); - - if tile_spacing.width > 0.0 { - flags |= EdgeAaSegmentMask::LEFT | EdgeAaSegmentMask::RIGHT; - } - if tile_spacing.height > 0.0 { - flags |= EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::BOTTOM; - } - - flags -} - -impl<'a> GpuDataRequest<'a> { - // Write the GPU cache data for an individual segment. - fn write_segment( + fn prepare_prim_for_render_inner( &mut self, - local_rect: LayoutRect, - extra_data: [f32; 4], + prim_index: PrimitiveIndex, + prim_run_context: &PrimitiveRunContext, + pic_state_for_children: PictureState, + pic_context: &PictureContext, + pic_state: &mut PictureState, + frame_context: &FrameBuildingContext, + frame_state: &mut FrameBuildingState, + is_chased: bool, ) { - let _ = VECS_PER_SEGMENT; - self.push(local_rect); - self.push(extra_data); - } -} - -fn write_brush_segment_description( - brush: &mut BrushPrimitive, - metadata: &PrimitiveMetadata, - clip_chain: &ClipChainInstance, - frame_state: &mut FrameBuildingState, -) { - match brush.segment_desc { - Some(ref segment_desc) => { - // If we already have a segment descriptor, only run through the - // clips list if we haven't already determined the mask kind. - if segment_desc.clip_mask_kind != BrushClipMaskKind::Unknown { - return; - } + let mut is_tiled = false; + let metadata = &mut self.metadata; + #[cfg(debug_assertions)] + { + metadata.prepared_frame_id = frame_state.render_tasks.frame_id(); } - None => { - // If no segment descriptor built yet, see if it is a brush - // type that wants to be segmented. - if !brush.kind.supports_segments(frame_state.resource_cache) { - return; + + match self.details { + PrimitiveDetails::TextRun(ref mut text) => { + // The transform only makes sense for screen space rasterization + let transform = prim_run_context.scroll_node.world_content_transform.to_transform(); + text.prepare_for_render( + frame_context.device_pixel_scale, + &transform, + pic_context.allow_subpixel_aa, + pic_context.display_list, + frame_state, + ); } - } - } + PrimitiveDetails::Brush(ref mut brush) => { - // If the brush is small, we generally want to skip building segments - // and just draw it as a single primitive with clip mask. However, - // if the clips are purely rectangles that have no per-fragment - // clip masks, we will segment anyway. This allows us to completely - // skip allocating a clip mask in these cases. - let is_large = metadata.local_rect.size.area() > MIN_BRUSH_SPLIT_AREA; + match brush.kind { + BrushKind::Image { + request, + sub_rect, + stretch_size, + ref mut tile_spacing, + ref mut source, + ref mut opacity_binding, + ref mut visible_tiles, + .. + } => { + let image_properties = frame_state + .resource_cache + .get_image_properties(request.key); - // TODO(gw): We should probably detect and store this on each - // ClipSources instance, to avoid having to iterate - // the clip sources here. - let mut rect_clips_only = true; - let mut segment_builder = SegmentBuilder::new( - metadata.local_rect, - None, - metadata.local_clip_rect - ); + // Set if we need to request the source image from the cache this frame. + if let Some(image_properties) = image_properties { + is_tiled = image_properties.tiling.is_some(); - // If this primitive is clipped by clips from a different coordinate system, then we - // need to apply a clip mask for the entire primitive. - let mut clip_mask_kind = if clip_chain.has_clips_from_other_coordinate_systems { - BrushClipMaskKind::Global - } else { - BrushClipMaskKind::Individual - }; + // If the opacity changed, invalidate the GPU cache so that + // the new color for this primitive gets uploaded. + if opacity_binding.update(frame_context.scene_properties) { + frame_state.gpu_cache.invalidate(&mut metadata.gpu_location); + } - // Segment the primitive on all the local-space clip sources that we can. - for i in 0 .. clip_chain.clips_range.count { - let (clip_node, flags) = frame_state.clip_store.get_node_from_range(&clip_chain.clips_range, i); + // Update opacity for this primitive to ensure the correct + // batching parameters are used. + metadata.opacity.is_opaque = + image_properties.descriptor.is_opaque && + opacity_binding.current == 1.0; - if !flags.contains(ClipNodeFlags::SAME_COORD_SYSTEM) { - continue; - } + if *tile_spacing != LayoutSize::zero() && !is_tiled { + *source = ImageSource::Cache { + // Size in device-pixels we need to allocate in render task cache. + size: image_properties.descriptor.size.to_i32(), + handle: None, + }; + } - // TODO(gw): We can easily extend the segment builder to support these clip sources in - // the future, but they are rarely used. - // We must do this check here in case we continue early below. - if clip_node.item.is_image_or_line_decoration_clip() { - clip_mask_kind = BrushClipMaskKind::Global; - } + // Work out whether this image is a normal / simple type, or if + // we need to pre-render it to the render task cache. + if let Some(rect) = sub_rect { + // We don't properly support this right now. + debug_assert!(!is_tiled); + *source = ImageSource::Cache { + // Size in device-pixels we need to allocate in render task cache. + size: rect.size, + handle: None, + }; + } - // If this clip item is positioned by another positioning node, its relative position - // could change during scrolling. This means that we would need to resegment. Instead - // of doing that, only segment with clips that have the same positioning node. - // TODO(mrobinson, #2858): It may make sense to include these nodes, resegmenting only - // when necessary while scrolling. - if !flags.contains(ClipNodeFlags::SAME_SPATIAL_NODE) { - // We don't need to generate a global clip mask for rectangle clips because we are - // in the same coordinate system and rectangular clips are handled by the local - // clip chain rectangle. - if !clip_node.item.is_rect() { - clip_mask_kind = BrushClipMaskKind::Global; - } - continue; - } + let mut request_source_image = false; - let (local_clip_rect, radius, mode) = match clip_node.item { - ClipItem::RoundedRectangle(rect, radii, clip_mode) => { - rect_clips_only = false; - (rect, Some(radii), clip_mode) - } - ClipItem::Rectangle(rect, mode) => { - (rect, None, mode) - } - ClipItem::BoxShadow(ref info) => { - rect_clips_only = false; + // Every frame, for cached items, we need to request the render + // task cache item. The closure will be invoked on the first + // time through, and any time the render task output has been + // evicted from the texture cache. + match *source { + ImageSource::Cache { ref mut size, ref mut handle } => { + let padding = DeviceIntSideOffsets::new( + 0, + (tile_spacing.width * size.width as f32 / stretch_size.width) as i32, + (tile_spacing.height * size.height as f32 / stretch_size.height) as i32, + 0, + ); - // For inset box shadows, we can clip out any - // pixels that are inside the shadow region - // and are beyond the inner rect, as they can't - // be affected by the blur radius. - let inner_clip_mode = match info.clip_mode { - BoxShadowClipMode::Outset => None, - BoxShadowClipMode::Inset => Some(ClipMode::ClipOut), - }; + let inner_size = *size; + size.width += padding.horizontal(); + size.height += padding.vertical(); - // Push a region into the segment builder where the - // box-shadow can have an effect on the result. This - // ensures clip-mask tasks get allocated for these - // pixel regions, even if no other clips affect them. - segment_builder.push_mask_region( - info.prim_shadow_rect, - info.prim_shadow_rect.inflate( - -0.5 * info.shadow_rect_alloc_size.width, - -0.5 * info.shadow_rect_alloc_size.height, - ), - inner_clip_mode, - ); + if padding != DeviceIntSideOffsets::zero() { + metadata.opacity.is_opaque = false; + } - continue; - } - ClipItem::LineDecoration(..) | ClipItem::Image(..) => { - rect_clips_only = false; - continue; - } - }; + let image_cache_key = ImageCacheKey { + request, + texel_rect: sub_rect, + }; - segment_builder.push_clip_rect(local_clip_rect, radius, mode); - } + // Request a pre-rendered image task. + *handle = Some(frame_state.resource_cache.request_render_task( + RenderTaskCacheKey { + size: *size, + kind: RenderTaskCacheKeyKind::Image(image_cache_key), + }, + frame_state.gpu_cache, + frame_state.render_tasks, + None, + image_properties.descriptor.is_opaque, + |render_tasks| { + // We need to render the image cache this frame, + // so will need access to the source texture. + request_source_image = true; - if is_large || rect_clips_only { - match brush.segment_desc { - Some(ref mut segment_desc) => { - segment_desc.clip_mask_kind = clip_mask_kind; - } - None => { - // TODO(gw): We can probably make the allocation - // patterns of this and the segment - // builder significantly better, by - // retaining it across primitives. - let mut segments = Vec::new(); + // Create a task to blit from the texture cache to + // a normal transient render task surface. This will + // copy only the sub-rect, if specified. + let cache_to_target_task = RenderTask::new_blit_with_padding( + inner_size, + &padding, + BlitSource::Image { key: image_cache_key }, + ); + let cache_to_target_task_id = render_tasks.add(cache_to_target_task); - segment_builder.build(|segment| { - segments.push( - BrushSegment::new( - segment.rect, - segment.has_mask, - segment.edge_flags, - [0.0; 4], - BrushFlags::empty(), - ), - ); - }); + // Create a task to blit the rect from the child render + // task above back into the right spot in the persistent + // render target cache. + let target_to_cache_task = RenderTask::new_blit( + *size, + BlitSource::RenderTask { + task_id: cache_to_target_task_id, + }, + ); + let target_to_cache_task_id = render_tasks.add(target_to_cache_task); - brush.segment_desc = Some(BrushSegmentDescriptor { - segments, - clip_mask_kind, - }); - } - } - } -} + // Hook this into the render task tree at the right spot. + pic_state.tasks.push(target_to_cache_task_id); -impl Primitive { - fn update_clip_task_for_brush( - &mut self, - prim_run_context: &PrimitiveRunContext, - clip_chain: &ClipChainInstance, - combined_outer_rect: &DeviceIntRect, - pic_state: &mut PictureState, - frame_context: &FrameBuildingContext, - frame_state: &mut FrameBuildingState, - ) -> bool { - debug_assert!(frame_context.screen_rect.contains_rect(combined_outer_rect)); + // Pass the image opacity, so that the cached render task + // item inherits the same opacity properties. + target_to_cache_task_id + } + )); + } + ImageSource::Default => { + // Normal images just reference the source texture each frame. + request_source_image = true; + } + } - let brush = match self.details { - PrimitiveDetails::Brush(ref mut brush) => brush, - PrimitiveDetails::TextRun(..) => return false, - }; + if let Some(tile_size) = image_properties.tiling { - write_brush_segment_description( - brush, - &self.metadata, - clip_chain, - frame_state, - ); + let device_image_size = image_properties.descriptor.size; - let segment_desc = match brush.segment_desc { - Some(ref mut description) => description, - None => return false, - }; - let clip_mask_kind = segment_desc.clip_mask_kind; + // Tighten the clip rect because decomposing the repeated image can + // produce primitives that are partially covering the original image + // rect and we want to clip these extra parts out. + let tight_clip_rect = metadata + .combined_local_clip_rect + .intersection(&metadata.local_rect).unwrap(); - for segment in &mut segment_desc.segments { - if !segment.may_need_clip_mask && clip_mask_kind != BrushClipMaskKind::Global { - segment.clip_task_id = BrushSegmentTaskId::Opaque; - continue; - } + let visible_rect = compute_conservative_visible_rect( + prim_run_context, + frame_context, + &metadata.screen_rect.unwrap().clipped, + &tight_clip_rect + ); - let intersected_rect = calculate_screen_bounding_rect( - &prim_run_context.scroll_node.world_content_transform, - &segment.local_rect, - frame_context.device_pixel_scale, - Some(&combined_outer_rect), - ); + let base_edge_flags = edge_flags_for_tile_spacing(tile_spacing); - let bounds = match intersected_rect { - Some(bounds) => bounds, - None => { - segment.clip_task_id = BrushSegmentTaskId::Empty; - continue; - } - }; + let stride = stretch_size + *tile_spacing; - if clip_chain.clips_range.count > 0 { - let clip_task = RenderTask::new_mask( - bounds, - clip_chain.clips_range, - frame_state.clip_store, - frame_state.gpu_cache, - frame_state.resource_cache, - frame_state.render_tasks, - ); + visible_tiles.clear(); - let clip_task_id = frame_state.render_tasks.add(clip_task); - pic_state.tasks.push(clip_task_id); - segment.clip_task_id = BrushSegmentTaskId::RenderTaskId(clip_task_id); + for_each_repetition( + &metadata.local_rect, + &visible_rect, + &stride, + &mut |origin, edge_flags| { + let edge_flags = base_edge_flags | edge_flags; + + let image_rect = LayoutRect { + origin: *origin, + size: stretch_size, + }; + + for_each_tile( + &image_rect, + &visible_rect, + &device_image_size, + tile_size as u32, + &mut |tile_rect, tile_offset, tile_flags| { + + frame_state.resource_cache.request_image( + request.with_tile(tile_offset), + frame_state.gpu_cache, + ); + + let mut handle = GpuCacheHandle::new(); + if let Some(mut request) = frame_state.gpu_cache.request(&mut handle) { + request.push(ColorF::new(1.0, 1.0, 1.0, opacity_binding.current).premultiplied()); + request.push(PremultipliedColorF::WHITE); + request.push([tile_rect.size.width, tile_rect.size.height, 0.0, 0.0]); + request.write_segment(*tile_rect, [0.0; 4]); + } + + visible_tiles.push(VisibleImageTile { + tile_offset, + handle, + edge_flags: tile_flags & edge_flags, + local_rect: *tile_rect, + local_clip_rect: tight_clip_rect, + }); + } + ); + } + ); + + if visible_tiles.is_empty() { + // At this point if we don't have tiles to show it means we could probably + // have done a better a job at culling during an earlier stage. + // Clearing the screen rect has the effect of "culling out" the primitive + // from the point of view of the batch builder, and ensures we don't hit + // assertions later on because we didn't request any image. + metadata.screen_rect = None; + } + } else if request_source_image { + frame_state.resource_cache.request_image( + request, + frame_state.gpu_cache, + ); + } + } + } + BrushKind::YuvImage { format, yuv_key, image_rendering, .. } => { + let channel_num = format.get_plane_num(); + debug_assert!(channel_num <= 3); + for channel in 0 .. channel_num { + frame_state.resource_cache.request_image( + ImageRequest { + key: yuv_key[channel], + rendering: image_rendering, + tile: None, + }, + frame_state.gpu_cache, + ); + } + } + BrushKind::Border { ref mut source, .. } => { + match *source { + BorderSource::Image(request) => { + let image_properties = frame_state + .resource_cache + .get_image_properties(request.key); + + if let Some(image_properties) = image_properties { + // Update opacity for this primitive to ensure the correct + // batching parameters are used. + metadata.opacity.is_opaque = + image_properties.descriptor.is_opaque; + + frame_state.resource_cache.request_image( + request, + frame_state.gpu_cache, + ); + } + } + BorderSource::Border { .. } => { + // Handled earlier since we need to update the segment + // descriptor *before* update_clip_task() is called. + } + } + } + BrushKind::RadialGradient { + stops_range, + center, + start_radius, + end_radius, + ratio_xy, + extend_mode, + stretch_size, + tile_spacing, + ref mut stops_handle, + ref mut visible_tiles, + .. + } => { + build_gradient_stops_request( + stops_handle, + stops_range, + false, + frame_state, + pic_context, + ); + + if tile_spacing != LayoutSize::zero() { + is_tiled = true; + + decompose_repeated_primitive( + visible_tiles, + metadata, + &stretch_size, + &tile_spacing, + prim_run_context, + frame_context, + frame_state, + &mut |rect, mut request| { + request.push([ + center.x, + center.y, + start_radius, + end_radius, + ]); + request.push([ + ratio_xy, + pack_as_float(extend_mode as u32), + stretch_size.width, + stretch_size.height, + ]); + request.write_segment(*rect, [0.0; 4]); + }, + ); + } + } + BrushKind::LinearGradient { + stops_range, + reverse_stops, + start_point, + end_point, + extend_mode, + stretch_size, + tile_spacing, + ref mut stops_handle, + ref mut visible_tiles, + .. + } => { + + build_gradient_stops_request( + stops_handle, + stops_range, + reverse_stops, + frame_state, + pic_context, + ); + + if tile_spacing != LayoutSize::zero() { + is_tiled = true; + + decompose_repeated_primitive( + visible_tiles, + metadata, + &stretch_size, + &tile_spacing, + prim_run_context, + frame_context, + frame_state, + &mut |rect, mut request| { + request.push([ + start_point.x, + start_point.y, + end_point.x, + end_point.y, + ]); + request.push([ + pack_as_float(extend_mode as u32), + stretch_size.width, + stretch_size.height, + 0.0, + ]); + request.write_segment(*rect, [0.0; 4]); + } + ); + } + } + BrushKind::Picture(ref mut pic) => { + pic.prepare_for_render( + prim_index, + metadata, + prim_run_context, + pic_state_for_children, + pic_state, + frame_context, + frame_state, + ); + } + BrushKind::Solid { ref color, ref mut opacity_binding, .. } => { + // If the opacity changed, invalidate the GPU cache so that + // the new color for this primitive gets uploaded. Also update + // the opacity field that controls which batches this primitive + // will be added to. + if opacity_binding.update(frame_context.scene_properties) { + metadata.opacity = PrimitiveOpacity::from_alpha(opacity_binding.current * color.a); + frame_state.gpu_cache.invalidate(&mut metadata.gpu_location); + } + } + BrushKind::Clear => {} + } } } - true - } + if is_tiled { + // we already requested each tile's gpu data. + return; + } - fn reset_clip_task(&mut self) { - self.metadata.clip_task_id = None; - if let PrimitiveDetails::Brush(ref mut brush) = self.details { - if let Some(ref mut desc) = brush.segment_desc { - for segment in &mut desc.segments { - segment.clip_task_id = BrushSegmentTaskId::Opaque; + // Mark this GPU resource as required for this frame. + if let Some(mut request) = frame_state.gpu_cache.request(&mut metadata.gpu_location) { + match self.details { + PrimitiveDetails::TextRun(ref mut text) => { + text.write_gpu_blocks(&mut request); + } + PrimitiveDetails::Brush(ref mut brush) => { + brush.write_gpu_blocks(&mut request, metadata.local_rect); + + match brush.segment_desc { + Some(ref segment_desc) => { + for segment in &segment_desc.segments { + if cfg!(debug_assertions) && is_chased { + println!("\t\t{:?}", segment); + } + // has to match VECS_PER_SEGMENT + request.write_segment( + segment.local_rect, + segment.extra_data, + ); + } + } + None => { + request.write_segment( + metadata.local_rect, + [0.0; 4], + ); + } + } } } } From 327a0481b76af35261e51178e0fc34d2d1b33869 Mon Sep 17 00:00:00 2001 From: Glenn Watson Date: Fri, 10 Aug 2018 16:11:00 +1000 Subject: [PATCH 06/11] Move update_clip_task to method of Primitive. --- webrender/src/prim_store.rs | 115 ++++++++++++++++++------------------ 1 file changed, 57 insertions(+), 58 deletions(-) diff --git a/webrender/src/prim_store.rs b/webrender/src/prim_store.rs index ebd07d467b..d589700c85 100644 --- a/webrender/src/prim_store.rs +++ b/webrender/src/prim_store.rs @@ -1531,60 +1531,6 @@ impl PrimitiveStore { } } - fn update_clip_task( - &mut self, - prim_index: PrimitiveIndex, - prim_run_context: &PrimitiveRunContext, - prim_screen_rect: &DeviceIntRect, - clip_chain: &ClipChainInstance, - pic_state: &mut PictureState, - frame_context: &FrameBuildingContext, - frame_state: &mut FrameBuildingState, - ) -> bool { - if cfg!(debug_assertions) && Some(prim_index) == self.chase_id { - println!("\tupdating clip task with screen rect {:?}", prim_screen_rect); - } - // Reset clips from previous frames since we may clip differently each frame. - let prim = &mut self.primitives[prim_index.0]; - prim.reset_clip_task(); - - // First try to render this primitive's mask using optimized brush rendering. - if prim.update_clip_task_for_brush( - prim_run_context, - &clip_chain, - prim_screen_rect, - pic_state, - frame_context, - frame_state, - ) { - if cfg!(debug_assertions) && Some(prim_index) == self.chase_id { - println!("\tsegment tasks have been created for clipping"); - } - return true; - } - - if clip_chain.clips_range.count > 0 { - let clip_task = RenderTask::new_mask( - *prim_screen_rect, - clip_chain.clips_range, - frame_state.clip_store, - frame_state.gpu_cache, - frame_state.resource_cache, - frame_state.render_tasks, - ); - - let clip_task_id = frame_state.render_tasks.add(clip_task); - if cfg!(debug_assertions) && Some(prim_index) == self.chase_id { - println!("\tcreated task {:?} with combined outer rect {:?}", - clip_task_id, prim_screen_rect); - } - prim.metadata.clip_task_id = Some(clip_task_id); - pic_state.tasks.push(clip_task_id); - } - - true - } - pub fn prepare_prim_for_render( &mut self, prim_index: PrimitiveIndex, @@ -1825,23 +1771,23 @@ impl PrimitiveStore { frame_context, ); - if may_need_clip_mask && !self.update_clip_task( - prim_index, + let prim = &mut self.primitives[prim_index.0]; + if may_need_clip_mask && !prim.update_clip_task( prim_run_context, &clipped_device_rect, &clip_chain, pic_state, frame_context, frame_state, + is_chased, ) { return None; } - if cfg!(debug_assertions) && Some(prim_index) == self.chase_id { + if cfg!(debug_assertions) && is_chased { println!("\tconsidered visible and ready with local rect {:?}", local_rect); } - let prim = &mut self.primitives[prim_index.0]; prim.prepare_prim_for_render_inner( prim_index, prim_run_context, @@ -2793,4 +2739,57 @@ impl Primitive { } } } + + fn update_clip_task( + &mut self, + prim_run_context: &PrimitiveRunContext, + prim_screen_rect: &DeviceIntRect, + clip_chain: &ClipChainInstance, + pic_state: &mut PictureState, + frame_context: &FrameBuildingContext, + frame_state: &mut FrameBuildingState, + is_chased: bool, + ) -> bool { + if cfg!(debug_assertions) && is_chased { + println!("\tupdating clip task with screen rect {:?}", prim_screen_rect); + } + // Reset clips from previous frames since we may clip differently each frame. + self.reset_clip_task(); + + // First try to render this primitive's mask using optimized brush rendering. + if self.update_clip_task_for_brush( + prim_run_context, + &clip_chain, + prim_screen_rect, + pic_state, + frame_context, + frame_state, + ) { + if cfg!(debug_assertions) && is_chased { + println!("\tsegment tasks have been created for clipping"); + } + return true; + } + + if clip_chain.clips_range.count > 0 { + let clip_task = RenderTask::new_mask( + *prim_screen_rect, + clip_chain.clips_range, + frame_state.clip_store, + frame_state.gpu_cache, + frame_state.resource_cache, + frame_state.render_tasks, + ); + + let clip_task_id = frame_state.render_tasks.add(clip_task); + if cfg!(debug_assertions) && is_chased { + println!("\tcreated task {:?} with combined outer rect {:?}", + clip_task_id, prim_screen_rect); + } + self.metadata.clip_task_id = Some(clip_task_id); + pic_state.tasks.push(clip_task_id); + } + + true + } } From 9628f38b81cad4d205e6d636fd99aff7809d79ad Mon Sep 17 00:00:00 2001 From: Glenn Watson Date: Fri, 10 Aug 2018 16:13:28 +1000 Subject: [PATCH 07/11] Move build_prim_segments_if_needed to Primitive --- webrender/src/prim_store.rs | 172 ++++++++++++++++++------------------ 1 file changed, 84 insertions(+), 88 deletions(-) diff --git a/webrender/src/prim_store.rs b/webrender/src/prim_store.rs index d589700c85..3bb2c12726 100644 --- a/webrender/src/prim_store.rs +++ b/webrender/src/prim_store.rs @@ -1446,91 +1446,6 @@ impl PrimitiveStore { self.primitives.len() } - fn build_prim_segments_if_needed( - &mut self, - prim_index: PrimitiveIndex, - pic_state: &mut PictureState, - frame_state: &mut FrameBuildingState, - frame_context: &FrameBuildingContext, - ) { - let prim = &mut self.primitives[prim_index.0]; - - let brush = match prim.details { - PrimitiveDetails::Brush(ref mut brush) => brush, - PrimitiveDetails::TextRun(..) => return, - }; - - if let BrushKind::Border { ref mut source, .. } = brush.kind { - if let BorderSource::Border { - ref border, - ref mut cache_key, - ref widths, - ref mut handle, - ref mut task_info, - .. - } = *source { - // TODO(gw): When drawing in screen raster mode, we should also incorporate a - // scale factor from the world transform to get an appropriately - // sized border task. - let world_scale = LayoutToWorldScale::new(1.0); - let mut scale = world_scale * frame_context.device_pixel_scale; - let max_scale = BorderRenderTaskInfo::get_max_scale(&border.radius, &widths); - scale.0 = scale.0.min(max_scale.0); - let scale_au = Au::from_f32_px(scale.0); - let needs_update = scale_au != cache_key.scale; - let mut new_segments = Vec::new(); - - if needs_update { - cache_key.scale = scale_au; - - *task_info = BorderRenderTaskInfo::new( - &prim.metadata.local_rect, - border, - widths, - scale, - &mut new_segments, - ); - } - - *handle = task_info.as_ref().map(|task_info| { - frame_state.resource_cache.request_render_task( - RenderTaskCacheKey { - size: DeviceIntSize::zero(), - kind: RenderTaskCacheKeyKind::Border(cache_key.clone()), - }, - frame_state.gpu_cache, - frame_state.render_tasks, - None, - false, // todo - |render_tasks| { - let task = RenderTask::new_border( - task_info.size, - task_info.build_instances(border), - ); - - let task_id = render_tasks.add(task); - - pic_state.tasks.push(task_id); - - task_id - } - ) - }); - - if needs_update { - brush.segment_desc = Some(BrushSegmentDescriptor { - segments: new_segments, - clip_mask_kind: BrushClipMaskKind::Unknown, - }); - - // The segments have changed, so force the GPU cache to - // re-upload the primitive information. - frame_state.gpu_cache.invalidate(&mut prim.metadata.gpu_location); - } - } - } - } - pub fn prepare_prim_for_render( &mut self, prim_index: PrimitiveIndex, @@ -1764,14 +1679,13 @@ impl PrimitiveStore { } }; - self.build_prim_segments_if_needed( - prim_index, + let prim = &mut self.primitives[prim_index.0]; + prim.build_prim_segments_if_needed( pic_state, frame_state, frame_context, ); - let prim = &mut self.primitives[prim_index.0]; if may_need_clip_mask && !prim.update_clip_task( prim_run_context, &clipped_device_rect, @@ -2792,4 +2706,86 @@ impl Primitive { true } + + fn build_prim_segments_if_needed( + &mut self, + pic_state: &mut PictureState, + frame_state: &mut FrameBuildingState, + frame_context: &FrameBuildingContext, + ) { + let brush = match self.details { + PrimitiveDetails::Brush(ref mut brush) => brush, + PrimitiveDetails::TextRun(..) => return, + }; + + if let BrushKind::Border { ref mut source, .. } = brush.kind { + if let BorderSource::Border { + ref border, + ref mut cache_key, + ref widths, + ref mut handle, + ref mut task_info, + .. + } = *source { + // TODO(gw): When drawing in screen raster mode, we should also incorporate a + // scale factor from the world transform to get an appropriately + // sized border task. + let world_scale = LayoutToWorldScale::new(1.0); + let mut scale = world_scale * frame_context.device_pixel_scale; + let max_scale = BorderRenderTaskInfo::get_max_scale(&border.radius); + scale.0 = scale.0.min(max_scale.0); + let scale_au = Au::from_f32_px(scale.0); + let needs_update = scale_au != cache_key.scale; + let mut new_segments = Vec::new(); + + if needs_update { + cache_key.scale = scale_au; + + *task_info = BorderRenderTaskInfo::new( + &self.metadata.local_rect, + border, + widths, + scale, + &mut new_segments, + ); + } + + *handle = task_info.as_ref().map(|task_info| { + frame_state.resource_cache.request_render_task( + RenderTaskCacheKey { + size: DeviceIntSize::zero(), + kind: RenderTaskCacheKeyKind::Border(cache_key.clone()), + }, + frame_state.gpu_cache, + frame_state.render_tasks, + None, + false, // todo + |render_tasks| { + let task = RenderTask::new_border( + task_info.size, + task_info.build_instances(border), + ); + + let task_id = render_tasks.add(task); + + pic_state.tasks.push(task_id); + + task_id + } + ) + }); + + if needs_update { + brush.segment_desc = Some(BrushSegmentDescriptor { + segments: new_segments, + clip_mask_kind: BrushClipMaskKind::Unknown, + }); + + // The segments have changed, so force the GPU cache to + // re-upload the primitive information. + frame_state.gpu_cache.invalidate(&mut self.metadata.gpu_location); + } + } + } + } } From 81074aca9e09e2e613c5022d7e376959a2e304ac Mon Sep 17 00:00:00 2001 From: Glenn Watson Date: Fri, 10 Aug 2018 16:15:16 +1000 Subject: [PATCH 08/11] Remove a borrowck hack --- webrender/src/prim_store.rs | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/webrender/src/prim_store.rs b/webrender/src/prim_store.rs index 3bb2c12726..f7fda7c01e 100644 --- a/webrender/src/prim_store.rs +++ b/webrender/src/prim_store.rs @@ -1659,27 +1659,23 @@ impl PrimitiveStore { None => return None, }; - let ccr = { - let metadata = &mut self.primitives[prim_index.0].metadata; - - metadata.screen_rect = Some(ScreenRect { - clipped: clipped_device_rect, - unclipped: unclipped_device_rect, - }); + let prim = &mut self.primitives[prim_index.0]; + prim.metadata.screen_rect = Some(ScreenRect { + clipped: clipped_device_rect, + unclipped: unclipped_device_rect, + }); - metadata.combined_local_clip_rect = if pic_context.apply_local_clip_rect { - clip_chain.local_clip_rect - } else { - local_clip_rect - }; + prim.metadata.combined_local_clip_rect = if pic_context.apply_local_clip_rect { + clip_chain.local_clip_rect + } else { + local_clip_rect + }; - match metadata.combined_local_clip_rect.intersection(&local_rect) { - Some(ccr) => ccr, - None => return None, - } + let ccr = match prim.metadata.combined_local_clip_rect.intersection(&local_rect) { + Some(ccr) => ccr, + None => return None, }; - let prim = &mut self.primitives[prim_index.0]; prim.build_prim_segments_if_needed( pic_state, frame_state, From 8e78cd74b838a39411b3933d4913bf1c02a89d13 Mon Sep 17 00:00:00 2001 From: Glenn Watson Date: Fri, 10 Aug 2018 16:18:31 +1000 Subject: [PATCH 09/11] Remove another borrowck hack. --- webrender/src/prim_store.rs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/webrender/src/prim_store.rs b/webrender/src/prim_store.rs index f7fda7c01e..fca45e07b4 100644 --- a/webrender/src/prim_store.rs +++ b/webrender/src/prim_store.rs @@ -1559,7 +1559,7 @@ impl PrimitiveStore { } } - let (local_rect, local_clip_rect, clip_chain_id) = { + let (local_rect, clip_chain_id) = { let metadata = &mut self.primitives[prim_index.0].metadata; if metadata.local_rect.size.width <= 0.0 || metadata.local_rect.size.height <= 0.0 { @@ -1589,15 +1589,17 @@ impl PrimitiveStore { } }; - (local_rect, metadata.local_clip_rect, metadata.clip_chain_id) + (local_rect, metadata.clip_chain_id) }; + let prim = &mut self.primitives[prim_index.0]; + let clip_chain = frame_state .clip_store .build_clip_chain_instance( clip_chain_id, local_rect, - local_clip_rect, + prim.metadata.local_clip_rect, prim_run_context.spatial_node_index, &frame_context.clip_scroll_tree.spatial_nodes, frame_state.gpu_cache, @@ -1608,7 +1610,7 @@ impl PrimitiveStore { let clip_chain = match clip_chain { Some(clip_chain) => clip_chain, None => { - self.primitives[prim_index.0].metadata.screen_rect = None; + prim.metadata.screen_rect = None; return None; } }; @@ -1659,7 +1661,6 @@ impl PrimitiveStore { None => return None, }; - let prim = &mut self.primitives[prim_index.0]; prim.metadata.screen_rect = Some(ScreenRect { clipped: clipped_device_rect, unclipped: unclipped_device_rect, @@ -1668,7 +1669,7 @@ impl PrimitiveStore { prim.metadata.combined_local_clip_rect = if pic_context.apply_local_clip_rect { clip_chain.local_clip_rect } else { - local_clip_rect + prim.metadata.local_clip_rect }; let ccr = match prim.metadata.combined_local_clip_rect.intersection(&local_rect) { From 8149af7fa87a4df80559cebadd457dc775b61bcf Mon Sep 17 00:00:00 2001 From: Glenn Watson Date: Fri, 10 Aug 2018 16:23:49 +1000 Subject: [PATCH 10/11] Tidy up prepare_prim_for_render a little --- webrender/src/prim_store.rs | 70 +++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 37 deletions(-) diff --git a/webrender/src/prim_store.rs b/webrender/src/prim_store.rs index fca45e07b4..6ef71cc604 100644 --- a/webrender/src/prim_store.rs +++ b/webrender/src/prim_store.rs @@ -1470,7 +1470,7 @@ impl PrimitiveStore { // Do some basic checks first, that can early out // without even knowing the local rect. if !prim.metadata.is_backface_visible && prim_run_context.transform.backface_is_visible { - if cfg!(debug_assertions) && Some(prim_index) == self.chase_id { + if cfg!(debug_assertions) && is_chased { println!("\tculled for not having visible back faces"); } return None; @@ -1481,7 +1481,7 @@ impl PrimitiveStore { match brush.kind { BrushKind::Picture(ref mut pic) => { if !pic.resolve_scene_properties(frame_context.scene_properties) { - if cfg!(debug_assertions) && Some(prim_index) == self.chase_id { + if cfg!(debug_assertions) && is_chased { println!("\tculled for carrying an invisible composite filter"); } return None; @@ -1559,45 +1559,41 @@ impl PrimitiveStore { } } - let (local_rect, clip_chain_id) = { - let metadata = &mut self.primitives[prim_index.0].metadata; - if metadata.local_rect.size.width <= 0.0 || - metadata.local_rect.size.height <= 0.0 { - if cfg!(debug_assertions) && Some(prim_index) == self.chase_id { - println!("\tculled for zero local rectangle"); - } - return None; + let prim = &mut self.primitives[prim_index.0]; + prim.metadata.screen_rect = None; + + if prim.metadata.local_rect.size.width <= 0.0 || + prim.metadata.local_rect.size.height <= 0.0 { + if cfg!(debug_assertions) && is_chased { + println!("\tculled for zero local rectangle"); } + return None; + } - metadata.screen_rect = None; - - // Inflate the local rect for this primitive by the inflation factor of - // the picture context. This ensures that even if the primitive itself - // is not visible, any effects from the blur radius will be correctly - // taken into account. - let local_rect = metadata.local_rect - .inflate(pic_context.inflation_factor, pic_context.inflation_factor) - .intersection(&metadata.local_clip_rect); - let local_rect = match local_rect { - Some(local_rect) => local_rect, - None => { - if cfg!(debug_assertions) && Some(prim_index) == self.chase_id { - println!("\tculled for being out of the local clip rectangle: {:?}", - metadata.local_clip_rect); - } - return None + // Inflate the local rect for this primitive by the inflation factor of + // the picture context. This ensures that even if the primitive itself + // is not visible, any effects from the blur radius will be correctly + // taken into account. + let local_rect = prim + .metadata + .local_rect + .inflate(pic_context.inflation_factor, pic_context.inflation_factor) + .intersection(&prim.metadata.local_clip_rect); + let local_rect = match local_rect { + Some(local_rect) => local_rect, + None => { + if cfg!(debug_assertions) && is_chased { + println!("\tculled for being out of the local clip rectangle: {:?}", + prim.metadata.local_clip_rect); } - }; - - (local_rect, metadata.clip_chain_id) + return None + } }; - let prim = &mut self.primitives[prim_index.0]; - let clip_chain = frame_state .clip_store .build_clip_chain_instance( - clip_chain_id, + prim.metadata.clip_chain_id, local_rect, prim.metadata.local_clip_rect, prim_run_context.spatial_node_index, @@ -1615,7 +1611,7 @@ impl PrimitiveStore { } }; - if self.chase_id == Some(prim_index) { + if cfg!(debug_assertions) && is_chased { println!("\teffective clip chain from {:?} {}", clip_chain.clips_range, if pic_context.apply_local_clip_rect { "(applied)" } else { "" }, @@ -1632,7 +1628,7 @@ impl PrimitiveStore { ) { Some(rect) => rect, None => { - if cfg!(debug_assertions) && Some(prim_index) == self.chase_id { + if cfg!(debug_assertions) && is_chased { println!("\tculled for being behind the near plane of transform: {:?}", prim_run_context.scroll_node.world_content_transform); } @@ -1648,7 +1644,7 @@ impl PrimitiveStore { ) { Some(rect) => rect, None => { - if cfg!(debug_assertions) && Some(prim_index) == self.chase_id { + if cfg!(debug_assertions) && is_chased { println!("\tculled for being behind the near plane of transform: {:?}", prim_run_context.scroll_node.world_content_transform); } @@ -2729,7 +2725,7 @@ impl Primitive { // sized border task. let world_scale = LayoutToWorldScale::new(1.0); let mut scale = world_scale * frame_context.device_pixel_scale; - let max_scale = BorderRenderTaskInfo::get_max_scale(&border.radius); + let max_scale = BorderRenderTaskInfo::get_max_scale(&border.radius, &widths); scale.0 = scale.0.min(max_scale.0); let scale_au = Au::from_f32_px(scale.0); let needs_update = scale_au != cache_key.scale; From b8b64060cbd57a77c67b1dd4b14c3f82c981d4ca Mon Sep 17 00:00:00 2001 From: Glenn Watson Date: Mon, 13 Aug 2018 07:44:29 +1000 Subject: [PATCH 11/11] Address review comments. --- webrender/src/display_list_flattener.rs | 4 +- webrender/src/picture.rs | 2 +- webrender/src/prim_store.rs | 146 +++++++++++------------- 3 files changed, 72 insertions(+), 80 deletions(-) diff --git a/webrender/src/display_list_flattener.rs b/webrender/src/display_list_flattener.rs index 813df42a72..ea20b63f8b 100644 --- a/webrender/src/display_list_flattener.rs +++ b/webrender/src/display_list_flattener.rs @@ -27,7 +27,7 @@ use picture::{PictureCompositeMode, PictureId, PicturePrimitive}; use prim_store::{BrushClipMaskKind, BrushKind, BrushPrimitive, BrushSegmentDescriptor}; use prim_store::{EdgeAaSegmentMask, ImageSource}; use prim_store::{BorderSource, BrushSegment, PrimitiveContainer, PrimitiveIndex, PrimitiveStore}; -use prim_store::{OpacityBinding, ScrollNodeAndClipChain, TextRunPrimitiveCpu}; +use prim_store::{OpacityBinding, ScrollNodeAndClipChain, TextRunPrimitive}; use render_backend::{DocumentView}; use resource_cache::{FontInstanceMap, ImageRequest}; use scene::{Scene, ScenePipeline, StackingContextHelpers}; @@ -1902,7 +1902,7 @@ impl<'a> DisplayListFlattener<'a> { font_instance.platform_options, font_instance.variations.clone(), ); - TextRunPrimitiveCpu::new( + TextRunPrimitive::new( prim_font, run_offset, glyph_range, diff --git a/webrender/src/picture.rs b/webrender/src/picture.rs index 36cec393ed..0ba9e96d2b 100644 --- a/webrender/src/picture.rs +++ b/webrender/src/picture.rs @@ -222,7 +222,7 @@ impl PicturePrimitive { }); } - pub fn update_local_rect( + pub fn update_local_rect_and_set_runs( &mut self, prim_run_rect: PrimitiveRunLocalRect, prim_runs: Vec, diff --git a/webrender/src/prim_store.rs b/webrender/src/prim_store.rs index 6ef71cc604..3e42e6966c 100644 --- a/webrender/src/prim_store.rs +++ b/webrender/src/prim_store.rs @@ -756,7 +756,7 @@ impl<'a> GradientGpuBlockBuilder<'a> { } #[derive(Debug, Clone)] -pub struct TextRunPrimitiveCpu { +pub struct TextRunPrimitive { pub specified_font: FontInstance, pub used_font: FontInstance, pub offset: LayoutVector2D, @@ -767,7 +767,7 @@ pub struct TextRunPrimitiveCpu { pub glyph_raster_space: GlyphRasterSpace, } -impl TextRunPrimitiveCpu { +impl TextRunPrimitive { pub fn new( font: FontInstance, offset: LayoutVector2D, @@ -776,7 +776,7 @@ impl TextRunPrimitiveCpu { shadow: bool, glyph_raster_space: GlyphRasterSpace, ) -> Self { - TextRunPrimitiveCpu { + TextRunPrimitive { specified_font: font.clone(), used_font: font, offset, @@ -1109,7 +1109,7 @@ impl ClipData { #[derive(Debug)] pub enum PrimitiveContainer { - TextRun(TextRunPrimitiveCpu), + TextRun(TextRunPrimitive), Brush(BrushPrimitive), } @@ -1159,7 +1159,7 @@ impl PrimitiveContainer { font.disable_subpixel_aa(); } - PrimitiveContainer::TextRun(TextRunPrimitiveCpu::new( + PrimitiveContainer::TextRun(TextRunPrimitive::new( font, info.offset + shadow.offset, info.glyph_range, @@ -1193,7 +1193,7 @@ impl PrimitiveContainer { pub enum PrimitiveDetails { Brush(BrushPrimitive), - TextRun(TextRunPrimitiveCpu), + TextRun(TextRunPrimitive), } pub struct Primitive { @@ -1204,13 +1204,8 @@ pub struct Primitive { impl Primitive { pub fn as_pic(&self) -> &PicturePrimitive { match self.details { - PrimitiveDetails::Brush(ref brush) => { - match brush.kind { - BrushKind::Picture(ref pic) => pic, - _ => panic!("bug: not a picture!"), - } - } - PrimitiveDetails::TextRun(..) => { + PrimitiveDetails::Brush(BrushPrimitive { kind: BrushKind::Picture(ref pic), .. }) => pic, + _ => { panic!("bug: not a picture!"); } } @@ -1218,13 +1213,8 @@ impl Primitive { pub fn as_pic_mut(&mut self) -> &mut PicturePrimitive { match self.details { - PrimitiveDetails::Brush(ref mut brush) => { - match brush.kind { - BrushKind::Picture(ref mut pic) => pic, - _ => panic!("bug: not a picture!"), - } - } - PrimitiveDetails::TextRun(..) => { + PrimitiveDetails::Brush(BrushPrimitive { kind: BrushKind::Picture(ref mut pic), .. }) => pic, + _ => { panic!("bug: not a picture!"); } } @@ -1550,7 +1540,10 @@ impl PrimitiveStore { let prim = &mut self.primitives[prim_index.0]; let new_local_rect = prim .as_pic_mut() - .update_local_rect(result, pic_context_for_children.prim_runs); + .update_local_rect_and_set_runs( + result, + pic_context_for_children.prim_runs + ); if new_local_rect != prim.metadata.local_rect { prim.metadata.local_rect = new_local_rect; @@ -2227,7 +2220,6 @@ impl Primitive { ); } PrimitiveDetails::Brush(ref mut brush) => { - match brush.kind { BrushKind::Image { request, @@ -2711,73 +2703,73 @@ impl Primitive { PrimitiveDetails::TextRun(..) => return, }; - if let BrushKind::Border { ref mut source, .. } = brush.kind { - if let BorderSource::Border { + if let BrushKind::Border { + source: BorderSource::Border { ref border, ref mut cache_key, ref widths, ref mut handle, ref mut task_info, .. - } = *source { - // TODO(gw): When drawing in screen raster mode, we should also incorporate a - // scale factor from the world transform to get an appropriately - // sized border task. - let world_scale = LayoutToWorldScale::new(1.0); - let mut scale = world_scale * frame_context.device_pixel_scale; - let max_scale = BorderRenderTaskInfo::get_max_scale(&border.radius, &widths); - scale.0 = scale.0.min(max_scale.0); - let scale_au = Au::from_f32_px(scale.0); - let needs_update = scale_au != cache_key.scale; - let mut new_segments = Vec::new(); - - if needs_update { - cache_key.scale = scale_au; - - *task_info = BorderRenderTaskInfo::new( - &self.metadata.local_rect, - border, - widths, - scale, - &mut new_segments, - ); - } + } + } = brush.kind { + // TODO(gw): When drawing in screen raster mode, we should also incorporate a + // scale factor from the world transform to get an appropriately + // sized border task. + let world_scale = LayoutToWorldScale::new(1.0); + let mut scale = world_scale * frame_context.device_pixel_scale; + let max_scale = BorderRenderTaskInfo::get_max_scale(&border.radius, &widths); + scale.0 = scale.0.min(max_scale.0); + let scale_au = Au::from_f32_px(scale.0); + let needs_update = scale_au != cache_key.scale; + let mut new_segments = Vec::new(); + + if needs_update { + cache_key.scale = scale_au; + + *task_info = BorderRenderTaskInfo::new( + &self.metadata.local_rect, + border, + widths, + scale, + &mut new_segments, + ); + } - *handle = task_info.as_ref().map(|task_info| { - frame_state.resource_cache.request_render_task( - RenderTaskCacheKey { - size: DeviceIntSize::zero(), - kind: RenderTaskCacheKeyKind::Border(cache_key.clone()), - }, - frame_state.gpu_cache, - frame_state.render_tasks, - None, - false, // todo - |render_tasks| { - let task = RenderTask::new_border( - task_info.size, - task_info.build_instances(border), - ); + *handle = task_info.as_ref().map(|task_info| { + frame_state.resource_cache.request_render_task( + RenderTaskCacheKey { + size: DeviceIntSize::zero(), + kind: RenderTaskCacheKeyKind::Border(cache_key.clone()), + }, + frame_state.gpu_cache, + frame_state.render_tasks, + None, + false, // todo + |render_tasks| { + let task = RenderTask::new_border( + task_info.size, + task_info.build_instances(border), + ); - let task_id = render_tasks.add(task); + let task_id = render_tasks.add(task); - pic_state.tasks.push(task_id); + pic_state.tasks.push(task_id); - task_id - } - ) - }); + task_id + } + ) + }); - if needs_update { - brush.segment_desc = Some(BrushSegmentDescriptor { - segments: new_segments, - clip_mask_kind: BrushClipMaskKind::Unknown, - }); + if needs_update { + brush.segment_desc = Some(BrushSegmentDescriptor { + segments: new_segments, + clip_mask_kind: BrushClipMaskKind::Unknown, + }); - // The segments have changed, so force the GPU cache to - // re-upload the primitive information. - frame_state.gpu_cache.invalidate(&mut self.metadata.gpu_location); - } + // The segments have changed, so force the GPU cache to + // re-upload the primitive information. + frame_state.gpu_cache.invalidate(&mut self.metadata.gpu_location); } } }