From 18b12e5037f9a171b09d1eb1e5b3a1b05924be9a Mon Sep 17 00:00:00 2001 From: Dzmitry Malyshau Date: Tue, 24 Oct 2017 00:34:14 -0400 Subject: [PATCH 1/2] Synchonized font instance map --- webrender/src/frame.rs | 11 +- webrender/src/resource_cache.rs | 270 +++++++++++++++----------------- 2 files changed, 136 insertions(+), 145 deletions(-) diff --git a/webrender/src/frame.rs b/webrender/src/frame.rs index 8d46183d80..fe148d5522 100644 --- a/webrender/src/frame.rs +++ b/webrender/src/frame.rs @@ -17,7 +17,7 @@ use frame_builder::{FrameBuilder, FrameBuilderConfig}; use gpu_cache::GpuCache; use internal_types::{FastHashMap, FastHashSet, RendererFrame}; use profiler::{GpuCacheProfileCounters, TextureCacheProfileCounters}; -use resource_cache::{ResourceCache, TiledImageMap}; +use resource_cache::{FontInstanceMap,ResourceCache, TiledImageMap}; use scene::{Scene, StackingContextHelpers, ScenePipeline}; use tiling::{CompositeOps, Frame, PrimitiveFlags}; use util::{subtract_rect, ComplexClipRegionHelpers}; @@ -35,7 +35,7 @@ static DEFAULT_SCROLLBAR_COLOR: ColorF = ColorF { struct FlattenContext<'a> { scene: &'a Scene, builder: &'a mut FrameBuilder, - resource_cache: &'a ResourceCache, + font_instances: FontInstanceMap, tiled_image_map: TiledImageMap, replacements: Vec<(ClipId, ClipId)>, } @@ -49,7 +49,7 @@ impl<'a> FlattenContext<'a> { FlattenContext { scene, builder, - resource_cache, + font_instances: resource_cache.get_font_instances(), tiled_image_map: resource_cache.get_tiled_image_map(), replacements: Vec::new(), } @@ -484,7 +484,10 @@ impl FrameContext { ); } SpecificDisplayItem::Text(ref text_info) => { - match context.resource_cache.get_font_instance(text_info.font_key) { + let instance_map =context.font_instances + .read() + .unwrap(); + match instance_map.get(&text_info.font_key) { Some(instance) => { context.builder.add_text( clip_and_scroll, diff --git a/webrender/src/resource_cache.rs b/webrender/src/resource_cache.rs index 30dcffec18..bd13611ca5 100644 --- a/webrender/src/resource_cache.rs +++ b/webrender/src/resource_cache.rs @@ -26,7 +26,7 @@ use std::cmp; use std::fmt::Debug; use std::hash::Hash; use std::mem; -use std::sync::Arc; +use std::sync::{Arc, RwLock}; use texture_cache::{TextureCache, TextureCacheHandle}; const DEFAULT_TILE_SIZE: TileSize = 512; @@ -189,9 +189,12 @@ impl Into for ImageRequest { } } +type ImageCache = ResourceClassCache; +pub type FontInstanceMap = Arc>>; + struct Resources { font_templates: FastHashMap, - font_instances: FastHashMap, + font_instances: FontInstanceMap, image_templates: ImageTemplates, } @@ -208,7 +211,7 @@ impl BlobImageResources for Resources { pub struct ResourceCache { cached_glyphs: GlyphCache, - cached_images: ResourceClassCache, + cached_images: ImageCache, resources: Resources, state: State, @@ -239,7 +242,7 @@ impl ResourceCache { cached_images: ResourceClassCache::new(), resources: Resources { font_templates: FastHashMap::default(), - font_instances: FastHashMap::default(), + font_instances: Arc::new(RwLock::new(FastHashMap::default())), image_templates: ImageTemplates::new(), }, cached_glyph_dimensions: FastHashMap::default(), @@ -256,8 +259,7 @@ impl ResourceCache { self.texture_cache.max_texture_size() } - fn should_tile(&self, descriptor: &ImageDescriptor, data: &ImageData) -> bool { - let limit = self.max_texture_size(); + fn should_tile(limit: u32, descriptor: &ImageDescriptor, data: &ImageData) -> bool { let size_check = descriptor.width > limit || descriptor.height > limit; match *data { ImageData::Raw(_) | ImageData::Blob(_) => size_check, @@ -365,18 +367,24 @@ impl ResourceCache { if self.glyph_rasterizer.is_bitmap_font(&instance) { instance.render_mode = instance.render_mode.limit_by(FontRenderMode::Bitmap); } - self.resources.font_instances.insert(instance_key, instance); + self.resources.font_instances + .write() + .unwrap() + .insert(instance_key, instance); } pub fn delete_font_instance(&mut self, instance_key: FontInstanceKey) { - self.resources.font_instances.remove(&instance_key); + self.resources.font_instances + .write() + .unwrap() + .remove(&instance_key); if let Some(ref mut r) = self.blob_image_renderer { r.delete_font_instance(instance_key); } } - pub fn get_font_instance(&self, instance_key: FontInstanceKey) -> Option<&FontInstance> { - self.resources.font_instances.get(&instance_key) + pub fn get_font_instances(&self) -> FontInstanceMap { + self.resources.font_instances.clone() } pub fn add_image_template( @@ -386,7 +394,7 @@ impl ResourceCache { mut data: ImageData, mut tiling: Option, ) { - if tiling.is_none() && self.should_tile(&descriptor, &data) { + if tiling.is_none() && Self::should_tile(self.max_texture_size(), &descriptor, &data) { // We aren't going to be able to upload a texture this big, so tile it, even // if tiling was not requested. tiling = Some(DEFAULT_TILE_SIZE); @@ -418,40 +426,32 @@ impl ResourceCache { mut data: ImageData, dirty_rect: Option, ) { - let resource = if let Some(image) = self.resources.image_templates.get(image_key) { - let next_epoch = Epoch(image.epoch.0 + 1); + let max_texture_size = self.max_texture_size(); + let image = match self.resources.image_templates.get_mut(image_key) { + Some(res) => res, + None => panic!( + "Attempt to update non-existent image (key {:?}).", + image_key + ), + }; - let mut tiling = image.tiling; - if tiling.is_none() && self.should_tile(&descriptor, &data) { - tiling = Some(DEFAULT_TILE_SIZE); - } + if image.tiling.is_none() && Self::should_tile(max_texture_size, &descriptor, &data) { + image.tiling = Some(DEFAULT_TILE_SIZE); + } - if let ImageData::Blob(ref mut blob) = data { - self.blob_image_renderer - .as_mut() - .unwrap() - .update(image_key, mem::replace(blob, BlobImageData::new()), dirty_rect); - } + if let ImageData::Blob(ref mut blob) = data { + self.blob_image_renderer + .as_mut() + .unwrap() + .update(image_key, mem::replace(blob, BlobImageData::new()), dirty_rect); + } - ImageResource { - descriptor, - data, - epoch: next_epoch, - tiling, - dirty_rect: match (dirty_rect, image.dirty_rect) { - (Some(rect), Some(prev_rect)) => Some(rect.union(&prev_rect)), - (Some(rect), None) => Some(rect), - _ => None, - }, - } - } else { - panic!( - "Attempt to update non-existant image (key {:?}).", - image_key - ); + image.epoch.0 += 1; + image.dirty_rect = match (dirty_rect, image.dirty_rect) { + (Some(rect), Some(prev_rect)) => Some(rect.union(&prev_rect)), + (Some(rect), None) => Some(rect), + (None, _) => None, }; - - self.resources.image_templates.insert(image_key, resource); } pub fn delete_image_template(&mut self, image_key: ImageKey) { @@ -484,99 +484,100 @@ impl ResourceCache { tile, }; - match self.resources.image_templates.get(key) { - Some(template) => { - // Images that don't use the texture cache can early out. - if !template.data.uses_texture_cache() { - return; - } + let template = match self.resources.image_templates.get(key) { + Some(template) => template, + None => { + warn!( + "ERROR: Trying to render deleted / non-existent key {:?}", + key + ); + return + } + }; - let side_size = - template.tiling.map_or(cmp::max(template.descriptor.width, template.descriptor.height), - |tile_size| tile_size as u32); - if side_size > self.texture_cache.max_texture_size() { - // The image or tiling size is too big for hardware texture size. - warn!("Dropping image, image:(w:{},h:{}, tile:{}) is too big for hardware!", - template.descriptor.width, template.descriptor.height, template.tiling.unwrap_or(0)); - self.cached_images.insert(request, Err(ResourceClassCacheError::OverLimitSize)); - return; - } + // Images that don't use the texture cache can early out. + if !template.data.uses_texture_cache() { + return; + } - // If this image exists in the texture cache, *and* the epoch - // in the cache matches that of the template, then it is - // valid to use as-is. - let (entry, needs_update) = match self.cached_images.entry(request) { - Occupied(entry) => { - let needs_update = entry.get().as_ref().unwrap().epoch != template.epoch; - (entry.into_mut(), needs_update) + let side_size = + template.tiling.map_or(cmp::max(template.descriptor.width, template.descriptor.height), + |tile_size| tile_size as u32); + if side_size > self.texture_cache.max_texture_size() { + // The image or tiling size is too big for hardware texture size. + warn!("Dropping image, image:(w:{},h:{}, tile:{}) is too big for hardware!", + template.descriptor.width, template.descriptor.height, template.tiling.unwrap_or(0)); + self.cached_images.insert(request, Err(ResourceClassCacheError::OverLimitSize)); + return; + } + + // If this image exists in the texture cache, *and* the epoch + // in the cache matches that of the template, then it is + // valid to use as-is. + let (entry, needs_update) = match self.cached_images.entry(request) { + Occupied(entry) => { + let needs_update = entry.get().as_ref().unwrap().epoch != template.epoch; + (entry.into_mut(), needs_update) + } + Vacant(entry) => ( + entry.insert(Ok( + CachedImageInfo { + epoch: template.epoch, + texture_cache_handle: TextureCacheHandle::new(), } - Vacant(entry) => ( - entry.insert(Ok( - CachedImageInfo { - epoch: template.epoch, - texture_cache_handle: TextureCacheHandle::new(), - } - )), - true, - ), - }; + )), + true, + ), + }; - let needs_upload = self.texture_cache - .request(&mut entry.as_mut().unwrap().texture_cache_handle, gpu_cache); + let needs_upload = self.texture_cache + .request(&mut entry.as_mut().unwrap().texture_cache_handle, gpu_cache); - if !needs_upload && !needs_update { - return; - } + if !needs_upload && !needs_update { + return; + } - // We can start a worker thread rasterizing right now, if: - // - The image is a blob. - // - The blob hasn't already been requested this frame. - if self.pending_image_requests.insert(request) { - if template.data.is_blob() { - if let Some(ref mut renderer) = self.blob_image_renderer { - let (offset, w, h) = match template.tiling { - Some(tile_size) => { - let tile_offset = request.tile.unwrap(); - let (w, h) = compute_tile_size( - &template.descriptor, - tile_size, - tile_offset, - ); - let offset = DevicePoint::new( - tile_offset.x as f32 * tile_size as f32, - tile_offset.y as f32 * tile_size as f32, - ); - - (offset, w, h) - } - None => ( - DevicePoint::zero(), - template.descriptor.width, - template.descriptor.height, - ), - }; - - renderer.request( - &self.resources, - request.into(), - &BlobImageDescriptor { - width: w, - height: h, - offset, - format: template.descriptor.format, - }, - template.dirty_rect, + // We can start a worker thread rasterizing right now, if: + // - The image is a blob. + // - The blob hasn't already been requested this frame. + if self.pending_image_requests.insert(request) { + if template.data.is_blob() { + if let Some(ref mut renderer) = self.blob_image_renderer { + let (offset, w, h) = match template.tiling { + Some(tile_size) => { + let tile_offset = request.tile.unwrap(); + let (w, h) = compute_tile_size( + &template.descriptor, + tile_size, + tile_offset, + ); + let offset = DevicePoint::new( + tile_offset.x as f32 * tile_size as f32, + tile_offset.y as f32 * tile_size as f32, ); + + (offset, w, h) } - } + None => ( + DevicePoint::zero(), + template.descriptor.width, + template.descriptor.height, + ), + }; + + renderer.request( + &self.resources, + request.into(), + &BlobImageDescriptor { + width: w, + height: h, + offset, + format: template.descriptor.format, + }, + template.dirty_rect, + ); } } - None => { - warn!( - "ERROR: Trying to render deleted / non-existent key {:?}", - key - ); - } } } @@ -879,27 +880,14 @@ impl ResourceCache { } pub fn clear_namespace(&mut self, namespace: IdNamespace) { - //TODO: use `retain` when we are on Rust-1.18 - let image_keys: Vec<_> = self.resources + self.resources .image_templates .images - .keys() - .filter(|&key| key.0 == namespace) - .cloned() - .collect(); - for key in &image_keys { - self.resources.image_templates.images.remove(key); - } + .retain(|key, _| key.0 != namespace); - let font_keys: Vec<_> = self.resources + self.resources .font_templates - .keys() - .filter(|&key| key.0 == namespace) - .cloned() - .collect(); - for key in &font_keys { - self.resources.font_templates.remove(key); - } + .retain(|key, _| key.0 != namespace); self.cached_images .clear_keys(|request| request.key.0 == namespace); From 72692761f0f01d8e5629e20610e1c679fa037b8a Mon Sep 17 00:00:00 2001 From: Dzmitry Malyshau Date: Wed, 25 Oct 2017 12:04:20 -0400 Subject: [PATCH 2/2] Move flattening logic into FlattenContext --- webrender/src/frame.rs | 711 +++++++++++++++----------------- webrender/src/frame_builder.rs | 2 +- webrender/src/resource_cache.rs | 2 +- 3 files changed, 343 insertions(+), 372 deletions(-) diff --git a/webrender/src/frame.rs b/webrender/src/frame.rs index fe148d5522..413ffd1bf6 100644 --- a/webrender/src/frame.rs +++ b/webrender/src/frame.rs @@ -34,27 +34,15 @@ static DEFAULT_SCROLLBAR_COLOR: ColorF = ColorF { struct FlattenContext<'a> { scene: &'a Scene, - builder: &'a mut FrameBuilder, + builder: FrameBuilder, + clip_scroll_tree: &'a mut ClipScrollTree, font_instances: FontInstanceMap, tiled_image_map: TiledImageMap, + pipeline_epochs: Vec<(PipelineId, Epoch)>, replacements: Vec<(ClipId, ClipId)>, } impl<'a> FlattenContext<'a> { - fn new( - scene: &'a Scene, - builder: &'a mut FrameBuilder, - resource_cache: &'a ResourceCache, - ) -> FlattenContext<'a> { - FlattenContext { - scene, - builder, - font_instances: resource_cache.get_font_instances(), - tiled_image_map: resource_cache.get_tiled_image_map(), - replacements: Vec::new(), - } - } - /// Since WebRender still handles fixed position and reference frame content internally /// we need to apply this table of id replacements only to the id that affects the /// position of a node. We can eventually remove this when clients start handling @@ -83,161 +71,110 @@ impl<'a> FlattenContext<'a> { .get(complex_clips) .collect() } -} - -/// Frame context contains the information required to update -/// (e.g. scroll) a renderer frame builder (`FrameBuilder`). -pub struct FrameContext { - clip_scroll_tree: ClipScrollTree, - pipeline_epoch_map: FastHashMap, - id: FrameId, - frame_builder_config: FrameBuilderConfig, -} -impl FrameContext { - pub fn new(config: FrameBuilderConfig) -> Self { - FrameContext { - pipeline_epoch_map: FastHashMap::default(), - clip_scroll_tree: ClipScrollTree::new(), - id: FrameId(0), - frame_builder_config: config, - } - } - - pub fn reset(&mut self) -> ScrollStates { - self.pipeline_epoch_map.clear(); - - // Advance to the next frame. - self.id.0 += 1; + fn flatten_root( + &mut self, + traversal: &mut BuiltDisplayListIter<'a>, + pipeline_id: PipelineId, + content_size: &LayoutSize, + ) { + self.builder.push_stacking_context( + &LayerVector2D::zero(), + pipeline_id, + CompositeOps::default(), + TransformStyle::Flat, + true, + true, + ); - self.clip_scroll_tree.drain() - } + // We do this here, rather than above because we want any of the top-level + // stacking contexts in the display list to be treated like root stacking contexts. + // FIXME(mrobinson): Currently only the first one will, which for the moment is + // sufficient for all our use cases. + self.builder.notify_waiting_for_root_stacking_context(); - pub fn get_clip_scroll_tree(&self) -> &ClipScrollTree { - &self.clip_scroll_tree - } + // For the root pipeline, there's no need to add a full screen rectangle + // here, as it's handled by the framebuffer clear. + let clip_id = ClipId::root_scroll_node(pipeline_id); + if self.scene.root_pipeline_id != Some(pipeline_id) { + if let Some(pipeline) = self.scene.pipelines.get(&pipeline_id) { + if let Some(bg_color) = pipeline.background_color { + let root_bounds = LayerRect::new(LayerPoint::zero(), *content_size); + let info = LayerPrimitiveInfo::new(root_bounds); + self.builder.add_solid_rectangle( + ClipAndScrollInfo::simple(clip_id), + &info, + &bg_color, + PrimitiveFlags::None, + ); + } + } + } - pub fn get_scroll_node_state(&self) -> Vec { - self.clip_scroll_tree.get_scroll_node_state() - } - /// Returns true if the node actually changed position or false otherwise. - pub fn scroll_node(&mut self, origin: LayerPoint, id: ClipId, clamp: ScrollClamping) -> bool { - self.clip_scroll_tree.scroll_node(origin, id, clamp) - } + self.flatten_items(traversal, pipeline_id, LayerVector2D::zero()); - /// Returns true if any nodes actually changed position or false otherwise. - pub fn scroll( - &mut self, - scroll_location: ScrollLocation, - cursor: WorldPoint, - phase: ScrollEventPhase, - ) -> bool { - self.clip_scroll_tree.scroll(scroll_location, cursor, phase) - } + if self.builder.config.enable_scrollbars { + let scrollbar_rect = LayerRect::new(LayerPoint::zero(), LayerSize::new(10.0, 70.0)); + let info = LayerPrimitiveInfo::new(scrollbar_rect); - pub fn tick_scrolling_bounce_animations(&mut self) { - self.clip_scroll_tree.tick_scrolling_bounce_animations(); - } + self.builder.add_solid_rectangle( + ClipAndScrollInfo::simple(clip_id), + &info, + &DEFAULT_SCROLLBAR_COLOR, + PrimitiveFlags::Scrollbar(self.clip_scroll_tree.topmost_scrolling_node_id(), 4.0), + ); + } - pub fn discard_frame_state_for_pipeline(&mut self, pipeline_id: PipelineId) { - self.clip_scroll_tree - .discard_frame_state_for_pipeline(pipeline_id); + self.builder.pop_stacking_context(); } - pub fn create( + fn flatten_items( &mut self, - old_builder: Option, - scene: &Scene, - resource_cache: &mut ResourceCache, - window_size: DeviceUintSize, - inner_rect: DeviceUintRect, - device_pixel_ratio: f32, - ) -> Option { - let root_pipeline_id = match scene.root_pipeline_id { - Some(root_pipeline_id) => root_pipeline_id, - None => return old_builder, - }; - - let root_pipeline = match scene.pipelines.get(&root_pipeline_id) { - Some(root_pipeline) => root_pipeline, - None => return old_builder, - }; - - if window_size.width == 0 || window_size.height == 0 { - error!("ERROR: Invalid window dimensions! Please call api.set_window_size()"); - } - - let old_scrolling_states = self.reset(); - - self.pipeline_epoch_map - .insert(root_pipeline_id, root_pipeline.epoch); - - let background_color = root_pipeline - .background_color - .and_then(|color| if color.a > 0.0 { Some(color) } else { None }); - - let mut frame_builder = FrameBuilder::new( - old_builder, - window_size, - background_color, - self.frame_builder_config, - ); - - { - let mut context = FlattenContext::new(scene, &mut frame_builder, resource_cache); + traversal: &mut BuiltDisplayListIter<'a>, + pipeline_id: PipelineId, + reference_frame_relative_offset: LayerVector2D, + ) { + loop { + let subtraversal = { + let item = match traversal.next() { + Some(item) => item, + None => break, + }; - context.builder.push_root( - root_pipeline_id, - &root_pipeline.viewport_size, - &root_pipeline.content_size, - &mut self.clip_scroll_tree, - ); + if SpecificDisplayItem::PopStackingContext == *item.item() { + return; + } - context.builder.setup_viewport_offset( - window_size, - inner_rect, - device_pixel_ratio, - &mut self.clip_scroll_tree, - ); + self.flatten_item(item, pipeline_id, reference_frame_relative_offset) + }; - self.flatten_root( - &mut root_pipeline.display_list.iter(), - root_pipeline_id, - &mut context, - &root_pipeline.content_size, - ); + // If flatten_item created a sub-traversal, we need `traversal` to have the + // same state as the completed subtraversal, so we reinitialize it here. + if let Some(subtraversal) = subtraversal { + *traversal = subtraversal; + } } - - self.clip_scroll_tree - .finalize_and_apply_pending_scroll_offsets(old_scrolling_states); - Some(frame_builder) - } - - pub fn update_epoch(&mut self, pipeline_id: PipelineId, epoch: Epoch) { - self.pipeline_epoch_map.insert(pipeline_id, epoch); } - fn flatten_clip<'a>( + fn flatten_clip( &mut self, - context: &mut FlattenContext, pipeline_id: PipelineId, parent_id: &ClipId, new_clip_id: &ClipId, clip_region: ClipRegion, ) { - context.builder.add_clip_node( + self.builder.add_clip_node( *new_clip_id, *parent_id, pipeline_id, clip_region, - &mut self.clip_scroll_tree, + self.clip_scroll_tree, ); } - fn flatten_scroll_frame<'a>( + fn flatten_scroll_frame( &mut self, - context: &mut FlattenContext, pipeline_id: PipelineId, parent_id: &ClipId, new_scroll_frame_id: &ClipId, @@ -247,30 +184,29 @@ impl FrameContext { scroll_sensitivity: ScrollSensitivity, ) { let clip_id = self.clip_scroll_tree.generate_new_clip_id(pipeline_id); - context.builder.add_clip_node( + self.builder.add_clip_node( clip_id, *parent_id, pipeline_id, clip_region, - &mut self.clip_scroll_tree, + self.clip_scroll_tree, ); - context.builder.add_scroll_frame( + self.builder.add_scroll_frame( *new_scroll_frame_id, clip_id, pipeline_id, &frame_rect, &content_rect.size, scroll_sensitivity, - &mut self.clip_scroll_tree, + self.clip_scroll_tree, ); } - fn flatten_stacking_context<'a>( + fn flatten_stacking_context( &mut self, traversal: &mut BuiltDisplayListIter<'a>, pipeline_id: PipelineId, - context: &mut FlattenContext, context_scroll_node_id: ClipId, mut reference_frame_relative_offset: LayerVector2D, bounds: &LayerRect, @@ -286,7 +222,7 @@ impl FrameContext { let composition_operations = { // TODO(optimization?): self.traversal.display_list() - let display_list = &context + let display_list = &self .scene .pipelines .get(&pipeline_id) @@ -296,16 +232,16 @@ impl FrameContext { stacking_context.filter_ops_for_compositing( display_list, filters, - &context.scene.properties, + &self.scene.properties, ), stacking_context.mix_blend_mode_for_compositing(), ) }; if stacking_context.scroll_policy == ScrollPolicy::Fixed { - context.replacements.push(( + self.replacements.push(( context_scroll_node_id, - context.builder.current_reference_frame_id(), + self.builder.current_reference_frame_id(), )); } @@ -315,7 +251,7 @@ impl FrameContext { stacking_context.transform.is_some() || stacking_context.perspective.is_some(); if is_reference_frame { let transform = stacking_context.transform.as_ref(); - let transform = context.scene.properties.resolve_layout_transform(transform); + let transform = self.scene.properties.resolve_layout_transform(transform); let perspective = stacking_context .perspective .unwrap_or_else(LayoutTransform::identity); @@ -325,17 +261,17 @@ impl FrameContext { .pre_mul(&perspective); let reference_frame_bounds = LayerRect::new(LayerPoint::zero(), bounds.size); - let mut clip_id = context.apply_scroll_frame_id_replacement(context_scroll_node_id); - clip_id = context.builder.push_reference_frame( + let mut clip_id = self.apply_scroll_frame_id_replacement(context_scroll_node_id); + clip_id = self.builder.push_reference_frame( Some(clip_id), pipeline_id, &reference_frame_bounds, &transform, origin, false, - &mut self.clip_scroll_tree, + self.clip_scroll_tree, ); - context.replacements.push((context_scroll_node_id, clip_id)); + self.replacements.push((context_scroll_node_id, clip_id)); reference_frame_relative_offset = LayerVector2D::zero(); } else { reference_frame_relative_offset = LayerVector2D::new( @@ -344,7 +280,7 @@ impl FrameContext { ); } - context.builder.push_stacking_context( + self.builder.push_stacking_context( &reference_frame_relative_offset, pipeline_id, composition_operations, @@ -356,32 +292,30 @@ impl FrameContext { self.flatten_items( traversal, pipeline_id, - context, reference_frame_relative_offset, ); if stacking_context.scroll_policy == ScrollPolicy::Fixed { - context.replacements.pop(); + self.replacements.pop(); } if is_reference_frame { - context.replacements.pop(); - context.builder.pop_reference_frame(); + self.replacements.pop(); + self.builder.pop_reference_frame(); } - context.builder.pop_stacking_context(); + self.builder.pop_stacking_context(); } - fn flatten_iframe<'a>( + fn flatten_iframe( &mut self, pipeline_id: PipelineId, parent_id: ClipId, bounds: &LayerRect, local_clip: &LocalClip, - context: &mut FlattenContext, reference_frame_relative_offset: LayerVector2D, ) { - let pipeline = match context.scene.pipelines.get(&pipeline_id) { + let pipeline = match self.scene.pipelines.get(&pipeline_id) { Some(pipeline) => pipeline, None => return, }; @@ -391,91 +325,91 @@ impl FrameContext { let parent_pipeline_id = parent_id.pipeline_id(); let clip_id = self.clip_scroll_tree .generate_new_clip_id(parent_pipeline_id); - context.builder.add_clip_node( + self.builder.add_clip_node( clip_id, parent_id, parent_pipeline_id, clip_region, - &mut self.clip_scroll_tree, + self.clip_scroll_tree, ); - self.pipeline_epoch_map.insert(pipeline_id, pipeline.epoch); + self.pipeline_epochs.push((pipeline_id, pipeline.epoch)); let iframe_rect = LayerRect::new(LayerPoint::zero(), bounds.size); let origin = reference_frame_relative_offset + bounds.origin.to_vector(); let transform = LayerToScrollTransform::create_translation(origin.x, origin.y, 0.0); - let iframe_reference_frame_id = context.builder.push_reference_frame( + let iframe_reference_frame_id = self.builder.push_reference_frame( Some(clip_id), pipeline_id, &iframe_rect, &transform, origin, true, - &mut self.clip_scroll_tree, + self.clip_scroll_tree, ); - context.builder.add_scroll_frame( + self.builder.add_scroll_frame( ClipId::root_scroll_node(pipeline_id), iframe_reference_frame_id, pipeline_id, &iframe_rect, &pipeline.content_size, ScrollSensitivity::ScriptAndInputEvents, - &mut self.clip_scroll_tree, + self.clip_scroll_tree, ); self.flatten_root( &mut pipeline.display_list.iter(), pipeline_id, - context, &pipeline.content_size, ); - context.builder.pop_reference_frame(); + self.builder.pop_reference_frame(); } - fn flatten_item<'a, 'b>( - &mut self, + fn flatten_item<'b>( + &'b mut self, item: DisplayItemRef<'a, 'b>, pipeline_id: PipelineId, - context: &mut FlattenContext, reference_frame_relative_offset: LayerVector2D, ) -> Option> { let mut clip_and_scroll = item.clip_and_scroll(); let unreplaced_scroll_id = clip_and_scroll.scroll_node_id; clip_and_scroll.scroll_node_id = - context.apply_scroll_frame_id_replacement(clip_and_scroll.scroll_node_id); + self.apply_scroll_frame_id_replacement(clip_and_scroll.scroll_node_id); let prim_info = item.get_layer_primitive_info(&reference_frame_relative_offset); match *item.item() { SpecificDisplayItem::Image(ref info) => { - if let Some(tiling) = context.tiled_image_map.get(&info.image_key) { - // The image resource is tiled. We have to generate an image primitive - // for each tile. - self.decompose_image( - clip_and_scroll, - &mut context.builder, - &prim_info, - info, - tiling.image_size, - tiling.tile_size as u32, - ); - } else { - context.builder.add_image( - clip_and_scroll, - &prim_info, - &info.stretch_size, - &info.tile_spacing, - None, - info.image_key, - info.image_rendering, - None, - ); + match self.tiled_image_map.get(&info.image_key).cloned() { + Some(tiling) => { + // The image resource is tiled. We have to generate an image primitive + // for each tile. + self.decompose_image( + clip_and_scroll, + &prim_info, + info, + tiling.image_size, + tiling.tile_size as u32, + ); + } + None => { + self.builder.add_image( + clip_and_scroll, + &prim_info, + &info.stretch_size, + &info.tile_spacing, + None, + info.image_key, + info.image_rendering, + None, + ); + } } } SpecificDisplayItem::YuvImage(ref info) => { - context.builder.add_yuv_image( + self.builder.add_yuv_image( clip_and_scroll, &prim_info, info.yuv_data, @@ -484,12 +418,12 @@ impl FrameContext { ); } SpecificDisplayItem::Text(ref text_info) => { - let instance_map =context.font_instances + let instance_map = self.font_instances .read() .unwrap(); match instance_map.get(&text_info.font_key) { Some(instance) => { - context.builder.add_text( + self.builder.add_text( clip_and_scroll, reference_frame_relative_offset, &prim_info, @@ -506,13 +440,12 @@ impl FrameContext { } } SpecificDisplayItem::Rectangle(ref info) => { - if !try_to_add_rectangle_splitting_on_clip( - context, + if !self.try_to_add_rectangle_splitting_on_clip( &prim_info, &info.color, &clip_and_scroll, ) { - context.builder.add_solid_rectangle( + self.builder.add_solid_rectangle( clip_and_scroll, &prim_info, &info.color, @@ -521,7 +454,7 @@ impl FrameContext { } } SpecificDisplayItem::Line(ref info) => { - context.builder.add_line( + self.builder.add_line( clip_and_scroll, &prim_info, info.wavy_line_thickness, @@ -531,7 +464,7 @@ impl FrameContext { ); } SpecificDisplayItem::Gradient(ref info) => { - context.builder.add_gradient( + self.builder.add_gradient( clip_and_scroll, &prim_info, info.gradient.start_point, @@ -544,7 +477,7 @@ impl FrameContext { ); } SpecificDisplayItem::RadialGradient(ref info) => { - context.builder.add_radial_gradient( + self.builder.add_radial_gradient( clip_and_scroll, &prim_info, info.gradient.start_center, @@ -564,7 +497,7 @@ impl FrameContext { .translate(&reference_frame_relative_offset); let mut prim_info = prim_info.clone(); prim_info.rect = bounds; - context.builder.add_box_shadow( + self.builder.add_box_shadow( clip_and_scroll, &prim_info, &box_shadow_info.offset, @@ -576,7 +509,7 @@ impl FrameContext { ); } SpecificDisplayItem::Border(ref info) => { - context.builder.add_border( + self.builder.add_border( clip_and_scroll, &prim_info, info, @@ -589,7 +522,6 @@ impl FrameContext { self.flatten_stacking_context( &mut subtraversal, pipeline_id, - context, unreplaced_scroll_id, reference_frame_relative_offset, &item.rect(), @@ -605,12 +537,11 @@ impl FrameContext { clip_and_scroll.scroll_node_id, &item.rect(), &item.local_clip(), - context, reference_frame_relative_offset, ); } SpecificDisplayItem::Clip(ref info) => { - let complex_clips = context.get_complex_clips(pipeline_id, item.complex_clip().0); + let complex_clips = self.get_complex_clips(pipeline_id, item.complex_clip().0); let mut clip_region = ClipRegion::create_for_clip_node( *item.local_clip().clip_rect(), complex_clips, @@ -619,7 +550,6 @@ impl FrameContext { clip_region.origin += reference_frame_relative_offset; self.flatten_clip( - context, pipeline_id, &clip_and_scroll.scroll_node_id, &info.id, @@ -627,7 +557,7 @@ impl FrameContext { ); } SpecificDisplayItem::ScrollFrame(ref info) => { - let complex_clips = context.get_complex_clips(pipeline_id, item.complex_clip().0); + let complex_clips = self.get_complex_clips(pipeline_id, item.complex_clip().0); let mut clip_region = ClipRegion::create_for_clip_node( *item.local_clip().clip_rect(), complex_clips, @@ -644,7 +574,6 @@ impl FrameContext { .translate(&reference_frame_relative_offset); let content_rect = item.rect().translate(&reference_frame_relative_offset); self.flatten_scroll_frame( - context, pipeline_id, &clip_and_scroll.scroll_node_id, &info.id, @@ -673,102 +602,78 @@ impl FrameContext { SpecificDisplayItem::PushShadow(shadow) => { let mut prim_info = prim_info.clone(); prim_info.rect = LayerRect::zero(); - context - .builder + self.builder .push_shadow(shadow, clip_and_scroll, &prim_info); } SpecificDisplayItem::PopAllShadows => { - context.builder.pop_all_shadows(); + self.builder.pop_all_shadows(); } } None } - fn flatten_root<'a>( + /// Try to optimize the rendering of a solid rectangle that is clipped by a single + /// rounded rectangle, by only masking the parts of the rectangle that intersect + /// the rounded parts of the clip. This is pretty simple now, so has a lot of + /// potential for further optimizations. + fn try_to_add_rectangle_splitting_on_clip( &mut self, - traversal: &mut BuiltDisplayListIter<'a>, - pipeline_id: PipelineId, - context: &mut FlattenContext, - content_size: &LayoutSize, - ) { - context.builder.push_stacking_context( - &LayerVector2D::zero(), - pipeline_id, - CompositeOps::default(), - TransformStyle::Flat, - true, - true, - ); - - // We do this here, rather than above because we want any of the top-level - // stacking contexts in the display list to be treated like root stacking contexts. - // FIXME(mrobinson): Currently only the first one will, which for the moment is - // sufficient for all our use cases. - context.builder.notify_waiting_for_root_stacking_context(); + info: &LayerPrimitiveInfo, + color: &ColorF, + clip_and_scroll: &ClipAndScrollInfo, + ) -> bool { + // If this rectangle is not opaque, splitting the rectangle up + // into an inner opaque region just ends up hurting batching and + // doing more work than necessary. + if color.a != 1.0 { + return false; + } - // For the root pipeline, there's no need to add a full screen rectangle - // here, as it's handled by the framebuffer clear. - let clip_id = ClipId::root_scroll_node(pipeline_id); - if context.scene.root_pipeline_id != Some(pipeline_id) { - if let Some(pipeline) = context.scene.pipelines.get(&pipeline_id) { - if let Some(bg_color) = pipeline.background_color { - let root_bounds = LayerRect::new(LayerPoint::zero(), *content_size); - let info = LayerPrimitiveInfo::new(root_bounds); - context.builder.add_solid_rectangle( - ClipAndScrollInfo::simple(clip_id), - &info, - &bg_color, - PrimitiveFlags::None, - ); + let inner_unclipped_rect = match &info.local_clip { + &LocalClip::Rect(_) => return false, + &LocalClip::RoundedRect(_, ref region) => { + if region.mode == ClipMode::ClipOut { + return false; } + region.get_inner_rect_full() } - } - + }; + let inner_unclipped_rect = match inner_unclipped_rect { + Some(rect) => rect, + None => return false, + }; - self.flatten_items(traversal, pipeline_id, context, LayerVector2D::zero()); + // The inner rectangle is not clipped by its assigned clipping node, so we can + // let it be clipped by the parent of the clipping node, which may result in + // less masking some cases. + let mut clipped_rects = Vec::new(); + subtract_rect(&info.rect, &inner_unclipped_rect, &mut clipped_rects); + + let prim_info = LayerPrimitiveInfo { + rect: inner_unclipped_rect, + local_clip: LocalClip::from(*info.local_clip.clip_rect()), + is_backface_visible: info.is_backface_visible, + tag: None, + }; - if self.frame_builder_config.enable_scrollbars { - let scrollbar_rect = LayerRect::new(LayerPoint::zero(), LayerSize::new(10.0, 70.0)); - let info = LayerPrimitiveInfo::new(scrollbar_rect); + self.builder.add_solid_rectangle( + *clip_and_scroll, + &prim_info, + color, + PrimitiveFlags::None, + ); - context.builder.add_solid_rectangle( - ClipAndScrollInfo::simple(clip_id), + for clipped_rect in &clipped_rects { + let mut info = info.clone(); + info.rect = *clipped_rect; + self.builder.add_solid_rectangle( + *clip_and_scroll, &info, - &DEFAULT_SCROLLBAR_COLOR, - PrimitiveFlags::Scrollbar(self.clip_scroll_tree.topmost_scrolling_node_id(), 4.0), + color, + PrimitiveFlags::None, ); } - - context.builder.pop_stacking_context(); - } - - fn flatten_items<'a>( - &mut self, - traversal: &mut BuiltDisplayListIter<'a>, - pipeline_id: PipelineId, - context: &mut FlattenContext, - reference_frame_relative_offset: LayerVector2D, - ) { - loop { - let subtraversal = { - let item = match traversal.next() { - Some(item) => item, - None => break, - }; - - if SpecificDisplayItem::PopStackingContext == *item.item() { - return; - } - - self.flatten_item(item, pipeline_id, context, reference_frame_relative_offset) - }; - - // If flatten_item created a sub-traversal, we need `traversal` to have the - // same state as the completed subtraversal, so we reinitialize it here. - if let Some(subtraversal) = subtraversal { - *traversal = subtraversal; - } - } + true } /// Decomposes an image display item that is repeated into an image per individual repetition. @@ -785,7 +690,6 @@ impl FrameContext { fn decompose_image( &mut self, clip_and_scroll: ClipAndScrollInfo, - builder: &mut FrameBuilder, prim_info: &LayerPrimitiveInfo, info: &ImageDisplayItem, image_size: DeviceUintSize, @@ -797,7 +701,6 @@ impl FrameContext { if no_vertical_tiling && no_vertical_spacing { self.decompose_image_row( clip_and_scroll, - builder, prim_info, info, image_size, @@ -821,7 +724,6 @@ impl FrameContext { prim_info.rect = row_rect; self.decompose_image_row( clip_and_scroll, - builder, &prim_info, info, image_size, @@ -834,7 +736,6 @@ impl FrameContext { fn decompose_image_row( &mut self, clip_and_scroll: ClipAndScrollInfo, - builder: &mut FrameBuilder, prim_info: &LayerPrimitiveInfo, info: &ImageDisplayItem, image_size: DeviceUintSize, @@ -845,7 +746,6 @@ impl FrameContext { if no_horizontal_tiling && no_horizontal_spacing { self.decompose_tiled_image( clip_and_scroll, - builder, prim_info, info, image_size, @@ -870,7 +770,6 @@ impl FrameContext { prim_info.rect = decomposed_rect; self.decompose_tiled_image( clip_and_scroll, - builder, &prim_info, info, image_size, @@ -883,7 +782,6 @@ impl FrameContext { fn decompose_tiled_image( &mut self, clip_and_scroll: ClipAndScrollInfo, - builder: &mut FrameBuilder, prim_info: &LayerPrimitiveInfo, info: &ImageDisplayItem, image_size: DeviceUintSize, @@ -958,7 +856,6 @@ impl FrameContext { for tx in 0 .. num_tiles_x { self.add_tile_primitive( clip_and_scroll, - builder, prim_info, info, TileOffset::new(tx, ty), @@ -973,7 +870,6 @@ impl FrameContext { // Tiles on the right edge that are smaller than the tile size. self.add_tile_primitive( clip_and_scroll, - builder, prim_info, info, TileOffset::new(num_tiles_x, ty), @@ -991,7 +887,6 @@ impl FrameContext { // Tiles on the bottom edge that are smaller than the tile size. self.add_tile_primitive( clip_and_scroll, - builder, prim_info, info, TileOffset::new(tx, num_tiles_y), @@ -1007,7 +902,6 @@ impl FrameContext { // Finally, the bottom-right tile with a "leftover" size. self.add_tile_primitive( clip_and_scroll, - builder, prim_info, info, TileOffset::new(num_tiles_x, num_tiles_y), @@ -1024,7 +918,6 @@ impl FrameContext { fn add_tile_primitive( &mut self, clip_and_scroll: ClipAndScrollInfo, - builder: &mut FrameBuilder, prim_info: &LayerPrimitiveInfo, info: &ImageDisplayItem, tile_offset: TileOffset, @@ -1071,7 +964,7 @@ impl FrameContext { if let Some(prim_rect) = prim_rect.intersection(&prim_info.rect) { let mut prim_info = prim_info.clone(); prim_info.rect = prim_rect; - builder.add_image( + self.builder.add_image( clip_and_scroll, &prim_info, &stretched_size, @@ -1083,6 +976,148 @@ impl FrameContext { ); } } +} + +/// Frame context contains the information required to update +/// (e.g. scroll) a renderer frame builder (`FrameBuilder`). +pub struct FrameContext { + clip_scroll_tree: ClipScrollTree, + pipeline_epoch_map: FastHashMap, + id: FrameId, + frame_builder_config: FrameBuilderConfig, +} + +impl FrameContext { + pub fn new(config: FrameBuilderConfig) -> Self { + FrameContext { + pipeline_epoch_map: FastHashMap::default(), + clip_scroll_tree: ClipScrollTree::new(), + id: FrameId(0), + frame_builder_config: config, + } + } + + pub fn reset(&mut self) -> ScrollStates { + self.pipeline_epoch_map.clear(); + + // Advance to the next frame. + self.id.0 += 1; + + self.clip_scroll_tree.drain() + } + + pub fn get_clip_scroll_tree(&self) -> &ClipScrollTree { + &self.clip_scroll_tree + } + + pub fn get_scroll_node_state(&self) -> Vec { + self.clip_scroll_tree.get_scroll_node_state() + } + + /// Returns true if the node actually changed position or false otherwise. + pub fn scroll_node(&mut self, origin: LayerPoint, id: ClipId, clamp: ScrollClamping) -> bool { + self.clip_scroll_tree.scroll_node(origin, id, clamp) + } + + /// Returns true if any nodes actually changed position or false otherwise. + pub fn scroll( + &mut self, + scroll_location: ScrollLocation, + cursor: WorldPoint, + phase: ScrollEventPhase, + ) -> bool { + self.clip_scroll_tree.scroll(scroll_location, cursor, phase) + } + + pub fn tick_scrolling_bounce_animations(&mut self) { + self.clip_scroll_tree.tick_scrolling_bounce_animations(); + } + + pub fn discard_frame_state_for_pipeline(&mut self, pipeline_id: PipelineId) { + self.clip_scroll_tree + .discard_frame_state_for_pipeline(pipeline_id); + } + + pub fn create( + &mut self, + old_builder: Option, + scene: &Scene, + resource_cache: &mut ResourceCache, + window_size: DeviceUintSize, + inner_rect: DeviceUintRect, + device_pixel_ratio: f32, + ) -> Option { + let root_pipeline_id = match scene.root_pipeline_id { + Some(root_pipeline_id) => root_pipeline_id, + None => return old_builder, + }; + + let root_pipeline = match scene.pipelines.get(&root_pipeline_id) { + Some(root_pipeline) => root_pipeline, + None => return old_builder, + }; + + if window_size.width == 0 || window_size.height == 0 { + error!("ERROR: Invalid window dimensions! Please call api.set_window_size()"); + } + + let old_scrolling_states = self.reset(); + + self.pipeline_epoch_map + .insert(root_pipeline_id, root_pipeline.epoch); + + let background_color = root_pipeline + .background_color + .and_then(|color| if color.a > 0.0 { Some(color) } else { None }); + + let frame_builder = { + let mut roller = FlattenContext { + scene, + builder: FrameBuilder::new( + old_builder, + window_size, + background_color, + self.frame_builder_config, + ), + clip_scroll_tree: &mut self.clip_scroll_tree, + font_instances: resource_cache.get_font_instances(), + tiled_image_map: resource_cache.get_tiled_image_map(), + pipeline_epochs: Vec::new(), + replacements: Vec::new(), + }; + + roller.builder.push_root( + root_pipeline_id, + &root_pipeline.viewport_size, + &root_pipeline.content_size, + roller.clip_scroll_tree, + ); + + roller.builder.setup_viewport_offset( + window_size, + inner_rect, + device_pixel_ratio, + roller.clip_scroll_tree, + ); + + roller.flatten_root( + &mut root_pipeline.display_list.iter(), + root_pipeline_id, + &root_pipeline.content_size, + ); + + self.pipeline_epoch_map.extend(roller.pipeline_epochs.drain(..)); + roller.builder + }; + + self.clip_scroll_tree + .finalize_and_apply_pending_scroll_offsets(old_scrolling_states); + Some(frame_builder) + } + + pub fn update_epoch(&mut self, pipeline_id: PipelineId, epoch: Epoch) { + self.pipeline_epoch_map.insert(pipeline_id, epoch); + } fn get_renderer_frame_impl(&self, frame: Option) -> RendererFrame { let nodes_bouncing_back = self.clip_scroll_tree.collect_nodes_bouncing_back(); @@ -1121,67 +1156,3 @@ impl FrameContext { self.get_renderer_frame_impl(None) } } - -/// Try to optimize the rendering of a solid rectangle that is clipped by a single -/// rounded rectangle, by only masking the parts of the rectangle that intersect -/// the rounded parts of the clip. This is pretty simple now, so has a lot of -/// potential for further optimizations. -fn try_to_add_rectangle_splitting_on_clip( - context: &mut FlattenContext, - info: &LayerPrimitiveInfo, - color: &ColorF, - clip_and_scroll: &ClipAndScrollInfo, -) -> bool { - // If this rectangle is not opaque, splitting the rectangle up - // into an inner opaque region just ends up hurting batching and - // doing more work than necessary. - if color.a != 1.0 { - return false; - } - - let inner_unclipped_rect = match &info.local_clip { - &LocalClip::Rect(_) => return false, - &LocalClip::RoundedRect(_, ref region) => { - if region.mode == ClipMode::ClipOut { - return false; - } - region.get_inner_rect_full() - } - }; - let inner_unclipped_rect = match inner_unclipped_rect { - Some(rect) => rect, - None => return false, - }; - - // The inner rectangle is not clipped by its assigned clipping node, so we can - // let it be clipped by the parent of the clipping node, which may result in - // less masking some cases. - let mut clipped_rects = Vec::new(); - subtract_rect(&info.rect, &inner_unclipped_rect, &mut clipped_rects); - - let prim_info = LayerPrimitiveInfo { - rect: inner_unclipped_rect, - local_clip: LocalClip::from(*info.local_clip.clip_rect()), - is_backface_visible: info.is_backface_visible, - tag: None, - }; - - context.builder.add_solid_rectangle( - *clip_and_scroll, - &prim_info, - color, - PrimitiveFlags::None, - ); - - for clipped_rect in &clipped_rects { - let mut info = info.clone(); - info.rect = *clipped_rect; - context.builder.add_solid_rectangle( - *clip_and_scroll, - &info, - color, - PrimitiveFlags::None, - ); - } - true -} diff --git a/webrender/src/frame_builder.rs b/webrender/src/frame_builder.rs index 77ce956acb..9107946a2a 100644 --- a/webrender/src/frame_builder.rs +++ b/webrender/src/frame_builder.rs @@ -109,7 +109,7 @@ pub struct FrameBuilder { pub clip_store: ClipStore, cmds: Vec, hit_testing_runs: Vec, - config: FrameBuilderConfig, + pub config: FrameBuilderConfig, stacking_context_store: Vec, clip_scroll_group_store: Vec, diff --git a/webrender/src/resource_cache.rs b/webrender/src/resource_cache.rs index bd13611ca5..5beddacd7f 100644 --- a/webrender/src/resource_cache.rs +++ b/webrender/src/resource_cache.rs @@ -73,7 +73,7 @@ struct ImageResource { dirty_rect: Option, } -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct ImageTiling { pub image_size: DeviceUintSize, pub tile_size: TileSize,