diff --git a/gfx/wr/webrender/src/composite.rs b/gfx/wr/webrender/src/composite.rs index b51ab8b639eb..a65a38f457dd 100644 --- a/gfx/wr/webrender/src/composite.rs +++ b/gfx/wr/webrender/src/composite.rs @@ -100,6 +100,12 @@ pub struct ExternalSurfaceDescriptor { pub yuv_format: YuvFormat, pub yuv_rescale: f32, pub z_id: ZBufferId, + /// If native compositing is enabled, the native compositor surface handle. + /// Otherwise, this will be None + pub native_surface_id: Option, + /// If the native surface needs to be updated, this will contain the size + /// of the native surface as Some(size). If not dirty, this is None. + pub update_params: Option, } /// Information about a plane in a YUV surface. @@ -140,6 +146,9 @@ pub struct ResolvedExternalSurface { pub yuv_format: YuvFormat, pub yuv_rescale: f32, pub image_buffer_kind: ImageBufferKind, + + // Update information for a native surface if it's dirty + pub update_params: Option<(NativeSurfaceId, DeviceIntSize)>, } /// Public interface specified in `RendererOptions` that configures @@ -501,6 +510,16 @@ impl CompositeState { external_surface_index: ResolvedExternalSurfaceIndex(self.external_surfaces.len()), }; + // If the external surface descriptor reports that the native surface + // needs to be updated, create an update params tuple for the renderer + // to use. + let update_params = external_surface.update_params.map(|surface_size| { + ( + external_surface.native_surface_id.expect("bug: no native surface!"), + surface_size + ) + }); + self.external_surfaces.push(ResolvedExternalSurface { yuv_color_space: external_surface.yuv_color_space, yuv_format: external_surface.yuv_format, @@ -508,6 +527,7 @@ impl CompositeState { image_buffer_kind: get_buffer_kind(yuv_planes[0].texture), image_dependencies: external_surface.image_dependencies, yuv_planes, + update_params, }); let tile = CompositeTile { @@ -524,10 +544,7 @@ impl CompositeState { // a dependency on the compositor surface external image keys / generations. self.descriptor.surfaces.push( CompositeSurfaceDescriptor { - // TODO(gw): When we add native compositor surfaces, this be - // need to be set, as that's how native compositor - // surfaces are added to the visual tree. - surface_id: None, + surface_id: external_surface.native_surface_id, offset: tile.rect.origin, clip_rect: tile.clip_rect, image_dependencies: external_surface.image_dependencies, diff --git a/gfx/wr/webrender/src/frame_builder.rs b/gfx/wr/webrender/src/frame_builder.rs index 6f027572aaac..deb2c9a5c34e 100644 --- a/gfx/wr/webrender/src/frame_builder.rs +++ b/gfx/wr/webrender/src/frame_builder.rs @@ -406,6 +406,10 @@ impl FrameBuilder { visibility_state.resource_cache.destroy_compositor_surface(native_surface.opaque); visibility_state.resource_cache.destroy_compositor_surface(native_surface.alpha); } + + for (_, external_surface) in cache_state.external_native_surface_cache.drain() { + visibility_state.resource_cache.destroy_compositor_surface(external_surface.native_surface_id) + } } } diff --git a/gfx/wr/webrender/src/picture.rs b/gfx/wr/webrender/src/picture.rs index 18edf5d958ed..633115973586 100644 --- a/gfx/wr/webrender/src/picture.rs +++ b/gfx/wr/webrender/src/picture.rs @@ -273,6 +273,8 @@ pub struct PictureCacheState { allocations: PictureCacheRecycledAllocations, /// Currently allocated native compositor surface for this picture cache. pub native_surface: Option, + /// A cache of compositor surfaces that are retained between display lists + pub external_native_surface_cache: FastHashMap, } pub struct PictureCacheRecycledAllocations { @@ -2128,6 +2130,28 @@ pub struct NativeSurface { pub alpha: NativeSurfaceId, } +/// Hash key for an external native compositor surface +#[derive(PartialEq, Eq, Hash)] +pub struct ExternalNativeSurfaceKey { + /// The YUV image keys that are used to draw this surface. + pub image_keys: [ImageKey; 3], + /// The current device size of the surface. + pub size: DeviceIntSize, +} + +/// Information about a native compositor surface cached between frames. +pub struct ExternalNativeSurface { + /// If true, the surface was used this frame. Used for a simple form + /// of GC to remove old surfaces. + pub used_this_frame: bool, + /// The native compositor surface handle + pub native_surface_id: NativeSurfaceId, + /// List of image keys, and current image generations, that are drawn in this surface. + /// The image generations are used to check if the compositor surface is dirty and + /// needs to be updated. + pub image_dependencies: [ImageDependency; 3], +} + /// Represents a cache of tiles that make up a picture primitives. pub struct TileCacheInstance { /// Index of the tile cache / slice for this frame builder. It's determined @@ -2228,6 +2252,8 @@ pub struct TileCacheInstance { pub external_surfaces: Vec, /// z-buffer ID assigned to opaque tiles in this slice pub z_id_opaque: ZBufferId, + /// A cache of compositor surfaces that are retained between frames + pub external_native_surface_cache: FastHashMap, } impl TileCacheInstance { @@ -2282,6 +2308,7 @@ impl TileCacheInstance { tile_size_override: None, external_surfaces: Vec::new(), z_id_opaque: ZBufferId::invalid(), + external_native_surface_cache: FastHashMap::default(), } } @@ -2410,6 +2437,7 @@ impl TileCacheInstance { self.color_bindings = prev_state.color_bindings; self.current_tile_size = prev_state.current_tile_size; self.native_surface = prev_state.native_surface; + self.external_native_surface_cache = prev_state.external_native_surface_cache; fn recycle_map( ideal_len: usize, @@ -2442,6 +2470,15 @@ impl TileCacheInstance { ); } + // At the start of the frame, step through each current compositor surface + // and mark it as unused. Later, this is used to free old compositor surfaces. + // TODO(gw): In future, we might make this more sophisticated - for example, + // retaining them for >1 frame if unused, or retaining them in some + // kind of pool to reduce future allocations. + for external_native_surface in self.external_native_surface_cache.values_mut() { + external_native_surface.used_this_frame = false; + } + // Only evaluate what tile size to use fairly infrequently, so that we don't end // up constantly invalidating and reallocating tiles if the picture rect size is // changing near a threshold value. @@ -2684,6 +2721,10 @@ impl TileCacheInstance { frame_state.resource_cache.destroy_compositor_surface(native_surface.opaque); frame_state.resource_cache.destroy_compositor_surface(native_surface.alpha); } + + for (_, external_surface) in self.external_native_surface_cache.drain() { + frame_state.resource_cache.destroy_compositor_surface(external_surface.native_surface_id) + } } CompositorKind::Native { .. } => { // This could hit even when compositor mode is not changed, @@ -2712,7 +2753,7 @@ impl TileCacheInstance { data_stores: &DataStores, clip_store: &ClipStore, pictures: &[PicturePrimitive], - resource_cache: &ResourceCache, + resource_cache: &mut ResourceCache, opacity_binding_store: &OpacityBindingStorage, color_bindings: &ColorBindingStorage, image_instances: &ImageInstanceStorage, @@ -2909,10 +2950,9 @@ impl TileCacheInstance { // extract the logic below and support RGBA compositor surfaces too. let mut promote_to_surface = false; - // For initial implementation, only the simple (draw) compositor mode - // supports primitives as compositor surfaces. We can remove this restriction - // as a follow up, when we support this for native compositor modes. - if let CompositorKind::Draw { .. } = composite_state.compositor_kind { + + // If picture caching is disabled, we can't support any compositor surfaces. + if composite_state.picture_caching_is_enabled { // Check if this primitive _wants_ to be promoted to a compositor surface. if prim_data.common.flags.contains(PrimitiveFlags::PREFER_COMPOSITOR_SURFACE) { promote_to_surface = true; @@ -3001,6 +3041,64 @@ impl TileCacheInstance { } } + // When using native compositing, we need to find an existing native surface + // handle to use, or allocate a new one. For existing native surfaces, we can + // also determine whether this needs to be updated, depending on whether the + // image generation(s) of the YUV planes have changed since last composite. + let (native_surface_id, update_params) = match composite_state.compositor_kind { + CompositorKind::Draw { .. } => { + (None, None) + } + CompositorKind::Native { .. } => { + let native_surface_size = device_rect.size.round().to_i32(); + + let key = ExternalNativeSurfaceKey { + image_keys: prim_data.kind.yuv_key, + size: native_surface_size, + }; + + let native_surface = self.external_native_surface_cache + .entry(key) + .or_insert_with(|| { + // No existing surface, so allocate a new compositor surface and + // a single compositor tile that covers the entire compositor surface. + + let native_surface_id = resource_cache.create_compositor_surface( + native_surface_size, + true, + ); + + let tile_id = NativeTileId { + surface_id: native_surface_id, + x: 0, + y: 0, + }; + + resource_cache.create_compositor_tile(tile_id); + + ExternalNativeSurface { + used_this_frame: true, + native_surface_id, + image_dependencies: [ImageDependency::INVALID; 3], + } + }); + + // Mark that the surface is referenced this frame so that the + // backing native surface handle isn't freed. + native_surface.used_this_frame = true; + + // If the image dependencies match, there is no need to update + // the backing native surface. + let update_params = if image_dependencies == native_surface.image_dependencies { + None + } else { + Some(native_surface_size) + }; + + (Some(native_surface.native_surface_id), update_params) + } + }; + // Each compositor surface allocates a unique z-id self.external_surfaces.push(ExternalSurfaceDescriptor { local_rect: prim_info.prim_clip_rect, @@ -3013,6 +3111,8 @@ impl TileCacheInstance { yuv_format: prim_data.kind.format, yuv_rescale: prim_data.kind.color_depth.rescaling_factor(), z_id: composite_state.z_generator.next(), + native_surface_id, + update_params, }); } } else { @@ -3239,6 +3339,16 @@ impl TileCacheInstance { ); } + // A simple GC of the native external surface cache, to remove and free any + // surfaces that were not referenced during the update_prim_dependencies pass. + self.external_native_surface_cache.retain(|_, surface| { + if !surface.used_this_frame { + frame_state.resource_cache.destroy_compositor_surface(surface.native_surface_id); + } + + surface.used_this_frame + }); + // Detect if the picture cache was scrolled or scaled. In this case, // the device space dirty rects aren't applicable (until we properly // integrate with OS compositors that can handle scrolling slices). @@ -4174,6 +4284,7 @@ impl PicturePrimitive { root_transform: tile_cache.root_transform, current_tile_size: tile_cache.current_tile_size, native_surface: tile_cache.native_surface, + external_native_surface_cache: tile_cache.external_native_surface_cache, allocations: PictureCacheRecycledAllocations { old_opacity_bindings: tile_cache.old_opacity_bindings, old_color_bindings: tile_cache.old_color_bindings, diff --git a/gfx/wr/webrender/src/renderer.rs b/gfx/wr/webrender/src/renderer.rs index edae98bf751c..a7e1da350cfb 100644 --- a/gfx/wr/webrender/src/renderer.rs +++ b/gfx/wr/webrender/src/renderer.rs @@ -68,7 +68,7 @@ use crate::glyph_rasterizer::{GlyphFormat, GlyphRasterizer}; use crate::gpu_cache::{GpuBlockData, GpuCacheUpdate, GpuCacheUpdateList}; use crate::gpu_cache::{GpuCacheDebugChunk, GpuCacheDebugCmd}; use crate::gpu_types::{PrimitiveHeaderI, PrimitiveHeaderF, ScalingInstance, SvgFilterInstance, TransformData}; -use crate::gpu_types::{CompositeInstance, ResolveInstanceData}; +use crate::gpu_types::{CompositeInstance, ResolveInstanceData, ZBufferId}; use crate::internal_types::{TextureSource, ORTHO_FAR_PLANE, ORTHO_NEAR_PLANE, ResourceCacheError}; use crate::internal_types::{CacheTextureId, DebugOutput, FastHashMap, FastHashSet, LayerIndex, RenderedDocument, ResultMsg}; use crate::internal_types::{TextureCacheAllocationKind, TextureCacheUpdate, TextureUpdateList, TextureUpdateSource}; @@ -4251,6 +4251,127 @@ impl Renderer { } } + /// Rasterize any external compositor surfaces that require updating + fn update_external_native_surfaces( + &mut self, + external_surfaces: &[ResolvedExternalSurface], + results: &mut RenderResults, + ) { + if external_surfaces.is_empty() { + return; + } + + let opaque_sampler = self.gpu_profile.start_sampler(GPU_SAMPLER_TAG_OPAQUE); + + self.device.disable_depth(); + self.set_blend(false, FramebufferKind::Main); + + for surface in external_surfaces { + // See if this surface needs to be updated + let (native_surface_id, surface_size) = match surface.update_params { + Some(params) => params, + None => continue, + }; + + // When updating an external surface, the entire surface rect is used + // for all of the draw, dirty, valid and clip rect parameters. + let surface_rect = surface_size.into(); + + // Bind the native compositor surface to update + let surface_info = self.compositor_config + .compositor() + .unwrap() + .bind( + NativeTileId { + surface_id: native_surface_id, + x: 0, + y: 0, + }, + surface_rect, + surface_rect, + ); + + // Bind the native surface to current FBO target + let draw_target = DrawTarget::NativeSurface { + offset: surface_info.origin, + external_fbo_id: surface_info.fbo_id, + dimensions: surface_size, + }; + self.device.bind_draw_target(draw_target); + + let projection = Transform3D::ortho( + 0.0, + surface_size.width as f32, + 0.0, + surface_size.height as f32, + ORTHO_NEAR_PLANE, + ORTHO_FAR_PLANE, + ); + + // Bind an appropriate YUV shader for the texture format kind + self.shaders + .borrow_mut() + .get_composite_shader( + CompositeSurfaceFormat::Yuv, + surface.image_buffer_kind, + ).bind( + &mut self.device, + &projection, + &mut self.renderer_errors + ); + + let textures = BatchTextures { + colors: [ + surface.yuv_planes[0].texture, + surface.yuv_planes[1].texture, + surface.yuv_planes[2].texture, + ], + }; + + // When the texture is an external texture, the UV rect is not known when + // the external surface descriptor is created, because external textures + // are not resolved until the lock() callback is invoked at the start of + // the frame render. To handle this, query the texture resolver for the + // UV rect if it's an external texture, otherwise use the default UV rect. + let uv_rects = [ + self.texture_resolver.get_uv_rect(&textures.colors[0], surface.yuv_planes[0].uv_rect), + self.texture_resolver.get_uv_rect(&textures.colors[1], surface.yuv_planes[1].uv_rect), + self.texture_resolver.get_uv_rect(&textures.colors[2], surface.yuv_planes[2].uv_rect), + ]; + + let instance = CompositeInstance::new_yuv( + surface_rect.to_f32(), + surface_rect.to_f32(), + // z-id is not relevant when updating a native compositor surface. + // TODO(gw): Support compositor surfaces without z-buffer, for memory / perf win here. + ZBufferId(0), + surface.yuv_color_space, + surface.yuv_format, + surface.yuv_rescale, + [ + surface.yuv_planes[0].texture_layer as f32, + surface.yuv_planes[1].texture_layer as f32, + surface.yuv_planes[2].texture_layer as f32, + ], + uv_rects, + ); + + self.draw_instanced_batch( + &[instance], + VertexArrayKind::Composite, + &textures, + &mut results.stats, + ); + + self.compositor_config + .compositor() + .unwrap() + .unbind(); + } + + self.gpu_profile.finish_sampler(opaque_sampler); + } + /// Draw a list of tiles to the framebuffer fn draw_tile_list<'a, I: Iterator>( &mut self, @@ -5448,6 +5569,10 @@ impl Renderer { // to specify how to composite each of the picture cache surfaces. match self.current_compositor_kind { CompositorKind::Native { .. } => { + self.update_external_native_surfaces( + &frame.composite_state.external_surfaces, + results, + ); let compositor = self.compositor_config.compositor().unwrap(); frame.composite_state.composite_native(&mut **compositor); }