diff --git a/webrender/src/batch.rs b/webrender/src/batch.rs index 4798781a52..4cfe8aa061 100644 --- a/webrender/src/batch.rs +++ b/webrender/src/batch.rs @@ -16,9 +16,9 @@ use gpu_types::{PrimitiveHeader, PrimitiveHeaderIndex, TransformPaletteId, Trans use internal_types::{FastHashMap, SavedTargetIndex, TextureSource}; use picture::{Picture3DContext, PictureCompositeMode, PicturePrimitive, PictureSurface}; use prim_store::{BrushKind, BrushPrimitive, DeferredResolve, PrimitiveTemplateKind, PrimitiveDataStore}; -use prim_store::{EdgeAaSegmentMask, ImageSource, PrimitiveInstanceKind, PrimitiveStore}; +use prim_store::{EdgeAaSegmentMask, ImageSource, PrimitiveInstanceKind}; use prim_store::{VisibleGradientTile, PrimitiveInstance, PrimitiveOpacity, SegmentInstanceIndex}; -use prim_store::{BrushSegment, ClipMaskKind, ClipTaskIndex, PrimitiveDetails, Primitive}; +use prim_store::{BrushSegment, ClipMaskKind, ClipTaskIndex, PrimitiveDetails}; use render_task::{RenderTaskAddress, RenderTaskId, RenderTaskTree}; use renderer::{BlendMode, ImageBufferKind, ShaderColorMode}; use renderer::BLOCKS_PER_UV_RECT; @@ -907,6 +907,7 @@ impl AlphaBatchBuilder { PrimitiveInstanceKind::ImageBorder { .. } | PrimitiveInstanceKind::Rectangle { .. } | PrimitiveInstanceKind::YuvImage { .. } | + PrimitiveInstanceKind::Image { .. } | PrimitiveInstanceKind::Clear => { unreachable!(); } @@ -1409,14 +1410,13 @@ impl AlphaBatchBuilder { 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(), BrushKind::RadialGradient { ref visible_tiles, .. } => !visible_tiles.is_empty(), } } }; - let specified_blend_mode = prim_instance.get_blend_mode(&prim.details); + let specified_blend_mode = BlendMode::PremultipliedAlpha; match prim.details { PrimitiveDetails::Brush(ref brush) => { @@ -1450,41 +1450,6 @@ impl AlphaBatchBuilder { } match brush.kind { - BrushKind::Image { alpha_type, request, opacity_binding_index, ref visible_tiles, .. } if !visible_tiles.is_empty() => { - let opacity_binding = ctx.prim_store.get_opacity_binding(opacity_binding_index); - - for tile in visible_tiles { - if let Some((batch_kind, textures, user_data, uv_rect_address)) = get_image_tile_params( - ctx.resource_cache, - gpu_cache, - deferred_resolves, - request.with_tile(tile.tile_offset), - alpha_type, - get_shader_opacity(opacity_binding), - ) { - let prim_cache_address = gpu_cache.get_address(&tile.handle); - let prim_header = PrimitiveHeader { - specific_prim_address: prim_cache_address, - local_rect: tile.local_rect, - local_clip_rect: tile.local_clip_rect, - ..prim_header - }; - let prim_header_index = prim_headers.push(&prim_header, z_id, user_data); - - self.add_image_tile_to_batch( - batch_kind, - specified_blend_mode, - textures, - prim_header_index, - clip_task_address, - bounding_rect, - tile.edge_flags, - uv_rect_address, - z_id, - ); - } - } - } BrushKind::LinearGradient { ref stops_handle, ref visible_tiles, .. } if !visible_tiles.is_empty() => { add_gradient_tiles( visible_tiles, @@ -1517,11 +1482,7 @@ impl AlphaBatchBuilder { } _ => { if let Some(params) = brush.get_batch_params( - ctx.resource_cache, gpu_cache, - deferred_resolves, - prim_instance, - ctx.prim_store, ) { let prim_header_index = prim_headers.push(&prim_header, z_id, params.prim_user_data); if prim_instance.is_chased() { @@ -1793,6 +1754,146 @@ impl AlphaBatchBuilder { ctx, ); } + ( + PrimitiveInstanceKind::Image { image_instance_index, .. }, + PrimitiveTemplateKind::Image { source, alpha_type, key, image_rendering, .. } + ) => { + let image_instance = &ctx.prim_store.images[*image_instance_index]; + let opacity_binding = ctx.prim_store.get_opacity_binding(image_instance.opacity_binding_index); + let specified_blend_mode = match alpha_type { + AlphaType::PremultipliedAlpha => BlendMode::PremultipliedAlpha, + AlphaType::Alpha => BlendMode::Alpha, + }; + let request = ImageRequest { + key: *key, + rendering: *image_rendering, + tile: None, + }; + + if image_instance.visible_tiles.is_empty() { + let cache_item = match *source { + ImageSource::Default => { + resolve_image( + request, + ctx.resource_cache, + gpu_cache, + deferred_resolves, + ) + } + ImageSource::Cache { ref handle, .. } => { + let rt_handle = handle + .as_ref() + .expect("bug: render task handle not allocated"); + let rt_cache_entry = ctx.resource_cache + .get_cached_render_task(rt_handle); + ctx.resource_cache.get_texture_cache_item(&rt_cache_entry.handle) + } + }; + + if cache_item.texture_id == TextureSource::Invalid { + return; + } + + let textures = BatchTextures::color(cache_item.texture_id); + + let opacity = PrimitiveOpacity::from_alpha(opacity_binding); + let opacity = opacity.combine(prim_data.opacity); + + let non_segmented_blend_mode = if !opacity.is_opaque || + prim_instance.clip_task_index != ClipTaskIndex::INVALID || + transform_kind == TransformedRectKind::Complex + { + specified_blend_mode + } else { + BlendMode::None + }; + + let batch_params = BrushBatchParameters::shared( + BrushBatchKind::Image(get_buffer_kind(cache_item.texture_id)), + textures, + [ + ShaderColorMode::Image as i32 | ((*alpha_type as i32) << 16), + RasterizationSpace::Local as i32, + get_shader_opacity(opacity_binding), + ], + cache_item.uv_rect_handle.as_int(gpu_cache), + ); + + debug_assert!(image_instance.segment_instance_index != SegmentInstanceIndex::INVALID); + let (prim_cache_address, segments) = if image_instance.segment_instance_index == SegmentInstanceIndex::UNUSED { + (gpu_cache.get_address(&prim_data.gpu_cache_handle), None) + } else { + let segment_instance = &ctx.scratch.segment_instances[image_instance.segment_instance_index]; + let segments = Some(&ctx.scratch.segments[segment_instance.segments_range]); + (gpu_cache.get_address(&segment_instance.gpu_cache_handle), segments) + }; + + let prim_header = PrimitiveHeader { + local_rect: prim_data.prim_rect, + local_clip_rect: prim_instance.combined_local_clip_rect, + task_address, + specific_prim_address: prim_cache_address, + clip_task_address, + transform_id, + }; + + let prim_header_index = prim_headers.push( + &prim_header, + z_id, + batch_params.prim_user_data, + ); + + self.add_segmented_prim_to_batch( + segments, + opacity, + &batch_params, + specified_blend_mode, + non_segmented_blend_mode, + prim_header_index, + clip_task_address, + bounding_rect, + transform_kind, + render_tasks, + z_id, + prim_instance.clip_task_index, + ctx, + ); + } else { + for tile in &image_instance.visible_tiles { + if let Some((batch_kind, textures, user_data, uv_rect_address)) = get_image_tile_params( + ctx.resource_cache, + gpu_cache, + deferred_resolves, + request.with_tile(tile.tile_offset), + *alpha_type, + get_shader_opacity(opacity_binding), + ) { + let prim_cache_address = gpu_cache.get_address(&tile.handle); + let prim_header = PrimitiveHeader { + specific_prim_address: prim_cache_address, + local_rect: tile.local_rect, + local_clip_rect: tile.local_clip_rect, + task_address, + clip_task_address, + transform_id, + }; + let prim_header_index = prim_headers.push(&prim_header, z_id, user_data); + + self.add_image_tile_to_batch( + batch_kind, + specified_blend_mode, + textures, + prim_header_index, + clip_task_address, + bounding_rect, + tile.edge_flags, + uv_rect_address, + z_id, + ); + } + } + } + } _ => { unreachable!(); } @@ -2127,54 +2228,9 @@ impl BrushBatchParameters { impl BrushPrimitive { fn get_batch_params( &self, - resource_cache: &ResourceCache, gpu_cache: &mut GpuCache, - deferred_resolves: &mut Vec, - prim_instance: &PrimitiveInstance, - prim_store: &PrimitiveStore, ) -> Option { match self.kind { - BrushKind::Image { alpha_type, request, ref source, opacity_binding_index, .. } => { - let cache_item = match *source { - ImageSource::Default => { - resolve_image( - request, - resource_cache, - gpu_cache, - deferred_resolves, - ) - } - ImageSource::Cache { ref handle, .. } => { - let rt_handle = handle - .as_ref() - .expect("bug: render task handle not allocated"); - let rt_cache_entry = resource_cache - .get_cached_render_task(rt_handle); - resource_cache.get_texture_cache_item(&rt_cache_entry.handle) - } - }; - if prim_instance.is_chased() { - println!("\tsource {:?}", cache_item); - } - - if cache_item.texture_id == TextureSource::Invalid { - None - } else { - let textures = BatchTextures::color(cache_item.texture_id); - let opacity_binding = prim_store.get_opacity_binding(opacity_binding_index); - - Some(BrushBatchParameters::shared( - BrushBatchKind::Image(get_buffer_kind(cache_item.texture_id)), - textures, - [ - ShaderColorMode::Image as i32 | ((alpha_type as i32) << 16), - RasterizationSpace::Local as i32, - get_shader_opacity(opacity_binding), - ], - cache_item.uv_rect_handle.as_int(gpu_cache), - )) - } - } BrushKind::RadialGradient { ref stops_handle, .. } => { Some(BrushBatchParameters::shared( BrushBatchKind::RadialGradient, @@ -2204,55 +2260,26 @@ impl BrushPrimitive { } impl PrimitiveInstance { - fn get_blend_mode( - &self, - details: &PrimitiveDetails, - ) -> BlendMode { - match *details { - PrimitiveDetails::Brush(ref brush) => { - match brush.kind { - BrushKind::Image { alpha_type, .. } => { - match alpha_type { - AlphaType::PremultipliedAlpha => BlendMode::PremultipliedAlpha, - AlphaType::Alpha => BlendMode::Alpha, - } - } - BrushKind::RadialGradient { .. } | - BrushKind::LinearGradient { .. } => { - BlendMode::PremultipliedAlpha - } - } - } - } - } - pub fn is_cacheable( &self, - primitives: &[Primitive], prim_data_store: &PrimitiveDataStore, resource_cache: &ResourceCache, ) -> bool { let image_key = match self.kind { - PrimitiveInstanceKind::LegacyPrimitive { prim_index, .. } => { - let prim = &primitives[prim_index.0]; - match prim.details { - PrimitiveDetails::Brush(BrushPrimitive { kind: BrushKind::Image{ request, .. }, .. }) => { - request.key - } - PrimitiveDetails::Brush(_) => { - return true - } - } - } + PrimitiveInstanceKind::Image { .. } | PrimitiveInstanceKind::YuvImage { .. } => { let prim_data = &prim_data_store[self.prim_data_handle]; match prim_data.kind { PrimitiveTemplateKind::YuvImage { ref yuv_key, .. } => { yuv_key[0] } + PrimitiveTemplateKind::Image { key, .. } => { + key + } _ => unreachable!(), } } + PrimitiveInstanceKind::LegacyPrimitive { .. } | PrimitiveInstanceKind::Picture { .. } | PrimitiveInstanceKind::TextRun { .. } | PrimitiveInstanceKind::LineDecoration { .. } | diff --git a/webrender/src/display_list_flattener.rs b/webrender/src/display_list_flattener.rs index 35f2b14a3f..0d53d6f799 100644 --- a/webrender/src/display_list_flattener.rs +++ b/webrender/src/display_list_flattener.rs @@ -24,9 +24,9 @@ use image::simplify_repeated_primitive; use internal_types::{FastHashMap, FastHashSet}; use picture::{Picture3DContext, PictureCompositeMode, PicturePrimitive, PrimitiveList}; use prim_store::{BrushKind, BrushPrimitive, PrimitiveInstance, PrimitiveDataInterner, PrimitiveKeyKind}; -use prim_store::{ImageSource, PrimitiveOpacity, PrimitiveKey, PrimitiveSceneData, PrimitiveInstanceKind}; +use prim_store::{PrimitiveOpacity, PrimitiveKey, PrimitiveSceneData, PrimitiveInstanceKind}; use prim_store::{PrimitiveContainer, PrimitiveDataHandle, PrimitiveStore, PrimitiveStoreStats, BrushSegmentDescriptor}; -use prim_store::{ScrollNodeAndClipChain, PictureIndex, register_prim_chase_id, OpacityBindingIndex}; +use prim_store::{ScrollNodeAndClipChain, PictureIndex, register_prim_chase_id}; use render_backend::{DocumentView}; use resource_cache::{FontInstanceMap, ImageRequest}; use scene::{Scene, ScenePipeline, StackingContextHelpers}; @@ -2050,30 +2050,19 @@ impl<'a> DisplayListFlattener<'a> { ) }); - let prim = BrushPrimitive::new( - BrushKind::Image { - request: ImageRequest { - key: image_key, - rendering: image_rendering, - tile: None, - }, - alpha_type, - stretch_size, - tile_spacing, - color, - source: ImageSource::Default, - sub_rect, - visible_tiles: Vec::new(), - opacity_binding_index: OpacityBindingIndex::INVALID, - }, - None, - ); - self.add_primitive( clip_and_scroll, &info, Vec::new(), - PrimitiveContainer::Brush(prim), + PrimitiveContainer::Image { + key: image_key, + tile_spacing, + stretch_size, + color, + sub_rect, + image_rendering, + alpha_type, + }, ); } diff --git a/webrender/src/picture.rs b/webrender/src/picture.rs index c1581a61f4..0f1a053b6e 100644 --- a/webrender/src/picture.rs +++ b/webrender/src/picture.rs @@ -20,7 +20,7 @@ use internal_types::FastHashSet; use plane_split::{Clipper, Polygon, Splitter}; use prim_store::{PictureIndex, PrimitiveInstance, SpaceMapper, VisibleFace, PrimitiveInstanceKind}; use prim_store::{get_raster_rects, PrimitiveDataInterner, PrimitiveDataStore, CoordinateSpaceMapping}; -use prim_store::{PrimitiveDetails, BrushKind, Primitive, OpacityBindingStorage, PrimitiveTemplateKind}; +use prim_store::{OpacityBindingStorage, PrimitiveTemplateKind, ImageInstanceStorage}; use render_task::{ClearMode, RenderTask, RenderTaskCacheEntryHandle, TileBlit}; use render_task::{RenderTaskCacheKey, RenderTaskCacheKeyKind, RenderTaskId, RenderTaskLocation}; use resource_cache::ResourceCache; @@ -345,11 +345,11 @@ impl TileCache { clip_scroll_tree: &ClipScrollTree, prim_data_store: &PrimitiveDataStore, clip_chain_nodes: &[ClipChainNode], - primitives: &[Primitive], pictures: &[PicturePrimitive], resource_cache: &ResourceCache, scene_properties: &SceneProperties, opacity_binding_store: &OpacityBindingStorage, + image_instances: &ImageInstanceStorage, ) { self.space_mapper.set_target_spatial_node( prim_instance.spatial_node_index, @@ -389,7 +389,6 @@ impl TileCache { // Some primitives can not be cached (e.g. external video images) let is_cacheable = prim_instance.is_cacheable( - primitives, prim_data_store, resource_cache, ); @@ -404,40 +403,33 @@ impl TileCache { } } } - PrimitiveInstanceKind::LegacyPrimitive { prim_index } => { - let prim = &primitives[prim_index.0]; - - match prim.details { - PrimitiveDetails::Brush(ref brush) => { - match brush.kind { - // Rectangles and images may depend on opacity bindings. - // TODO(gw): In future, we might be able to completely remove - // opacity collapsing support. It's of limited use - // once we have full picture caching. - BrushKind::Image { opacity_binding_index, ref request, .. } => { - let opacity_binding = &opacity_binding_store[opacity_binding_index]; - for binding in &opacity_binding.bindings { - if let PropertyBinding::Binding(key, default) = binding { - opacity_bindings.push((key.id, *default)); - } - } - - image_keys.push(request.key); - } - BrushKind::RadialGradient { .. } | - BrushKind::LinearGradient { .. } => { - } - } + PrimitiveInstanceKind::Rectangle { opacity_binding_index, .. } => { + let opacity_binding = &opacity_binding_store[opacity_binding_index]; + for binding in &opacity_binding.bindings { + if let PropertyBinding::Binding(key, default) = binding { + opacity_bindings.push((key.id, *default)); } } } - PrimitiveInstanceKind::Rectangle { opacity_binding_index, .. } => { + PrimitiveInstanceKind::Image { image_instance_index, .. } => { + let image_instance = &image_instances[image_instance_index]; + let opacity_binding_index = image_instance.opacity_binding_index; + let opacity_binding = &opacity_binding_store[opacity_binding_index]; for binding in &opacity_binding.bindings { if let PropertyBinding::Binding(key, default) = binding { opacity_bindings.push((key.id, *default)); } } + + match prim_data.kind { + PrimitiveTemplateKind::Image { key, .. } => { + image_keys.push(key); + } + _ => { + unreachable!(); + } + } } PrimitiveInstanceKind::YuvImage { .. } => { match prim_data.kind { @@ -449,6 +441,7 @@ impl TileCache { } } } + PrimitiveInstanceKind::LegacyPrimitive { .. } | PrimitiveInstanceKind::TextRun { .. } | PrimitiveInstanceKind::LineDecoration { .. } | PrimitiveInstanceKind::Clear | @@ -1583,10 +1576,10 @@ impl PicturePrimitive { frame_context: &FrameBuildingContext, resource_cache: &mut ResourceCache, prim_data_store: &PrimitiveDataStore, - primitives: &[Primitive], pictures: &[PicturePrimitive], clip_store: &ClipStore, opacity_binding_store: &OpacityBindingStorage, + image_instances: &ImageInstanceStorage, ) { if state.tile_cache_update_count == 0 { return; @@ -1600,11 +1593,11 @@ impl PicturePrimitive { &frame_context.clip_scroll_tree, prim_data_store, &clip_store.clip_chain_nodes, - primitives, pictures, resource_cache, frame_context.scene_properties, opacity_binding_store, + image_instances, ); } } diff --git a/webrender/src/prim_store.rs b/webrender/src/prim_store.rs index 2201b8470d..233b515c26 100644 --- a/webrender/src/prim_store.rs +++ b/webrender/src/prim_store.rs @@ -394,6 +394,15 @@ pub enum PrimitiveKeyKind { color_space: YuvColorSpace, image_rendering: ImageRendering, }, + Image { + key: ImageKey, + stretch_size: SizeKey, + tile_spacing: SizeKey, + color: ColorU, + sub_rect: Option, + image_rendering: ImageRendering, + alpha_type: AlphaType, + }, } #[cfg_attr(feature = "capture", derive(Serialize))] @@ -437,6 +446,39 @@ impl From for RectangleKey { } } +/// A hashable size for using as a key during primitive interning. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] +#[derive(Debug, Clone, PartialEq)] +pub struct SizeKey { + w: f32, + h: f32, +} + +impl Eq for SizeKey {} + +impl hash::Hash for SizeKey { + fn hash(&self, state: &mut H) { + self.w.to_bits().hash(state); + self.h.to_bits().hash(state); + } +} + +impl From for LayoutSize { + fn from(key: SizeKey) -> LayoutSize { + LayoutSize::new(key.w, key.h) + } +} + +impl From for SizeKey { + fn from(size: LayoutSize) -> SizeKey { + SizeKey { + w: size.width, + h: size.height, + } + } +} + #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] #[derive(Debug, Clone, Eq, PartialEq, Hash)] @@ -508,6 +550,19 @@ impl PrimitiveKey { segment_instance_index: SegmentInstanceIndex::INVALID, } } + PrimitiveKeyKind::Image { .. } => { + // TODO(gw): Refactor this to not need a separate image + // instance (see ImageInstance struct). + let image_instance_index = prim_store.images.push(ImageInstance { + opacity_binding_index: OpacityBindingIndex::INVALID, + segment_instance_index: SegmentInstanceIndex::INVALID, + visible_tiles: Vec::new(), + }); + + PrimitiveInstanceKind::Image { + image_instance_index, + } + } PrimitiveKeyKind::Unused => { // Should never be hit as this method should not be // called for old style primitives. @@ -557,6 +612,16 @@ pub enum PrimitiveTemplateKind { color_space: YuvColorSpace, image_rendering: ImageRendering, }, + Image { + key: ImageKey, + stretch_size: LayoutSize, + tile_spacing: LayoutSize, + color: ColorF, + source: ImageSource, + image_rendering: ImageRendering, + sub_rect: Option, + alpha_type: AlphaType, + }, Clear, Unused, } @@ -658,6 +723,18 @@ impl PrimitiveKeyKind { image_rendering, } } + PrimitiveKeyKind::Image { alpha_type, key, color, stretch_size, tile_spacing, image_rendering, sub_rect, .. } => { + PrimitiveTemplateKind::Image { + key, + color: color.into(), + stretch_size: stretch_size.into(), + tile_spacing: tile_spacing.into(), + source: ImageSource::Default, + sub_rect, + image_rendering, + alpha_type, + } + } PrimitiveKeyKind::LineDecoration { cache_key, color } => { PrimitiveTemplateKind::LineDecoration { cache_key, @@ -800,6 +877,18 @@ impl PrimitiveTemplateKind { 0.0 ]); } + PrimitiveTemplateKind::Image { stretch_size, tile_spacing, color, .. } => { + // Images are drawn as a white color, modulated by the total + // opacity coming from any collapsed property bindings. + request.push(color.premultiplied()); + request.push(PremultipliedColorF::WHITE); + request.push([ + stretch_size.width + tile_spacing.width, + stretch_size.height + tile_spacing.height, + 0.0, + 0.0, + ]); + } PrimitiveTemplateKind::Unused => {} } } @@ -840,6 +929,7 @@ impl PrimitiveTemplateKind { [0.0; 4], ); } + PrimitiveTemplateKind::Image { .. } | PrimitiveTemplateKind::Rectangle { .. } | PrimitiveTemplateKind::TextRun { .. } | PrimitiveTemplateKind::YuvImage { .. } | @@ -855,6 +945,12 @@ impl PrimitiveTemplate { /// done if the cache entry is invalid (due to first use or eviction). pub fn update( &mut self, + // TODO(gw): Passing in surface_index here is not ideal. The primitive template + // code shouldn't depend on current surface state. This is due to a + // limitation in how render task caching works. We should fix this by + // allowing render task caching to assign to surfaces implicitly + // during pass allocation. + surface_index: SurfaceIndex, frame_state: &mut FrameBuildingState, ) { if let Some(mut request) = frame_state.gpu_cache.request(&mut self.gpu_cache_handle) { @@ -915,6 +1011,137 @@ impl PrimitiveTemplate { PrimitiveOpacity::translucent() } + PrimitiveTemplateKind::Image { key, stretch_size, ref color, tile_spacing, ref mut source, sub_rect, image_rendering, .. } => { + let image_properties = frame_state + .resource_cache + .get_image_properties(key); + + match image_properties { + Some(image_properties) => { + let is_tiled = image_properties.tiling.is_some(); + + 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, + }; + } + + // 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, + }; + } + + let mut request_source_image = false; + let mut is_opaque = image_properties.descriptor.is_opaque; + let request = ImageRequest { + key, + rendering: image_rendering, + tile: None, + }; + + // 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 inner_size = *size; + size.width += padding.horizontal(); + size.height += padding.vertical(); + + is_opaque &= padding == DeviceIntSideOffsets::zero(); + + let image_cache_key = ImageCacheKey { + request, + texel_rect: sub_rect, + }; + let surfaces = &mut frame_state.surfaces; + + // 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; + + // 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); + + // 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); + + // Hook this into the render task tree at the right spot. + surfaces[surface_index.0].tasks.push(target_to_cache_task_id); + + // 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; + } + } + + if request_source_image && !is_tiled { + frame_state.resource_cache.request_image( + request, + frame_state.gpu_cache, + ); + } + + if is_opaque { + PrimitiveOpacity::from_alpha(color.a) + } else { + PrimitiveOpacity::translucent() + } + } + None => { + PrimitiveOpacity::opaque() + } + } + } PrimitiveTemplateKind::Unused => { PrimitiveOpacity::translucent() } @@ -1009,17 +1236,6 @@ pub struct BorderSegmentInfo { } pub enum BrushKind { - Image { - request: ImageRequest, - alpha_type: AlphaType, - stretch_size: LayoutSize, - tile_spacing: LayoutSize, - color: ColorF, - source: ImageSource, - sub_rect: Option, - opacity_binding_index: OpacityBindingIndex, - visible_tiles: Vec, - }, RadialGradient { stops_handle: GpuCacheHandle, stops_range: ItemRange, @@ -1046,43 +1262,6 @@ pub enum BrushKind { }, } -impl BrushKind { - fn supports_segments(&self, resource_cache: &ResourceCache) -> bool { - match *self { - BrushKind::Image { ref request, .. } => { - // tiled images don't support segmentation - resource_cache - .get_image_properties(request.key) - .and_then(|properties| properties.tiling) - .is_none() - } - - BrushKind::RadialGradient { .. } | - BrushKind::LinearGradient { .. } => true, - } - } - - // Construct a brush that is an image wisth `stretch_size` dimensions and - // `color`. - pub fn new_image( - request: ImageRequest, - stretch_size: LayoutSize, - color: ColorF - ) -> BrushKind { - BrushKind::Image { - request, - alpha_type: AlphaType::PremultipliedAlpha, - stretch_size, - tile_spacing: LayoutSize::new(0., 0.), - color, - source: ImageSource::Default, - sub_rect: None, - opacity_binding_index: OpacityBindingIndex::INVALID, - visible_tiles: Vec::new(), - } - } -} - bitflags! { /// Each bit of the edge AA mask is: /// 0, when the edge of the primitive needs to be considered for AA @@ -1229,18 +1408,6 @@ impl BrushPrimitive { if let Some(mut request) = gpu_cache.request(&mut self.gpu_location) { // has to match VECS_PER_SPECIFIC_BRUSH match self.kind { - // Images are drawn as a white color, modulated by the total - // opacity coming from any collapsed property bindings. - BrushKind::Image { stretch_size, tile_spacing, color, .. } => { - request.push(color.premultiplied()); - request.push(PremultipliedColorF::WHITE); - request.push([ - stretch_size.width + tile_spacing.width, - stretch_size.height + tile_spacing.height, - 0.0, - 0.0, - ]); - } BrushKind::LinearGradient { stretch_size, start_point, end_point, extend_mode, .. } => { request.push([ start_point.x, @@ -1313,6 +1480,8 @@ pub struct LineDecorationCacheKey { } // Where to find the texture data for an image primitive. +#[cfg_attr(feature = "capture", derive(Serialize))] +#[cfg_attr(feature = "replay", derive(Deserialize))] #[derive(Debug)] pub enum ImageSource { // A normal image - just reference the texture cache. @@ -1855,6 +2024,15 @@ pub enum PrimitiveContainer { color_space: YuvColorSpace, image_rendering: ImageRendering, }, + Image { + key: ImageKey, + color: ColorF, + tile_spacing: LayoutSize, + stretch_size: LayoutSize, + sub_rect: Option, + image_rendering: ImageRendering, + alpha_type: AlphaType, + }, } impl PrimitiveContainer { @@ -1872,7 +2050,6 @@ impl PrimitiveContainer { } PrimitiveContainer::Brush(ref brush) => { match brush.kind { - BrushKind::Image { .. } | BrushKind::RadialGradient { .. } | BrushKind::LinearGradient { .. } => { true @@ -1882,6 +2059,7 @@ impl PrimitiveContainer { PrimitiveContainer::NormalBorder { .. } | PrimitiveContainer::ImageBorder { .. } | PrimitiveContainer::YuvImage { .. } | + PrimitiveContainer::Image { .. } | PrimitiveContainer::Clear => { true } @@ -1919,6 +2097,19 @@ impl PrimitiveContainer { (key, None) } + PrimitiveContainer::Image { alpha_type, key, stretch_size, color, tile_spacing, image_rendering, sub_rect, .. } => { + let key = PrimitiveKeyKind::Image { + key, + tile_spacing: tile_spacing.into(), + stretch_size: stretch_size.into(), + color: color.into(), + sub_rect, + image_rendering, + alpha_type, + }; + + (key, None) + } PrimitiveContainer::YuvImage { color_depth, yuv_key, format, color_space, image_rendering, .. } => { let key = PrimitiveKeyKind::YuvImage { color_depth, @@ -2078,22 +2269,18 @@ impl PrimitiveContainer { widths, } } - PrimitiveContainer::Brush(ref brush) => { - match brush.kind { - BrushKind::Image { request, stretch_size, .. } => { - PrimitiveContainer::Brush(BrushPrimitive::new( - BrushKind::new_image(request.clone(), - stretch_size.clone(), - shadow.color), - None, - )) - } - BrushKind::RadialGradient { .. } | - BrushKind::LinearGradient { .. } => { - panic!("bug: other brush kinds not expected here yet"); - } + PrimitiveContainer::Image { alpha_type, image_rendering, tile_spacing, stretch_size, key, sub_rect, .. } => { + PrimitiveContainer::Image { + tile_spacing, + stretch_size, + key, + sub_rect, + image_rendering, + alpha_type, + color: shadow.color, } } + PrimitiveContainer::Brush(..) | PrimitiveContainer::ImageBorder { .. } | PrimitiveContainer::YuvImage { .. } | PrimitiveContainer::Clear => { @@ -2113,6 +2300,25 @@ pub struct Primitive { pub details: PrimitiveDetails, } +/// Instance specific fields for an image primitive. These are +/// currently stored in a separate array to avoid bloating the +/// size of PrimitiveInstance. In the future, we should be able +/// to remove this and store the information inline, by: +/// (a) Removing opacity collapse / binding support completely. +/// Once we have general picture caching, we don't need this. +/// (b) Change visible_tiles to use Storage in the primitive +/// scratch buffer. This will reduce the size of the +/// visible_tiles field here, and save memory allocation +/// when image tiling is used. I've left it as a Vec for +/// now to reduce the number of changes, and because image +/// tiling is very rare on real pages. +#[derive(Debug)] +pub struct ImageInstance { + pub opacity_binding_index: OpacityBindingIndex, + pub segment_instance_index: SegmentInstanceIndex, + pub visible_tiles: Vec, +} + #[derive(Clone, Copy, Debug, PartialEq)] #[cfg_attr(feature = "capture", derive(Serialize))] #[cfg_attr(feature = "replay", derive(Deserialize))] @@ -2158,6 +2364,9 @@ pub enum PrimitiveInstanceKind { YuvImage { segment_instance_index: SegmentInstanceIndex, }, + Image { + image_instance_index: ImageInstanceIndex, + }, /// Clear out a rect, used for special effects. Clear, } @@ -2257,6 +2466,8 @@ pub type SegmentStorage = storage::Storage; pub type SegmentsRange = storage::Range; pub type SegmentInstanceStorage = storage::Storage; pub type SegmentInstanceIndex = storage::Index; +pub type ImageInstanceStorage = storage::Storage; +pub type ImageInstanceIndex = storage::Index; /// Contains various vecs of data that is used only during frame building, /// where we want to recycle the memory each new display list, to avoid constantly @@ -2322,6 +2533,7 @@ pub struct PrimitiveStoreStats { picture_count: usize, text_run_count: usize, opacity_binding_count: usize, + image_count: usize, } impl PrimitiveStoreStats { @@ -2331,6 +2543,7 @@ impl PrimitiveStoreStats { picture_count: 0, text_run_count: 0, opacity_binding_count: 0, + image_count: 0, } } } @@ -2340,6 +2553,11 @@ pub struct PrimitiveStore { pub pictures: Vec, pub text_runs: TextRunStorage, + /// A list of image instances. These are stored separately as + /// storing them inline in the instance makes the structure bigger + /// for other types. + pub images: ImageInstanceStorage, + /// List of animated opacity bindings for a primitive. pub opacity_bindings: OpacityBindingStorage, } @@ -2350,6 +2568,7 @@ impl PrimitiveStore { primitives: Vec::with_capacity(stats.primitive_count), pictures: Vec::with_capacity(stats.picture_count), text_runs: TextRunStorage::new(stats.text_run_count), + images: ImageInstanceStorage::new(stats.image_count), opacity_bindings: OpacityBindingStorage::new(stats.opacity_binding_count), } } @@ -2359,6 +2578,7 @@ impl PrimitiveStore { primitive_count: self.primitives.len(), picture_count: self.pictures.len(), text_run_count: self.text_runs.len(), + image_count: self.images.len(), opacity_binding_count: self.opacity_bindings.len(), } } @@ -2404,10 +2624,10 @@ impl PrimitiveStore { frame_context, resource_cache, prim_data_store, - &self.primitives, &self.pictures, clip_store, &self.opacity_bindings, + &self.images, ); self.pictures[pic_index.0].post_update( @@ -2470,7 +2690,10 @@ impl PrimitiveStore { // handled by this optimization. In the future, we can easily extend // this to other primitives, such as text runs and gradients. match prim_instance.kind { - PrimitiveInstanceKind::Rectangle { .. } => { + // If we find a single rect or image, we can use that + // as the primitive to collapse the opacity into. + PrimitiveInstanceKind::Rectangle { .. } | + PrimitiveInstanceKind::Image { .. } => { return Some(pic_index); } PrimitiveInstanceKind::Clear | @@ -2478,7 +2701,10 @@ impl PrimitiveStore { PrimitiveInstanceKind::NormalBorder { .. } | PrimitiveInstanceKind::ImageBorder { .. } | PrimitiveInstanceKind::YuvImage { .. } | - PrimitiveInstanceKind::LineDecoration { .. } => {} + PrimitiveInstanceKind::LegacyPrimitive { .. } | + PrimitiveInstanceKind::LineDecoration { .. } => { + // These prims don't support opacity collapse + } PrimitiveInstanceKind::Picture { pic_index } => { let pic = &self.pictures[pic_index.0]; @@ -2489,22 +2715,6 @@ impl PrimitiveStore { return self.get_opacity_collapse_prim(pic_index); } } - PrimitiveInstanceKind::LegacyPrimitive { prim_index } => { - let prim = &self.primitives[prim_index.0]; - match prim.details { - PrimitiveDetails::Brush(ref brush) => { - match brush.kind { - // If we find a single rect or image, we can use that - // as the primitive to collapse the opacity into. - BrushKind::Image { .. } => { - return Some(pic_index) - } - BrushKind::LinearGradient { .. } | - BrushKind::RadialGradient { .. } => {} - } - } - } - } } None @@ -2535,35 +2745,25 @@ impl PrimitiveStore { let pic = &mut self.pictures[pic_index.0]; let prim_instance = &mut pic.prim_list.prim_instances[0]; match prim_instance.kind { + PrimitiveInstanceKind::Image { image_instance_index, .. } => { + let image_instance = &mut self.images[image_instance_index]; + // By this point, we know we should only have found a primitive + // that supports opacity collapse. + if image_instance.opacity_binding_index == OpacityBindingIndex::INVALID { + image_instance.opacity_binding_index = self.opacity_bindings.push(OpacityBinding::new()); + } + let opacity_binding = &mut self.opacity_bindings[image_instance.opacity_binding_index]; + opacity_binding.push(binding); + } PrimitiveInstanceKind::Rectangle { ref mut opacity_binding_index, .. } => { + // By this point, we know we should only have found a primitive + // that supports opacity collapse. if *opacity_binding_index == OpacityBindingIndex::INVALID { *opacity_binding_index = self.opacity_bindings.push(OpacityBinding::new()); } let opacity_binding = &mut self.opacity_bindings[*opacity_binding_index]; opacity_binding.push(binding); } - PrimitiveInstanceKind::LegacyPrimitive { 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::Image { ref mut opacity_binding_index, .. } => { - if *opacity_binding_index == OpacityBindingIndex::INVALID { - *opacity_binding_index = self.opacity_bindings.push(OpacityBinding::new()); - } - let opacity_binding = &mut self.opacity_bindings[*opacity_binding_index]; - opacity_binding.push(binding); - } - BrushKind::LinearGradient { .. } | - BrushKind::RadialGradient { .. } => { - unreachable!("bug: invalid prim type for opacity collapse"); - } - } - } - } - } _ => { unreachable!(); } @@ -2635,6 +2835,7 @@ impl PrimitiveStore { PrimitiveInstanceKind::NormalBorder { .. } | PrimitiveInstanceKind::ImageBorder { .. } | PrimitiveInstanceKind::YuvImage { .. } | + PrimitiveInstanceKind::Image { .. } | PrimitiveInstanceKind::Clear => { None } @@ -2688,6 +2889,7 @@ impl PrimitiveStore { PrimitiveInstanceKind::ImageBorder { .. } | PrimitiveInstanceKind::Rectangle { .. } | PrimitiveInstanceKind::YuvImage { .. } | + PrimitiveInstanceKind::Image { .. } | PrimitiveInstanceKind::LineDecoration { .. } => { let prim_data = &resources .prim_data_store[prim_instance.prim_data_handle]; @@ -2821,7 +3023,7 @@ impl PrimitiveStore { frame_context, frame_state, &clip_node_collector, - &mut self.primitives, + self, resources, scratch, ); @@ -2837,7 +3039,6 @@ impl PrimitiveStore { } pic_state.is_cacheable &= prim_instance.is_cacheable( - &self.primitives, &resources.prim_data_store, frame_state.resource_cache, ); @@ -2889,6 +3090,7 @@ impl PrimitiveStore { PrimitiveInstanceKind::NormalBorder { .. } | PrimitiveInstanceKind::ImageBorder { .. } | PrimitiveInstanceKind::YuvImage { .. } | + PrimitiveInstanceKind::Image { .. } | PrimitiveInstanceKind::LineDecoration { .. } => { self.prepare_interned_prim_for_render( prim_instance, @@ -2908,10 +3110,8 @@ impl PrimitiveStore { prim_details, prim_context, pic_context, - frame_context, frame_state, display_list, - &mut self.opacity_bindings, ); } } @@ -3034,6 +3234,7 @@ impl PrimitiveStore { // Update the template this instane references, which may refresh the GPU // cache with any shared template data. prim_data.update( + pic_context.surface_index, frame_state, ); @@ -3206,6 +3407,109 @@ impl PrimitiveStore { ) => { *segment_instance_index } + ( + PrimitiveInstanceKind::Image { image_instance_index, .. }, + PrimitiveTemplateKind::Image { key, stretch_size, tile_spacing, image_rendering, .. } + ) => { + let image_instance = &mut self.images[*image_instance_index]; + + update_opacity_binding( + &mut self.opacity_bindings, + image_instance.opacity_binding_index, + frame_context.scene_properties, + ); + + image_instance.visible_tiles.clear(); + + let image_properties = frame_state + .resource_cache + .get_image_properties(*key); + + if let Some(image_properties) = image_properties { + if let Some(tile_size) = image_properties.tiling { + let device_image_size = image_properties.descriptor.size; + + // 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 = prim_instance + .combined_local_clip_rect + .intersection(&prim_data.prim_rect).unwrap(); + + let visible_rect = compute_conservative_visible_rect( + prim_context, + &pic_context.dirty_world_rect, + &tight_clip_rect + ); + + let base_edge_flags = edge_flags_for_tile_spacing(tile_spacing); + + let stride = *stretch_size + *tile_spacing; + + let repetitions = image::repetitions( + &prim_data.prim_rect, + &visible_rect, + stride, + ); + + let request = ImageRequest { + key: *key, + rendering: *image_rendering, + tile: None, + }; + + for Repetition { origin, edge_flags } in repetitions { + let edge_flags = base_edge_flags | edge_flags; + + let image_rect = LayoutRect { + origin, + size: *stretch_size, + }; + + let tiles = image::tiles( + &image_rect, + &visible_rect, + &device_image_size, + tile_size as i32, + ); + + for tile in tiles { + 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(PremultipliedColorF::WHITE); + 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]); + } + + image_instance.visible_tiles.push(VisibleImageTile { + tile_offset: tile.offset, + handle, + edge_flags: tile.edge_flags & edge_flags, + local_rect: tile.rect, + local_clip_rect: tight_clip_rect, + }); + } + } + + if image_instance.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. + prim_instance.bounding_rect = None; + } + } + } + + image_instance.segment_instance_index + } _ => { unreachable!(); } @@ -3482,57 +3786,45 @@ impl PrimitiveInstance { prim_local_clip_rect: LayoutRect, prim_clip_chain: &ClipChainInstance, frame_state: &mut FrameBuildingState, - primitives: &mut [Primitive], + prim_store: &mut PrimitiveStore, resources: &FrameResources, scratch: &mut PrimitiveScratchBuffer, ) { - match self.kind { + let segment_instance_index = match self.kind { PrimitiveInstanceKind::Rectangle { ref mut segment_instance_index, .. } | PrimitiveInstanceKind::YuvImage { ref mut segment_instance_index, .. } => { - if *segment_instance_index == SegmentInstanceIndex::INVALID { - let mut segments: SmallVec<[BrushSegment; 8]> = SmallVec::new(); - - if write_brush_segment_description( - prim_local_rect, - prim_local_clip_rect, - prim_clip_chain, - &mut frame_state.segment_builder, - frame_state.clip_store, - resources, - ) { - frame_state.segment_builder.build(|segment| { - segments.push( - BrushSegment::new( - segment.rect, - segment.has_mask, - segment.edge_flags, - [0.0; 4], - BrushFlags::empty(), - ), - ); - }); + segment_instance_index + } + PrimitiveInstanceKind::Image { image_instance_index, .. } => { + let prim_data = &resources.prim_data_store[self.prim_data_handle]; + let image_instance = &mut prim_store.images[image_instance_index]; + match prim_data.kind { + PrimitiveTemplateKind::Image { key, .. } => { + // tiled images don't support segmentation + if frame_state + .resource_cache + .get_image_properties(key) + .and_then(|properties| properties.tiling) + .is_some() { + image_instance.segment_instance_index = SegmentInstanceIndex::UNUSED; + return; + } } - - if segments.is_empty() { - *segment_instance_index = SegmentInstanceIndex::UNUSED; - } else { - let segments_range = scratch - .segments - .extend(segments); - - let instance = SegmentedInstance { - segments_range, - gpu_cache_handle: GpuCacheHandle::new(), - }; - - *segment_instance_index = scratch - .segment_instances - .push(instance); - }; + _ => unreachable!(), } + &mut image_instance.segment_instance_index + } + PrimitiveInstanceKind::Picture { .. } | + PrimitiveInstanceKind::TextRun { .. } | + PrimitiveInstanceKind::NormalBorder { .. } | + PrimitiveInstanceKind::ImageBorder { .. } | + PrimitiveInstanceKind::Clear | + PrimitiveInstanceKind::LineDecoration { .. } => { + // These primitives don't support / need segments. + return; } PrimitiveInstanceKind::LegacyPrimitive { prim_index } => { - let prim = &mut primitives[prim_index.0]; + let prim = &mut prim_store.primitives[prim_index.0]; match prim.details { PrimitiveDetails::Brush(ref mut brush) => { match brush.segment_desc { @@ -3543,42 +3835,83 @@ impl PrimitiveInstance { 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) { - let mut segments = BrushSegmentVec::new(); - - if write_brush_segment_description( - prim_local_rect, - prim_local_clip_rect, - prim_clip_chain, - &mut frame_state.segment_builder, - frame_state.clip_store, - resources, - ) { - frame_state.segment_builder.build(|segment| { - segments.push( - BrushSegment::new( - segment.rect, - segment.has_mask, - segment.edge_flags, - [0.0; 4], - BrushFlags::empty(), - ), - ); - }); - } + let mut segments = BrushSegmentVec::new(); + + if write_brush_segment_description( + prim_local_rect, + prim_local_clip_rect, + prim_clip_chain, + &mut frame_state.segment_builder, + frame_state.clip_store, + resources, + ) { + frame_state.segment_builder.build(|segment| { + segments.push( + BrushSegment::new( + segment.rect, + segment.has_mask, + segment.edge_flags, + [0.0; 4], + BrushFlags::empty(), + ), + ); + }); + } - if !segments.is_empty() { - brush.segment_desc = Some(BrushSegmentDescriptor { - segments, - }); - } + if !segments.is_empty() { + brush.segment_desc = Some(BrushSegmentDescriptor { + segments, + }); } } } } } + + return; + } + }; + + if *segment_instance_index == SegmentInstanceIndex::INVALID { + let mut segments: SmallVec<[BrushSegment; 8]> = SmallVec::new(); + + if write_brush_segment_description( + prim_local_rect, + prim_local_clip_rect, + prim_clip_chain, + &mut frame_state.segment_builder, + frame_state.clip_store, + resources, + ) { + frame_state.segment_builder.build(|segment| { + segments.push( + BrushSegment::new( + segment.rect, + segment.has_mask, + segment.edge_flags, + [0.0; 4], + BrushFlags::empty(), + ), + ); + }); } - _ => {} + + if segments.is_empty() { + *segment_instance_index = SegmentInstanceIndex::UNUSED; + } else { + let segments_range = scratch + .segments + .extend(segments); + + let instance = SegmentedInstance { + segments_range, + gpu_cache_handle: GpuCacheHandle::new(), + }; + + *segment_instance_index = scratch + .segment_instances + .push(instance); + }; } } @@ -3594,7 +3927,7 @@ impl PrimitiveInstance { frame_context: &FrameBuildingContext, frame_state: &mut FrameBuildingState, clip_node_collector: &Option, - primitives: &[Primitive], + prim_store: &PrimitiveStore, resources: &mut FrameResources, scratch: &mut PrimitiveScratchBuffer, ) -> bool { @@ -3605,6 +3938,19 @@ impl PrimitiveInstance { PrimitiveInstanceKind::LineDecoration { .. } => { return false; } + PrimitiveInstanceKind::Image { image_instance_index, .. } => { + let segment_instance_index = prim_store + .images[image_instance_index] + .segment_instance_index; + + if segment_instance_index == SegmentInstanceIndex::UNUSED { + return false; + } + + let segment_instance = &scratch.segment_instances[segment_instance_index]; + + &scratch.segments[segment_instance.segments_range] + } PrimitiveInstanceKind::YuvImage { segment_instance_index, .. } | PrimitiveInstanceKind::Rectangle { segment_instance_index, .. } => { debug_assert!(segment_instance_index != SegmentInstanceIndex::INVALID); @@ -3615,7 +3961,7 @@ impl PrimitiveInstance { let segment_instance = &scratch.segment_instances[segment_instance_index]; - &mut scratch.segments[segment_instance.segments_range] + &scratch.segments[segment_instance.segments_range] } PrimitiveInstanceKind::ImageBorder { .. } => { let prim_data = &resources.prim_data_store[self.prim_data_handle]; @@ -3646,7 +3992,7 @@ impl PrimitiveInstance { } } PrimitiveInstanceKind::LegacyPrimitive { prim_index } => { - let prim = &primitives[prim_index.0]; + let prim = &prim_store.primitives[prim_index.0]; match prim.details { PrimitiveDetails::Brush(ref brush) => { match brush.segment_desc { @@ -3735,231 +4081,14 @@ impl PrimitiveInstance { prim_details: &mut PrimitiveDetails, prim_context: &PrimitiveContext, pic_context: &PictureContext, - frame_context: &FrameBuildingContext, frame_state: &mut FrameBuildingState, display_list: &BuiltDisplayList, - opacity_bindings: &mut OpacityBindingStorage, ) { let mut is_tiled = false; match *prim_details { PrimitiveDetails::Brush(ref mut brush) => { brush.opacity = match brush.kind { - BrushKind::Image { - request, - sub_rect, - stretch_size, - color, - ref mut tile_spacing, - ref mut source, - opacity_binding_index, - ref mut visible_tiles, - .. - } => { - let image_properties = frame_state - .resource_cache - .get_image_properties(request.key); - - - // 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(); - let current_opacity = update_opacity_binding( - opacity_bindings, - opacity_binding_index, - frame_context.scene_properties, - ); - - 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, - }; - } - - // 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, - }; - } - - let mut request_source_image = false; - let mut is_opaque = image_properties.descriptor.is_opaque; - - // 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 inner_size = *size; - size.width += padding.horizontal(); - size.height += padding.vertical(); - - is_opaque &= padding == DeviceIntSideOffsets::zero(); - - let image_cache_key = ImageCacheKey { - request, - texel_rect: sub_rect, - }; - let surfaces = &mut frame_state.surfaces; - - // 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; - - // 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); - - // 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); - - // Hook this into the render task tree at the right spot. - surfaces[pic_context.surface_index.0].tasks.push(target_to_cache_task_id); - - // 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; - } - } - - if let Some(tile_size) = image_properties.tiling { - let device_image_size = image_properties.descriptor.size; - - // 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 = self - .combined_local_clip_rect - .intersection(&prim_local_rect).unwrap(); - - let visible_rect = compute_conservative_visible_rect( - prim_context, - &pic_context.dirty_world_rect, - &tight_clip_rect - ); - - let base_edge_flags = edge_flags_for_tile_spacing(tile_spacing); - - let stride = stretch_size + *tile_spacing; - - visible_tiles.clear(); - - let repetitions = image::repetitions( - &prim_local_rect, - &visible_rect, - stride, - ); - - for Repetition { origin, edge_flags } in repetitions { - let edge_flags = base_edge_flags | edge_flags; - - let image_rect = LayoutRect { - origin, - size: stretch_size, - }; - - let tiles = image::tiles( - &image_rect, - &visible_rect, - &device_image_size, - tile_size as i32, - ); - - for tile in tiles { - 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(PremultipliedColorF::WHITE); - 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: tile.offset, - handle, - edge_flags: tile.edge_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. - self.bounding_rect = None; - } - } else if request_source_image { - frame_state.resource_cache.request_image( - request, - frame_state.gpu_cache, - ); - } - - if is_opaque { - PrimitiveOpacity::from_alpha(current_opacity * color.a) - } else { - PrimitiveOpacity::translucent() - } - } else { - PrimitiveOpacity::opaque() - } - } BrushKind::RadialGradient { stops_range, center, @@ -4111,7 +4240,7 @@ impl PrimitiveInstance { frame_context: &FrameBuildingContext, frame_state: &mut FrameBuildingState, clip_node_collector: &Option, - primitives: &mut [Primitive], + prim_store: &mut PrimitiveStore, resources: &mut FrameResources, scratch: &mut PrimitiveScratchBuffer, ) { @@ -4127,7 +4256,7 @@ impl PrimitiveInstance { prim_local_clip_rect, clip_chain, frame_state, - primitives, + prim_store, resources, scratch, ); @@ -4144,7 +4273,7 @@ impl PrimitiveInstance { frame_context, frame_state, clip_node_collector, - primitives, + prim_store, resources, scratch, ) { @@ -4286,12 +4415,12 @@ fn test_struct_sizes() { // test expectations and move on. // (b) You made a structure larger. This is not necessarily a problem, but should only // be done with care, and after checking if talos performance regresses badly. - assert_eq!(mem::size_of::(), 216, "PrimitiveContainer size changed"); + assert_eq!(mem::size_of::(), 200, "PrimitiveContainer size changed"); assert_eq!(mem::size_of::(), 120, "PrimitiveInstance size changed"); assert_eq!(mem::size_of::(), 16, "PrimitiveInstanceKind size changed"); assert_eq!(mem::size_of::(), 176, "PrimitiveTemplate size changed"); assert_eq!(mem::size_of::(), 112, "PrimitiveTemplateKind size changed"); assert_eq!(mem::size_of::(), 152, "PrimitiveKey size changed"); assert_eq!(mem::size_of::(), 112, "PrimitiveKeyKind size changed"); - assert_eq!(mem::size_of::(), 240, "Primitive size changed"); + assert_eq!(mem::size_of::(), 224, "Primitive size changed"); } diff --git a/webrender/src/surface.rs b/webrender/src/surface.rs index a858710524..6778850df0 100644 --- a/webrender/src/surface.rs +++ b/webrender/src/surface.rs @@ -240,6 +240,7 @@ impl SurfaceDescriptor { PrimitiveInstanceKind::LegacyPrimitive { .. } => { return None; } + PrimitiveInstanceKind::Image { .. } | PrimitiveInstanceKind::YuvImage { .. } | PrimitiveInstanceKind::LineDecoration { .. } | PrimitiveInstanceKind::TextRun { .. } |