/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ use api::units::*; use api::{ColorF, ImageBufferKind, ImageRendering, PremultipliedColorF}; use crate::batch::BatchTextures; use crate::composite::{ CompositeState, CompositeSurfaceFormat, CompositeTileSurface, CompositorConfig, CompositorInputLayer, CompositorSurfaceUsage, CompositorSurfaceTransform, CompositeRoundedCorner, NativeTileId, ResolvedExternalSurface, ResolvedExternalSurfaceColorData, ClipRadius, CompositeFeatures, CompositorKind, TileKind, }; use crate::frame_builder::Frame; use crate::{CompositorInputConfig, PictureCacheDebugInfo, debug_colors}; use api::{ClipMode, DebugFlags}; use std::collections::HashSet; use std::mem; use crate::debug_item::DebugItem; use crate::segment::EdgeAaSegmentMask; use crate::device::DrawTarget; use crate::gpu_types::{CompositeInstance, ZBufferId}; use crate::internal_types::{FastHashMap, TextureSource}; use crate::picture::ResolvedSurfaceTexture; use super::RenderResults; use crate::profiler::{self}; use crate::rectangle_occlusion as occlusion; use crate::renderer::{ GPU_SAMPLER_TAG_OPAQUE, GPU_SAMPLER_TAG_TRANSPARENT, GPU_TAG_COMPOSITE, PartialPresentMode, }; use crate::renderer::{FramebufferKind, Renderer, RendererStats, VertexArrayKind}; use crate::segment::SegmentBuilder; use crate::tile_cache::TileId; use euclid::{Scale, Transform3D, default}; #[derive(Debug, Copy, Clone)] pub(super) struct OcclusionItemKey { pub tile_index: usize, pub needs_mask: bool, } pub(super) struct SwapChainLayer { pub occlusion: occlusion::FrontToBackBuilder, } pub(super) struct CompositeTileState { pub local_rect: PictureRect, pub local_valid_rect: PictureRect, pub device_clip_rect: DeviceRect, pub z_id: ZBufferId, pub device_tile_box: DeviceRect, pub visible_rects: Vec, } impl CompositeTileState { pub fn same_state(&self, other: &CompositeTileState) -> bool { self.local_rect == other.local_rect && self.local_valid_rect == other.local_valid_rect && self.device_clip_rect == other.device_clip_rect && self.z_id == other.z_id && self.device_tile_box == other.device_tile_box } } pub(super) struct LayerCompositorFrameState { pub tile_states: FastHashMap, pub rects_without_id: Vec, } 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_profiler.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( &mut self.device, 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, self.device.ortho_near_plane(), self.device.ortho_far_plane(), ); let ( textures, instance ) = match surface.color_data { ResolvedExternalSurfaceColorData::Yuv{ ref planes, color_space, format, channel_bit_depth, .. } => { let textures = BatchTextures::composite_yuv( planes[0].texture, planes[1].texture, 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.input.colors[0], planes[0].uv_rect), self.texture_resolver.get_uv_rect(&textures.input.colors[1], planes[1].uv_rect), self.texture_resolver.get_uv_rect(&textures.input.colors[2], 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. color_space, format, channel_bit_depth, uv_rects, (false, false), None, ); // Bind an appropriate YUV shader for the texture format kind self.shaders .borrow_mut() .get_composite_shader( CompositeSurfaceFormat::Yuv, surface.image_buffer_kind, instance.get_yuv_features(), ).bind( &mut self.device, &projection, None, &mut self.renderer_errors, &mut self.profile, &mut self.command_log, ); ( textures, instance ) }, ResolvedExternalSurfaceColorData::Rgb{ ref plane, .. } => { let textures = BatchTextures::composite_rgb(plane.texture); let uv_rect = self.texture_resolver.get_uv_rect(&textures.input.colors[0], plane.uv_rect); let instance = CompositeInstance::new_rgb( surface_rect.to_f32(), surface_rect.to_f32(), PremultipliedColorF::WHITE, uv_rect, plane.texture.uses_normalized_uvs(), (false, false), None, ); let features = instance.get_rgb_features(); self.shaders .borrow_mut() .get_composite_shader( CompositeSurfaceFormat::Rgba, surface.image_buffer_kind, features, ).bind( &mut self.device, &projection, None, &mut self.renderer_errors, &mut self.profile, &mut self.command_log, ); ( textures, instance ) }, }; self.draw_instanced_batch( &[instance], VertexArrayKind::Composite, &textures, &mut results.stats, ); self.compositor_config .compositor() .unwrap() .unbind(&mut self.device); } self.gpu_profiler.finish_sampler(opaque_sampler); } /// Draw a list of tiles to the framebuffer fn draw_tile_list<'a, I: Iterator>>( &mut self, tiles_iter: I, composite_state: &CompositeState, external_surfaces: &[ResolvedExternalSurface], projection: &default::Transform3D, stats: &mut RendererStats, ) { let mut current_shader_params = ( CompositeSurfaceFormat::Rgba, ImageBufferKind::Texture2D, CompositeFeatures::empty(), None, ); let mut current_textures = BatchTextures::empty(); let mut instances = Vec::new(); for item in tiles_iter { let tile = &composite_state.tiles[item.key.tile_index]; let clip_rect = item.rectangle; let tile_rect = composite_state.get_device_rect(&tile.local_rect, tile.transform_index); let transform = composite_state.get_device_transform(tile.transform_index); let flip = (transform.scale.x < 0.0, transform.scale.y < 0.0); let clip = if item.key.needs_mask { tile.clip_index.map(|index| { composite_state.get_compositor_clip(index) }) } else { None }; // Work out the draw params based on the tile surface let (instance, textures, shader_params) = match tile.surface { CompositeTileSurface::Color { color } => { let dummy = TextureSource::Dummy; let image_buffer_kind = dummy.image_buffer_kind(); let instance = CompositeInstance::new( tile_rect, clip_rect, color.premultiplied(), flip, clip, ); let features = instance.get_rgb_features(); ( instance, BatchTextures::composite_rgb(dummy), (CompositeSurfaceFormat::Rgba, image_buffer_kind, features, None), ) } CompositeTileSurface::Texture { surface: ResolvedSurfaceTexture::TextureCache { texture } } => { let instance = CompositeInstance::new( tile_rect, clip_rect, PremultipliedColorF::WHITE, flip, clip, ); let features = instance.get_rgb_features(); ( instance, BatchTextures::composite_rgb(texture), ( CompositeSurfaceFormat::Rgba, ImageBufferKind::Texture2D, features, None, ), ) } CompositeTileSurface::ExternalSurface { external_surface_index } => { let surface = &external_surfaces[external_surface_index.0]; match surface.color_data { ResolvedExternalSurfaceColorData::Yuv{ ref planes, color_space, format, channel_bit_depth, .. } => { let textures = BatchTextures::composite_yuv( planes[0].texture, planes[1].texture, 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.input.colors[0], planes[0].uv_rect), self.texture_resolver.get_uv_rect(&textures.input.colors[1], planes[1].uv_rect), self.texture_resolver.get_uv_rect(&textures.input.colors[2], planes[2].uv_rect), ]; let instance = CompositeInstance::new_yuv( tile_rect, clip_rect, color_space, format, channel_bit_depth, uv_rects, flip, clip, ); let features = instance.get_yuv_features(); ( instance, textures, ( CompositeSurfaceFormat::Yuv, surface.image_buffer_kind, features, None ), ) }, ResolvedExternalSurfaceColorData::Rgb { ref plane, .. } => { let uv_rect = self.texture_resolver.get_uv_rect(&plane.texture, plane.uv_rect); let instance = CompositeInstance::new_rgb( tile_rect, clip_rect, PremultipliedColorF::WHITE, uv_rect, plane.texture.uses_normalized_uvs(), flip, clip, ); let features = instance.get_rgb_features(); ( instance, BatchTextures::composite_rgb(plane.texture), ( CompositeSurfaceFormat::Rgba, surface.image_buffer_kind, features, Some(self.texture_resolver.get_texture_size(&plane.texture).to_f32()), ), ) }, } } CompositeTileSurface::Texture { surface: ResolvedSurfaceTexture::Native { .. } } => { unreachable!("bug: found native surface in simple composite path"); } }; // Flush batch if shader params or textures changed let flush_batch = !current_textures.is_compatible_with(&textures) || shader_params != current_shader_params; if flush_batch && !instances.is_empty() { self.shaders .borrow_mut() .get_composite_shader( current_shader_params.0, current_shader_params.1, current_shader_params.2, ).bind( &mut self.device, projection, current_shader_params.3, &mut self.renderer_errors, &mut self.profile, &mut self.command_log, ); self.draw_instanced_batch( &instances, VertexArrayKind::Composite, ¤t_textures, stats, ); instances.clear(); } current_shader_params = shader_params; current_textures = textures; // Add instance to current batch instances.push(instance); } // Flush the last batch if !instances.is_empty() { self.shaders .borrow_mut() .get_composite_shader( current_shader_params.0, current_shader_params.1, current_shader_params.2, ).bind( &mut self.device, projection, current_shader_params.3, &mut self.renderer_errors, &mut self.profile, &mut self.command_log, ); self.draw_instanced_batch( &instances, VertexArrayKind::Composite, ¤t_textures, stats, ); } } // Composite tiles in a swapchain. When using LayerCompositor, we may // split the compositing in to multiple swapchains. fn composite_pass( &mut self, composite_state: &CompositeState, draw_target: DrawTarget, clear_color: ColorF, projection: &default::Transform3D, results: &mut RenderResults, partial_present_mode: Option, layer: &SwapChainLayer, ) { self.device.bind_draw_target(draw_target); self.device.disable_depth_write(); self.device.disable_depth(); // If using KHR_partial_update, call eglSetDamageRegion. // This must be called exactly once per frame, and prior to any rendering to the main // framebuffer. Additionally, on Mali-G77 we encountered rendering issues when calling // this earlier in the frame, during offscreen render passes. So call it now, immediately // before rendering to the main framebuffer. See bug 1685276 for details. if let Some(partial_present) = self.compositor_config.partial_present() { if let Some(PartialPresentMode::Single { dirty_rect }) = partial_present_mode { partial_present.set_buffer_damage_region(&[dirty_rect.to_i32()]); } } // Clear the framebuffer let clear_color = Some(clear_color.to_array()); match partial_present_mode { Some(PartialPresentMode::Single { dirty_rect }) => { // There is no need to clear if the dirty rect is occluded. Additionally, // on Mali-G77 we have observed artefacts when calling glClear (even with // the empty scissor rect set) after calling eglSetDamageRegion with an // empty damage region. So avoid clearing in that case. See bug 1709548. if !dirty_rect.is_empty() && layer.occlusion.test(&dirty_rect) { // We have a single dirty rect, so clear only that self.device.clear_target(clear_color, None, Some(draw_target.to_framebuffer_rect(dirty_rect.to_i32()))); } } None => { // Partial present is disabled, so clear the entire framebuffer self.device.clear_target(clear_color, None, None); } } // Draw opaque tiles let opaque_items = layer.occlusion.opaque_items(); if !opaque_items.is_empty() { let opaque_sampler = self.gpu_profiler.start_sampler(GPU_SAMPLER_TAG_OPAQUE); self.set_blend(false, FramebufferKind::Main); self.draw_tile_list( opaque_items.iter(), &composite_state, &composite_state.external_surfaces, projection, &mut results.stats, ); self.gpu_profiler.finish_sampler(opaque_sampler); } // Draw alpha tiles let alpha_items = layer.occlusion.alpha_items(); if !alpha_items.is_empty() { let transparent_sampler = self.gpu_profiler.start_sampler(GPU_SAMPLER_TAG_TRANSPARENT); self.set_blend(true, FramebufferKind::Main); self.set_blend_mode_premultiplied_alpha(FramebufferKind::Main); self.draw_tile_list( alpha_items.iter().rev(), &composite_state, &composite_state.external_surfaces, projection, &mut results.stats, ); self.gpu_profiler.finish_sampler(transparent_sampler); } } /// Composite picture cache tiles into the framebuffer. This is currently /// the only way that picture cache tiles get drawn. In future, the tiles /// will often be handed to the OS compositor, and this method will be /// rarely used. fn composite_simple( &mut self, composite_state: &CompositeState, frame_device_size: DeviceIntSize, fb_draw_target: DrawTarget, projection: &default::Transform3D, results: &mut RenderResults, partial_present_mode: Option, device_size: DeviceIntSize, ) { let _gm = self.gpu_profiler.start_marker("framebuffer"); let _timer = self.gpu_profiler.start_timer(GPU_TAG_COMPOSITE); let num_tiles = composite_state.tiles.len(); self.profile.set(profiler::PICTURE_TILES, num_tiles); let (window_is_opaque, enable_screenshot) = match self.compositor_config.layer_compositor() { Some(ref compositor) => { let props = compositor.get_window_properties(); (props.is_opaque, props.enable_screenshot) } None => (true, true) }; let mut input_layers: Vec = Vec::new(); let mut swapchain_layers = Vec::new(); let cap = composite_state.tiles.len(); let mut segment_builder = SegmentBuilder::new(); let mut tile_index_to_layer_index = vec![None; composite_state.tiles.len()]; let mut full_render_occlusion = occlusion::FrontToBackBuilder::with_capacity(cap, cap); let mut layer_compositor_frame_state = LayerCompositorFrameState{ tile_states: FastHashMap::default(), rects_without_id: Vec::new(), }; // Calculate layers with full device rect // Add a debug overlay request if enabled if self.debug_overlay_state.is_enabled { self.debug_overlay_state.layer_index = input_layers.len(); input_layers.push(CompositorInputLayer { usage: CompositorSurfaceUsage::DebugOverlay, is_opaque: false, offset: DeviceIntPoint::zero(), clip_rect: device_size.into(), rounded_clip_rect: device_size.into(), rounded_clip_radii: ClipRadius::EMPTY, }); swapchain_layers.push(SwapChainLayer { occlusion: occlusion::FrontToBackBuilder::with_capacity(cap, cap), }); } // NOTE: Tiles here are being iterated in front-to-back order by // z-id, due to the sort in composite_state.end_frame() for (idx, tile) in composite_state.tiles.iter().enumerate() { let device_tile_box = composite_state.get_device_rect( &tile.local_rect, tile.transform_index ); if let Some(ref _compositor) = self.compositor_config.layer_compositor() { match tile.tile_id { Some(tile_id) => { layer_compositor_frame_state. tile_states .insert( tile_id, CompositeTileState { local_rect: tile.local_rect, local_valid_rect: tile.local_valid_rect, device_clip_rect: tile.device_clip_rect, z_id: tile.z_id, device_tile_box: device_tile_box, visible_rects: Vec::new(), }, ); } None => {} } } // Simple compositor needs the valid rect in device space to match clip rect let device_valid_rect = composite_state .get_device_rect(&tile.local_valid_rect, tile.transform_index); let rect = device_tile_box .intersection_unchecked(&tile.device_clip_rect) .intersection_unchecked(&device_valid_rect); if rect.is_empty() { continue; } // Determine if the tile is an external surface or content let usage = match tile.surface { CompositeTileSurface::Texture { .. } | CompositeTileSurface::Color { .. } => { CompositorSurfaceUsage::Content } CompositeTileSurface::ExternalSurface { external_surface_index } => { match (self.current_compositor_kind, enable_screenshot) { (CompositorKind::Native { .. }, _) | (CompositorKind::Draw { .. }, _) => { CompositorSurfaceUsage::Content } (CompositorKind::Layer { .. }, true) => { CompositorSurfaceUsage::Content } (CompositorKind::Layer { .. }, false) => { let surface = &composite_state.external_surfaces[external_surface_index.0]; // TODO(gwc): For now, we only select a hardware overlay swapchain if we // have an external image, but it may make sense to do for compositor // surfaces without in future. match surface.external_image_id { Some(external_image_id) => { let image_key = match surface.color_data { ResolvedExternalSurfaceColorData::Rgb { image_dependency, .. } => image_dependency.key, ResolvedExternalSurfaceColorData::Yuv { image_dependencies, .. } => image_dependencies[0].key, }; CompositorSurfaceUsage::External { image_key, external_image_id, transform_index: tile.transform_index, } } None => { CompositorSurfaceUsage::Content } } } } } }; if let Some(ref _compositor) = self.compositor_config.layer_compositor() { if let CompositeTileSurface::ExternalSurface { .. } = tile.surface { assert!(tile.tile_id.is_none()); // ExternalSurface is not promoted to external composite. if let CompositorSurfaceUsage::Content = usage { layer_compositor_frame_state.rects_without_id.push(rect); } } else { assert!(tile.tile_id.is_some()); } } // Determine whether we need a new layer, and if so, what kind let new_layer_kind = match input_layers.last() { Some(curr_layer) => { match (curr_layer.usage, usage) { // Content -> content, composite in to same layer (CompositorSurfaceUsage::Content, CompositorSurfaceUsage::Content) => None, (CompositorSurfaceUsage::External { .. }, CompositorSurfaceUsage::Content) => Some(usage), // Switch of layer type, or video -> video, need new swapchain (CompositorSurfaceUsage::Content, CompositorSurfaceUsage::External { .. }) | (CompositorSurfaceUsage::External { .. }, CompositorSurfaceUsage::External { .. }) => { // Only create a new layer if we're using LayerCompositor match self.compositor_config { CompositorConfig::Draw { .. } | CompositorConfig::Native { .. } => None, CompositorConfig::Layer { .. } => { Some(usage) } } } (CompositorSurfaceUsage::DebugOverlay, _) => { Some(usage) } // Should not encounter debug layers as new layer (_, CompositorSurfaceUsage::DebugOverlay) => { unreachable!(); } } } None => { // No layers yet, so we need a new one Some(usage) } }; if let Some(new_layer_kind) = new_layer_kind { let (offset, clip_rect, is_opaque, rounded_clip_rect, rounded_clip_radii) = match usage { CompositorSurfaceUsage::Content => { ( DeviceIntPoint::zero(), device_size.into(), false, // Assume not opaque, we'll calculate this later device_size.into(), ClipRadius::EMPTY, ) } CompositorSurfaceUsage::External { .. } => { let rect = composite_state.get_device_rect( &tile.local_rect, tile.transform_index ); let clip_rect = tile.device_clip_rect.to_i32(); let is_opaque = tile.kind != TileKind::Alpha; if self.debug_flags.contains(DebugFlags::EXTERNAL_COMPOSITE_BORDERS) { self.external_composite_debug_items.push(DebugItem::Rect { outer_color: debug_colors::ORANGERED, inner_color: ColorF { r: 0.0, g: 0.0, b: 0.0, a: 0.0 }, rect: tile.device_clip_rect, thickness: 10, }); } let (rounded_clip_rect, rounded_clip_radii) = match tile.clip_index { Some(clip_index) => { let clip = composite_state.get_compositor_clip(clip_index); let radius = ClipRadius { top_left: clip.radius.top_left.width.round() as i32, top_right: clip.radius.top_right.width.round() as i32, bottom_left: clip.radius.bottom_left.width.round() as i32, bottom_right: clip.radius.bottom_right.width.round() as i32, }; (clip.rect.to_i32(), radius) } None => { (clip_rect, ClipRadius::EMPTY) } }; ( rect.min.to_i32(), clip_rect, is_opaque, rounded_clip_rect, rounded_clip_radii, ) } CompositorSurfaceUsage::DebugOverlay => unreachable!(), }; input_layers.push(CompositorInputLayer { usage: new_layer_kind, is_opaque, offset, clip_rect, rounded_clip_rect, rounded_clip_radii, }); swapchain_layers.push(SwapChainLayer { occlusion: occlusion::FrontToBackBuilder::with_capacity(cap, cap), }) } tile_index_to_layer_index[idx] = Some(input_layers.len() - 1); // Caluclate actual visible tile's rects let is_opaque = tile.kind == TileKind::Opaque; match tile.clip_index { Some(clip_index) => { let clip = composite_state.get_compositor_clip(clip_index); // TODO(gw): Make segment builder generic on unit to avoid casts below. segment_builder.initialize( rect.cast_unit(), None, rect.cast_unit(), ); segment_builder.push_clip_rect( clip.rect.cast_unit(), Some(clip.radius), ClipMode::Clip, ); segment_builder.build(|segment| { let key = OcclusionItemKey { tile_index: idx, needs_mask: segment.has_mask }; full_render_occlusion.add( &segment.rect.cast_unit(), is_opaque && !segment.has_mask, key, ); }); } None => { full_render_occlusion.add(&rect, is_opaque, OcclusionItemKey { tile_index: idx, needs_mask: false, }); } } } assert_eq!(swapchain_layers.len(), input_layers.len()); if window_is_opaque { match input_layers.last_mut() { Some(_layer) => { // If the window is opaque, and the last(back) layer is // a content layer then mark that as opaque. // TODO: This causes talos performance regressions. // if let CompositorSurfaceUsage::Content = layer.usage { // layer.is_opaque = true; // } } None => { // If no tiles were present, and we expect an opaque window, // add an empty layer to force a composite that clears the screen, // to match existing semantics. input_layers.push(CompositorInputLayer { usage: CompositorSurfaceUsage::Content, is_opaque: true, offset: DeviceIntPoint::zero(), clip_rect: device_size.into(), rounded_clip_rect: device_size.into(), rounded_clip_radii: ClipRadius::EMPTY, }); swapchain_layers.push(SwapChainLayer { occlusion: occlusion::FrontToBackBuilder::with_capacity(cap, cap), }); } } } let mut full_render = self.debug_overlay_state.is_enabled; // Start compositing if using OS compositor if let Some(ref mut compositor) = self.compositor_config.layer_compositor() { let input = CompositorInputConfig { enable_screenshot, layers: &input_layers, }; full_render |= compositor.begin_frame(&input); } // Full render is requested when layer tree is updated. let mut partial_present_mode = if full_render { None } else { partial_present_mode }; assert_eq!(swapchain_layers.len(), input_layers.len()); // Recalculate dirty rect for layer compositor if let Some(ref _compositor) = self.compositor_config.layer_compositor() { // Set visible rests of current frame to each tile's CompositeTileState. for item in full_render_occlusion .opaque_items() .iter() .chain(full_render_occlusion.alpha_items().iter()) { let tile = &composite_state.tiles[item.key.tile_index]; match tile.tile_id { Some(tile_id) => { if let Some(tile_state) = layer_compositor_frame_state.tile_states.get_mut(&tile_id) { tile_state.visible_rects.push(item.rectangle); } else { unreachable!(); } } None => {} } } let can_use_partial_present = !self.force_redraw && !full_render && self.layer_compositor_frame_state_in_prev_frame.is_some(); if can_use_partial_present { let mut combined_dirty_rect = DeviceRect::zero(); for tile in composite_state.tiles.iter() { if tile.tile_id.is_none() { match tile.surface { CompositeTileSurface::ExternalSurface { .. } => {} CompositeTileSurface::Texture { .. } | CompositeTileSurface::Color { .. } => { unreachable!(); }, } continue; } assert!(tile.tile_id.is_some()); let tiles_exists_in_prev_frame = self.layer_compositor_frame_state_in_prev_frame .as_ref() .unwrap() .tile_states .contains_key(&tile.tile_id.unwrap()); let tile_id = tile.tile_id.unwrap(); let tile_state = layer_compositor_frame_state.tile_states.get(&tile_id).unwrap(); if tiles_exists_in_prev_frame { let prev_tile_state = self.layer_compositor_frame_state_in_prev_frame .as_ref() .unwrap() .tile_states .get(&tile_id) .unwrap(); if tile_state.same_state(prev_tile_state) { // Case that tile is same state in previous frame and current frame. // Intersection of tile's dirty rect and tile's visible rects are actual dirty rects. let dirty_rect = composite_state.get_device_rect( &tile.local_dirty_rect, tile.transform_index, ); for rect in tile_state.visible_rects.iter() { let visible_dirty_rect = rect.intersection(&dirty_rect); if visible_dirty_rect.is_some() { combined_dirty_rect = combined_dirty_rect.union(&visible_dirty_rect.unwrap()); } } } else { // If tile is rendered in previous frame, but its state is different, // both visible rects in previous frame and current frame are dirty rects. for rect in tile_state.visible_rects .iter() .chain(prev_tile_state.visible_rects.iter()) { combined_dirty_rect = combined_dirty_rect.union(&rect); } } } else { // If tile is not rendered in previous frame, its all visible rects are dirty rects. for rect in &tile_state.visible_rects { combined_dirty_rect = combined_dirty_rect.union(&rect); } } } // Case that tile is rendered in pervious frame, but not in current frame. for (tile_id, tile_state) in self.layer_compositor_frame_state_in_prev_frame .as_ref() .unwrap() .tile_states .iter() { if !layer_compositor_frame_state.tile_states.contains_key(&tile_id) { for rect in tile_state.visible_rects.iter() { combined_dirty_rect = combined_dirty_rect.union(&rect); } } } // Case that ExternalSurface is not promoted to external composite. for rect in layer_compositor_frame_state .rects_without_id .iter() .chain(self.layer_compositor_frame_state_in_prev_frame.as_ref().unwrap().rects_without_id.iter()) { combined_dirty_rect = combined_dirty_rect.union(&rect); } partial_present_mode = Some(PartialPresentMode::Single { dirty_rect: combined_dirty_rect, }); } else { partial_present_mode = None; } self.layer_compositor_frame_state_in_prev_frame = Some(layer_compositor_frame_state); } // Check tiles handling with partial_present_mode let mut opaque_rounded_corners: HashSet = HashSet::new(); // NOTE: Tiles here are being iterated in front-to-back order by // z-id, due to the sort in composite_state.end_frame() for (idx, tile) in composite_state.tiles.iter().enumerate() { let device_tile_box = composite_state.get_device_rect( &tile.local_rect, tile.transform_index ); // Determine a clip rect to apply to this tile, depending on what // the partial present mode is. let partial_clip_rect = match partial_present_mode { Some(PartialPresentMode::Single { dirty_rect }) => dirty_rect, None => device_tile_box, }; // Simple compositor needs the valid rect in device space to match clip rect let device_valid_rect = composite_state .get_device_rect(&tile.local_valid_rect, tile.transform_index); let rect = device_tile_box .intersection_unchecked(&tile.device_clip_rect) .intersection_unchecked(&partial_clip_rect) .intersection_unchecked(&device_valid_rect); if rect.is_empty() { continue; } let layer_index = match tile_index_to_layer_index[idx] { None => { // The rect of partial present should be subset of the rect of full render. error!("rect {:?} should have valid layer index", rect); continue; } Some(layer_index) => layer_index, }; // For normal tiles, add to occlusion tracker let layer = &mut swapchain_layers[layer_index]; let is_opaque = tile.kind == TileKind::Opaque; match tile.clip_index { Some(clip_index) => { let clip = composite_state.get_compositor_clip(clip_index); // TODO(gw): Make segment builder generic on unit to avoid casts below. segment_builder.initialize( rect.cast_unit(), None, rect.cast_unit(), ); segment_builder.push_clip_rect( clip.rect.cast_unit(), Some(clip.radius), ClipMode::Clip, ); segment_builder.build(|segment| { let key = OcclusionItemKey { tile_index: idx, needs_mask: segment.has_mask }; let radius = if segment.edge_flags == EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::LEFT && !clip.radius.top_left.is_empty() { Some(clip.radius.top_left) } else if segment.edge_flags == EdgeAaSegmentMask::TOP | EdgeAaSegmentMask::RIGHT && !clip.radius.top_right.is_empty() { Some(clip.radius.top_right) } else if segment.edge_flags == EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::LEFT && !clip.radius.bottom_left.is_empty() { Some(clip.radius.bottom_left) } else if segment.edge_flags == EdgeAaSegmentMask::BOTTOM | EdgeAaSegmentMask::RIGHT && !clip.radius.bottom_right.is_empty() { Some(clip.radius.bottom_right) } else { None }; if let Some(radius) = radius { let rounded_corner = CompositeRoundedCorner { rect: segment.rect.cast_unit(), radius: radius, edge_flags: segment.edge_flags, }; // Drop overdraw rounded rect if opaque_rounded_corners.contains(&rounded_corner) { return; } if is_opaque { opaque_rounded_corners.insert(rounded_corner); } } layer.occlusion.add( &segment.rect.cast_unit(), is_opaque && !segment.has_mask, key, ); }); } None => { layer.occlusion.add(&rect, is_opaque, OcclusionItemKey { tile_index: idx, needs_mask: false, }); } } } assert_eq!(swapchain_layers.len(), input_layers.len()); let mut content_clear_color = Some(self.clear_color); for (layer_index, (layer, swapchain_layer)) in input_layers.iter().zip(swapchain_layers.iter()).enumerate() { self.device.reset_state(); // Skip compositing external images or debug layers here match layer.usage { CompositorSurfaceUsage::Content => {} CompositorSurfaceUsage::External { .. } | CompositorSurfaceUsage::DebugOverlay => { continue; } } // Only use supplied clear color for first content layer we encounter let clear_color = content_clear_color.take().unwrap_or(ColorF::TRANSPARENT); if let Some(ref mut _compositor) = self.compositor_config.layer_compositor() { if let Some(PartialPresentMode::Single { dirty_rect }) = partial_present_mode { if dirty_rect.is_empty() { continue; } } } let draw_target = match self.compositor_config { CompositorConfig::Layer { ref mut compositor } => { match partial_present_mode { Some(PartialPresentMode::Single { dirty_rect }) => { compositor.bind_layer(layer_index, &[dirty_rect.to_i32()]); } None => { compositor.bind_layer(layer_index, &[]); } }; DrawTarget::NativeSurface { offset: -layer.offset, external_fbo_id: 0, dimensions: frame_device_size, } } // Native can be hit when switching compositors (disable when using Layer) CompositorConfig::Draw { .. } | CompositorConfig::Native { .. } => { fb_draw_target } }; // TODO(gwc): When supporting external attached swapchains, need to skip the composite pass here // Draw each compositing pass in to a swap chain self.composite_pass( composite_state, draw_target, clear_color, projection, results, partial_present_mode, swapchain_layer, ); if let Some(ref mut compositor) = self.compositor_config.layer_compositor() { match partial_present_mode { Some(PartialPresentMode::Single { dirty_rect }) => { compositor.present_layer(layer_index, &[dirty_rect.to_i32()]); } None => { compositor.present_layer(layer_index, &[]); } }; } } // End frame notify for experimental compositor if let Some(ref mut compositor) = self.compositor_config.layer_compositor() { for (layer_index, layer) in input_layers.iter().enumerate() { // External surfaces need transform applied, but content // surfaces are always at identity let transform = match layer.usage { CompositorSurfaceUsage::Content => CompositorSurfaceTransform::identity(), CompositorSurfaceUsage::External { transform_index, .. } => composite_state.get_compositor_transform(transform_index), CompositorSurfaceUsage::DebugOverlay => CompositorSurfaceTransform::identity(), }; compositor.add_surface( layer_index, transform, layer.clip_rect, ImageRendering::Auto, layer.rounded_clip_rect, layer.rounded_clip_radii, ); } } } pub(super) fn composite_frame( &mut self, frame: &mut Frame, device_size: Option, results: &mut RenderResults, present_mode: Option, ) { profile_scope!("main target"); if let Some(device_size) = device_size { if let Some(history) = &mut self.command_log { history.begin_render_target("Window", device_size); } results.stats.color_target_count += 1; results.picture_cache_debug = mem::replace( &mut frame.composite_state.picture_cache_debug, PictureCacheDebugInfo::new(), ); let size = frame.device_rect.size().to_f32(); let surface_origin_is_top_left = self.device.surface_origin_is_top_left(); let (bottom, top) = if surface_origin_is_top_left { (0.0, size.height) } else { (size.height, 0.0) }; let projection = Transform3D::ortho( 0.0, size.width, bottom, top, self.device.ortho_near_plane(), self.device.ortho_far_plane(), ); let fb_scale = Scale::<_, _, FramebufferPixel>::new(1i32); let mut fb_rect = frame.device_rect * fb_scale; if !surface_origin_is_top_left { let h = fb_rect.height(); fb_rect.min.y = device_size.height - fb_rect.max.y; fb_rect.max.y = fb_rect.min.y + h; } let draw_target = DrawTarget::Default { rect: fb_rect, total_size: device_size * fb_scale, surface_origin_is_top_left, }; // If we have a native OS compositor, then make use of that interface // to specify how to composite each of the picture cache surfaces. match self.current_compositor_kind { CompositorKind::Native { .. } => { // We have already queued surfaces for early native composition by this point. // All that is left is to finally update any external native surfaces that were // invalidated so that composition can complete. self.update_external_native_surfaces( &frame.composite_state.external_surfaces, results, ); } CompositorKind::Draw { .. } | CompositorKind::Layer { .. } => { self.composite_simple( &frame.composite_state, frame.device_rect.size(), draw_target, &projection, results, present_mode, device_size, ); } } // Reset force_redraw. It was used in composite_simple() with layer compositor. self.force_redraw = false; } else { // Rendering a frame without presenting it will confuse the partial // present logic, so force a full present for the next frame. self.force_redraw = true; } } /// Update the dirty rects based on current compositing mode and config // TODO(gw): This can be tidied up significantly once the Draw compositor // is implemented in terms of the compositor trait. pub(super) fn calculate_dirty_rects( &mut self, buffer_age: usize, composite_state: &CompositeState, draw_target_dimensions: DeviceIntSize, results: &mut RenderResults, ) -> Option { if let Some(ref _compositor) = self.compositor_config.layer_compositor() { // Calculate dirty rects of layer compositor in composite_simple() return None; } let mut partial_present_mode = None; let (max_partial_present_rects, draw_previous_partial_present_regions) = match self.current_compositor_kind { CompositorKind::Native { .. } => { // Assume that we can return a single dirty rect for native // compositor for now, and that there is no buffer-age functionality. // These params can be exposed by the compositor capabilities struct // as the Draw compositor is ported to use it. (1, false) } CompositorKind::Draw { draw_previous_partial_present_regions, max_partial_present_rects, } => ( max_partial_present_rects, draw_previous_partial_present_regions, ), CompositorKind::Layer { .. } => { unreachable!(); } }; if max_partial_present_rects > 0 { let prev_frames_damage_rect = if let Some(..) = self.compositor_config.partial_present() { self.buffer_damage_tracker .get_damage_rect(buffer_age) .or_else(|| Some(DeviceRect::from_size(draw_target_dimensions.to_f32()))) } else { None }; let can_use_partial_present = composite_state.dirty_rects_are_valid && !self.force_redraw && !(prev_frames_damage_rect.is_none() && draw_previous_partial_present_regions) && !self.debug_overlay_state.is_enabled; if can_use_partial_present { let mut combined_dirty_rect = DeviceRect::zero(); let fb_rect = DeviceRect::from_size(draw_target_dimensions.to_f32()); // Work out how many dirty rects WR produced, and if that's more than // what the device supports. for tile in &composite_state.tiles { let dirty_rect = composite_state .get_device_rect(&tile.local_dirty_rect, tile.transform_index); // In pathological cases where a tile is extremely zoomed, it // may end up with device coords outside the range of an i32, // so clamp it to the frame buffer rect here, before it gets // casted to an i32 rect below. if let Some(dirty_rect) = dirty_rect.intersection(&fb_rect) { combined_dirty_rect = combined_dirty_rect.union(&dirty_rect); } } let combined_dirty_rect = combined_dirty_rect.round(); let combined_dirty_rect_i32 = combined_dirty_rect.to_i32(); // Return this frame's dirty region. If nothing has changed, don't return any dirty // rects at all (the client can use this as a signal to skip present completely). if !combined_dirty_rect.is_empty() { results.dirty_rects.push(combined_dirty_rect_i32); } // Track this frame's dirty region, for calculating subsequent frames' damage. if draw_previous_partial_present_regions { self.buffer_damage_tracker .push_dirty_rect(&combined_dirty_rect); } // If the implementation requires manually keeping the buffer consistent, // then we must combine this frame's dirty region with that of previous frames // to determine the total_dirty_rect. The is used to determine what region we // render to, and is what we send to the compositor as the buffer damage region // (eg for KHR_partial_update). let total_dirty_rect = if draw_previous_partial_present_regions { combined_dirty_rect.union(&prev_frames_damage_rect.unwrap()) } else { combined_dirty_rect }; partial_present_mode = Some(PartialPresentMode::Single { dirty_rect: total_dirty_rect, }); } else { // If we don't have a valid partial present scenario, return a single // dirty rect to the client that covers the entire framebuffer. let fb_rect = DeviceIntRect::from_size(draw_target_dimensions); results.dirty_rects.push(fb_rect); if draw_previous_partial_present_regions { self.buffer_damage_tracker .push_dirty_rect(&fb_rect.to_f32()); } } } partial_present_mode } }