/* 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 gleam::{gl, gl::Gl}; use std::cell::{Cell, UnsafeCell}; use std::collections::{hash_map::HashMap, VecDeque}; use std::ops::{Deref, DerefMut, Range}; use std::ptr; use std::sync::atomic::{AtomicBool, AtomicI8, AtomicPtr, AtomicU32, AtomicU8, Ordering}; use std::sync::{Arc, Condvar, Mutex, MutexGuard}; use std::thread; use crate::{ api::units::*, api::ColorDepth, api::ColorF, api::ExternalImageId, api::ImageRendering, api::YuvRangedColorSpace, Compositor, CompositorCapabilities, CompositorSurfaceTransform, NativeSurfaceId, NativeSurfaceInfo, NativeTileId, profiler, MappableCompositor, SWGLCompositeSurfaceInfo, WindowVisibility, device::Device, }; pub struct SwTile { x: i32, y: i32, fbo_id: u32, color_id: u32, valid_rect: DeviceIntRect, /// Composition of tiles must be ordered such that any tiles that may overlap /// an invalidated tile in an earlier surface only get drawn after that tile /// is actually updated. We store a count of the number of overlapping invalid /// here, that gets decremented when the invalid tiles are finally updated so /// that we know when it is finally safe to draw. Must use a Cell as we might /// be analyzing multiple tiles and surfaces overlaps: Cell, /// Whether the tile's contents has been invalidated invalid: Cell, /// Graph node for job dependencies of this tile graph_node: SwCompositeGraphNodeRef, } impl SwTile { fn new(x: i32, y: i32) -> Self { SwTile { x, y, fbo_id: 0, color_id: 0, valid_rect: DeviceIntRect::zero(), overlaps: Cell::new(0), invalid: Cell::new(false), graph_node: SwCompositeGraphNode::new(), } } /// The offset of the tile in the local space of the surface before any /// transform is applied. fn origin(&self, surface: &SwSurface) -> DeviceIntPoint { DeviceIntPoint::new(self.x * surface.tile_size.width, self.y * surface.tile_size.height) } /// The offset valid rect positioned within the local space of the surface /// before any transform is applied. fn local_bounds(&self, surface: &SwSurface) -> DeviceIntRect { self.valid_rect.translate(self.origin(surface).to_vector()) } /// Bounds used for determining overlap dependencies. This may either be the /// full tile bounds or the actual valid rect, depending on whether the tile /// is invalidated this frame. These bounds are more conservative as such and /// may differ from the precise bounds used to actually composite the tile. fn overlap_rect( &self, surface: &SwSurface, transform: &CompositorSurfaceTransform, clip_rect: &DeviceIntRect, ) -> Option { let bounds = self.local_bounds(surface); let device_rect = transform.map_rect(&bounds.to_f32()).round_out(); Some(device_rect.intersection(&clip_rect.to_f32())?.to_i32()) } /// Determine if the tile's bounds may overlap the dependency rect if it were /// to be composited at the given position. fn may_overlap( &self, surface: &SwSurface, transform: &CompositorSurfaceTransform, clip_rect: &DeviceIntRect, dep_rect: &DeviceIntRect, ) -> bool { self.overlap_rect(surface, transform, clip_rect) .map_or(false, |r| r.intersects(dep_rect)) } /// Get valid source and destination rectangles for composition of the tile /// within a surface, bounded by the clipping rectangle. May return None if /// it falls outside of the clip rect. fn composite_rects( &self, surface: &SwSurface, transform: &CompositorSurfaceTransform, clip_rect: &DeviceIntRect, ) -> Option<(DeviceIntRect, DeviceIntRect, bool, bool)> { // Offset the valid rect to the appropriate surface origin. let valid = self.local_bounds(surface); // The destination rect is the valid rect transformed and then clipped. let dest_rect = transform.map_rect(&valid.to_f32()).round_out(); if !dest_rect.intersects(&clip_rect.to_f32()) { return None; } // To get a valid source rect, we need to inverse transform the clipped destination rect to find out the effect // of the clip rect in source-space. After this, we subtract off the source-space valid rect origin to get // a source rect that is now relative to the surface origin rather than absolute. let inv_transform = transform.inverse(); let src_rect = inv_transform .map_rect(&dest_rect) .round() .translate(-valid.min.to_vector().to_f32()); // Ensure source and dest rects when transformed from Box2D to Rect formats will still fit in an i32. // If p0=i32::MIN and p1=i32::MAX, then evaluating the size with p1-p0 will overflow an i32 and not // be representable. if src_rect.size().try_cast::().is_none() || dest_rect.size().try_cast::().is_none() { return None; } let flip_x = transform.scale.x < 0.0; let flip_y = transform.scale.y < 0.0; Some((src_rect.try_cast()?, dest_rect.try_cast()?, flip_x, flip_y)) } } pub struct SwSurface { tile_size: DeviceIntSize, is_opaque: bool, tiles: Vec, /// An attached external image for this surface. external_image: Option, } impl SwSurface { fn new(tile_size: DeviceIntSize, is_opaque: bool) -> Self { SwSurface { tile_size, is_opaque, tiles: Vec::new(), external_image: None, } } /// Conserative approximation of local bounds of the surface by combining /// the local bounds of all enclosed tiles. fn local_bounds(&self) -> DeviceIntRect { let mut bounds = DeviceIntRect::zero(); for tile in &self.tiles { bounds = bounds.union(&tile.local_bounds(self)); } bounds } /// The transformed and clipped conservative device-space bounds of the /// surface. fn device_bounds( &self, transform: &CompositorSurfaceTransform, clip_rect: &DeviceIntRect, ) -> Option { let bounds = self.local_bounds(); let device_rect = transform.map_rect(&bounds.to_f32()).round_out(); Some(device_rect.intersection(&clip_rect.to_f32())?.to_i32()) } } fn image_rendering_to_gl_filter(filter: ImageRendering) -> gl::GLenum { match filter { ImageRendering::Pixelated => gl::NEAREST, ImageRendering::Auto | ImageRendering::CrispEdges => gl::LINEAR, } } /// A source for a composite job which can either be a single BGRA locked SWGL /// resource or a collection of SWGL resources representing a YUV surface. #[derive(Clone)] enum SwCompositeSource { BGRA(swgl::LockedResource), YUV( swgl::LockedResource, swgl::LockedResource, swgl::LockedResource, YuvRangedColorSpace, ColorDepth, ), } /// Mark ExternalImage's renderer field as safe to send to SwComposite thread. unsafe impl Send for SwCompositeSource {} /// A tile composition job to be processed by the SwComposite thread. /// Stores relevant details about the tile and where to composite it. #[derive(Clone)] struct SwCompositeJob { /// Locked texture that will be unlocked immediately following the job locked_src: SwCompositeSource, /// Locked framebuffer that may be shared among many jobs locked_dst: swgl::LockedResource, src_rect: DeviceIntRect, dst_rect: DeviceIntRect, clipped_dst: DeviceIntRect, opaque: bool, flip_x: bool, flip_y: bool, filter: ImageRendering, /// The total number of bands for this job num_bands: u8, } impl SwCompositeJob { /// Process a composite job fn process(&self, band_index: i32) { // Bands are allocated in reverse order, but we want to process them in increasing order. let num_bands = self.num_bands as i32; let band_index = num_bands - 1 - band_index; // Calculate the Y extents for the job's band, starting at the current index and spanning to // the following index. let band_offset = (self.clipped_dst.height() * band_index) / num_bands; let band_height = (self.clipped_dst.height() * (band_index + 1)) / num_bands - band_offset; // Create a rect that is the intersection of the band with the clipped dest let band_clip = DeviceIntRect::from_origin_and_size( DeviceIntPoint::new(self.clipped_dst.min.x, self.clipped_dst.min.y + band_offset), DeviceIntSize::new(self.clipped_dst.width(), band_height), ); match self.locked_src { SwCompositeSource::BGRA(ref resource) => { self.locked_dst.composite( resource, self.src_rect.min.x, self.src_rect.min.y, self.src_rect.width(), self.src_rect.height(), self.dst_rect.min.x, self.dst_rect.min.y, self.dst_rect.width(), self.dst_rect.height(), self.opaque, self.flip_x, self.flip_y, image_rendering_to_gl_filter(self.filter), band_clip.min.x, band_clip.min.y, band_clip.width(), band_clip.height(), ); } SwCompositeSource::YUV(ref y, ref u, ref v, color_space, color_depth) => { let swgl_color_space = match color_space { YuvRangedColorSpace::Rec601Narrow => swgl::YuvRangedColorSpace::Rec601Narrow, YuvRangedColorSpace::Rec601Full => swgl::YuvRangedColorSpace::Rec601Full, YuvRangedColorSpace::Rec709Narrow => swgl::YuvRangedColorSpace::Rec709Narrow, YuvRangedColorSpace::Rec709Full => swgl::YuvRangedColorSpace::Rec709Full, YuvRangedColorSpace::Rec2020Narrow => swgl::YuvRangedColorSpace::Rec2020Narrow, YuvRangedColorSpace::Rec2020Full => swgl::YuvRangedColorSpace::Rec2020Full, YuvRangedColorSpace::GbrIdentity => swgl::YuvRangedColorSpace::GbrIdentity, }; self.locked_dst.composite_yuv( y, u, v, swgl_color_space, color_depth.bit_depth(), self.src_rect.min.x, self.src_rect.min.y, self.src_rect.width(), self.src_rect.height(), self.dst_rect.min.x, self.dst_rect.min.y, self.dst_rect.width(), self.dst_rect.height(), self.flip_x, self.flip_y, band_clip.min.x, band_clip.min.y, band_clip.width(), band_clip.height(), ); } } } } /// A reference to a SwCompositeGraph node that can be passed from the render /// thread to the SwComposite thread. Consistency of mutation is ensured in /// SwCompositeGraphNode via use of Atomic operations that prevent more than /// one thread from mutating SwCompositeGraphNode at once. This avoids using /// messy and not-thread-safe RefCells or expensive Mutexes inside the graph /// node and at least signals to the compiler that potentially unsafe coercions /// are occurring. #[derive(Clone)] struct SwCompositeGraphNodeRef(Arc>); impl SwCompositeGraphNodeRef { fn new(graph_node: SwCompositeGraphNode) -> Self { SwCompositeGraphNodeRef(Arc::new(UnsafeCell::new(graph_node))) } fn get(&self) -> &SwCompositeGraphNode { unsafe { &*self.0.get() } } fn get_mut(&self) -> &mut SwCompositeGraphNode { unsafe { &mut *self.0.get() } } fn get_ptr_mut(&self) -> *mut SwCompositeGraphNode { self.0.get() } } unsafe impl Send for SwCompositeGraphNodeRef {} impl Deref for SwCompositeGraphNodeRef { type Target = SwCompositeGraphNode; fn deref(&self) -> &Self::Target { self.get() } } impl DerefMut for SwCompositeGraphNodeRef { fn deref_mut(&mut self) -> &mut Self::Target { self.get_mut() } } /// Dependency graph of composite jobs to be completed. Keeps a list of child jobs that are dependent on the completion of this job. /// Also keeps track of the number of parent jobs that this job is dependent upon before it can be processed. Once there are no more /// in-flight parent jobs that it depends on, the graph node is finally added to the job queue for processing. struct SwCompositeGraphNode { /// Job to be queued for this graph node once ready. job: Option, /// The number of remaining bands associated with this job. When this is /// non-zero and the node has no more parents left, then the node is being /// actively used by the composite thread to process jobs. Once it hits /// zero, the owning thread (which brought it to zero) can safely retire /// the node as no other thread is using it. remaining_bands: AtomicU8, /// The number of bands that are available for processing. available_bands: AtomicI8, /// Count of parents this graph node depends on. While this is non-zero the /// node must ensure that it is only being actively mutated by the render /// thread and otherwise never being accessed by the render thread. parents: AtomicU32, /// Graph nodes of child jobs that are dependent on this job children: Vec, } unsafe impl Sync for SwCompositeGraphNode {} impl SwCompositeGraphNode { fn new() -> SwCompositeGraphNodeRef { SwCompositeGraphNodeRef::new(SwCompositeGraphNode { job: None, remaining_bands: AtomicU8::new(0), available_bands: AtomicI8::new(0), parents: AtomicU32::new(0), children: Vec::new(), }) } /// Reset the node's state for a new frame fn reset(&mut self) { self.job = None; self.remaining_bands.store(0, Ordering::SeqCst); self.available_bands.store(0, Ordering::SeqCst); // Initialize parents to 1 as sentinel dependency for uninitialized job // to avoid queuing unitialized job as unblocked child dependency. self.parents.store(1, Ordering::SeqCst); self.children.clear(); } /// Add a dependent child node to dependency list. Update its parent count. fn add_child(&mut self, child: SwCompositeGraphNodeRef) { child.parents.fetch_add(1, Ordering::SeqCst); self.children.push(child); } /// Install a job for this node. Return whether or not the job has any unprocessed parents /// that would block immediate composition. fn set_job(&mut self, job: SwCompositeJob, num_bands: u8) -> bool { self.job = Some(job); self.remaining_bands.store(num_bands, Ordering::SeqCst); self.available_bands.store(num_bands as _, Ordering::SeqCst); // Subtract off the sentinel parent dependency now that job is initialized and check // whether there are any remaining parent dependencies to see if this job is ready. self.parents.fetch_sub(1, Ordering::SeqCst) <= 1 } /// Take an available band if possible. Also return whether there are no more bands left /// so the caller may properly clean up after. fn take_band(&self) -> (Option, bool) { let available = self.available_bands.fetch_sub(1, Ordering::SeqCst); if available > 0 { (Some(available as i32 - 1), available == 1) } else { (None, true) } } /// Try to take the job from this node for processing and then process it within the current band. fn process_job(&self, band_index: i32) { if let Some(ref job) = self.job { job.process(band_index); } } /// After processing a band, check all child dependencies and remove this parent from /// their dependency counts. If applicable, queue the new child bands for composition. fn unblock_children(&mut self, thread: &SwCompositeThread) { if self.remaining_bands.fetch_sub(1, Ordering::SeqCst) > 1 { return; } // Clear the job to release any locked resources. self.job = None; let mut lock = None; for child in self.children.drain(..) { // Remove the child's parent dependency on this node. If there are no more // parent dependencies left, send the child job bands for composition. if child.parents.fetch_sub(1, Ordering::SeqCst) <= 1 { if lock.is_none() { lock = Some(thread.lock()); } thread.send_job(lock.as_mut().unwrap(), child); } } } } /// The SwComposite thread processes a queue of composite jobs, also signaling /// via a condition when all available jobs have been processed, as tracked by /// the job count. struct SwCompositeThread { /// Queue of available composite jobs jobs: Mutex, /// Cache of the current job being processed. This maintains a pointer to /// the contents of the SwCompositeGraphNodeRef, which is safe due to the /// fact that SwCompositor maintains a strong reference to the contents /// in an SwTile to keep it alive while this is in use. current_job: AtomicPtr, /// Condition signaled when either there are jobs available to process or /// there are no more jobs left to process. Otherwise stated, this signals /// when the job queue transitions from an empty to non-empty state or from /// a non-empty to empty state. jobs_available: Condvar, /// Whether all available jobs have been processed. jobs_completed: AtomicBool, /// Whether the main thread is waiting for for job completeion. waiting_for_jobs: AtomicBool, /// Whether the SwCompositor is shutting down shutting_down: AtomicBool, } /// The SwCompositeThread struct is shared between the SwComposite thread /// and the rendering thread so that both ends can access the job queue. unsafe impl Sync for SwCompositeThread {} /// A FIFO queue of composite jobs to be processed. type SwCompositeJobQueue = VecDeque; /// Locked access to the composite job queue. type SwCompositeThreadLock<'a> = MutexGuard<'a, SwCompositeJobQueue>; impl SwCompositeThread { /// Create the SwComposite thread. Requires a SWGL context in which /// to do the composition. fn new() -> Arc { let info = Arc::new(SwCompositeThread { jobs: Mutex::new(SwCompositeJobQueue::new()), current_job: AtomicPtr::new(ptr::null_mut()), jobs_available: Condvar::new(), jobs_completed: AtomicBool::new(true), waiting_for_jobs: AtomicBool::new(false), shutting_down: AtomicBool::new(false), }); let result = info.clone(); let thread_name = "SwComposite"; thread::Builder::new() .name(thread_name.into()) // The composite thread only calls into SWGL to composite, and we // have potentially many composite threads for different windows, // so using the default stack size is excessive. A reasonably small // stack size should be more than enough for SWGL and reduce memory // overhead. // Bug 1731569 - Need at least 36K to avoid problems with ASAN. .stack_size(40 * 1024) .spawn(move || { profiler::register_thread(thread_name); // Process any available jobs. This will return a non-Ok // result when the job queue is dropped, causing the thread // to eventually exit. while let Some((job, band)) = info.take_job(true) { info.process_job(job, band); } profiler::unregister_thread(); }) .expect("Failed creating SwComposite thread"); result } fn deinit(&self) { // Signal that the thread needs to exit. self.shutting_down.store(true, Ordering::SeqCst); // Wake up the thread in case it is blocked waiting for new jobs self.jobs_available.notify_all(); } /// Process a job contained in a dependency graph node received from the job queue. /// Any child dependencies will be unblocked as appropriate after processing. The /// job count will be updated to reflect this. fn process_job(&self, graph_node: &mut SwCompositeGraphNode, band: i32) { // Do the actual processing of the job contained in this node. graph_node.process_job(band); // Unblock any child dependencies now that this job has been processed. graph_node.unblock_children(self); } /// Queue a tile for composition by adding to the queue and increasing the job count. fn queue_composite( &self, locked_src: SwCompositeSource, locked_dst: swgl::LockedResource, src_rect: DeviceIntRect, dst_rect: DeviceIntRect, clip_rect: DeviceIntRect, opaque: bool, flip_x: bool, flip_y: bool, filter: ImageRendering, mut graph_node: SwCompositeGraphNodeRef, job_queue: &mut SwCompositeJobQueue, ) { // For jobs that would span a sufficiently large destination rectangle, split // it into multiple horizontal bands so that multiple threads can process them. let clipped_dst = match dst_rect.intersection(&clip_rect) { Some(clipped_dst) => clipped_dst, None => return, }; let num_bands = if clipped_dst.width() >= 64 && clipped_dst.height() >= 64 { (clipped_dst.height() / 64).min(4) as u8 } else { 1 }; let job = SwCompositeJob { locked_src, locked_dst, src_rect, dst_rect, clipped_dst, opaque, flip_x, flip_y, filter, num_bands, }; if graph_node.set_job(job, num_bands) { self.send_job(job_queue, graph_node); } } fn prepare_for_composites(&self) { // Initially, the job queue is empty. Trivially, this means we consider all // jobs queued so far as completed. self.jobs_completed.store(true, Ordering::SeqCst); } /// Lock the thread for access to the job queue. fn lock(&self) -> SwCompositeThreadLock { self.jobs.lock().unwrap() } /// Send a job to the composite thread by adding it to the job queue. /// Signal that this job has been added in case the queue was empty and the /// SwComposite thread is waiting for jobs. fn send_job(&self, queue: &mut SwCompositeJobQueue, job: SwCompositeGraphNodeRef) { if queue.is_empty() { self.jobs_completed.store(false, Ordering::SeqCst); self.jobs_available.notify_all(); } queue.push_back(job); } /// Try to get a band of work from the currently cached job when available. /// If there is a job, but it has no available bands left, null out the job /// so that other threads do not bother checking the job. fn try_take_job(&self) -> Option<(&mut SwCompositeGraphNode, i32)> { let current_job_ptr = self.current_job.load(Ordering::SeqCst); if let Some(current_job) = unsafe { current_job_ptr.as_mut() } { let (band, done) = current_job.take_band(); if done { let _ = self.current_job.compare_exchange( current_job_ptr, ptr::null_mut(), Ordering::SeqCst, Ordering::SeqCst, ); } if let Some(band) = band { return Some((current_job, band)); } } return None; } /// Take a job from the queue. Optionally block waiting for jobs to become /// available if this is called from the SwComposite thread. fn take_job(&self, wait: bool) -> Option<(&mut SwCompositeGraphNode, i32)> { // First try checking the cached job outside the scope of the mutex. // For jobs that have multiple bands, this allows us to avoid having // to lock the mutex multiple times to check the job for each band. if let Some((job, band)) = self.try_take_job() { return Some((job, band)); } // Lock the job queue while checking for available jobs. The lock // won't be held while the job is processed later outside of this // function so that other threads can pull from the queue meanwhile. let mut jobs = self.lock(); loop { // While inside the mutex, check the cached job again to see if it // has been updated. if let Some((job, band)) = self.try_take_job() { return Some((job, band)); } // If no cached job was available, try to take a job from the queue // and install it as the current job. if let Some(job) = jobs.pop_front() { self.current_job.store(job.get_ptr_mut(), Ordering::SeqCst); continue; } // Otherwise, the job queue is currently empty. Depending on the // job status, we may either wait for jobs to become available or exit. if wait { // For the SwComposite thread, if we arrive here, the job queue // is empty. Signal that all available jobs have been completed. self.jobs_completed.store(true, Ordering::SeqCst); if self.waiting_for_jobs.load(Ordering::SeqCst) { // Wake the main thread if it is waiting for a change in job status. self.jobs_available.notify_all(); } else if self.shutting_down.load(Ordering::SeqCst) { // If SwComposite thread needs to shut down, then exit and stop // waiting for jobs. return None; } } else { // If all available jobs have been completed by the SwComposite // thread, then the main thread no longer needs to wait for any // new jobs to appear in the queue and should exit. if self.jobs_completed.load(Ordering::SeqCst) { return None; } // Otherwise, signal that the main thread is waiting for jobs. self.waiting_for_jobs.store(true, Ordering::SeqCst); } // Wait until jobs are added before checking the job queue again. jobs = self.jobs_available.wait(jobs).unwrap(); if !wait { // The main thread is done waiting for jobs. self.waiting_for_jobs.store(false, Ordering::SeqCst); } } } /// Wait for all queued composition jobs to be processed. /// Instead of blocking on the SwComposite thread to complete all jobs, /// this may steal some jobs and attempt to process them while waiting. /// This may optionally process jobs synchronously. When normally doing /// asynchronous processing, the graph dependencies are relied upon to /// properly order the jobs, which makes it safe for the render thread /// to steal jobs from the composite thread without violating those /// dependencies. Synchronous processing just disables this job stealing /// so that the composite thread always handles the jobs in the order /// they were queued without having to rely upon possibly unavailable /// graph dependencies. fn wait_for_composites(&self, sync: bool) { // If processing asynchronously, try to steal jobs from the composite // thread if it is busy. if !sync { while let Some((job, band)) = self.take_job(false) { self.process_job(job, band); } // Once there are no more jobs, just fall through to waiting // synchronously for the composite thread to finish processing. } // If processing synchronously, just wait for the composite thread // to complete processing any in-flight jobs, then bail. let mut jobs = self.lock(); // Signal that the main thread may wait for job completion so that the // SwComposite thread can wake it up if necessary. self.waiting_for_jobs.store(true, Ordering::SeqCst); // Wait for job completion to ensure there are no more in-flight jobs. while !self.jobs_completed.load(Ordering::SeqCst) { jobs = self.jobs_available.wait(jobs).unwrap(); } // Done waiting for job completion. self.waiting_for_jobs.store(false, Ordering::SeqCst); } } /// Parameters describing how to composite a surface within a frame type FrameSurface = ( NativeSurfaceId, CompositorSurfaceTransform, DeviceIntRect, ImageRendering, ); /// Adapter for RenderCompositors to work with SWGL that shuttles between /// WebRender and the RenderCompositr via the Compositor API. pub struct SwCompositor { gl: swgl::Context, compositor: Box, use_native_compositor: bool, surfaces: HashMap, frame_surfaces: Vec, /// Any surface added after we're already compositing (i.e. debug overlay) /// needs to be processed after those frame surfaces. For simplicity we /// store them in a separate queue that gets processed later. late_surfaces: Vec, /// Any composite surfaces that were locked during the frame and need to be /// unlocked. frame_surfaces and late_surfaces may be pruned, so we can't /// rely on them to contain all surfaces that were actually locked and must /// track those separately. composite_surfaces: HashMap, cur_tile: NativeTileId, /// The maximum tile size required for any of the allocated surfaces. max_tile_size: DeviceIntSize, /// Reuse the same depth texture amongst all tiles in all surfaces. /// This depth texture must be big enough to accommodate the largest used /// tile size for any surface. The maximum requested tile size is tracked /// to ensure that this depth texture is at least that big. /// This is initialized when the first surface is created and freed when /// the last surface is destroyed, to ensure compositors with no surfaces /// are not holding on to extra memory. depth_id: Option, /// Instance of the SwComposite thread, only created if we are not relying /// on a native RenderCompositor. composite_thread: Option>, /// SWGL locked resource for sharing framebuffer with SwComposite thread locked_framebuffer: Option, /// Whether we are currently in the middle of compositing is_compositing: bool, } impl SwCompositor { pub fn new( gl: swgl::Context, compositor: Box, use_native_compositor: bool, ) -> Self { // Only create the SwComposite thread if we're not using a native render // compositor. Thus, we are compositing into the main software framebuffer, // which benefits from compositing asynchronously while updating tiles. let composite_thread = if !use_native_compositor { Some(SwCompositeThread::new()) } else { None }; SwCompositor { gl, compositor, use_native_compositor, surfaces: HashMap::new(), frame_surfaces: Vec::new(), late_surfaces: Vec::new(), composite_surfaces: HashMap::new(), cur_tile: NativeTileId { surface_id: NativeSurfaceId(0), x: 0, y: 0, }, max_tile_size: DeviceIntSize::zero(), depth_id: None, composite_thread, locked_framebuffer: None, is_compositing: false, } } fn deinit_tile(&self, tile: &SwTile) { self.gl.delete_framebuffers(&[tile.fbo_id]); self.gl.delete_textures(&[tile.color_id]); } fn deinit_surface(&self, surface: &SwSurface) { for tile in &surface.tiles { self.deinit_tile(tile); } } /// Attempt to occlude any queued surfaces with an opaque occluder rect. If /// an existing surface is occluded, we attempt to restrict its clip rect /// so long as it can remain a single clip rect. Existing frame surfaces /// that are opaque will be fused if possible with the supplied occluder /// rect to further try and restrict any underlying surfaces. fn occlude_surfaces(&mut self) { // Check if inner rect is fully included in outer rect fn includes(outer: &Range, inner: &Range) -> bool { outer.start <= inner.start && outer.end >= inner.end } // Check if outer range overlaps either the start or end of a range. If // there is overlap, return the portion of the inner range remaining // after the overlap has been removed. fn overlaps(outer: &Range, inner: &Range) -> Option> { if outer.start <= inner.start && outer.end >= inner.start { Some(outer.end..inner.end.max(outer.end)) } else if outer.start <= inner.end && outer.end >= inner.end { Some(inner.start..outer.start.max(inner.start)) } else { None } } fn set_x_range(rect: &mut DeviceIntRect, range: &Range) { rect.min.x = range.start; rect.max.x = range.end; } fn set_y_range(rect: &mut DeviceIntRect, range: &Range) { rect.min.y = range.start; rect.max.y = range.end; } fn union(base: Range, extra: Range) -> Range { base.start.min(extra.start)..base.end.max(extra.end) } // Before we can try to occlude any surfaces, we need to fix their clip rects to tightly // bound the valid region. The clip rect might otherwise enclose an invalid area that // can't fully occlude anything even if the surface is opaque. for &mut (ref id, ref transform, ref mut clip_rect, _) in &mut self.frame_surfaces { if let Some(surface) = self.surfaces.get(id) { // Restrict the clip rect to fall within the valid region of the surface. *clip_rect = surface.device_bounds(transform, clip_rect).unwrap_or_default(); } } // For each frame surface, treat it as an occluder if it is non-empty and opaque. Look // through the preceding surfaces to see if any can be occluded. for occlude_index in 0..self.frame_surfaces.len() { let (ref occlude_id, _, ref occlude_rect, _) = self.frame_surfaces[occlude_index]; match self.surfaces.get(occlude_id) { Some(occluder) if occluder.is_opaque && !occlude_rect.is_empty() => {} _ => continue, } // Traverse the queued surfaces for this frame in the reverse order of // how they are composited, or rather, in order of visibility. For each // surface, check if the occluder can restrict the clip rect such that // the clip rect can remain a single rect. If the clip rect overlaps // the occluder on one axis interval while remaining fully included in // the occluder's other axis interval, then we can chop down the edge // of the clip rect on the overlapped axis. Further, if the surface is // opaque and its clip rect exactly matches the occluder rect on one // axis interval while overlapping on the other, fuse it with the // occluder rect before considering any underlying surfaces. let (mut occlude_x, mut occlude_y) = (occlude_rect.x_range(), occlude_rect.y_range()); for &mut (ref id, _, ref mut clip_rect, _) in self.frame_surfaces[..occlude_index].iter_mut().rev() { if let Some(surface) = self.surfaces.get(id) { let (clip_x, clip_y) = (clip_rect.x_range(), clip_rect.y_range()); if includes(&occlude_x, &clip_x) { if let Some(visible) = overlaps(&occlude_y, &clip_y) { set_y_range(clip_rect, &visible); if surface.is_opaque && occlude_x == clip_x { occlude_y = union(occlude_y, visible); } } } else if includes(&occlude_y, &clip_y) { if let Some(visible) = overlaps(&occlude_x, &clip_x) { set_x_range(clip_rect, &visible); if surface.is_opaque && occlude_y == clip_y { occlude_x = union(occlude_x, visible); } } } } } } } /// Reset tile dependency state for a new frame. fn reset_overlaps(&mut self) { for surface in self.surfaces.values_mut() { for tile in &mut surface.tiles { tile.overlaps.set(0); tile.invalid.set(false); tile.graph_node.reset(); } } } /// Computes an overlap count for a tile that falls within the given composite /// destination rectangle. This requires checking all surfaces currently queued for /// composition so far in this frame and seeing if they have any invalidated tiles /// whose destination rectangles would also overlap the supplied tile. If so, then the /// increment the overlap count to account for all such dependencies on invalid tiles. /// Tiles with the same overlap count will still be drawn with a stable ordering in /// the order the surfaces were queued, so it is safe to ignore other possible sources /// of composition ordering dependencies, as the later queued tile will still be drawn /// later than the blocking tiles within that stable order. We assume that the tile's /// surface hasn't yet been added to the current frame list of surfaces to composite /// so that we only process potential blockers from surfaces that would come earlier /// in composition. fn init_overlaps( &self, overlap_id: &NativeSurfaceId, overlap_surface: &SwSurface, overlap_tile: &SwTile, overlap_transform: &CompositorSurfaceTransform, overlap_clip_rect: &DeviceIntRect, ) { // Record an extra overlap for an invalid tile to track the tile's dependency // on its own future update. let mut overlaps = if overlap_tile.invalid.get() { 1 } else { 0 }; let overlap_rect = match overlap_tile.overlap_rect(overlap_surface, overlap_transform, overlap_clip_rect) { Some(overlap_rect) => overlap_rect, None => { overlap_tile.overlaps.set(overlaps); return; } }; for &(ref id, ref transform, ref clip_rect, _) in &self.frame_surfaces { // We only want to consider surfaces that were added before the current one we're // checking for overlaps. If we find that surface, then we're done. if id == overlap_id { break; } // If the surface's clip rect doesn't overlap the tile's rect, // then there is no need to check any tiles within the surface. if !overlap_rect.intersects(clip_rect) { continue; } if let Some(surface) = self.surfaces.get(id) { for tile in &surface.tiles { // If there is a deferred tile that might overlap the destination rectangle, // record the overlap. if tile.may_overlap(surface, transform, clip_rect, &overlap_rect) { if tile.overlaps.get() > 0 { overlaps += 1; } // Regardless of whether this tile is deferred, if it has dependency // overlaps, then record that it is potentially a dependency parent. tile.graph_node.get_mut().add_child(overlap_tile.graph_node.clone()); } } } } if overlaps > 0 { // Has a dependency on some invalid tiles, so need to defer composition. overlap_tile.overlaps.set(overlaps); } } /// Helper function that queues a composite job to the current locked framebuffer fn queue_composite( &self, surface: &SwSurface, transform: &CompositorSurfaceTransform, clip_rect: &DeviceIntRect, filter: ImageRendering, tile: &SwTile, job_queue: &mut SwCompositeJobQueue, ) { if let Some(ref composite_thread) = self.composite_thread { if let Some((src_rect, dst_rect, flip_x, flip_y)) = tile.composite_rects(surface, transform, clip_rect) { let source = if let Some(ref external_image) = surface.external_image { // If the surface has an attached external image, lock any textures supplied in the descriptor. match self.composite_surfaces.get(external_image) { Some(ref info) => match info.yuv_planes { 0 => match self.gl.lock_texture(info.textures[0]) { Some(texture) => SwCompositeSource::BGRA(texture), None => return, }, 3 => match ( self.gl.lock_texture(info.textures[0]), self.gl.lock_texture(info.textures[1]), self.gl.lock_texture(info.textures[2]), ) { (Some(y_texture), Some(u_texture), Some(v_texture)) => SwCompositeSource::YUV( y_texture, u_texture, v_texture, info.color_space, info.color_depth, ), _ => return, }, _ => panic!("unsupported number of YUV planes: {}", info.yuv_planes), }, None => return, } } else if let Some(texture) = self.gl.lock_texture(tile.color_id) { // Lock the texture representing the picture cache tile. SwCompositeSource::BGRA(texture) } else { return; }; if let Some(ref framebuffer) = self.locked_framebuffer { composite_thread.queue_composite( source, framebuffer.clone(), src_rect, dst_rect, *clip_rect, surface.is_opaque, flip_x, flip_y, filter, tile.graph_node.clone(), job_queue, ); } } } } /// Lock a surface with an attached external image for compositing. fn try_lock_composite_surface(&mut self, device: &mut Device, id: &NativeSurfaceId) { if let Some(surface) = self.surfaces.get_mut(id) { if let Some(external_image) = surface.external_image { assert!(!surface.tiles.is_empty()); let mut tile = &mut surface.tiles[0]; if let Some(info) = self.composite_surfaces.get(&external_image) { tile.valid_rect = DeviceIntRect::from_size(info.size); return; } // If the surface has an attached external image, attempt to lock the external image // for compositing. Yields a descriptor of textures and data necessary for their // interpretation on success. let mut info = SWGLCompositeSurfaceInfo { yuv_planes: 0, textures: [0; 3], color_space: YuvRangedColorSpace::GbrIdentity, color_depth: ColorDepth::Color8, size: DeviceIntSize::zero(), }; if self.compositor.lock_composite_surface(device, self.gl.into(), external_image, &mut info) { tile.valid_rect = DeviceIntRect::from_size(info.size); self.composite_surfaces.insert(external_image, info); } else { tile.valid_rect = DeviceIntRect::zero(); } } } } /// Look for any attached external images that have been locked and then unlock them. fn unlock_composite_surfaces(&mut self, device: &mut Device) { for &external_image in self.composite_surfaces.keys() { self.compositor.unlock_composite_surface(device, self.gl.into(), external_image); } self.composite_surfaces.clear(); } /// Issue composites for any tiles that are no longer blocked following a tile update. /// We process all surfaces and tiles in the order they were queued. fn flush_composites(&self, tile_id: &NativeTileId, surface: &SwSurface, tile: &SwTile) { let composite_thread = match &self.composite_thread { Some(composite_thread) => composite_thread, None => return, }; // Look for the tile in the frame list and composite it if it has no dependencies. let mut frame_surfaces = self .frame_surfaces .iter() .skip_while(|&(ref id, _, _, _)| *id != tile_id.surface_id); let (overlap_rect, mut lock) = match frame_surfaces.next() { Some(&(_, ref transform, ref clip_rect, filter)) => { // Remove invalid tile's update dependency. if tile.invalid.get() { tile.overlaps.set(tile.overlaps.get() - 1); } // If the tile still has overlaps, keep deferring it till later. if tile.overlaps.get() > 0 { return; } // Otherwise, the tile's dependencies are all resolved, so composite it. let mut lock = composite_thread.lock(); self.queue_composite(surface, transform, clip_rect, filter, tile, &mut lock); // Finally, get the tile's overlap rect used for tracking dependencies match tile.overlap_rect(surface, transform, clip_rect) { Some(overlap_rect) => (overlap_rect, lock), None => return, } } None => return, }; // Accumulate rects whose dependencies have been satisfied from this update. // Store the union of all these bounds to quickly reject unaffected tiles. let mut flushed_bounds = overlap_rect; let mut flushed_rects = vec![overlap_rect]; // Check surfaces following the update in the frame list and see if they would overlap it. for &(ref id, ref transform, ref clip_rect, filter) in frame_surfaces { // If the clip rect doesn't overlap the conservative bounds, we can skip the whole surface. if !flushed_bounds.intersects(clip_rect) { continue; } if let Some(surface) = self.surfaces.get(&id) { // Search through the surface's tiles for any blocked on this update and queue jobs for them. for tile in &surface.tiles { let mut overlaps = tile.overlaps.get(); // Only check tiles that have existing unresolved dependencies if overlaps == 0 { continue; } // Get this tile's overlap rect for tracking dependencies let overlap_rect = match tile.overlap_rect(surface, transform, clip_rect) { Some(overlap_rect) => overlap_rect, None => continue, }; // Do a quick check to see if the tile overlaps the conservative bounds. if !overlap_rect.intersects(&flushed_bounds) { continue; } // Decrement the overlap count if this tile is dependent on any flushed rects. for flushed_rect in &flushed_rects { if overlap_rect.intersects(flushed_rect) { overlaps -= 1; } } if overlaps != tile.overlaps.get() { // If the overlap count changed, this tile had a dependency on some flush rects. // If the count hit zero, it is ready to composite. tile.overlaps.set(overlaps); if overlaps == 0 { self.queue_composite(surface, transform, clip_rect, filter, tile, &mut lock); // Record that the tile got flushed to update any downwind dependencies. flushed_bounds = flushed_bounds.union(&overlap_rect); flushed_rects.push(overlap_rect); } } } } } } } impl Compositor for SwCompositor { fn create_surface( &mut self, device: &mut Device, id: NativeSurfaceId, virtual_offset: DeviceIntPoint, tile_size: DeviceIntSize, is_opaque: bool, ) { if self.use_native_compositor { self.compositor.create_surface(device, id, virtual_offset, tile_size, is_opaque); } self.max_tile_size = DeviceIntSize::new( self.max_tile_size.width.max(tile_size.width), self.max_tile_size.height.max(tile_size.height), ); if self.depth_id.is_none() { self.depth_id = Some(self.gl.gen_textures(1)[0]); } self.surfaces.insert(id, SwSurface::new(tile_size, is_opaque)); } fn create_external_surface(&mut self, device: &mut Device, id: NativeSurfaceId, is_opaque: bool) { if self.use_native_compositor { self.compositor.create_external_surface(device, id, is_opaque); } self.surfaces .insert(id, SwSurface::new(DeviceIntSize::zero(), is_opaque)); } fn create_backdrop_surface(&mut self, _device: &mut Device, _id: NativeSurfaceId, _color: ColorF) { unreachable!("Not implemented.") } fn destroy_surface(&mut self, device: &mut Device, id: NativeSurfaceId) { if let Some(surface) = self.surfaces.remove(&id) { self.deinit_surface(&surface); } if self.use_native_compositor { self.compositor.destroy_surface(device, id); } if self.surfaces.is_empty() { if let Some(depth_id) = self.depth_id.take() { self.gl.delete_textures(&[depth_id]); } } } fn deinit(&mut self, device: &mut Device) { if let Some(ref composite_thread) = self.composite_thread { composite_thread.deinit(); } for surface in self.surfaces.values() { self.deinit_surface(surface); } if let Some(depth_id) = self.depth_id.take() { self.gl.delete_textures(&[depth_id]); } if self.use_native_compositor { self.compositor.deinit(device); } } fn create_tile(&mut self, device: &mut Device, id: NativeTileId) { if self.use_native_compositor { self.compositor.create_tile(device, id); } if let Some(surface) = self.surfaces.get_mut(&id.surface_id) { let mut tile = SwTile::new(id.x, id.y); tile.color_id = self.gl.gen_textures(1)[0]; tile.fbo_id = self.gl.gen_framebuffers(1)[0]; let mut prev_fbo = [0]; unsafe { self.gl.get_integer_v(gl::DRAW_FRAMEBUFFER_BINDING, &mut prev_fbo); } self.gl.bind_framebuffer(gl::DRAW_FRAMEBUFFER, tile.fbo_id); self.gl.framebuffer_texture_2d( gl::DRAW_FRAMEBUFFER, gl::COLOR_ATTACHMENT0, gl::TEXTURE_2D, tile.color_id, 0, ); self.gl.framebuffer_texture_2d( gl::DRAW_FRAMEBUFFER, gl::DEPTH_ATTACHMENT, gl::TEXTURE_2D, self.depth_id.expect("depth texture should be initialized"), 0, ); self.gl.bind_framebuffer(gl::DRAW_FRAMEBUFFER, prev_fbo[0] as gl::GLuint); surface.tiles.push(tile); } } fn destroy_tile(&mut self, device: &mut Device, id: NativeTileId) { if let Some(surface) = self.surfaces.get_mut(&id.surface_id) { if let Some(idx) = surface.tiles.iter().position(|t| t.x == id.x && t.y == id.y) { let tile = surface.tiles.remove(idx); self.deinit_tile(&tile); } } if self.use_native_compositor { self.compositor.destroy_tile(device, id); } } fn attach_external_image(&mut self, device: &mut Device, id: NativeSurfaceId, external_image: ExternalImageId) { if self.use_native_compositor { self.compositor.attach_external_image(device, id, external_image); } if let Some(surface) = self.surfaces.get_mut(&id) { // Surfaces with attached external images have a single tile at the origin encompassing // the entire surface. assert!(surface.tile_size.is_empty()); surface.external_image = Some(external_image); if surface.tiles.is_empty() { surface.tiles.push(SwTile::new(0, 0)); } } } fn invalidate_tile(&mut self, device: &mut Device, id: NativeTileId, valid_rect: DeviceIntRect) { if self.use_native_compositor { self.compositor.invalidate_tile(device, id, valid_rect); } if let Some(surface) = self.surfaces.get_mut(&id.surface_id) { if let Some(tile) = surface.tiles.iter_mut().find(|t| t.x == id.x && t.y == id.y) { tile.invalid.set(true); tile.valid_rect = valid_rect; } } } fn bind(&mut self, device: &mut Device, id: NativeTileId, dirty_rect: DeviceIntRect, valid_rect: DeviceIntRect) -> NativeSurfaceInfo { let mut surface_info = NativeSurfaceInfo { origin: DeviceIntPoint::zero(), fbo_id: 0, }; self.cur_tile = id; if let Some(surface) = self.surfaces.get_mut(&id.surface_id) { if let Some(tile) = surface.tiles.iter_mut().find(|t| t.x == id.x && t.y == id.y) { assert_eq!(tile.valid_rect, valid_rect); if valid_rect.is_empty() { return surface_info; } let mut stride = 0; let mut buf = ptr::null_mut(); if self.use_native_compositor { if let Some(tile_info) = self.compositor.map_tile(device, id, dirty_rect, valid_rect) { stride = tile_info.stride; buf = tile_info.data; } } self.gl.set_texture_buffer( tile.color_id, gl::RGBA8, valid_rect.width(), valid_rect.height(), stride, buf, surface.tile_size.width, surface.tile_size.height, ); // Reallocate the shared depth buffer to fit the valid rect, but within // a buffer sized to actually fit at least the maximum possible tile size. // The maximum tile size is supplied to avoid reallocation by ensuring the // allocated buffer is actually big enough to accommodate the largest tile // size requested by any used surface, even though supplied valid rect may // actually be much smaller than this. This will only force a texture // reallocation inside SWGL if the maximum tile size has grown since the // last time it was supplied, instead simply reusing the buffer if the max // tile size is not bigger than what was previously allocated. self.gl.set_texture_buffer( self.depth_id.expect("depth texture should be initialized"), gl::DEPTH_COMPONENT, valid_rect.width(), valid_rect.height(), 0, ptr::null_mut(), self.max_tile_size.width, self.max_tile_size.height, ); surface_info.fbo_id = tile.fbo_id; surface_info.origin -= valid_rect.min.to_vector(); } } surface_info } fn unbind(&mut self, device: &mut Device) { let id = self.cur_tile; if let Some(surface) = self.surfaces.get(&id.surface_id) { if let Some(tile) = surface.tiles.iter().find(|t| t.x == id.x && t.y == id.y) { if tile.valid_rect.is_empty() { // If we didn't actually render anything, then just queue any // dependencies. self.flush_composites(&id, surface, tile); return; } // Force any delayed clears to be resolved. self.gl.resolve_framebuffer(tile.fbo_id); if self.use_native_compositor { self.compositor.unmap_tile(device); } else { // If we're not relying on a native compositor, then composite // any tiles that are dependent on this tile being updated but // are otherwise ready to composite. self.flush_composites(&id, surface, tile); } } } } fn begin_frame(&mut self, device: &mut Device) { self.reset_overlaps(); if self.use_native_compositor { self.compositor.begin_frame(device); } } fn add_surface( &mut self, device: &mut Device, id: NativeSurfaceId, transform: CompositorSurfaceTransform, clip_rect: DeviceIntRect, filter: ImageRendering, ) { if self.use_native_compositor { self.compositor.add_surface(device, id, transform, clip_rect, filter); } if self.composite_thread.is_some() { // If the surface has an attached external image, try to lock that now. self.try_lock_composite_surface(device, &id); // If we're already busy compositing, then add to the queue of late // surfaces instead of trying to sort into the main frame queue. // These late surfaces will not have any overlap tracking done for // them and must be processed synchronously at the end of the frame. if self.is_compositing { self.late_surfaces.push((id, transform, clip_rect, filter)); return; } } self.frame_surfaces.push((id, transform, clip_rect, filter)); } /// Now that all the dependency graph nodes have been built, start queuing /// composition jobs. Any surfaces that get added after this point in the /// frame will not have overlap dependencies assigned and so must instead /// be added to the late_surfaces queue to be processed at the end of the /// frame. fn start_compositing(&mut self, device: &mut Device, clear_color: ColorF, dirty_rects: &[DeviceIntRect], _opaque_rects: &[DeviceIntRect]) { self.is_compositing = true; // Opaque rects are currently only computed here, not by WR itself, so we // ignore the passed parameter and forward our own version onto the native // compositor. let mut opaque_rects: Vec = Vec::new(); for &(ref id, ref transform, ref clip_rect, _filter) in &self.frame_surfaces { if let Some(surface) = self.surfaces.get(id) { if !surface.is_opaque { continue; } for tile in &surface.tiles { if let Some(rect) = tile.overlap_rect(surface, transform, clip_rect) { opaque_rects.push(rect); } } } } self.compositor.start_compositing(device, clear_color, dirty_rects, &opaque_rects); if let Some(dirty_rect) = dirty_rects .iter() .fold(DeviceIntRect::zero(), |acc, dirty_rect| acc.union(dirty_rect)) .to_non_empty() { // Factor dirty rect into surface clip rects for &mut (_, _, ref mut clip_rect, _) in &mut self.frame_surfaces { *clip_rect = clip_rect.intersection(&dirty_rect).unwrap_or_default(); } } self.occlude_surfaces(); // Discard surfaces that are entirely clipped out self.frame_surfaces .retain(|&(_, _, ref clip_rect, _)| !clip_rect.is_empty()); if let Some(ref composite_thread) = self.composite_thread { // Compute overlap dependencies for surfaces. for &(ref id, ref transform, ref clip_rect, _filter) in &self.frame_surfaces { if let Some(surface) = self.surfaces.get(id) { for tile in &surface.tiles { self.init_overlaps(id, surface, tile, transform, clip_rect); } } } self.locked_framebuffer = self.gl.lock_framebuffer(0); composite_thread.prepare_for_composites(); // Issue any initial composite jobs for the SwComposite thread. let mut lock = composite_thread.lock(); for &(ref id, ref transform, ref clip_rect, filter) in &self.frame_surfaces { if let Some(surface) = self.surfaces.get(id) { for tile in &surface.tiles { if tile.overlaps.get() == 0 { // Not dependent on any tiles, so go ahead and composite now. self.queue_composite(surface, transform, clip_rect, filter, tile, &mut lock); } } } } } } fn end_frame(&mut self, device: &mut Device,) { self.is_compositing = false; if self.use_native_compositor { self.compositor.end_frame(device); } else if let Some(ref composite_thread) = self.composite_thread { // Need to wait for the SwComposite thread to finish any queued jobs. composite_thread.wait_for_composites(false); if !self.late_surfaces.is_empty() { // All of the main frame surface have been processed by now. But if there // are any late surfaces, we need to kick off a new synchronous composite // phase. These late surfaces don't have any overlap/dependency tracking, // so we just queue them directly and wait synchronously for the composite // thread to process them in order. composite_thread.prepare_for_composites(); { let mut lock = composite_thread.lock(); for &(ref id, ref transform, ref clip_rect, filter) in &self.late_surfaces { if let Some(surface) = self.surfaces.get(id) { for tile in &surface.tiles { self.queue_composite(surface, transform, clip_rect, filter, tile, &mut lock); } } } } composite_thread.wait_for_composites(true); } self.locked_framebuffer = None; self.unlock_composite_surfaces(device); } self.frame_surfaces.clear(); self.late_surfaces.clear(); self.reset_overlaps(); } fn enable_native_compositor(&mut self, device: &mut Device, enable: bool) { // TODO: The SwComposite thread is not properly instantiated if this is // ever actually toggled. assert_eq!(self.use_native_compositor, enable); self.compositor.enable_native_compositor(device, enable); self.use_native_compositor = enable; } fn get_capabilities(&self, device: &mut Device) -> CompositorCapabilities { self.compositor.get_capabilities(device) } fn get_window_visibility(&self, device: &mut Device) -> WindowVisibility { self.compositor.get_window_visibility(device) } }