1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5 use gleam::{gl, gl::Gl};
6 use std::cell::{Cell, UnsafeCell};
7 use std::collections::{hash_map::HashMap, VecDeque};
8 use std::ops::{Deref, DerefMut, Range};
9 use std::ptr;
10 use std::sync::atomic::{AtomicBool, AtomicI8, AtomicPtr, AtomicU32, AtomicU8, Ordering};
11 use std::sync::{Arc, Condvar, Mutex, MutexGuard};
12 use std::thread;
13 use crate::{
14 api::units::*, api::ColorDepth, api::ColorF, api::ExternalImageId, api::ImageRendering, api::YuvRangedColorSpace,
15 Compositor, CompositorCapabilities, CompositorSurfaceTransform, NativeSurfaceId, NativeSurfaceInfo, NativeTileId,
16 profiler, MappableCompositor, SWGLCompositeSurfaceInfo,
17 };
18
19 pub struct SwTile {
20 x: i32,
21 y: i32,
22 fbo_id: u32,
23 color_id: u32,
24 valid_rect: DeviceIntRect,
25 /// Composition of tiles must be ordered such that any tiles that may overlap
26 /// an invalidated tile in an earlier surface only get drawn after that tile
27 /// is actually updated. We store a count of the number of overlapping invalid
28 /// here, that gets decremented when the invalid tiles are finally updated so
29 /// that we know when it is finally safe to draw. Must use a Cell as we might
30 /// be analyzing multiple tiles and surfaces
31 overlaps: Cell<u32>,
32 /// Whether the tile's contents has been invalidated
33 invalid: Cell<bool>,
34 /// Graph node for job dependencies of this tile
35 graph_node: SwCompositeGraphNodeRef,
36 }
37
38 impl SwTile {
new(x: i32, y: i32) -> Self39 fn new(x: i32, y: i32) -> Self {
40 SwTile {
41 x,
42 y,
43 fbo_id: 0,
44 color_id: 0,
45 valid_rect: DeviceIntRect::zero(),
46 overlaps: Cell::new(0),
47 invalid: Cell::new(false),
48 graph_node: SwCompositeGraphNode::new(),
49 }
50 }
51
52 /// The offset of the tile in the local space of the surface before any
53 /// transform is applied.
origin(&self, surface: &SwSurface) -> DeviceIntPoint54 fn origin(&self, surface: &SwSurface) -> DeviceIntPoint {
55 DeviceIntPoint::new(self.x * surface.tile_size.width, self.y * surface.tile_size.height)
56 }
57
58 /// The offset valid rect positioned within the local space of the surface
59 /// before any transform is applied.
local_bounds(&self, surface: &SwSurface) -> DeviceIntRect60 fn local_bounds(&self, surface: &SwSurface) -> DeviceIntRect {
61 self.valid_rect.translate(self.origin(surface).to_vector())
62 }
63
64 /// Bounds used for determining overlap dependencies. This may either be the
65 /// full tile bounds or the actual valid rect, depending on whether the tile
66 /// is invalidated this frame. These bounds are more conservative as such and
67 /// may differ from the precise bounds used to actually composite the tile.
overlap_rect( &self, surface: &SwSurface, transform: &CompositorSurfaceTransform, clip_rect: &DeviceIntRect, ) -> Option<DeviceIntRect>68 fn overlap_rect(
69 &self,
70 surface: &SwSurface,
71 transform: &CompositorSurfaceTransform,
72 clip_rect: &DeviceIntRect,
73 ) -> Option<DeviceIntRect> {
74 let bounds = self.local_bounds(surface);
75 let device_rect = transform.outer_transformed_box2d(&bounds.to_f32())?.round_out().to_i32();
76 device_rect.intersection(clip_rect)
77 }
78
79 /// Determine if the tile's bounds may overlap the dependency rect if it were
80 /// to be composited at the given position.
may_overlap( &self, surface: &SwSurface, transform: &CompositorSurfaceTransform, clip_rect: &DeviceIntRect, dep_rect: &DeviceIntRect, ) -> bool81 fn may_overlap(
82 &self,
83 surface: &SwSurface,
84 transform: &CompositorSurfaceTransform,
85 clip_rect: &DeviceIntRect,
86 dep_rect: &DeviceIntRect,
87 ) -> bool {
88 self.overlap_rect(surface, transform, clip_rect)
89 .map_or(false, |r| r.intersects(dep_rect))
90 }
91
92 /// Get valid source and destination rectangles for composition of the tile
93 /// within a surface, bounded by the clipping rectangle. May return None if
94 /// it falls outside of the clip rect.
composite_rects( &self, surface: &SwSurface, transform: &CompositorSurfaceTransform, clip_rect: &DeviceIntRect, ) -> Option<(DeviceIntRect, DeviceIntRect, bool)>95 fn composite_rects(
96 &self,
97 surface: &SwSurface,
98 transform: &CompositorSurfaceTransform,
99 clip_rect: &DeviceIntRect,
100 ) -> Option<(DeviceIntRect, DeviceIntRect, bool)> {
101 // Offset the valid rect to the appropriate surface origin.
102 let valid = self.local_bounds(surface);
103 // The destination rect is the valid rect transformed and then clipped.
104 let dest_rect = transform.outer_transformed_box2d(&valid.to_f32())?.round_out().to_i32();
105 if !dest_rect.intersects(clip_rect) {
106 return None;
107 }
108 // To get a valid source rect, we need to inverse transform the clipped destination rect to find out the effect
109 // of the clip rect in source-space. After this, we subtract off the source-space valid rect origin to get
110 // a source rect that is now relative to the surface origin rather than absolute.
111 let inv_transform = transform.inverse()?;
112 let src_rect = inv_transform
113 .outer_transformed_box2d(&dest_rect.to_f32())?
114 .round()
115 .to_i32()
116 .translate(-valid.min.to_vector());
117 Some((src_rect, dest_rect, transform.m22 < 0.0))
118 }
119 }
120
121 pub struct SwSurface {
122 tile_size: DeviceIntSize,
123 is_opaque: bool,
124 tiles: Vec<SwTile>,
125 /// An attached external image for this surface.
126 external_image: Option<ExternalImageId>,
127 /// Descriptor for the external image if successfully locked for composite.
128 composite_surface: Option<SWGLCompositeSurfaceInfo>,
129 }
130
131 impl SwSurface {
new(tile_size: DeviceIntSize, is_opaque: bool) -> Self132 fn new(tile_size: DeviceIntSize, is_opaque: bool) -> Self {
133 SwSurface {
134 tile_size,
135 is_opaque,
136 tiles: Vec::new(),
137 external_image: None,
138 composite_surface: None,
139 }
140 }
141
142 /// Conserative approximation of local bounds of the surface by combining
143 /// the local bounds of all enclosed tiles.
local_bounds(&self) -> DeviceIntRect144 fn local_bounds(&self) -> DeviceIntRect {
145 let mut bounds = DeviceIntRect::zero();
146 for tile in &self.tiles {
147 bounds = bounds.union(&tile.local_bounds(self));
148 }
149 bounds
150 }
151
152 /// The transformed and clipped conservative device-space bounds of the
153 /// surface.
device_bounds( &self, transform: &CompositorSurfaceTransform, clip_rect: &DeviceIntRect, ) -> Option<DeviceIntRect>154 fn device_bounds(
155 &self,
156 transform: &CompositorSurfaceTransform,
157 clip_rect: &DeviceIntRect,
158 ) -> Option<DeviceIntRect> {
159 let bounds = self.local_bounds();
160 let device_rect = transform.outer_transformed_box2d(&bounds.to_f32())?.round_out().to_i32();
161 device_rect.intersection(clip_rect)
162 }
163 }
164
image_rendering_to_gl_filter(filter: ImageRendering) -> gl::GLenum165 fn image_rendering_to_gl_filter(filter: ImageRendering) -> gl::GLenum {
166 match filter {
167 ImageRendering::Pixelated => gl::NEAREST,
168 ImageRendering::Auto | ImageRendering::CrispEdges => gl::LINEAR,
169 }
170 }
171
172 /// A source for a composite job which can either be a single BGRA locked SWGL
173 /// resource or a collection of SWGL resources representing a YUV surface.
174 #[derive(Clone)]
175 enum SwCompositeSource {
176 BGRA(swgl::LockedResource),
177 YUV(
178 swgl::LockedResource,
179 swgl::LockedResource,
180 swgl::LockedResource,
181 YuvRangedColorSpace,
182 ColorDepth,
183 ),
184 }
185
186 /// Mark ExternalImage's renderer field as safe to send to SwComposite thread.
187 unsafe impl Send for SwCompositeSource {}
188
189 /// A tile composition job to be processed by the SwComposite thread.
190 /// Stores relevant details about the tile and where to composite it.
191 #[derive(Clone)]
192 struct SwCompositeJob {
193 /// Locked texture that will be unlocked immediately following the job
194 locked_src: SwCompositeSource,
195 /// Locked framebuffer that may be shared among many jobs
196 locked_dst: swgl::LockedResource,
197 src_rect: DeviceIntRect,
198 dst_rect: DeviceIntRect,
199 clipped_dst: DeviceIntRect,
200 opaque: bool,
201 flip_y: bool,
202 filter: ImageRendering,
203 /// The total number of bands for this job
204 num_bands: u8,
205 }
206
207 impl SwCompositeJob {
208 /// Process a composite job
process(&self, band_index: i32)209 fn process(&self, band_index: i32) {
210 // Bands are allocated in reverse order, but we want to process them in increasing order.
211 let num_bands = self.num_bands as i32;
212 let band_index = num_bands - 1 - band_index;
213 // Calculate the Y extents for the job's band, starting at the current index and spanning to
214 // the following index.
215 let band_offset = (self.clipped_dst.height() * band_index) / num_bands;
216 let band_height = (self.clipped_dst.height() * (band_index + 1)) / num_bands - band_offset;
217 // Create a rect that is the intersection of the band with the clipped dest
218 let band_clip = DeviceIntRect::from_origin_and_size(
219 DeviceIntPoint::new(self.clipped_dst.min.x, self.clipped_dst.min.y + band_offset),
220 DeviceIntSize::new(self.clipped_dst.width(), band_height),
221 );
222 match self.locked_src {
223 SwCompositeSource::BGRA(ref resource) => {
224 self.locked_dst.composite(
225 resource,
226 self.src_rect.min.x,
227 self.src_rect.min.y,
228 self.src_rect.width(),
229 self.src_rect.height(),
230 self.dst_rect.min.x,
231 self.dst_rect.min.y,
232 self.dst_rect.width(),
233 self.dst_rect.height(),
234 self.opaque,
235 self.flip_y,
236 image_rendering_to_gl_filter(self.filter),
237 band_clip.min.x,
238 band_clip.min.y,
239 band_clip.width(),
240 band_clip.height(),
241 );
242 }
243 SwCompositeSource::YUV(ref y, ref u, ref v, color_space, color_depth) => {
244 let swgl_color_space = match color_space {
245 YuvRangedColorSpace::Rec601Narrow => swgl::YuvRangedColorSpace::Rec601Narrow,
246 YuvRangedColorSpace::Rec601Full => swgl::YuvRangedColorSpace::Rec601Full,
247 YuvRangedColorSpace::Rec709Narrow => swgl::YuvRangedColorSpace::Rec709Narrow,
248 YuvRangedColorSpace::Rec709Full => swgl::YuvRangedColorSpace::Rec709Full,
249 YuvRangedColorSpace::Rec2020Narrow => swgl::YuvRangedColorSpace::Rec2020Narrow,
250 YuvRangedColorSpace::Rec2020Full => swgl::YuvRangedColorSpace::Rec2020Full,
251 YuvRangedColorSpace::GbrIdentity => swgl::YuvRangedColorSpace::GbrIdentity,
252 };
253 self.locked_dst.composite_yuv(
254 y,
255 u,
256 v,
257 swgl_color_space,
258 color_depth.bit_depth(),
259 self.src_rect.min.x,
260 self.src_rect.min.y,
261 self.src_rect.width(),
262 self.src_rect.height(),
263 self.dst_rect.min.x,
264 self.dst_rect.min.y,
265 self.dst_rect.width(),
266 self.dst_rect.height(),
267 self.flip_y,
268 band_clip.min.x,
269 band_clip.min.y,
270 band_clip.width(),
271 band_clip.height(),
272 );
273 }
274 }
275 }
276 }
277
278 /// A reference to a SwCompositeGraph node that can be passed from the render
279 /// thread to the SwComposite thread. Consistency of mutation is ensured in
280 /// SwCompositeGraphNode via use of Atomic operations that prevent more than
281 /// one thread from mutating SwCompositeGraphNode at once. This avoids using
282 /// messy and not-thread-safe RefCells or expensive Mutexes inside the graph
283 /// node and at least signals to the compiler that potentially unsafe coercions
284 /// are occurring.
285 #[derive(Clone)]
286 struct SwCompositeGraphNodeRef(Arc<UnsafeCell<SwCompositeGraphNode>>);
287
288 impl SwCompositeGraphNodeRef {
new(graph_node: SwCompositeGraphNode) -> Self289 fn new(graph_node: SwCompositeGraphNode) -> Self {
290 SwCompositeGraphNodeRef(Arc::new(UnsafeCell::new(graph_node)))
291 }
292
get(&self) -> &SwCompositeGraphNode293 fn get(&self) -> &SwCompositeGraphNode {
294 unsafe { &*self.0.get() }
295 }
296
get_mut(&self) -> &mut SwCompositeGraphNode297 fn get_mut(&self) -> &mut SwCompositeGraphNode {
298 unsafe { &mut *self.0.get() }
299 }
300
get_ptr_mut(&self) -> *mut SwCompositeGraphNode301 fn get_ptr_mut(&self) -> *mut SwCompositeGraphNode {
302 self.0.get()
303 }
304 }
305
306 unsafe impl Send for SwCompositeGraphNodeRef {}
307
308 impl Deref for SwCompositeGraphNodeRef {
309 type Target = SwCompositeGraphNode;
310
deref(&self) -> &Self::Target311 fn deref(&self) -> &Self::Target {
312 self.get()
313 }
314 }
315
316 impl DerefMut for SwCompositeGraphNodeRef {
deref_mut(&mut self) -> &mut Self::Target317 fn deref_mut(&mut self) -> &mut Self::Target {
318 self.get_mut()
319 }
320 }
321
322 /// Dependency graph of composite jobs to be completed. Keeps a list of child jobs that are dependent on the completion of this job.
323 /// 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
324 /// in-flight parent jobs that it depends on, the graph node is finally added to the job queue for processing.
325 struct SwCompositeGraphNode {
326 /// Job to be queued for this graph node once ready.
327 job: Option<SwCompositeJob>,
328 /// The number of remaining bands associated with this job. When this is
329 /// non-zero and the node has no more parents left, then the node is being
330 /// actively used by the composite thread to process jobs. Once it hits
331 /// zero, the owning thread (which brought it to zero) can safely retire
332 /// the node as no other thread is using it.
333 remaining_bands: AtomicU8,
334 /// The number of bands that are available for processing.
335 available_bands: AtomicI8,
336 /// Count of parents this graph node depends on. While this is non-zero the
337 /// node must ensure that it is only being actively mutated by the render
338 /// thread and otherwise never being accessed by the render thread.
339 parents: AtomicU32,
340 /// Graph nodes of child jobs that are dependent on this job
341 children: Vec<SwCompositeGraphNodeRef>,
342 }
343
344 unsafe impl Sync for SwCompositeGraphNode {}
345
346 impl SwCompositeGraphNode {
new() -> SwCompositeGraphNodeRef347 fn new() -> SwCompositeGraphNodeRef {
348 SwCompositeGraphNodeRef::new(SwCompositeGraphNode {
349 job: None,
350 remaining_bands: AtomicU8::new(0),
351 available_bands: AtomicI8::new(0),
352 parents: AtomicU32::new(0),
353 children: Vec::new(),
354 })
355 }
356
357 /// Reset the node's state for a new frame
reset(&mut self)358 fn reset(&mut self) {
359 self.job = None;
360 self.remaining_bands.store(0, Ordering::SeqCst);
361 self.available_bands.store(0, Ordering::SeqCst);
362 // Initialize parents to 1 as sentinel dependency for uninitialized job
363 // to avoid queuing unitialized job as unblocked child dependency.
364 self.parents.store(1, Ordering::SeqCst);
365 self.children.clear();
366 }
367
368 /// Add a dependent child node to dependency list. Update its parent count.
add_child(&mut self, child: SwCompositeGraphNodeRef)369 fn add_child(&mut self, child: SwCompositeGraphNodeRef) {
370 child.parents.fetch_add(1, Ordering::SeqCst);
371 self.children.push(child);
372 }
373
374 /// Install a job for this node. Return whether or not the job has any unprocessed parents
375 /// that would block immediate composition.
set_job(&mut self, job: SwCompositeJob, num_bands: u8) -> bool376 fn set_job(&mut self, job: SwCompositeJob, num_bands: u8) -> bool {
377 self.job = Some(job);
378 self.remaining_bands.store(num_bands, Ordering::SeqCst);
379 self.available_bands.store(num_bands as _, Ordering::SeqCst);
380 // Subtract off the sentinel parent dependency now that job is initialized and check
381 // whether there are any remaining parent dependencies to see if this job is ready.
382 self.parents.fetch_sub(1, Ordering::SeqCst) <= 1
383 }
384
385 /// Take an available band if possible. Also return whether there are no more bands left
386 /// so the caller may properly clean up after.
take_band(&self) -> (Option<i32>, bool)387 fn take_band(&self) -> (Option<i32>, bool) {
388 let available = self.available_bands.fetch_sub(1, Ordering::SeqCst);
389 if available > 0 {
390 (Some(available as i32 - 1), available == 1)
391 } else {
392 (None, true)
393 }
394 }
395
396 /// Try to take the job from this node for processing and then process it within the current band.
process_job(&self, band_index: i32)397 fn process_job(&self, band_index: i32) {
398 if let Some(ref job) = self.job {
399 job.process(band_index);
400 }
401 }
402
403 /// After processing a band, check all child dependencies and remove this parent from
404 /// their dependency counts. If applicable, queue the new child bands for composition.
unblock_children(&mut self, thread: &SwCompositeThread)405 fn unblock_children(&mut self, thread: &SwCompositeThread) {
406 if self.remaining_bands.fetch_sub(1, Ordering::SeqCst) > 1 {
407 return;
408 }
409 // Clear the job to release any locked resources.
410 self.job = None;
411 let mut lock = None;
412 for child in self.children.drain(..) {
413 // Remove the child's parent dependency on this node. If there are no more
414 // parent dependencies left, send the child job bands for composition.
415 if child.parents.fetch_sub(1, Ordering::SeqCst) <= 1 {
416 if lock.is_none() {
417 lock = Some(thread.lock());
418 }
419 thread.send_job(lock.as_mut().unwrap(), child);
420 }
421 }
422 }
423 }
424
425 /// The SwComposite thread processes a queue of composite jobs, also signaling
426 /// via a condition when all available jobs have been processed, as tracked by
427 /// the job count.
428 struct SwCompositeThread {
429 /// Queue of available composite jobs
430 jobs: Mutex<SwCompositeJobQueue>,
431 /// Cache of the current job being processed. This maintains a pointer to
432 /// the contents of the SwCompositeGraphNodeRef, which is safe due to the
433 /// fact that SwCompositor maintains a strong reference to the contents
434 /// in an SwTile to keep it alive while this is in use.
435 current_job: AtomicPtr<SwCompositeGraphNode>,
436 /// Condition signaled when either there are jobs available to process or
437 /// there are no more jobs left to process. Otherwise stated, this signals
438 /// when the job queue transitions from an empty to non-empty state or from
439 /// a non-empty to empty state.
440 jobs_available: Condvar,
441 /// Whether all available jobs have been processed.
442 jobs_completed: AtomicBool,
443 /// Whether the main thread is waiting for for job completeion.
444 waiting_for_jobs: AtomicBool,
445 /// Whether the SwCompositor is shutting down
446 shutting_down: AtomicBool,
447 }
448
449 /// The SwCompositeThread struct is shared between the SwComposite thread
450 /// and the rendering thread so that both ends can access the job queue.
451 unsafe impl Sync for SwCompositeThread {}
452
453 /// A FIFO queue of composite jobs to be processed.
454 type SwCompositeJobQueue = VecDeque<SwCompositeGraphNodeRef>;
455
456 /// Locked access to the composite job queue.
457 type SwCompositeThreadLock<'a> = MutexGuard<'a, SwCompositeJobQueue>;
458
459 impl SwCompositeThread {
460 /// Create the SwComposite thread. Requires a SWGL context in which
461 /// to do the composition.
new() -> Arc<SwCompositeThread>462 fn new() -> Arc<SwCompositeThread> {
463 let info = Arc::new(SwCompositeThread {
464 jobs: Mutex::new(SwCompositeJobQueue::new()),
465 current_job: AtomicPtr::new(ptr::null_mut()),
466 jobs_available: Condvar::new(),
467 jobs_completed: AtomicBool::new(true),
468 waiting_for_jobs: AtomicBool::new(false),
469 shutting_down: AtomicBool::new(false),
470 });
471 let result = info.clone();
472 let thread_name = "SwComposite";
473 thread::Builder::new()
474 .name(thread_name.into())
475 // The composite thread only calls into SWGL to composite, and we
476 // have potentially many composite threads for different windows,
477 // so using the default stack size is excessive. A reasonably small
478 // stack size should be more than enough for SWGL and reduce memory
479 // overhead.
480 .stack_size(32 * 1024)
481 .spawn(move || {
482 profiler::register_thread(thread_name);
483 // Process any available jobs. This will return a non-Ok
484 // result when the job queue is dropped, causing the thread
485 // to eventually exit.
486 while let Some((job, band)) = info.take_job(true) {
487 info.process_job(job, band);
488 }
489 profiler::unregister_thread();
490 })
491 .expect("Failed creating SwComposite thread");
492 result
493 }
494
deinit(&self)495 fn deinit(&self) {
496 // Signal that the thread needs to exit.
497 self.shutting_down.store(true, Ordering::SeqCst);
498 // Wake up the thread in case it is blocked waiting for new jobs
499 self.jobs_available.notify_all();
500 }
501
502 /// Process a job contained in a dependency graph node received from the job queue.
503 /// Any child dependencies will be unblocked as appropriate after processing. The
504 /// job count will be updated to reflect this.
process_job(&self, graph_node: &mut SwCompositeGraphNode, band: i32)505 fn process_job(&self, graph_node: &mut SwCompositeGraphNode, band: i32) {
506 // Do the actual processing of the job contained in this node.
507 graph_node.process_job(band);
508 // Unblock any child dependencies now that this job has been processed.
509 graph_node.unblock_children(self);
510 }
511
512 /// Queue a tile for composition by adding to the queue and increasing the job count.
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, )513 fn queue_composite(
514 &self,
515 locked_src: SwCompositeSource,
516 locked_dst: swgl::LockedResource,
517 src_rect: DeviceIntRect,
518 dst_rect: DeviceIntRect,
519 clip_rect: DeviceIntRect,
520 opaque: bool,
521 flip_y: bool,
522 filter: ImageRendering,
523 mut graph_node: SwCompositeGraphNodeRef,
524 job_queue: &mut SwCompositeJobQueue,
525 ) {
526 // For jobs that would span a sufficiently large destination rectangle, split
527 // it into multiple horizontal bands so that multiple threads can process them.
528 let clipped_dst = match dst_rect.intersection(&clip_rect) {
529 Some(clipped_dst) => clipped_dst,
530 None => return,
531 };
532
533 let num_bands = if clipped_dst.width() >= 64 && clipped_dst.height() >= 64 {
534 (clipped_dst.height() / 64).min(4) as u8
535 } else {
536 1
537 };
538 let job = SwCompositeJob {
539 locked_src,
540 locked_dst,
541 src_rect,
542 dst_rect,
543 clipped_dst,
544 opaque,
545 flip_y,
546 filter,
547 num_bands,
548 };
549 if graph_node.set_job(job, num_bands) {
550 self.send_job(job_queue, graph_node);
551 }
552 }
553
prepare_for_composites(&self)554 fn prepare_for_composites(&self) {
555 // Initially, the job queue is empty. Trivially, this means we consider all
556 // jobs queued so far as completed.
557 self.jobs_completed.store(true, Ordering::SeqCst);
558 }
559
560 /// Lock the thread for access to the job queue.
lock(&self) -> SwCompositeThreadLock561 fn lock(&self) -> SwCompositeThreadLock {
562 self.jobs.lock().unwrap()
563 }
564
565 /// Send a job to the composite thread by adding it to the job queue.
566 /// Signal that this job has been added in case the queue was empty and the
567 /// SwComposite thread is waiting for jobs.
send_job(&self, queue: &mut SwCompositeJobQueue, job: SwCompositeGraphNodeRef)568 fn send_job(&self, queue: &mut SwCompositeJobQueue, job: SwCompositeGraphNodeRef) {
569 if queue.is_empty() {
570 self.jobs_completed.store(false, Ordering::SeqCst);
571 self.jobs_available.notify_all();
572 }
573 queue.push_back(job);
574 }
575
576 /// Try to get a band of work from the currently cached job when available.
577 /// If there is a job, but it has no available bands left, null out the job
578 /// so that other threads do not bother checking the job.
try_take_job(&self) -> Option<(&mut SwCompositeGraphNode, i32)>579 fn try_take_job(&self) -> Option<(&mut SwCompositeGraphNode, i32)> {
580 let current_job_ptr = self.current_job.load(Ordering::SeqCst);
581 if let Some(current_job) = unsafe { current_job_ptr.as_mut() } {
582 let (band, done) = current_job.take_band();
583 if done {
584 let _ = self.current_job.compare_exchange(
585 current_job_ptr,
586 ptr::null_mut(),
587 Ordering::SeqCst,
588 Ordering::SeqCst,
589 );
590 }
591 if let Some(band) = band {
592 return Some((current_job, band));
593 }
594 }
595 return None;
596 }
597
598 /// Take a job from the queue. Optionally block waiting for jobs to become
599 /// available if this is called from the SwComposite thread.
take_job(&self, wait: bool) -> Option<(&mut SwCompositeGraphNode, i32)>600 fn take_job(&self, wait: bool) -> Option<(&mut SwCompositeGraphNode, i32)> {
601 // First try checking the cached job outside the scope of the mutex.
602 // For jobs that have multiple bands, this allows us to avoid having
603 // to lock the mutex multiple times to check the job for each band.
604 if let Some((job, band)) = self.try_take_job() {
605 return Some((job, band));
606 }
607 // Lock the job queue while checking for available jobs. The lock
608 // won't be held while the job is processed later outside of this
609 // function so that other threads can pull from the queue meanwhile.
610 let mut jobs = self.lock();
611 loop {
612 // While inside the mutex, check the cached job again to see if it
613 // has been updated.
614 if let Some((job, band)) = self.try_take_job() {
615 return Some((job, band));
616 }
617 // If no cached job was available, try to take a job from the queue
618 // and install it as the current job.
619 if let Some(job) = jobs.pop_front() {
620 self.current_job.store(job.get_ptr_mut(), Ordering::SeqCst);
621 continue;
622 }
623 // Otherwise, the job queue is currently empty. Depending on the
624 // job status, we may either wait for jobs to become available or exit.
625 if wait {
626 // For the SwComposite thread, if we arrive here, the job queue
627 // is empty. Signal that all available jobs have been completed.
628 self.jobs_completed.store(true, Ordering::SeqCst);
629 if self.waiting_for_jobs.load(Ordering::SeqCst) {
630 // Wake the main thread if it is waiting for a change in job status.
631 self.jobs_available.notify_all();
632 } else if self.shutting_down.load(Ordering::SeqCst) {
633 // If SwComposite thread needs to shut down, then exit and stop
634 // waiting for jobs.
635 return None;
636 }
637 } else {
638 // If all available jobs have been completed by the SwComposite
639 // thread, then the main thread no longer needs to wait for any
640 // new jobs to appear in the queue and should exit.
641 if self.jobs_completed.load(Ordering::SeqCst) {
642 return None;
643 }
644 // Otherwise, signal that the main thread is waiting for jobs.
645 self.waiting_for_jobs.store(true, Ordering::SeqCst);
646 }
647 // Wait until jobs are added before checking the job queue again.
648 jobs = self.jobs_available.wait(jobs).unwrap();
649 if !wait {
650 // The main thread is done waiting for jobs.
651 self.waiting_for_jobs.store(false, Ordering::SeqCst);
652 }
653 }
654 }
655
656 /// Wait for all queued composition jobs to be processed.
657 /// Instead of blocking on the SwComposite thread to complete all jobs,
658 /// this may steal some jobs and attempt to process them while waiting.
659 /// This may optionally process jobs synchronously. When normally doing
660 /// asynchronous processing, the graph dependencies are relied upon to
661 /// properly order the jobs, which makes it safe for the render thread
662 /// to steal jobs from the composite thread without violating those
663 /// dependencies. Synchronous processing just disables this job stealing
664 /// so that the composite thread always handles the jobs in the order
665 /// they were queued without having to rely upon possibly unavailable
666 /// graph dependencies.
wait_for_composites(&self, sync: bool)667 fn wait_for_composites(&self, sync: bool) {
668 // If processing asynchronously, try to steal jobs from the composite
669 // thread if it is busy.
670 if !sync {
671 while let Some((job, band)) = self.take_job(false) {
672 self.process_job(job, band);
673 }
674 // Once there are no more jobs, just fall through to waiting
675 // synchronously for the composite thread to finish processing.
676 }
677 // If processing synchronously, just wait for the composite thread
678 // to complete processing any in-flight jobs, then bail.
679 let mut jobs = self.lock();
680 // Signal that the main thread may wait for job completion so that the
681 // SwComposite thread can wake it up if necessary.
682 self.waiting_for_jobs.store(true, Ordering::SeqCst);
683 // Wait for job completion to ensure there are no more in-flight jobs.
684 while !self.jobs_completed.load(Ordering::SeqCst) {
685 jobs = self.jobs_available.wait(jobs).unwrap();
686 }
687 // Done waiting for job completion.
688 self.waiting_for_jobs.store(false, Ordering::SeqCst);
689 }
690
691 /// Check if all in-flight jobs have not been completed yet. If they have
692 /// not, then we assume the SwComposite thread is currently busy compositing.
is_busy_compositing(&self) -> bool693 fn is_busy_compositing(&self) -> bool {
694 !self.jobs_completed.load(Ordering::SeqCst)
695 }
696 }
697
698 /// Parameters describing how to composite a surface within a frame
699 type FrameSurface = (
700 NativeSurfaceId,
701 CompositorSurfaceTransform,
702 DeviceIntRect,
703 ImageRendering,
704 );
705
706 /// Adapter for RenderCompositors to work with SWGL that shuttles between
707 /// WebRender and the RenderCompositr via the Compositor API.
708 pub struct SwCompositor {
709 gl: swgl::Context,
710 compositor: Box<dyn MappableCompositor>,
711 use_native_compositor: bool,
712 surfaces: HashMap<NativeSurfaceId, SwSurface>,
713 frame_surfaces: Vec<FrameSurface>,
714 /// Any surface added after we're already compositing (i.e. debug overlay)
715 /// needs to be processed after those frame surfaces. For simplicity we
716 /// store them in a separate queue that gets processed later.
717 late_surfaces: Vec<FrameSurface>,
718 cur_tile: NativeTileId,
719 /// The maximum tile size required for any of the allocated surfaces.
720 max_tile_size: DeviceIntSize,
721 /// Reuse the same depth texture amongst all tiles in all surfaces.
722 /// This depth texture must be big enough to accommodate the largest used
723 /// tile size for any surface. The maximum requested tile size is tracked
724 /// to ensure that this depth texture is at least that big.
725 depth_id: u32,
726 /// Instance of the SwComposite thread, only created if we are not relying
727 /// on a native RenderCompositor.
728 composite_thread: Option<Arc<SwCompositeThread>>,
729 /// SWGL locked resource for sharing framebuffer with SwComposite thread
730 locked_framebuffer: Option<swgl::LockedResource>,
731 }
732
733 impl SwCompositor {
new( gl: swgl::Context, compositor: Box<dyn MappableCompositor>, use_native_compositor: bool, ) -> Self734 pub fn new(
735 gl: swgl::Context,
736 compositor: Box<dyn MappableCompositor>,
737 use_native_compositor: bool,
738 ) -> Self {
739 let depth_id = gl.gen_textures(1)[0];
740 // Only create the SwComposite thread if we're not using a native render
741 // compositor. Thus, we are compositing into the main software framebuffer,
742 // which benefits from compositing asynchronously while updating tiles.
743 let composite_thread = if !use_native_compositor {
744 Some(SwCompositeThread::new())
745 } else {
746 None
747 };
748 SwCompositor {
749 gl,
750 compositor,
751 use_native_compositor,
752 surfaces: HashMap::new(),
753 frame_surfaces: Vec::new(),
754 late_surfaces: Vec::new(),
755 cur_tile: NativeTileId {
756 surface_id: NativeSurfaceId(0),
757 x: 0,
758 y: 0,
759 },
760 max_tile_size: DeviceIntSize::zero(),
761 depth_id,
762 composite_thread,
763 locked_framebuffer: None,
764 }
765 }
766
deinit_tile(&self, tile: &SwTile)767 fn deinit_tile(&self, tile: &SwTile) {
768 self.gl.delete_framebuffers(&[tile.fbo_id]);
769 self.gl.delete_textures(&[tile.color_id]);
770 }
771
deinit_surface(&self, surface: &SwSurface)772 fn deinit_surface(&self, surface: &SwSurface) {
773 for tile in &surface.tiles {
774 self.deinit_tile(tile);
775 }
776 }
777
778 /// Attempt to occlude any queued surfaces with an opaque occluder rect. If
779 /// an existing surface is occluded, we attempt to restrict its clip rect
780 /// so long as it can remain a single clip rect. Existing frame surfaces
781 /// that are opaque will be fused if possible with the supplied occluder
782 /// rect to further try and restrict any underlying surfaces.
occlude_surfaces(&mut self)783 fn occlude_surfaces(&mut self) {
784 // Check if inner rect is fully included in outer rect
785 fn includes(outer: &Range<i32>, inner: &Range<i32>) -> bool {
786 outer.start <= inner.start && outer.end >= inner.end
787 }
788
789 // Check if outer range overlaps either the start or end of a range. If
790 // there is overlap, return the portion of the inner range remaining
791 // after the overlap has been removed.
792 fn overlaps(outer: &Range<i32>, inner: &Range<i32>) -> Option<Range<i32>> {
793 if outer.start <= inner.start && outer.end >= inner.start {
794 Some(outer.end..inner.end.max(outer.end))
795 } else if outer.start <= inner.end && outer.end >= inner.end {
796 Some(inner.start..outer.start.max(inner.start))
797 } else {
798 None
799 }
800 }
801
802 fn set_x_range(rect: &mut DeviceIntRect, range: &Range<i32>) {
803 rect.min.x = range.start;
804 rect.max.x = range.end;
805 }
806
807 fn set_y_range(rect: &mut DeviceIntRect, range: &Range<i32>) {
808 rect.min.y = range.start;
809 rect.max.y = range.end;
810 }
811
812 fn union(base: Range<i32>, extra: Range<i32>) -> Range<i32> {
813 base.start.min(extra.start)..base.end.max(extra.end)
814 }
815
816 // Before we can try to occlude any surfaces, we need to fix their clip rects to tightly
817 // bound the valid region. The clip rect might otherwise enclose an invalid area that
818 // can't fully occlude anything even if the surface is opaque.
819 for &mut (ref id, ref transform, ref mut clip_rect, _) in &mut self.frame_surfaces {
820 if let Some(surface) = self.surfaces.get(id) {
821 // Restrict the clip rect to fall within the valid region of the surface.
822 *clip_rect = surface.device_bounds(transform, clip_rect).unwrap_or_default();
823 }
824 }
825
826 // For each frame surface, treat it as an occluder if it is non-empty and opaque. Look
827 // through the preceding surfaces to see if any can be occluded.
828 for occlude_index in 0..self.frame_surfaces.len() {
829 let (ref occlude_id, _, ref occlude_rect, _) = self.frame_surfaces[occlude_index];
830 match self.surfaces.get(occlude_id) {
831 Some(occluder) if occluder.is_opaque && !occlude_rect.is_empty() => {}
832 _ => continue,
833 }
834
835 // Traverse the queued surfaces for this frame in the reverse order of
836 // how they are composited, or rather, in order of visibility. For each
837 // surface, check if the occluder can restrict the clip rect such that
838 // the clip rect can remain a single rect. If the clip rect overlaps
839 // the occluder on one axis interval while remaining fully included in
840 // the occluder's other axis interval, then we can chop down the edge
841 // of the clip rect on the overlapped axis. Further, if the surface is
842 // opaque and its clip rect exactly matches the occluder rect on one
843 // axis interval while overlapping on the other, fuse it with the
844 // occluder rect before considering any underlying surfaces.
845 let (mut occlude_x, mut occlude_y) = (occlude_rect.x_range(), occlude_rect.y_range());
846 for &mut (ref id, _, ref mut clip_rect, _) in self.frame_surfaces[..occlude_index].iter_mut().rev() {
847 if let Some(surface) = self.surfaces.get(id) {
848 let (clip_x, clip_y) = (clip_rect.x_range(), clip_rect.y_range());
849 if includes(&occlude_x, &clip_x) {
850 if let Some(visible) = overlaps(&occlude_y, &clip_y) {
851 set_y_range(clip_rect, &visible);
852 if surface.is_opaque && occlude_x == clip_x {
853 occlude_y = union(occlude_y, visible);
854 }
855 }
856 } else if includes(&occlude_y, &clip_y) {
857 if let Some(visible) = overlaps(&occlude_x, &clip_x) {
858 set_x_range(clip_rect, &visible);
859 if surface.is_opaque && occlude_y == clip_y {
860 occlude_x = union(occlude_x, visible);
861 }
862 }
863 }
864 }
865 }
866 }
867 }
868
869 /// Reset tile dependency state for a new frame.
reset_overlaps(&mut self)870 fn reset_overlaps(&mut self) {
871 for surface in self.surfaces.values_mut() {
872 for tile in &mut surface.tiles {
873 tile.overlaps.set(0);
874 tile.invalid.set(false);
875 tile.graph_node.reset();
876 }
877 }
878 }
879
880 /// Computes an overlap count for a tile that falls within the given composite
881 /// destination rectangle. This requires checking all surfaces currently queued for
882 /// composition so far in this frame and seeing if they have any invalidated tiles
883 /// whose destination rectangles would also overlap the supplied tile. If so, then the
884 /// increment the overlap count to account for all such dependencies on invalid tiles.
885 /// Tiles with the same overlap count will still be drawn with a stable ordering in
886 /// the order the surfaces were queued, so it is safe to ignore other possible sources
887 /// of composition ordering dependencies, as the later queued tile will still be drawn
888 /// later than the blocking tiles within that stable order. We assume that the tile's
889 /// surface hasn't yet been added to the current frame list of surfaces to composite
890 /// so that we only process potential blockers from surfaces that would come earlier
891 /// in composition.
init_overlaps( &self, overlap_id: &NativeSurfaceId, overlap_surface: &SwSurface, overlap_tile: &SwTile, overlap_transform: &CompositorSurfaceTransform, overlap_clip_rect: &DeviceIntRect, )892 fn init_overlaps(
893 &self,
894 overlap_id: &NativeSurfaceId,
895 overlap_surface: &SwSurface,
896 overlap_tile: &SwTile,
897 overlap_transform: &CompositorSurfaceTransform,
898 overlap_clip_rect: &DeviceIntRect,
899 ) {
900 // Record an extra overlap for an invalid tile to track the tile's dependency
901 // on its own future update.
902 let mut overlaps = if overlap_tile.invalid.get() { 1 } else { 0 };
903
904 let overlap_rect = match overlap_tile.overlap_rect(overlap_surface, overlap_transform, overlap_clip_rect) {
905 Some(overlap_rect) => overlap_rect,
906 None => {
907 overlap_tile.overlaps.set(overlaps);
908 return;
909 }
910 };
911
912 for &(ref id, ref transform, ref clip_rect, _) in &self.frame_surfaces {
913 // We only want to consider surfaces that were added before the current one we're
914 // checking for overlaps. If we find that surface, then we're done.
915 if id == overlap_id {
916 break;
917 }
918 // If the surface's clip rect doesn't overlap the tile's rect,
919 // then there is no need to check any tiles within the surface.
920 if !overlap_rect.intersects(clip_rect) {
921 continue;
922 }
923 if let Some(surface) = self.surfaces.get(id) {
924 for tile in &surface.tiles {
925 // If there is a deferred tile that might overlap the destination rectangle,
926 // record the overlap.
927 if tile.may_overlap(surface, transform, clip_rect, &overlap_rect) {
928 if tile.overlaps.get() > 0 {
929 overlaps += 1;
930 }
931 // Regardless of whether this tile is deferred, if it has dependency
932 // overlaps, then record that it is potentially a dependency parent.
933 tile.graph_node.get_mut().add_child(overlap_tile.graph_node.clone());
934 }
935 }
936 }
937 }
938 if overlaps > 0 {
939 // Has a dependency on some invalid tiles, so need to defer composition.
940 overlap_tile.overlaps.set(overlaps);
941 }
942 }
943
944 /// Helper function that queues a composite job to the current locked framebuffer
queue_composite( &self, surface: &SwSurface, transform: &CompositorSurfaceTransform, clip_rect: &DeviceIntRect, filter: ImageRendering, tile: &SwTile, job_queue: &mut SwCompositeJobQueue, )945 fn queue_composite(
946 &self,
947 surface: &SwSurface,
948 transform: &CompositorSurfaceTransform,
949 clip_rect: &DeviceIntRect,
950 filter: ImageRendering,
951 tile: &SwTile,
952 job_queue: &mut SwCompositeJobQueue,
953 ) {
954 if let Some(ref composite_thread) = self.composite_thread {
955 if let Some((src_rect, dst_rect, flip_y)) = tile.composite_rects(surface, transform, clip_rect) {
956 let source = if surface.external_image.is_some() {
957 // If the surface has an attached external image, lock any textures supplied in the descriptor.
958 match surface.composite_surface {
959 Some(ref info) => match info.yuv_planes {
960 0 => match self.gl.lock_texture(info.textures[0]) {
961 Some(texture) => SwCompositeSource::BGRA(texture),
962 None => return,
963 },
964 3 => match (
965 self.gl.lock_texture(info.textures[0]),
966 self.gl.lock_texture(info.textures[1]),
967 self.gl.lock_texture(info.textures[2]),
968 ) {
969 (Some(y_texture), Some(u_texture), Some(v_texture)) => SwCompositeSource::YUV(
970 y_texture,
971 u_texture,
972 v_texture,
973 info.color_space,
974 info.color_depth,
975 ),
976 _ => return,
977 },
978 _ => panic!("unsupported number of YUV planes: {}", info.yuv_planes),
979 },
980 None => return,
981 }
982 } else if let Some(texture) = self.gl.lock_texture(tile.color_id) {
983 // Lock the texture representing the picture cache tile.
984 SwCompositeSource::BGRA(texture)
985 } else {
986 return;
987 };
988 if let Some(ref framebuffer) = self.locked_framebuffer {
989 composite_thread.queue_composite(
990 source,
991 framebuffer.clone(),
992 src_rect,
993 dst_rect,
994 *clip_rect,
995 surface.is_opaque,
996 flip_y,
997 filter,
998 tile.graph_node.clone(),
999 job_queue,
1000 );
1001 }
1002 }
1003 }
1004 }
1005
1006 /// Lock a surface with an attached external image for compositing.
try_lock_composite_surface(&mut self, id: &NativeSurfaceId)1007 fn try_lock_composite_surface(&mut self, id: &NativeSurfaceId) {
1008 if let Some(surface) = self.surfaces.get_mut(id) {
1009 if let Some(external_image) = surface.external_image {
1010 // If the surface has an attached external image, attempt to lock the external image
1011 // for compositing. Yields a descriptor of textures and data necessary for their
1012 // interpretation on success.
1013 let mut info = SWGLCompositeSurfaceInfo {
1014 yuv_planes: 0,
1015 textures: [0; 3],
1016 color_space: YuvRangedColorSpace::GbrIdentity,
1017 color_depth: ColorDepth::Color8,
1018 size: DeviceIntSize::zero(),
1019 };
1020 assert!(!surface.tiles.is_empty());
1021 let mut tile = &mut surface.tiles[0];
1022 if self.compositor.lock_composite_surface(self.gl.into(), external_image, &mut info) {
1023 tile.valid_rect = DeviceIntRect::from_size(info.size);
1024 surface.composite_surface = Some(info);
1025 } else {
1026 tile.valid_rect = DeviceIntRect::zero();
1027 surface.composite_surface = None;
1028 }
1029 }
1030 }
1031 }
1032
1033 /// Look for any attached external images that have been locked and then unlock them.
unlock_composite_surfaces(&mut self)1034 fn unlock_composite_surfaces(&mut self) {
1035 for &(ref id, _, _, _) in self.frame_surfaces.iter().chain(self.late_surfaces.iter()) {
1036 if let Some(surface) = self.surfaces.get_mut(id) {
1037 if let Some(external_image) = surface.external_image {
1038 if surface.composite_surface.is_some() {
1039 self.compositor.unlock_composite_surface(self.gl.into(), external_image);
1040 surface.composite_surface = None;
1041 }
1042 }
1043 }
1044 }
1045 }
1046
1047 /// Issue composites for any tiles that are no longer blocked following a tile update.
1048 /// We process all surfaces and tiles in the order they were queued.
flush_composites(&self, tile_id: &NativeTileId, surface: &SwSurface, tile: &SwTile)1049 fn flush_composites(&self, tile_id: &NativeTileId, surface: &SwSurface, tile: &SwTile) {
1050 let composite_thread = match &self.composite_thread {
1051 Some(composite_thread) => composite_thread,
1052 None => return,
1053 };
1054
1055 // Look for the tile in the frame list and composite it if it has no dependencies.
1056 let mut frame_surfaces = self
1057 .frame_surfaces
1058 .iter()
1059 .skip_while(|&(ref id, _, _, _)| *id != tile_id.surface_id);
1060 let (overlap_rect, mut lock) = match frame_surfaces.next() {
1061 Some(&(_, ref transform, ref clip_rect, filter)) => {
1062 // Remove invalid tile's update dependency.
1063 if tile.invalid.get() {
1064 tile.overlaps.set(tile.overlaps.get() - 1);
1065 }
1066 // If the tile still has overlaps, keep deferring it till later.
1067 if tile.overlaps.get() > 0 {
1068 return;
1069 }
1070 // Otherwise, the tile's dependencies are all resolved, so composite it.
1071 let mut lock = composite_thread.lock();
1072 self.queue_composite(surface, transform, clip_rect, filter, tile, &mut lock);
1073 // Finally, get the tile's overlap rect used for tracking dependencies
1074 match tile.overlap_rect(surface, transform, clip_rect) {
1075 Some(overlap_rect) => (overlap_rect, lock),
1076 None => return,
1077 }
1078 }
1079 None => return,
1080 };
1081
1082 // Accumulate rects whose dependencies have been satisfied from this update.
1083 // Store the union of all these bounds to quickly reject unaffected tiles.
1084 let mut flushed_bounds = overlap_rect;
1085 let mut flushed_rects = vec![overlap_rect];
1086
1087 // Check surfaces following the update in the frame list and see if they would overlap it.
1088 for &(ref id, ref transform, ref clip_rect, filter) in frame_surfaces {
1089 // If the clip rect doesn't overlap the conservative bounds, we can skip the whole surface.
1090 if !flushed_bounds.intersects(clip_rect) {
1091 continue;
1092 }
1093 if let Some(surface) = self.surfaces.get(&id) {
1094 // Search through the surface's tiles for any blocked on this update and queue jobs for them.
1095 for tile in &surface.tiles {
1096 let mut overlaps = tile.overlaps.get();
1097 // Only check tiles that have existing unresolved dependencies
1098 if overlaps == 0 {
1099 continue;
1100 }
1101 // Get this tile's overlap rect for tracking dependencies
1102 let overlap_rect = match tile.overlap_rect(surface, transform, clip_rect) {
1103 Some(overlap_rect) => overlap_rect,
1104 None => continue,
1105 };
1106 // Do a quick check to see if the tile overlaps the conservative bounds.
1107 if !overlap_rect.intersects(&flushed_bounds) {
1108 continue;
1109 }
1110 // Decrement the overlap count if this tile is dependent on any flushed rects.
1111 for flushed_rect in &flushed_rects {
1112 if overlap_rect.intersects(flushed_rect) {
1113 overlaps -= 1;
1114 }
1115 }
1116 if overlaps != tile.overlaps.get() {
1117 // If the overlap count changed, this tile had a dependency on some flush rects.
1118 // If the count hit zero, it is ready to composite.
1119 tile.overlaps.set(overlaps);
1120 if overlaps == 0 {
1121 self.queue_composite(surface, transform, clip_rect, filter, tile, &mut lock);
1122 // Record that the tile got flushed to update any downwind dependencies.
1123 flushed_bounds = flushed_bounds.union(&overlap_rect);
1124 flushed_rects.push(overlap_rect);
1125 }
1126 }
1127 }
1128 }
1129 }
1130 }
1131 }
1132
1133 impl Compositor for SwCompositor {
create_surface( &mut self, id: NativeSurfaceId, virtual_offset: DeviceIntPoint, tile_size: DeviceIntSize, is_opaque: bool, )1134 fn create_surface(
1135 &mut self,
1136 id: NativeSurfaceId,
1137 virtual_offset: DeviceIntPoint,
1138 tile_size: DeviceIntSize,
1139 is_opaque: bool,
1140 ) {
1141 if self.use_native_compositor {
1142 self.compositor.create_surface(id, virtual_offset, tile_size, is_opaque);
1143 }
1144 self.max_tile_size = DeviceIntSize::new(
1145 self.max_tile_size.width.max(tile_size.width),
1146 self.max_tile_size.height.max(tile_size.height),
1147 );
1148 self.surfaces.insert(id, SwSurface::new(tile_size, is_opaque));
1149 }
1150
create_external_surface(&mut self, id: NativeSurfaceId, is_opaque: bool)1151 fn create_external_surface(&mut self, id: NativeSurfaceId, is_opaque: bool) {
1152 if self.use_native_compositor {
1153 self.compositor.create_external_surface(id, is_opaque);
1154 }
1155 self.surfaces
1156 .insert(id, SwSurface::new(DeviceIntSize::zero(), is_opaque));
1157 }
1158
destroy_surface(&mut self, id: NativeSurfaceId)1159 fn destroy_surface(&mut self, id: NativeSurfaceId) {
1160 if let Some(surface) = self.surfaces.remove(&id) {
1161 self.deinit_surface(&surface);
1162 }
1163 if self.use_native_compositor {
1164 self.compositor.destroy_surface(id);
1165 }
1166 }
1167
deinit(&mut self)1168 fn deinit(&mut self) {
1169 if let Some(ref composite_thread) = self.composite_thread {
1170 composite_thread.deinit();
1171 }
1172
1173 for surface in self.surfaces.values() {
1174 self.deinit_surface(surface);
1175 }
1176
1177 self.gl.delete_textures(&[self.depth_id]);
1178
1179 if self.use_native_compositor {
1180 self.compositor.deinit();
1181 }
1182 }
1183
create_tile(&mut self, id: NativeTileId)1184 fn create_tile(&mut self, id: NativeTileId) {
1185 if self.use_native_compositor {
1186 self.compositor.create_tile(id);
1187 }
1188 if let Some(surface) = self.surfaces.get_mut(&id.surface_id) {
1189 let mut tile = SwTile::new(id.x, id.y);
1190 tile.color_id = self.gl.gen_textures(1)[0];
1191 tile.fbo_id = self.gl.gen_framebuffers(1)[0];
1192 let mut prev_fbo = [0];
1193 unsafe {
1194 self.gl.get_integer_v(gl::DRAW_FRAMEBUFFER_BINDING, &mut prev_fbo);
1195 }
1196 self.gl.bind_framebuffer(gl::DRAW_FRAMEBUFFER, tile.fbo_id);
1197 self.gl.framebuffer_texture_2d(
1198 gl::DRAW_FRAMEBUFFER,
1199 gl::COLOR_ATTACHMENT0,
1200 gl::TEXTURE_2D,
1201 tile.color_id,
1202 0,
1203 );
1204 self.gl.framebuffer_texture_2d(
1205 gl::DRAW_FRAMEBUFFER,
1206 gl::DEPTH_ATTACHMENT,
1207 gl::TEXTURE_2D,
1208 self.depth_id,
1209 0,
1210 );
1211 self.gl.bind_framebuffer(gl::DRAW_FRAMEBUFFER, prev_fbo[0] as gl::GLuint);
1212
1213 surface.tiles.push(tile);
1214 }
1215 }
1216
destroy_tile(&mut self, id: NativeTileId)1217 fn destroy_tile(&mut self, id: NativeTileId) {
1218 if let Some(surface) = self.surfaces.get_mut(&id.surface_id) {
1219 if let Some(idx) = surface.tiles.iter().position(|t| t.x == id.x && t.y == id.y) {
1220 let tile = surface.tiles.remove(idx);
1221 self.deinit_tile(&tile);
1222 }
1223 }
1224 if self.use_native_compositor {
1225 self.compositor.destroy_tile(id);
1226 }
1227 }
1228
attach_external_image(&mut self, id: NativeSurfaceId, external_image: ExternalImageId)1229 fn attach_external_image(&mut self, id: NativeSurfaceId, external_image: ExternalImageId) {
1230 if self.use_native_compositor {
1231 self.compositor.attach_external_image(id, external_image);
1232 }
1233 if let Some(surface) = self.surfaces.get_mut(&id) {
1234 // Surfaces with attached external images have a single tile at the origin encompassing
1235 // the entire surface.
1236 assert!(surface.tile_size.is_empty());
1237 surface.external_image = Some(external_image);
1238 if surface.tiles.is_empty() {
1239 surface.tiles.push(SwTile::new(0, 0));
1240 }
1241 }
1242 }
1243
invalidate_tile(&mut self, id: NativeTileId, valid_rect: DeviceIntRect)1244 fn invalidate_tile(&mut self, id: NativeTileId, valid_rect: DeviceIntRect) {
1245 if self.use_native_compositor {
1246 self.compositor.invalidate_tile(id, valid_rect);
1247 }
1248 if let Some(surface) = self.surfaces.get_mut(&id.surface_id) {
1249 if let Some(tile) = surface.tiles.iter_mut().find(|t| t.x == id.x && t.y == id.y) {
1250 tile.invalid.set(true);
1251 tile.valid_rect = valid_rect;
1252 }
1253 }
1254 }
1255
bind(&mut self, id: NativeTileId, dirty_rect: DeviceIntRect, valid_rect: DeviceIntRect) -> NativeSurfaceInfo1256 fn bind(&mut self, id: NativeTileId, dirty_rect: DeviceIntRect, valid_rect: DeviceIntRect) -> NativeSurfaceInfo {
1257 let mut surface_info = NativeSurfaceInfo {
1258 origin: DeviceIntPoint::zero(),
1259 fbo_id: 0,
1260 };
1261
1262 self.cur_tile = id;
1263
1264 if let Some(surface) = self.surfaces.get_mut(&id.surface_id) {
1265 if let Some(tile) = surface.tiles.iter_mut().find(|t| t.x == id.x && t.y == id.y) {
1266 assert_eq!(tile.valid_rect, valid_rect);
1267 if valid_rect.is_empty() {
1268 return surface_info;
1269 }
1270
1271 let mut stride = 0;
1272 let mut buf = ptr::null_mut();
1273 if self.use_native_compositor {
1274 if let Some(tile_info) = self.compositor.map_tile(id, dirty_rect, valid_rect) {
1275 stride = tile_info.stride;
1276 buf = tile_info.data;
1277 }
1278 }
1279 self.gl.set_texture_buffer(
1280 tile.color_id,
1281 gl::RGBA8,
1282 valid_rect.width(),
1283 valid_rect.height(),
1284 stride,
1285 buf,
1286 surface.tile_size.width,
1287 surface.tile_size.height,
1288 );
1289 // Reallocate the shared depth buffer to fit the valid rect, but within
1290 // a buffer sized to actually fit at least the maximum possible tile size.
1291 // The maximum tile size is supplied to avoid reallocation by ensuring the
1292 // allocated buffer is actually big enough to accommodate the largest tile
1293 // size requested by any used surface, even though supplied valid rect may
1294 // actually be much smaller than this. This will only force a texture
1295 // reallocation inside SWGL if the maximum tile size has grown since the
1296 // last time it was supplied, instead simply reusing the buffer if the max
1297 // tile size is not bigger than what was previously allocated.
1298 self.gl.set_texture_buffer(
1299 self.depth_id,
1300 gl::DEPTH_COMPONENT,
1301 valid_rect.width(),
1302 valid_rect.height(),
1303 0,
1304 ptr::null_mut(),
1305 self.max_tile_size.width,
1306 self.max_tile_size.height,
1307 );
1308 surface_info.fbo_id = tile.fbo_id;
1309 surface_info.origin -= valid_rect.min.to_vector();
1310 }
1311 }
1312
1313 surface_info
1314 }
1315
unbind(&mut self)1316 fn unbind(&mut self) {
1317 let id = self.cur_tile;
1318 if let Some(surface) = self.surfaces.get(&id.surface_id) {
1319 if let Some(tile) = surface.tiles.iter().find(|t| t.x == id.x && t.y == id.y) {
1320 if tile.valid_rect.is_empty() {
1321 // If we didn't actually render anything, then just queue any
1322 // dependencies.
1323 self.flush_composites(&id, surface, tile);
1324 return;
1325 }
1326
1327 // Force any delayed clears to be resolved.
1328 self.gl.resolve_framebuffer(tile.fbo_id);
1329
1330 if self.use_native_compositor {
1331 self.compositor.unmap_tile();
1332 } else {
1333 // If we're not relying on a native compositor, then composite
1334 // any tiles that are dependent on this tile being updated but
1335 // are otherwise ready to composite.
1336 self.flush_composites(&id, surface, tile);
1337 }
1338 }
1339 }
1340 }
1341
begin_frame(&mut self)1342 fn begin_frame(&mut self) {
1343 self.reset_overlaps();
1344
1345 if self.use_native_compositor {
1346 self.compositor.begin_frame();
1347 }
1348 }
1349
add_surface( &mut self, id: NativeSurfaceId, transform: CompositorSurfaceTransform, clip_rect: DeviceIntRect, filter: ImageRendering, )1350 fn add_surface(
1351 &mut self,
1352 id: NativeSurfaceId,
1353 transform: CompositorSurfaceTransform,
1354 clip_rect: DeviceIntRect,
1355 filter: ImageRendering,
1356 ) {
1357 if self.use_native_compositor {
1358 self.compositor.add_surface(id, transform, clip_rect, filter);
1359 }
1360
1361 if self.composite_thread.is_some() {
1362 // If the surface has an attached external image, try to lock that now.
1363 self.try_lock_composite_surface(&id);
1364
1365 // If we're already busy compositing, then add to the queue of late
1366 // surfaces instead of trying to sort into the main frame queue.
1367 // These late surfaces will not have any overlap tracking done for
1368 // them and must be processed synchronously at the end of the frame.
1369 if self.composite_thread.as_ref().unwrap().is_busy_compositing() {
1370 self.late_surfaces.push((id, transform, clip_rect, filter));
1371 return;
1372 }
1373 }
1374
1375 self.frame_surfaces.push((id, transform, clip_rect, filter));
1376 }
1377
1378 /// Now that all the dependency graph nodes have been built, start queuing
1379 /// composition jobs. Any surfaces that get added after this point in the
1380 /// frame will not have overlap dependencies assigned and so must instead
1381 /// be added to the late_surfaces queue to be processed at the end of the
1382 /// frame.
start_compositing(&mut self, clear_color: ColorF, dirty_rects: &[DeviceIntRect], _opaque_rects: &[DeviceIntRect])1383 fn start_compositing(&mut self, clear_color: ColorF, dirty_rects: &[DeviceIntRect], _opaque_rects: &[DeviceIntRect]) {
1384 // Opaque rects are currently only computed here, not by WR itself, so we
1385 // ignore the passed parameter and forward our own version onto the native
1386 // compositor.
1387 let mut opaque_rects: Vec<DeviceIntRect> = Vec::new();
1388 for &(ref id, ref transform, ref clip_rect, _filter) in &self.frame_surfaces {
1389 if let Some(surface) = self.surfaces.get(id) {
1390 if !surface.is_opaque {
1391 continue;
1392 }
1393
1394 for tile in &surface.tiles {
1395 if let Some(rect) = tile.overlap_rect(surface, transform, clip_rect) {
1396 opaque_rects.push(rect);
1397 }
1398 }
1399 }
1400 }
1401
1402 self.compositor.start_compositing(clear_color, dirty_rects, &opaque_rects);
1403
1404 if let Some(dirty_rect) = dirty_rects
1405 .iter()
1406 .fold(DeviceIntRect::zero(), |acc, dirty_rect| acc.union(dirty_rect))
1407 .to_non_empty()
1408 {
1409 // Factor dirty rect into surface clip rects
1410 for &mut (_, _, ref mut clip_rect, _) in &mut self.frame_surfaces {
1411 *clip_rect = clip_rect.intersection(&dirty_rect).unwrap_or_default();
1412 }
1413 }
1414
1415 self.occlude_surfaces();
1416
1417 // Discard surfaces that are entirely clipped out
1418 self.frame_surfaces
1419 .retain(|&(_, _, clip_rect, _)| !clip_rect.is_empty());
1420
1421 if let Some(ref composite_thread) = self.composite_thread {
1422 // Compute overlap dependencies for surfaces.
1423 for &(ref id, ref transform, ref clip_rect, _filter) in &self.frame_surfaces {
1424 if let Some(surface) = self.surfaces.get(id) {
1425 for tile in &surface.tiles {
1426 self.init_overlaps(id, surface, tile, transform, clip_rect);
1427 }
1428 }
1429 }
1430
1431 self.locked_framebuffer = self.gl.lock_framebuffer(0);
1432
1433 composite_thread.prepare_for_composites();
1434
1435 // Issue any initial composite jobs for the SwComposite thread.
1436 let mut lock = composite_thread.lock();
1437 for &(ref id, ref transform, ref clip_rect, filter) in &self.frame_surfaces {
1438 if let Some(surface) = self.surfaces.get(id) {
1439 for tile in &surface.tiles {
1440 if tile.overlaps.get() == 0 {
1441 // Not dependent on any tiles, so go ahead and composite now.
1442 self.queue_composite(surface, transform, clip_rect, filter, tile, &mut lock);
1443 }
1444 }
1445 }
1446 }
1447 }
1448 }
1449
end_frame(&mut self)1450 fn end_frame(&mut self) {
1451 if self.use_native_compositor {
1452 self.compositor.end_frame();
1453 } else if let Some(ref composite_thread) = self.composite_thread {
1454 // Need to wait for the SwComposite thread to finish any queued jobs.
1455 composite_thread.wait_for_composites(false);
1456
1457 if !self.late_surfaces.is_empty() {
1458 // All of the main frame surface have been processed by now. But if there
1459 // are any late surfaces, we need to kick off a new synchronous composite
1460 // phase. These late surfaces don't have any overlap/dependency tracking,
1461 // so we just queue them directly and wait synchronously for the composite
1462 // thread to process them in order.
1463 composite_thread.prepare_for_composites();
1464 {
1465 let mut lock = composite_thread.lock();
1466 for &(ref id, ref transform, ref clip_rect, filter) in &self.late_surfaces {
1467 if let Some(surface) = self.surfaces.get(id) {
1468 for tile in &surface.tiles {
1469 self.queue_composite(surface, transform, clip_rect, filter, tile, &mut lock);
1470 }
1471 }
1472 }
1473 }
1474 composite_thread.wait_for_composites(true);
1475 }
1476
1477 self.locked_framebuffer = None;
1478
1479 self.unlock_composite_surfaces();
1480 }
1481
1482 self.frame_surfaces.clear();
1483 self.late_surfaces.clear();
1484
1485 self.reset_overlaps();
1486 }
1487
enable_native_compositor(&mut self, enable: bool)1488 fn enable_native_compositor(&mut self, enable: bool) {
1489 // TODO: The SwComposite thread is not properly instantiated if this is
1490 // ever actually toggled.
1491 assert_eq!(self.use_native_compositor, enable);
1492 self.compositor.enable_native_compositor(enable);
1493 self.use_native_compositor = enable;
1494 }
1495
get_capabilities(&self) -> CompositorCapabilities1496 fn get_capabilities(&self) -> CompositorCapabilities {
1497 self.compositor.get_capabilities()
1498 }
1499 }
1500