diff options
Diffstat (limited to 'gfx/webrender_bindings/src/swgl_bindings.rs')
-rw-r--r-- | gfx/webrender_bindings/src/swgl_bindings.rs | 1766 |
1 files changed, 1766 insertions, 0 deletions
diff --git a/gfx/webrender_bindings/src/swgl_bindings.rs b/gfx/webrender_bindings/src/swgl_bindings.rs new file mode 100644 index 0000000000..c5a9434b9b --- /dev/null +++ b/gfx/webrender_bindings/src/swgl_bindings.rs @@ -0,0 +1,1766 @@ +/* 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 bindings::{GeckoProfilerThreadListener, WrCompositor}; +use gleam::{gl, gl::GLenum, gl::Gl}; +use std::cell::{Cell, UnsafeCell}; +use std::collections::{hash_map::HashMap, VecDeque}; +use std::ops::{Deref, DerefMut}; +use std::os::raw::c_void; +use std::ptr; +use std::rc::Rc; +use std::sync::atomic::{AtomicIsize, AtomicPtr, AtomicU32, AtomicU8, Ordering}; +use std::sync::{Arc, Condvar, Mutex, MutexGuard}; +use std::thread; +use webrender::{ + api::units::*, api::ColorDepth, api::ExternalImageId, api::ImageRendering, api::YuvColorSpace, Compositor, + CompositorCapabilities, CompositorSurfaceTransform, NativeSurfaceId, NativeSurfaceInfo, NativeTileId, + ThreadListener, +}; + +#[no_mangle] +pub extern "C" fn wr_swgl_create_context() -> *mut c_void { + swgl::Context::create().into() +} + +#[no_mangle] +pub extern "C" fn wr_swgl_reference_context(ctx: *mut c_void) { + swgl::Context::from(ctx).reference(); +} + +#[no_mangle] +pub extern "C" fn wr_swgl_destroy_context(ctx: *mut c_void) { + swgl::Context::from(ctx).destroy(); +} + +#[no_mangle] +pub extern "C" fn wr_swgl_make_current(ctx: *mut c_void) { + swgl::Context::from(ctx).make_current(); +} + +#[no_mangle] +pub extern "C" fn wr_swgl_init_default_framebuffer( + ctx: *mut c_void, + x: i32, + y: i32, + width: i32, + height: i32, + stride: i32, + buf: *mut c_void, +) { + swgl::Context::from(ctx).init_default_framebuffer(x, y, width, height, stride, buf); +} + +#[no_mangle] +pub extern "C" fn wr_swgl_gen_texture(ctx: *mut c_void) -> u32 { + swgl::Context::from(ctx).gen_textures(1)[0] +} + +#[no_mangle] +pub extern "C" fn wr_swgl_delete_texture(ctx: *mut c_void, tex: u32) { + swgl::Context::from(ctx).delete_textures(&[tex]); +} + +#[no_mangle] +pub extern "C" fn wr_swgl_set_texture_parameter(ctx: *mut c_void, tex: u32, pname: u32, param: i32) { + swgl::Context::from(ctx).set_texture_parameter(tex, pname, param); +} + +#[no_mangle] +pub extern "C" fn wr_swgl_set_texture_buffer( + ctx: *mut c_void, + tex: u32, + internal_format: u32, + width: i32, + height: i32, + stride: i32, + buf: *mut c_void, + min_width: i32, + min_height: i32, +) { + swgl::Context::from(ctx).set_texture_buffer( + tex, + internal_format, + width, + height, + stride, + buf, + min_width, + min_height, + ); +} + +/// Descriptor for a locked surface that will be directly composited by SWGL. +#[repr(C)] +struct WrSWGLCompositeSurfaceInfo { + /// The number of YUV planes in the surface. 0 indicates non-YUV BGRA. + /// 1 is interleaved YUV. 2 is NV12. 3 is planar YUV. + yuv_planes: u32, + /// Textures for planes of the surface, or 0 if not applicable. + textures: [u32; 3], + /// Color space of surface if using a YUV format. + color_space: YuvColorSpace, + /// Color depth of surface if using a YUV format. + color_depth: ColorDepth, + /// The actual source surface size before transformation. + size: DeviceIntSize, +} + +extern "C" { + fn wr_swgl_lock_composite_surface( + ctx: *mut c_void, + external_image_id: ExternalImageId, + composite_info: *mut WrSWGLCompositeSurfaceInfo, + ) -> bool; + fn wr_swgl_unlock_composite_surface(ctx: *mut c_void, external_image_id: ExternalImageId); +} + +pub struct SwTile { + x: i32, + y: i32, + fbo_id: u32, + color_id: u32, + tex_id: u32, + pbo_id: u32, + dirty_rect: DeviceIntRect, + 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<u32>, + /// Whether the tile's contents has been invalidated + invalid: Cell<bool>, + /// 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, + tex_id: 0, + pbo_id: 0, + dirty_rect: DeviceIntRect::zero(), + valid_rect: DeviceIntRect::zero(), + overlaps: Cell::new(0), + invalid: Cell::new(false), + graph_node: SwCompositeGraphNode::new(), + } + } + + fn origin(&self, surface: &SwSurface) -> DeviceIntPoint { + DeviceIntPoint::new(self.x * surface.tile_size.width, self.y * surface.tile_size.height) + } + + /// 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<DeviceIntRect> { + let origin = self.origin(surface); + let bounds = self.valid_rect.translate(origin.to_vector()); + let device_rect = transform.outer_transformed_rect(&bounds.to_f32())?.round_out().to_i32(); + device_rect.intersection(clip_rect) + } + + /// 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)> { + // Offset the valid rect to the appropriate surface origin. + let valid = self.valid_rect.translate(self.origin(surface).to_vector()); + // The destination rect is the valid rect transformed and then clipped. + let dest_rect = transform.outer_transformed_rect(&valid.to_f32())?.round_out().to_i32(); + if !dest_rect.intersects(clip_rect) { + 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 + .outer_transformed_rect(&dest_rect.to_f32())? + .round() + .to_i32() + .translate(-valid.origin.to_vector()); + Some((src_rect, dest_rect, transform.m22 < 0.0)) + } +} + +pub struct SwSurface { + tile_size: DeviceIntSize, + is_opaque: bool, + tiles: Vec<SwTile>, + /// An attached external image for this surface. + external_image: Option<ExternalImageId>, + /// Descriptor for the external image if successfully locked for composite. + composite_surface: Option<WrSWGLCompositeSurfaceInfo>, +} + +impl SwSurface { + fn new(tile_size: DeviceIntSize, is_opaque: bool) -> Self { + SwSurface { + tile_size, + is_opaque, + tiles: Vec::new(), + external_image: None, + composite_surface: None, + } + } +} + +fn image_rendering_to_gl_filter(filter: ImageRendering) -> gl::GLenum { + match filter { + ImageRendering::Pixelated => gl::NEAREST, + ImageRendering::Auto | ImageRendering::CrispEdges => gl::LINEAR, + } +} + +struct DrawTileHelper { + gl: Rc<dyn gl::Gl>, + prog: u32, + quad_vbo: u32, + quad_vao: u32, + dest_matrix_loc: i32, + tex_matrix_loc: i32, +} + +impl DrawTileHelper { + fn new(gl: Rc<dyn gl::Gl>) -> Self { + let quad_vbo = gl.gen_buffers(1)[0]; + gl.bind_buffer(gl::ARRAY_BUFFER, quad_vbo); + let quad_data: [f32; 8] = [0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0]; + gl::buffer_data(&*gl, gl::ARRAY_BUFFER, &quad_data, gl::STATIC_DRAW); + + let quad_vao = gl.gen_vertex_arrays(1)[0]; + gl.bind_vertex_array(quad_vao); + gl.enable_vertex_attrib_array(0); + gl.vertex_attrib_pointer(0, 2, gl::FLOAT, false, 0, 0); + gl.bind_vertex_array(0); + + let version = match gl.get_type() { + gl::GlType::Gl => "#version 150", + gl::GlType::Gles => "#version 300 es", + }; + let vert_source = " + in vec2 aVert; + uniform mat3 uDestMatrix; + uniform mat3 uTexMatrix; + out vec2 vTexCoord; + void main(void) { + gl_Position = vec4((uDestMatrix * vec3(aVert, 1.0)).xy, 0.0, 1.0); + vTexCoord = (uTexMatrix * vec3(aVert, 1.0)).xy; + } + "; + let vs = gl.create_shader(gl::VERTEX_SHADER); + gl.shader_source(vs, &[version.as_bytes(), vert_source.as_bytes()]); + gl.compile_shader(vs); + let frag_source = " + #ifdef GL_ES + #ifdef GL_FRAGMENT_PRECISION_HIGH + precision highp float; + #else + precision mediump float; + #endif + #endif + in vec2 vTexCoord; + out vec4 oFragColor; + uniform sampler2D uTex; + void main(void) { + oFragColor = texture(uTex, vTexCoord); + } + "; + let fs = gl.create_shader(gl::FRAGMENT_SHADER); + gl.shader_source(fs, &[version.as_bytes(), frag_source.as_bytes()]); + gl.compile_shader(fs); + + let prog = gl.create_program(); + gl.attach_shader(prog, vs); + gl.attach_shader(prog, fs); + gl.bind_attrib_location(prog, 0, "aVert"); + gl.link_program(prog); + + let mut status = [0]; + unsafe { + gl.get_program_iv(prog, gl::LINK_STATUS, &mut status); + } + assert!(status[0] != 0); + + //println!("vert: {}", gl.get_shader_info_log(vs)); + //println!("frag: {}", gl.get_shader_info_log(fs)); + //println!("status: {}, {}", status[0], gl.get_program_info_log(prog)); + + gl.use_program(prog); + let dest_matrix_loc = gl.get_uniform_location(prog, "uDestMatrix"); + assert!(dest_matrix_loc != -1); + let tex_matrix_loc = gl.get_uniform_location(prog, "uTexMatrix"); + assert!(tex_matrix_loc != -1); + let tex_loc = gl.get_uniform_location(prog, "uTex"); + assert!(tex_loc != -1); + gl.uniform_1i(tex_loc, 0); + gl.use_program(0); + + gl.delete_shader(vs); + gl.delete_shader(fs); + + DrawTileHelper { + gl, + prog, + quad_vao, + quad_vbo, + dest_matrix_loc, + tex_matrix_loc, + } + } + + fn deinit(&self) { + self.gl.delete_program(self.prog); + self.gl.delete_vertex_arrays(&[self.quad_vao]); + self.gl.delete_buffers(&[self.quad_vbo]); + } + + fn enable(&self, viewport: &DeviceIntRect) { + self.gl.viewport( + viewport.origin.x, + viewport.origin.y, + viewport.size.width, + viewport.size.height, + ); + self.gl.bind_vertex_array(self.quad_vao); + self.gl.use_program(self.prog); + self.gl.active_texture(gl::TEXTURE0); + } + + fn draw( + &self, + viewport: &DeviceIntRect, + dest: &DeviceIntRect, + src: &DeviceIntRect, + _clip: &DeviceIntRect, + surface: &SwSurface, + tile: &SwTile, + flip_y: bool, + filter: GLenum, + ) { + let dx = dest.origin.x as f32 / viewport.size.width as f32; + let dy = dest.origin.y as f32 / viewport.size.height as f32; + let dw = dest.size.width as f32 / viewport.size.width as f32; + let dh = dest.size.height as f32 / viewport.size.height as f32; + self.gl.uniform_matrix_3fv( + self.dest_matrix_loc, + false, + &[ + 2.0 * dw, + 0.0, + 0.0, + 0.0, + if flip_y { 2.0 * dh } else { -2.0 * dh }, + 0.0, + -1.0 + 2.0 * dx, + if flip_y { -1.0 + 2.0 * dy } else { 1.0 - 2.0 * dy }, + 1.0, + ], + ); + let sx = src.origin.x as f32 / surface.tile_size.width as f32; + let sy = src.origin.y as f32 / surface.tile_size.height as f32; + let sw = src.size.width as f32 / surface.tile_size.width as f32; + let sh = src.size.height as f32 / surface.tile_size.height as f32; + self.gl + .uniform_matrix_3fv(self.tex_matrix_loc, false, &[sw, 0.0, 0.0, 0.0, sh, 0.0, sx, sy, 1.0]); + + self.gl.bind_texture(gl::TEXTURE_2D, tile.tex_id); + self.gl + .tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, filter as gl::GLint); + self.gl + .tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, filter as gl::GLint); + self.gl.draw_arrays(gl::TRIANGLE_STRIP, 0, 4); + } + + fn disable(&self) { + self.gl.use_program(0); + self.gl.bind_vertex_array(0); + } +} + +/// 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, + YuvColorSpace, + 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_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: u8) { + // Calculate the Y extents for the job's band, starting at the current index and spanning to + // the following index. + let band_index = band_index as i32; + let num_bands = self.num_bands as i32; + let band_offset = (self.clipped_dst.size.height * band_index) / num_bands; + let band_height = (self.clipped_dst.size.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::new( + DeviceIntPoint::new(self.clipped_dst.origin.x, self.clipped_dst.origin.y + band_offset), + DeviceIntSize::new(self.clipped_dst.size.width, band_height), + ); + match self.locked_src { + SwCompositeSource::BGRA(ref resource) => { + self.locked_dst.composite( + resource, + self.src_rect.origin.x, + self.src_rect.origin.y, + self.src_rect.size.width, + self.src_rect.size.height, + self.dst_rect.origin.x, + self.dst_rect.origin.y, + self.dst_rect.size.width, + self.dst_rect.size.height, + self.opaque, + self.flip_y, + image_rendering_to_gl_filter(self.filter), + band_clip.origin.x, + band_clip.origin.y, + band_clip.size.width, + band_clip.size.height, + ); + } + SwCompositeSource::YUV(ref y, ref u, ref v, color_space, color_depth) => { + let swgl_color_space = match color_space { + YuvColorSpace::Rec601 => swgl::YUVColorSpace::Rec601, + YuvColorSpace::Rec709 => swgl::YUVColorSpace::Rec709, + YuvColorSpace::Rec2020 => swgl::YUVColorSpace::Rec2020, + YuvColorSpace::Identity => swgl::YUVColorSpace::Identity, + }; + self.locked_dst.composite_yuv( + y, + u, + v, + swgl_color_space, + color_depth.bit_depth(), + self.src_rect.origin.x, + self.src_rect.origin.y, + self.src_rect.size.width, + self.src_rect.size.height, + self.dst_rect.origin.x, + self.dst_rect.origin.y, + self.dst_rect.size.width, + self.dst_rect.size.height, + self.flip_y, + band_clip.origin.x, + band_clip.origin.y, + band_clip.size.width, + band_clip.size.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<UnsafeCell<SwCompositeGraphNode>>); + +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<SwCompositeJob>, + /// The maximum number of available bands associated with this job. + max_bands: AtomicU8, + /// 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 have been taken for processing. + band_index: AtomicU8, + /// 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<SwCompositeGraphNodeRef>, +} + +unsafe impl Sync for SwCompositeGraphNode {} + +impl SwCompositeGraphNode { + fn new() -> SwCompositeGraphNodeRef { + SwCompositeGraphNodeRef::new(SwCompositeGraphNode { + job: None, + max_bands: AtomicU8::new(0), + remaining_bands: AtomicU8::new(0), + band_index: AtomicU8::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.max_bands.store(0, Ordering::SeqCst); + self.remaining_bands.store(0, Ordering::SeqCst); + self.band_index.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.max_bands.store(num_bands, Ordering::SeqCst); + self.remaining_bands.store(num_bands, 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 + } + + fn take_band(&self) -> Option<u8> { + let band_index = self.band_index.fetch_add(1, Ordering::SeqCst); + if band_index < self.max_bands.load(Ordering::SeqCst) { + Some(band_index) + } else { + None + } + } + + /// Try to take the job from this node for processing and then process it within the current band. + fn process_job(&self, band_index: u8) { + 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<SwCompositeJobQueue>, + /// 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<SwCompositeGraphNode>, + /// Count of unprocessed jobs still in the queue + job_count: AtomicIsize, + /// 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, +} + +/// 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<SwCompositeGraphNodeRef>; + +/// 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<SwCompositeThread> { + let info = Arc::new(SwCompositeThread { + jobs: Mutex::new(SwCompositeJobQueue::new()), + current_job: AtomicPtr::new(ptr::null_mut()), + job_count: AtomicIsize::new(0), + jobs_available: Condvar::new(), + }); + 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. + .stack_size(32 * 1024) + .spawn(move || { + let thread_listener = GeckoProfilerThreadListener::new(); + thread_listener.thread_started(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); + } + thread_listener.thread_stopped(thread_name); + }) + .expect("Failed creating SwComposite thread"); + result + } + + fn deinit(&self) { + // Force the job count to be negative to signal the thread needs to exit. + self.job_count.store(isize::MIN / 2, 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: u8) { + // 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); + // Decrement the job count. + self.job_count.fetch_sub(1, Ordering::SeqCst); + } + + /// 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_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.size.width >= 64 && clipped_dst.size.height >= 64 { + (clipped_dst.size.height / 64).min(4) as u8 + } else { + 1 + }; + let job = SwCompositeJob { + locked_src, + locked_dst, + src_rect, + dst_rect, + clipped_dst, + opaque, + flip_y, + filter, + num_bands, + }; + self.job_count.fetch_add(num_bands as isize, Ordering::SeqCst); + if graph_node.set_job(job, num_bands) { + self.send_job(job_queue, graph_node); + } + } + + fn start_compositing(&self) { + // Initialize the job count to 1 to prevent spurious signaling of job completion + // in the middle of queuing compositing jobs until we're actually waiting for + // composition. + self.job_count.store(1, 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_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, u8)> { + let current_job_ptr = self.current_job.load(Ordering::SeqCst); + if let Some(current_job) = unsafe { current_job_ptr.as_mut() } { + if let Some(band) = current_job.take_band() { + return Some((current_job, band)); + } + self.current_job + .compare_and_swap(current_job_ptr, ptr::null_mut(), Ordering::Relaxed); + } + 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, u8)> { + // 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 + // value of the job count we may either wait for jobs to become + // available or exit. + match self.job_count.load(Ordering::SeqCst) { + // If we completed all available jobs, signal completion. If + // waiting inside the SwCompositeThread, then block waiting for + // more jobs to become available in a new frame. Otherwise, + // return immediately. + 0 => { + self.jobs_available.notify_all(); + if !wait { + return None; + } + } + // A negative job count signals to exit immediately. + job_count if job_count < 0 => return None, + _ => {} + } + // The SwCompositeThread needs to wait for jobs to become + // available to avoid busy waiting on the queue. + jobs = self.jobs_available.wait(jobs).unwrap(); + } + } + + /// 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) { + // Subtract off the bias to signal we're now waiting on composition and + // need to know if jobs are completed. + self.job_count.fetch_sub(1, Ordering::SeqCst); + // 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(); + // If the job count is non-zero here, then there are in-flight jobs. + while self.job_count.load(Ordering::SeqCst) > 0 { + jobs = self.jobs_available.wait(jobs).unwrap(); + } + } + + /// Check if there is a non-zero job count (including sentinel job) that + /// would indicate we are starting to already process jobs in the composite + /// thread. + fn is_busy_compositing(&self) -> bool { + self.job_count.load(Ordering::SeqCst) > 0 + } +} + +/// Adapter for RenderCompositors to work with SWGL that shuttles between +/// WebRender and the RenderCompositr via the Compositor API. +pub struct SwCompositor { + gl: swgl::Context, + native_gl: Option<Rc<dyn gl::Gl>>, + compositor: WrCompositor, + use_native_compositor: bool, + surfaces: HashMap<NativeSurfaceId, SwSurface>, + frame_surfaces: Vec<( + NativeSurfaceId, + CompositorSurfaceTransform, + DeviceIntRect, + ImageRendering, + )>, + /// 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<( + NativeSurfaceId, + CompositorSurfaceTransform, + DeviceIntRect, + ImageRendering, + )>, + cur_tile: NativeTileId, + draw_tile: Option<DrawTileHelper>, + /// 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. + depth_id: u32, + /// Instance of the SwComposite thread, only created if we are not relying + /// on OpenGL compositing or a native RenderCompositor. + composite_thread: Option<Arc<SwCompositeThread>>, + /// SWGL locked resource for sharing framebuffer with SwComposite thread + locked_framebuffer: Option<swgl::LockedResource>, +} + +impl SwCompositor { + pub fn new( + gl: swgl::Context, + native_gl: Option<Rc<dyn gl::Gl>>, + compositor: WrCompositor, + use_native_compositor: bool, + ) -> Self { + let depth_id = gl.gen_textures(1)[0]; + // Only create the SwComposite thread if we're neither using OpenGL composition nor a native + // render compositor. Thus, we are compositing into the main software framebuffer, which in + // that case benefits from compositing asynchronously while we are updating tiles. + assert!(native_gl.is_none() || !use_native_compositor); + let composite_thread = if native_gl.is_none() && !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(), + cur_tile: NativeTileId { + surface_id: NativeSurfaceId(0), + x: 0, + y: 0, + }, + draw_tile: native_gl.as_ref().map(|gl| DrawTileHelper::new(gl.clone())), + native_gl, + max_tile_size: DeviceIntSize::zero(), + depth_id, + composite_thread, + locked_framebuffer: None, + } + } + + fn deinit_shader(&mut self) { + if let Some(draw_tile) = &self.draw_tile { + draw_tile.deinit(); + } + self.draw_tile = None; + } + + fn deinit_tile(&self, tile: &SwTile) { + self.gl.delete_framebuffers(&[tile.fbo_id]); + self.gl.delete_textures(&[tile.color_id]); + if let Some(native_gl) = &self.native_gl { + native_gl.delete_textures(&[tile.tex_id]); + native_gl.delete_buffers(&[tile.pbo_id]); + } + } + + fn deinit_surface(&self, surface: &SwSurface) { + for tile in &surface.tiles { + self.deinit_tile(tile); + } + } + + /// 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_y)) = tile.composite_rects(surface, transform, clip_rect) { + let source = if surface.external_image.is_some() { + // If the surface has an attached external image, lock any textures supplied in the descriptor. + match surface.composite_surface { + 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_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, id: &NativeSurfaceId) { + if let Some(surface) = self.surfaces.get_mut(id) { + if let Some(external_image) = surface.external_image { + // 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 = WrSWGLCompositeSurfaceInfo { + yuv_planes: 0, + textures: [0; 3], + color_space: YuvColorSpace::Identity, + color_depth: ColorDepth::Color8, + size: DeviceIntSize::zero(), + }; + assert!(!surface.tiles.is_empty()); + let mut tile = &mut surface.tiles[0]; + if unsafe { wr_swgl_lock_composite_surface(self.gl.into(), external_image, &mut info) } { + tile.valid_rect = DeviceIntRect::from_size(info.size); + surface.composite_surface = Some(info); + } else { + tile.valid_rect = DeviceIntRect::zero(); + surface.composite_surface = None; + } + } + } + } + + /// Look for any attached external images that have been locked and then unlock them. + fn unlock_composite_surfaces(&mut self) { + for &(ref id, _, _, _) in self.frame_surfaces.iter().chain(self.late_surfaces.iter()) { + if let Some(surface) = self.surfaces.get_mut(id) { + if let Some(external_image) = surface.external_image { + if surface.composite_surface.is_some() { + unsafe { wr_swgl_unlock_composite_surface(self.gl.into(), external_image) }; + surface.composite_surface = None; + } + } + } + } + } + + /// 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, + id: NativeSurfaceId, + virtual_offset: DeviceIntPoint, + tile_size: DeviceIntSize, + is_opaque: bool, + ) { + if self.use_native_compositor { + self.compositor.create_surface(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), + ); + self.surfaces.insert(id, SwSurface::new(tile_size, is_opaque)); + } + + fn create_external_surface(&mut self, id: NativeSurfaceId, is_opaque: bool) { + if self.use_native_compositor { + self.compositor.create_external_surface(id, is_opaque); + } + self.surfaces + .insert(id, SwSurface::new(DeviceIntSize::zero(), is_opaque)); + } + + fn destroy_surface(&mut self, id: NativeSurfaceId) { + if let Some(surface) = self.surfaces.remove(&id) { + self.deinit_surface(&surface); + } + if self.use_native_compositor { + self.compositor.destroy_surface(id); + } + } + + fn deinit(&mut self) { + if let Some(ref composite_thread) = self.composite_thread { + composite_thread.deinit(); + } + + for surface in self.surfaces.values() { + self.deinit_surface(surface); + } + + self.gl.delete_textures(&[self.depth_id]); + + self.deinit_shader(); + + if self.use_native_compositor { + self.compositor.deinit(); + } + } + + fn create_tile(&mut self, id: NativeTileId) { + if self.use_native_compositor { + self.compositor.create_tile(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]; + 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, + 0, + ); + self.gl.bind_framebuffer(gl::DRAW_FRAMEBUFFER, 0); + + if let Some(native_gl) = &self.native_gl { + tile.tex_id = native_gl.gen_textures(1)[0]; + native_gl.bind_texture(gl::TEXTURE_2D, tile.tex_id); + native_gl.tex_image_2d( + gl::TEXTURE_2D, + 0, + gl::RGBA8 as gl::GLint, + surface.tile_size.width, + surface.tile_size.height, + 0, + gl::RGBA, + gl::UNSIGNED_BYTE, + None, + ); + native_gl.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as gl::GLint); + native_gl.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as gl::GLint); + native_gl.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as gl::GLint); + native_gl.tex_parameter_i(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as gl::GLint); + native_gl.bind_texture(gl::TEXTURE_2D, 0); + + tile.pbo_id = native_gl.gen_buffers(1)[0]; + native_gl.bind_buffer(gl::PIXEL_UNPACK_BUFFER, tile.pbo_id); + native_gl.buffer_data_untyped( + gl::PIXEL_UNPACK_BUFFER, + surface.tile_size.area() as isize * 4, + ptr::null(), + gl::DYNAMIC_DRAW, + ); + native_gl.bind_buffer(gl::PIXEL_UNPACK_BUFFER, 0); + } + + surface.tiles.push(tile); + } + } + + fn destroy_tile(&mut self, 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(id); + } + } + + fn attach_external_image(&mut self, id: NativeSurfaceId, external_image: ExternalImageId) { + if self.use_native_compositor { + self.compositor.attach_external_image(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, id: NativeTileId, valid_rect: DeviceIntRect) { + if self.use_native_compositor { + self.compositor.invalidate_tile(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, 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) { + tile.dirty_rect = dirty_rect; + 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(id, dirty_rect, valid_rect) { + stride = tile_info.stride; + buf = tile_info.data; + } + } else if let Some(native_gl) = &self.native_gl { + if tile.pbo_id != 0 { + native_gl.bind_buffer(gl::PIXEL_UNPACK_BUFFER, tile.pbo_id); + buf = native_gl.map_buffer_range( + gl::PIXEL_UNPACK_BUFFER, + 0, + valid_rect.size.area() as isize * 4, + gl::MAP_WRITE_BIT | gl::MAP_INVALIDATE_BUFFER_BIT, + ); // | gl::MAP_UNSYNCHRONIZED_BIT); + if buf != ptr::null_mut() { + stride = valid_rect.size.width * 4; + } else { + native_gl.bind_buffer(gl::PIXEL_UNPACK_BUFFER, 0); + native_gl.delete_buffers(&[tile.pbo_id]); + tile.pbo_id = 0; + } + } + } + self.gl.set_texture_buffer( + tile.color_id, + gl::RGBA8, + valid_rect.size.width, + valid_rect.size.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, + gl::DEPTH_COMPONENT16, + valid_rect.size.width, + valid_rect.size.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.origin.to_vector(); + } + } + + surface_info + } + + fn unbind(&mut self) { + 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() { + self.flush_composites(&id, surface, tile); + return; + } + + // get the color buffer even if we have a self.compositor, to make + // sure that any delayed clears are resolved + let (swbuf, _, _, stride) = self.gl.get_color_buffer(tile.fbo_id, true); + + if self.use_native_compositor { + self.compositor.unmap_tile(); + return; + } + + let native_gl = match &self.native_gl { + Some(native_gl) => native_gl, + None => { + // If we're not relying on a native compositor or OpenGL compositing, + // then composite any tiles that are dependent on this tile being + // updated but are otherwise ready to composite. + self.flush_composites(&id, surface, tile); + return; + } + }; + + assert!(stride % 4 == 0); + let buf = if tile.pbo_id != 0 { + native_gl.unmap_buffer(gl::PIXEL_UNPACK_BUFFER); + std::ptr::null_mut::<c_void>() + } else { + swbuf + }; + let dirty = tile.dirty_rect; + let src = unsafe { + (buf as *mut u32).offset( + (dirty.origin.y - tile.valid_rect.origin.y) as isize * (stride / 4) as isize + + (dirty.origin.x - tile.valid_rect.origin.x) as isize, + ) + }; + native_gl.active_texture(gl::TEXTURE0); + native_gl.bind_texture(gl::TEXTURE_2D, tile.tex_id); + native_gl.pixel_store_i(gl::UNPACK_ROW_LENGTH, stride / 4); + native_gl.tex_sub_image_2d_pbo( + gl::TEXTURE_2D, + 0, + dirty.origin.x, + dirty.origin.y, + dirty.size.width, + dirty.size.height, + gl::BGRA, + gl::UNSIGNED_BYTE, + src as _, + ); + native_gl.pixel_store_i(gl::UNPACK_ROW_LENGTH, 0); + if tile.pbo_id != 0 { + native_gl.bind_buffer(gl::PIXEL_UNPACK_BUFFER, 0); + } + + native_gl.bind_texture(gl::TEXTURE_2D, 0); + } + } + } + + fn begin_frame(&mut self) { + if self.use_native_compositor { + self.compositor.begin_frame(); + } + self.frame_surfaces.clear(); + self.late_surfaces.clear(); + + self.reset_overlaps(); + } + + fn add_surface( + &mut self, + id: NativeSurfaceId, + transform: CompositorSurfaceTransform, + clip_rect: DeviceIntRect, + filter: ImageRendering, + ) { + if self.use_native_compositor { + self.compositor.add_surface(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(&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.composite_thread.as_ref().unwrap().is_busy_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, + dirty_rects: &[DeviceIntRect], + _opaque_rects: &[DeviceIntRect], + ) { + // 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<DeviceIntRect> = 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(dirty_rects, &opaque_rects); + + if dirty_rects.len() == 1 { + // Factor dirty rect into surface clip rects and discard surfaces that are + // entirely clipped out. + for &mut (ref _id, ref _transform, ref mut clip_rect, _filter) in &mut self.frame_surfaces { + *clip_rect = clip_rect.intersection(&dirty_rects[0]).unwrap_or_default(); + } + self.frame_surfaces + .retain(|&(_, _, 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.start_compositing(); + // 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) { + if self.use_native_compositor { + self.compositor.end_frame(); + } else if let Some(native_gl) = &self.native_gl { + let (_, fw, fh, _) = self.gl.get_color_buffer(0, false); + let viewport = DeviceIntRect::from_size(DeviceIntSize::new(fw, fh)); + let draw_tile = self.draw_tile.as_ref().unwrap(); + draw_tile.enable(&viewport); + let mut blend = false; + native_gl.blend_func(gl::ONE, gl::ONE_MINUS_SRC_ALPHA); + 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 { + if blend { + native_gl.disable(gl::BLEND); + blend = false; + } + } else if !blend { + native_gl.enable(gl::BLEND); + blend = true; + } + for tile in &surface.tiles { + if let Some((src_rect, dst_rect, flip_y)) = tile.composite_rects(surface, transform, clip_rect) + { + draw_tile.draw( + &viewport, + &dst_rect, + &src_rect, + clip_rect, + surface, + tile, + flip_y, + image_rendering_to_gl_filter(filter), + ); + } + } + } + } + if blend { + native_gl.disable(gl::BLEND); + } + draw_tile.disable(); + } 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.start_compositing(); + { + 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(); + } + } + + fn enable_native_compositor(&mut self, enable: bool) { + self.compositor.enable_native_compositor(enable); + self.use_native_compositor = enable; + } + + fn get_capabilities(&self) -> CompositorCapabilities { + self.compositor.get_capabilities() + } +} |