Skip to main content

ferroui_base/rendering/composition/server/
server_compositor.rs

1use super::{
2    CompositorPools, IServerObject, IServerRenderResource, ReadbackIndices, ServerCompositionTarget,
3    ServerCompositionVisual, ServerCompositorAnimations, ServerObjectId,
4};
5use crate::platform::surfaces::IPlatformRenderSurface;
6use crate::platform::{IBitmapImpl, IPlatformGraphics, IRenderTarget, LtrbRect};
7use crate::rendering::composition::transport::{
8    BatchMarker, BatchObject, BatchStreamData, BatchStreamReader, CommittedBatch, CompositionBatch, ServerJob,
9};
10use crate::rendering::composition::CompositionOptions;
11use crate::rendering::PlatformRenderInterfaceContextManager;
12use crate::reactive::IDisposable;
13use crate::threading::Dispatcher;
14use crate::{Matrix, PixelSize, Size, Vector};
15use std::any::{Any, TypeId};
16use std::cell::{Cell, RefCell};
17use std::collections::{HashMap, HashSet, VecDeque};
18use std::rc::{Rc, Weak};
19use std::sync::{Arc, Mutex};
20use std::time::Duration;
21
22/// The source of the compositor clock: time elapsed since an arbitrary
23/// origin. It is read from both sides of the transport; like the server
24/// compositor it is confined to the compositor's thread for now, because
25/// the dispatcher's clock is.
26pub type CompositorClock = Rc<dyn Fn() -> Duration>;
27
28/// The queue of committed batches: the one point where data passes from
29/// the UI-thread compositor to the server compositor.
30///
31/// The UI thread enqueues and rents buffers, the render thread dequeues and
32/// returns them: both under a lock.
33#[derive(Default)]
34pub struct BatchQueue {
35    batches: Mutex<VecDeque<CommittedBatch>>,
36    /// Emptied stream buffers, returned by the server for reuse.
37    data_pool: Mutex<Vec<BatchStreamData>>,
38}
39
40impl BatchQueue {
41    pub(crate) fn enqueue(&self, batch: CommittedBatch) {
42        self.batches.lock().unwrap().push_back(batch);
43    }
44
45    fn dequeue(&self) -> Option<CommittedBatch> {
46        self.batches.lock().unwrap().pop_front()
47    }
48
49    /// Takes a stream buffer for a new batch.
50    pub(crate) fn rent_data(&self) -> BatchStreamData {
51        self.data_pool.lock().unwrap().pop().unwrap_or_default()
52    }
53
54    /// Completes the batches nothing will apply any more, without applying
55    /// them: the compositor is gone and its server compositor is released,
56    /// or about to be. Whoever waits for such a batch, or holds back its
57    /// next commit until the batch is processed (the media context does, for
58    /// every compositor at once), is answered; what the batches carry (their
59    /// jobs among it) is dropped.
60    ///
61    /// Not from upstream, where a compositor and its server live as long as
62    /// the process.
63    pub(crate) fn complete_all(&self) {
64        loop {
65            // Outside the lock of the queue: a continuation of the batch
66            // runs here.
67            let Some(batch) = self.dequeue() else { break };
68            batch.batch.notify_processed();
69            batch.batch.notify_rendered();
70        }
71    }
72
73    fn return_data(&self, mut data: BatchStreamData) {
74        data.reset();
75        let mut pool = self.data_pool.lock().unwrap();
76        if pool.len() < 16 {
77            pool.push(data);
78        }
79    }
80}
81
82/// The queue is what the two threads share.
83const _: fn() = || {
84    fn shared<T: Send + Sync>() {}
85    shared::<BatchQueue>();
86};
87
88const COMMIT_GRACE_TICKS: i32 = 10;
89
90/// A job in the queues of the server compositor: a job of a batch, or one
91/// bound to the server object it was sent for. It never leaves the render
92/// thread, unlike the [`ServerJob`] of a batch.
93type RenderThreadJob = Box<dyn FnOnce(&ServerCompositor)>;
94
95/// Server-side counterpart of the compositor: owns the server objects,
96/// applies the batches committed by the UI thread and renders.
97///
98/// Confined to the render thread. UI-thread code interacts with it only by
99/// enqueueing batches (and by reading the clock and the readback indices,
100/// which are thread-safe values).
101pub struct ServerCompositor {
102    this: Weak<ServerCompositor>,
103    batches: Arc<BatchQueue>,
104    objects: RefCell<Vec<Option<Rc<dyn IServerObject>>>>,
105    received_job_queue: RefCell<VecDeque<RenderThreadJob>>,
106    disposed_in_batch: RefCell<Vec<ServerObjectId>>,
107    received_post_target_job_queue: RefCell<VecDeque<RenderThreadJob>>,
108    last_batch_id: Cell<i64>,
109    clock: CompositorClock,
110    server_now: Cell<Duration>,
111    ui_thread_is_inside_render: Cell<bool>,
112    /// Set by [`stop_rendering`](Self::stop_rendering).
113    rendering_stopped: Cell<bool>,
114    render_interface: Rc<PlatformRenderInterfaceContextManager>,
115    options: CompositionOptions,
116    readback: Arc<ReadbackIndices>,
117    ticks_since_last_commit: Cell<i32>,
118    to_notify_processed: RefCell<Vec<Arc<CompositionBatch>>>,
119    to_notify_rendered: RefCell<Vec<Arc<CompositionBatch>>>,
120    render_resources_invalidation_queue: RefCell<VecDeque<Rc<dyn IServerRenderResource>>>,
121    render_resources_invalidation_set: RefCell<HashSet<*const ()>>,
122    // TODO: parallel processing maybe
123    visual_own_properties_recompute_pass: RefCell<VecDeque<Rc<ServerCompositionVisual>>>,
124    visual_readback_update_pass_queue: RefCell<VecDeque<Rc<ServerCompositionVisual>>>,
125    adorner_update_queue: RefCell<VecDeque<Rc<ServerCompositionVisual>>>,
126    active_targets: RefCell<Vec<Rc<ServerCompositionTarget>>>,
127    pools: CompositorPools,
128    animations: ServerCompositorAnimations,
129    render_interface_feature_cache: RefCell<Option<Rc<HashMap<TypeId, Rc<dyn Any>>>>>,
130    /// The text renderer of the debug overlays. It is made from the default
131    /// typeface, which belongs to the thread of the font manager: see
132    /// [`diagnostic_text_renderer`](Self::diagnostic_text_renderer).
133    diagnostic_text_renderer: RefCell<Option<Rc<super::DiagnosticTextRenderer>>>,
134    _context_subscriptions: RefCell<Vec<Rc<dyn IDisposable>>>,
135}
136
137impl ServerCompositor {
138    /// Creates the server compositor on the thread that renders. The queue of
139    /// batches and the readback are what it shares with its compositor, which
140    /// creates them.
141    pub(crate) fn new(
142        platform_graphics: Option<Arc<dyn IPlatformGraphics>>,
143        options: CompositionOptions,
144        batches: Arc<BatchQueue>,
145        readback: Arc<ReadbackIndices>,
146        clock: CompositorClock,
147    ) -> Rc<ServerCompositor> {
148        let compositor = Rc::new_cyclic(|this| ServerCompositor {
149            this: this.clone(),
150            batches,
151            objects: RefCell::new(Vec::new()),
152            received_job_queue: RefCell::new(VecDeque::new()),
153            disposed_in_batch: RefCell::new(Vec::new()),
154            received_post_target_job_queue: RefCell::new(VecDeque::new()),
155            last_batch_id: Cell::new(0),
156            clock,
157            server_now: Cell::new(Duration::ZERO),
158            ui_thread_is_inside_render: Cell::new(false),
159            rendering_stopped: Cell::new(false),
160            render_interface: PlatformRenderInterfaceContextManager::new(platform_graphics),
161            options,
162            readback,
163            ticks_since_last_commit: Cell::new(0),
164            to_notify_processed: RefCell::new(Vec::new()),
165            to_notify_rendered: RefCell::new(Vec::new()),
166            render_resources_invalidation_queue: RefCell::new(VecDeque::new()),
167            render_resources_invalidation_set: RefCell::new(HashSet::new()),
168            visual_own_properties_recompute_pass: RefCell::new(VecDeque::new()),
169            visual_readback_update_pass_queue: RefCell::new(VecDeque::new()),
170            adorner_update_queue: RefCell::new(VecDeque::new()),
171            active_targets: RefCell::new(Vec::new()),
172            pools: CompositorPools::default(),
173            animations: ServerCompositorAnimations::new(),
174            render_interface_feature_cache: RefCell::new(None),
175            diagnostic_text_renderer: RefCell::new(None),
176            _context_subscriptions: RefCell::new(Vec::new()),
177        });
178        let weak = Rc::downgrade(&compositor);
179        let disposed = compositor.render_interface.context_disposed(move || {
180            if let Some(compositor) = weak.upgrade() {
181                compositor.rt_on_context_disposed();
182            }
183        });
184        let weak = Rc::downgrade(&compositor);
185        let created = compositor.render_interface.context_created(move |context| {
186            if let Some(compositor) = weak.upgrade() {
187                compositor.rt_on_context_created(&**context);
188            }
189        });
190        *compositor._context_subscriptions.borrow_mut() = vec![disposed, created];
191        compositor
192    }
193
194    /// The object pools of the tree walks.
195    pub fn pools(&self) -> &CompositorPools {
196        &self.pools
197    }
198
199    /// The clock items and the animated objects of the compositor.
200    pub fn animations(&self) -> &ServerCompositorAnimations {
201        &self.animations
202    }
203
204    /// The sequence id of the batch applied last.
205    pub fn last_batch_id(&self) -> i64 {
206        self.last_batch_id.get()
207    }
208
209    /// The time elapsed on the compositor clock.
210    pub fn clock_elapsed(&self) -> Duration {
211        (self.clock)()
212    }
213
214    /// The compositor clock itself.
215    pub fn clock(&self) -> &CompositorClock {
216        &self.clock
217    }
218
219    /// The compositor time of the frame being rendered.
220    pub fn server_now(&self) -> Duration {
221        self.server_now.get()
222    }
223
224    pub fn render_interface(&self) -> &Rc<PlatformRenderInterfaceContextManager> {
225        &self.render_interface
226    }
227
228    /// The text renderer of the debug overlays, shared by the targets.
229    ///
230    /// It can only be created on a thread that has the font manager (the UI
231    /// thread; upstream's services are global and the first overlay creates
232    /// it wherever it is drawn). The compositor asks for it under the lock
233    /// when debug overlays are switched on; a frame of the render thread
234    /// uses what is there and draws no overlay text until then.
235    pub fn diagnostic_text_renderer(&self) -> Option<Rc<super::DiagnosticTextRenderer>> {
236        if self.diagnostic_text_renderer.borrow().is_none() {
237            // Absent in a unit test without a platform, and on a thread
238            // without services.
239            {
240                use crate::LocatorExtensions as _;
241                crate::FerroLocator::current().get_service::<dyn crate::platform::IFontManagerImpl>()?;
242            }
243            let renderer = Rc::new(super::DiagnosticTextRenderer::create_default());
244            // The labels of the FPS counter, shaped here on the UI thread: the counter is
245            // created by the first frame of the render thread, which cannot create glyph runs
246            // where it has no services (the render thread of the browser).
247            for label in super::FpsCounter::LABELS {
248                renderer.shape_label(label);
249            }
250            *self.diagnostic_text_renderer.borrow_mut() = Some(renderer);
251        }
252        self.diagnostic_text_renderer.borrow().clone()
253    }
254
255    pub fn options(&self) -> &CompositionOptions {
256        &self.options
257    }
258
259    /// The indices of the values the UI thread reads back from the server.
260    pub fn readback(&self) -> &Arc<ReadbackIndices> {
261        &self.readback
262    }
263
264    pub(crate) fn update_server_time(&self) {
265        self.server_now.set(self.clock_elapsed());
266    }
267
268    // --- object table -------------------------------------------------------
269
270    /// The server object with the given id, if it is alive.
271    pub fn get_object(&self, id: ServerObjectId) -> Option<Rc<dyn IServerObject>> {
272        self.objects.borrow().get(id.index()).and_then(Clone::clone)
273    }
274
275    /// The server object with the given id as its concrete type.
276    pub fn get<T: IServerObject>(&self, id: ServerObjectId) -> Option<Rc<T>> {
277        self.get_object(id).and_then(|object| object.into_any_rc().downcast::<T>().ok())
278    }
279
280    /// The server object with the given id as an animatable object.
281    pub fn get_animated_object(&self, id: ServerObjectId) -> Option<Rc<dyn super::IAnimatedServerObject>> {
282        self.get_object(id).and_then(|object| object.as_animated())
283    }
284
285    /// The number of live server objects.
286    pub fn object_count(&self) -> usize {
287        self.objects.borrow().iter().filter(|o| o.is_some()).count()
288    }
289
290    fn insert_object(&self, id: ServerObjectId, object: Rc<dyn IServerObject>) {
291        let mut objects = self.objects.borrow_mut();
292        if objects.len() <= id.index() {
293            objects.resize_with(id.index() + 1, || None);
294        }
295        debug_assert!(objects[id.index()].is_none(), "a server object id was reused while still alive");
296        objects[id.index()] = Some(object);
297    }
298
299    fn remove_object(&self, id: ServerObjectId) -> Option<Rc<dyn IServerObject>> {
300        self.objects.borrow_mut().get_mut(id.index()).and_then(Option::take)
301    }
302
303    // --- batches ------------------------------------------------------------
304
305    fn apply_pending_batches(&self) {
306        let mut had_batches = false;
307        while let Some(mut batch) = self.batches.dequeue() {
308            {
309                let mut stream = BatchStreamReader::new(&mut batch.changes);
310                while !stream.is_object_eof() {
311                    match stream.read_object() {
312                        BatchObject::Marker(BatchMarker::CreateStart) => self.read_create_jobs(&mut stream),
313                        BatchObject::Marker(BatchMarker::RenderThreadJobsStart) => self.read_server_jobs(
314                            &mut stream,
315                            &self.received_job_queue,
316                            BatchMarker::RenderThreadJobsEnd,
317                        ),
318                        BatchObject::Marker(BatchMarker::RenderThreadPostTargetJobsStart) => self.read_server_jobs(
319                            &mut stream,
320                            &self.received_post_target_job_queue,
321                            BatchMarker::RenderThreadPostTargetJobsEnd,
322                        ),
323                        BatchObject::Marker(BatchMarker::RenderThreadDisposeStart) => {
324                            self.read_dispose_jobs(&mut stream)
325                        }
326                        BatchObject::ServerObject(id) => {
327                            let Some(target) = self.get_object(id) else {
328                                panic!("a batch refers to server object {id:?}, which does not exist");
329                            };
330                            target.deserialize_changes(&mut stream, batch.committed_at);
331                            if cfg!(debug_assertions) {
332                                // Each object's changes are followed by a
333                                // marker in debug builds, which catches a
334                                // serializer and deserializer that disagree.
335                                match stream.read_object() {
336                                    BatchObject::Marker(BatchMarker::ObjectEnd) => {}
337                                    _ => panic!("server object {id:?} failed to deserialize properly on the object stream"),
338                                }
339                                if stream.read::<u64>() != OBJECT_END_MAGIC {
340                                    panic!("server object {id:?} failed to deserialize properly on the data stream");
341                                }
342                            }
343                        }
344                        _ => panic!("unexpected item at the top level of a batch object stream"),
345                    }
346                }
347            }
348            // The objects the batch released leave the table once the whole
349            // batch has been read: its jobs, which follow the dispose list,
350            // resolve them.
351            for id in std::mem::take(&mut *self.disposed_in_batch.borrow_mut()) {
352                self.remove_object(id);
353            }
354            self.batches.return_data(std::mem::take(&mut batch.changes));
355            self.last_batch_id.set(batch.batch.sequence_id());
356            self.to_notify_processed.borrow_mut().push(batch.batch);
357            had_batches = true;
358        }
359
360        if had_batches {
361            self.ticks_since_last_commit.set(0);
362        } else if self.ticks_since_last_commit.get() < i32::MAX {
363            self.ticks_since_last_commit.set(self.ticks_since_last_commit.get() + 1);
364        }
365    }
366
367    fn read_create_jobs(&self, reader: &mut BatchStreamReader<'_>) {
368        let Some(this) = self.this.upgrade() else { return };
369        let mut count = reader.read::<i32>();
370        while count > 0 {
371            let Some(id) = reader.read_server_object() else {
372                panic!("a batch creates a server object without an id");
373            };
374            let BatchObject::Create(factory) = reader.read_object() else {
375                panic!("a batch creates a server object without a factory");
376            };
377            self.insert_object(id, factory(&this, id));
378            count -= 1;
379        }
380    }
381
382    fn read_server_jobs(&self, reader: &mut BatchStreamReader<'_>, queue: &RefCell<VecDeque<RenderThreadJob>>, end: BatchMarker) {
383        loop {
384            match reader.read_object() {
385                BatchObject::Marker(marker) if marker == end => break,
386                BatchObject::Job(job) => queue.borrow_mut().push_back(job),
387                BatchObject::ServerObject(id) => {
388                    let target = self.get_object(id);
389                    match reader.read_object() {
390                        BatchObject::ObjectJob(job) => {
391                            queue.borrow_mut().push_back(Box::new(move |compositor| job(compositor, target)))
392                        }
393                        _ => panic!("a server object in the job list of a batch is not followed by its job"),
394                    }
395                }
396                _ => panic!("unexpected item in the job list of a batch"),
397            }
398        }
399    }
400
401    fn read_dispose_jobs(&self, reader: &mut BatchStreamReader<'_>) {
402        let mut count = reader.read::<i32>();
403        while count > 0 {
404            let id = reader.read_server_object();
405            let dispose = reader.read::<bool>();
406            let release = reader.read::<bool>();
407            if let Some(id) = id {
408                if let Some(object) = self.get_object(id) {
409                    if dispose {
410                        object.dispose();
411                    }
412                    if release {
413                        self.disposed_in_batch.borrow_mut().push(id);
414                    }
415                }
416            }
417            count -= 1;
418        }
419    }
420
421    fn execute_server_jobs(&self, queue: &RefCell<VecDeque<RenderThreadJob>>) {
422        loop {
423            let Some(job) = queue.borrow_mut().pop_front() else { break };
424            job(self);
425        }
426    }
427
428    fn notify_batches_processed(&self) {
429        let processed = std::mem::take(&mut *self.to_notify_processed.borrow_mut());
430        for batch in &processed {
431            batch.notify_processed();
432        }
433        self.to_notify_rendered.borrow_mut().extend(processed);
434    }
435
436    fn notify_batches_rendered(&self) {
437        let rendered = std::mem::take(&mut *self.to_notify_rendered.borrow_mut());
438        for batch in &rendered {
439            batch.notify_rendered();
440        }
441    }
442
443    // --- passes -------------------------------------------------------------
444
445    fn apply_enqueued_render_resource_changes_pass(&self) {
446        loop {
447            let Some(resource) = self.render_resources_invalidation_queue.borrow_mut().pop_front() else { break };
448            // Removed before the call so a resource invalidated again while
449            // observers react is queued again, like the dequeue upstream.
450            resource.queued_invalidate();
451        }
452        self.render_resources_invalidation_set.borrow_mut().clear();
453    }
454
455    /// Queues a render resource whose observers must be told that it
456    /// changed, once per frame.
457    pub fn enqueue_render_resource_for_invalidation(&self, resource: Rc<dyn IServerRenderResource>) {
458        let key = Rc::as_ptr(&resource) as *const ();
459        if self.render_resources_invalidation_set.borrow_mut().insert(key) {
460            self.render_resources_invalidation_queue.borrow_mut().push_back(resource);
461        }
462    }
463
464    fn visual_own_properties_update_pass(&self) {
465        loop {
466            let Some(visual) = self.visual_own_properties_recompute_pass.borrow_mut().pop_front() else { break };
467            visual.recompute_own_properties();
468        }
469    }
470
471    pub fn enqueue_visual_for_own_properties_update_pass(&self, visual: Rc<ServerCompositionVisual>) {
472        self.visual_own_properties_recompute_pass.borrow_mut().push_back(visual);
473    }
474
475    fn visual_readback_update_pass(&self) {
476        if self.visual_readback_update_pass_queue.borrow().is_empty() {
477            return;
478        }
479
480        // visual.HitTest is waiting for this lock to be released, so we need to be quick
481        // this is why we have a queue in the first place
482        let scope = self.readback.begin_write();
483        let read = self.readback.read_revision();
484        let write = self.readback.write_revision();
485        loop {
486            let Some(visual) = self.visual_readback_update_pass_queue.borrow_mut().pop_front() else { break };
487            visual.update_readback(write, read);
488        }
489        drop(scope);
490    }
491
492    pub fn enqueue_visual_for_readback_update_pass(&self, visual: Rc<ServerCompositionVisual>) {
493        self.visual_readback_update_pass_queue.borrow_mut().push_back(visual);
494    }
495
496    pub fn enqueue_adorner_update(&self, visual: Rc<ServerCompositionVisual>) {
497        self.adorner_update_queue.borrow_mut().push_back(visual);
498    }
499
500    fn adorner_update_pass(&self) {
501        loop {
502            let Some(adorner) = self.adorner_update_queue.borrow_mut().pop_front() else { break };
503            adorner.update_adorner();
504        }
505    }
506
507    // --- targets ------------------------------------------------------------
508
509    pub fn add_composition_target(&self, target: &Rc<ServerCompositionTarget>) {
510        self.active_targets.borrow_mut().push(target.clone());
511    }
512
513    pub fn remove_composition_target(&self, target: &Rc<ServerCompositionTarget>) {
514        let mut targets = self.active_targets.borrow_mut();
515        if let Some(index) = targets.iter().position(|t| Rc::ptr_eq(t, target)) {
516            targets.remove(index);
517        }
518    }
519
520    pub fn create_render_target(&self, surfaces: &[std::sync::Arc<dyn IPlatformRenderSurface>]) -> Rc<dyn IRenderTarget> {
521        let current = self.render_interface.ensure_current();
522        let target = self.render_interface.create_render_target(surfaces);
523        current.dispose();
524        target
525    }
526
527    pub fn is_ready_to_create_render_target(&self, surfaces: &[std::sync::Arc<dyn IPlatformRenderSurface>]) -> bool {
528        self.render_interface.is_ready_to_create_render_target(surfaces)
529    }
530
531    // --- user APIs ----------------------------------------------------------
532
533    fn rt_on_context_created(&self, context: &dyn crate::platform::IPlatformRenderInterfaceContext) {
534        *self.render_interface_feature_cache.borrow_mut() = Some(Rc::new(context.public_features()));
535    }
536
537    fn rt_on_context_disposed(&self) {
538        *self.render_interface_feature_cache.borrow_mut() = None;
539    }
540
541    /// The public features of the render interface, if a backend context
542    /// exists (callable from any place that can reach the compositor).
543    pub fn at_try_get_cached_render_interface_features(&self) -> Option<Rc<HashMap<TypeId, Rc<dyn Any>>>> {
544        self.render_interface_feature_cache.borrow().clone()
545    }
546
547    /// The public features of the render interface; creates the backend
548    /// context when needed.
549    pub fn rt_get_render_interface_features(&self) -> Rc<HashMap<TypeId, Rc<dyn Any>>> {
550        if let Some(features) = self.render_interface_feature_cache.borrow().clone() {
551            return features;
552        }
553        let features = Rc::new(self.render_interface.value().public_features());
554        *self.render_interface_feature_cache.borrow_mut() = Some(features.clone());
555        features
556    }
557
558    /// Renders a visual (and, with `render_children`, its subtree) into a
559    /// new bitmap.
560    pub fn create_composition_visual_snapshot(
561        &self,
562        visual: &Rc<ServerCompositionVisual>,
563        scaling: f64,
564        render_children: bool,
565    ) -> std::sync::Arc<crate::platform::SharedBitmapImpl> {
566        let current = self.render_interface.ensure_current();
567        let size = visual.size();
568        let pixel_size = PixelSize::from_size(Size::new(size.x, size.y), scaling);
569        let scale_transform = Matrix::create_scale(scaling, scaling);
570
571        let target = self.render_interface.value().create_offscreen_render_target(
572            pixel_size,
573            Vector::new(scaling, scaling),
574            true,
575        );
576        {
577            let mut canvas = target.create_drawing_context();
578            canvas.set_transform(scale_transform);
579            visual.render(&mut *canvas, LtrbRect::INFINITE, None, render_children, false, false);
580            canvas.dispose();
581        }
582
583        let result: std::sync::Arc<crate::platform::SharedBitmapImpl> = match target.as_layer_with_render_context_affinity() {
584            Some(affined) if affined.has_render_context_affinity() => {
585                let snapshot = affined.create_non_affined_snapshot();
586                target.dispose();
587                snapshot
588            }
589            // The original returns the target itself; a layer does not
590            // leave the render thread here, so its contents do.
591            _ => {
592                let snapshot = target.create_shared_snapshot();
593                target.dispose();
594                snapshot
595            }
596        };
597        current.dispose();
598        result
599    }
600
601    pub fn reset_all_gpu_resources(&self) {
602        let targets = self.active_targets.borrow().clone();
603        for target in targets {
604            target.reset_render_target();
605        }
606        self.render_interface.reset();
607    }
608
609    /// Releases what the compositor draws with, while its graphics context
610    /// is current: the layer and the render target of every active target,
611    /// then the backend context. The context that was current before is
612    /// restored. Does nothing when no backend context was ever created, and
613    /// nothing for a graphics context that is lost (nothing can be released
614    /// in it).
615    ///
616    /// For the two places where these objects go for good: before the graph
617    /// of a compositor is dropped, and when the surface the compositor draws
618    /// to is closed. An object of a graphics interface with a current
619    /// context (OpenGL) is deleted in whatever context is current when it is
620    /// dropped: with none current the call fails, and with the context of
621    /// another compositor current it deletes an object of that one. Once the
622    /// backend context is released here, what is dropped later touches no
623    /// graphics context.
624    ///
625    /// Unlike [`reset_all_gpu_resources`](Self::reset_all_gpu_resources),
626    /// nothing is asked for that may have to be created: it also works for
627    /// graphics that are not ready, and after the compositor has taken its
628    /// platform handles back.
629    ///
630    /// Not from upstream, where these objects are finalized by the garbage
631    /// collector, on the thread of the context.
632    pub fn release_gpu_resources(&self) {
633        if self.render_interface.existing_backend_context().is_none() {
634            return;
635        }
636        let gpu_context = self.render_interface.gpu_context();
637        if gpu_context.as_ref().is_some_and(|context| context.is_lost()) {
638            return;
639        }
640        let current = gpu_context.map(|context| context.ensure_current());
641        let targets = self.active_targets.borrow().clone();
642        for target in targets {
643            target.release_render_target();
644        }
645        self.render_interface.reset();
646        if let Some(current) = current {
647            current.dispose();
648        }
649    }
650
651    /// Ends the rendering of the compositor for good: from here on a frame
652    /// applies the batches that arrive (so that whoever waits for one is
653    /// answered), drops the jobs they carry, renders nothing and does not
654    /// ask for another tick.
655    ///
656    /// For a compositor whose surface is closed while the compositor lives
657    /// on: its graphics would be "not ready" for ever, and a compositor that
658    /// is not ready asks for the next tick each time. Upstream's browser
659    /// backend takes the server compositor out of the render loop at that
660    /// point; here the compositor stays in the loop, because the thread of
661    /// the compositor still waits for the batch that disposes its target.
662    pub fn stop_rendering(&self) {
663        self.rendering_stopped.set(true);
664    }
665
666    /// Whether [`stop_rendering`](Self::stop_rendering) was called.
667    pub fn is_rendering_stopped(&self) -> bool {
668        self.rendering_stopped.get()
669    }
670
671    pub fn invalidate_all_composition_targets(&self) {
672        let targets = self.active_targets.borrow().clone();
673        for target in targets {
674            target.request_full_redraw();
675        }
676    }
677
678    // --- rendering ----------------------------------------------------------
679
680    /// Whether the caller may access the objects of the compositor: whether
681    /// the compositor is rendering a frame. Upstream the frame is rendered
682    /// under the compositor lock by the thread that holds it.
683    pub fn check_access(&self) -> bool {
684        self.ui_thread_is_inside_render.get()
685    }
686
687    /// Panics when the caller may not access the objects of the compositor
688    /// (see [`check_access`](Self::check_access)).
689    pub fn verify_access(&self) {
690        if !self.check_access() {
691            panic!("This object can be only accessed under compositor lock");
692        }
693    }
694
695    /// [`verify_access`](Self::verify_access) of a compositor that may be
696    /// gone: no frame of a compositor that is gone is being rendered.
697    /// Returns the compositor.
698    pub fn verify_access_of(compositor: Option<&Rc<ServerCompositor>>) -> Rc<ServerCompositor> {
699        match compositor {
700            Some(compositor) => {
701                compositor.verify_access();
702                compositor.clone()
703            }
704            None => panic!("This object can be only accessed under compositor lock"),
705        }
706    }
707
708    /// Runs one frame: applies the pending batches, runs the global passes
709    /// and the queued jobs. Returns whether another tick is needed even if
710    /// nothing else wakes the render loop up.
711    ///
712    /// Panics on reentrancy.
713    pub fn render(&self) -> bool {
714        if self.ui_thread_is_inside_render.replace(true) {
715            panic!("Reentrancy is not supported");
716        }
717        let _reset = ResetFlag(&self.ui_thread_is_inside_render);
718
719        // While the UI thread renders, nothing may pump its dispatcher from
720        // inside the frame. A frame of the render thread leaves the
721        // dispatcher alone.
722        let dispatcher = Dispatcher::ui_thread();
723        let processing_disabled = dispatcher.check_access().then(|| dispatcher.disable_processing());
724        let _notify = NotifyRendered(self);
725        // Declared after `_notify`, so processing is enabled again before
726        // the rendered notification runs.
727        let _enable = EnableProcessing(processing_disabled);
728        self.render_core()
729    }
730
731    fn execute_global_passes(&self) -> Duration {
732        let compositor_global_passes_started = self.clock_elapsed();
733        self.apply_pending_batches();
734        self.notify_batches_processed();
735        self.animations.process();
736        self.apply_enqueued_render_resource_changes_pass();
737        self.visual_own_properties_update_pass();
738        // Adorners need to be updated after own properties recompute pass,
739        // because they may depend on ancestor's transform chain to be consistent
740        self.adorner_update_pass();
741        self.clock_elapsed().saturating_sub(compositor_global_passes_started)
742    }
743
744    fn render_core(&self) -> bool {
745        self.update_server_time();
746        let compositor_global_passes_elapsed = self.execute_global_passes();
747
748        if self.rendering_stopped.get() {
749            // The jobs are dropped outside the borrow of their queue.
750            let jobs = std::mem::take(&mut *self.received_job_queue.borrow_mut());
751            let post_target_jobs = std::mem::take(&mut *self.received_post_target_job_queue.borrow_mut());
752            drop((jobs, post_target_jobs));
753            return false;
754        }
755
756        if !self.render_interface.is_ready() {
757            return true;
758        }
759        self.render_interface.ensure_valid_backend_context();
760        self.execute_server_jobs(&self.received_job_queue);
761
762        let targets = self.active_targets.borrow().clone();
763        for target in &targets {
764            target.update(compositor_global_passes_elapsed);
765            target.render();
766        }
767
768        self.visual_readback_update_pass();
769
770        self.execute_server_jobs(&self.received_post_target_job_queue);
771
772        // Request a tick if we have active animations or if there are recent batches
773        if self.animations.need_next_tick() || self.ticks_since_last_commit.get() < COMMIT_GRACE_TICKS {
774            return true;
775        }
776
777        // Request a tick if we had unready targets in the last tick, to check if they are ready next time
778        // But skip targets that are waiting for a render loop wakeup from the platform
779        let targets = self.active_targets.borrow().clone();
780        for target in &targets {
781            if target.is_waiting_for_ready_render_target() && !target.is_waiting_for_render_loop_wakeup() {
782                return true;
783            }
784        }
785
786        // Otherwise there is no need to waste CPU cycles, tell the timer to pause
787        false
788    }
789}
790
791/// The value written to the value stream after each object's changes in
792/// debug builds.
793pub(crate) const OBJECT_END_MAGIC: u64 = 0x4F42_4A45_4E44_2121;
794
795struct ResetFlag<'a>(&'a Cell<bool>);
796
797impl Drop for ResetFlag<'_> {
798    fn drop(&mut self) {
799        self.0.set(false);
800    }
801}
802
803struct NotifyRendered<'a>(&'a ServerCompositor);
804
805impl Drop for NotifyRendered<'_> {
806    fn drop(&mut self) {
807        self.0.notify_batches_rendered();
808    }
809}
810
811/// Re-enables dispatcher processing when the frame ends, also when it
812/// unwinds: the handle only re-enables on `dispose`.
813struct EnableProcessing(Option<crate::threading::DispatcherProcessingDisabled>);
814
815impl Drop for EnableProcessing {
816    fn drop(&mut self) {
817        if let Some(disabled) = &self.0 {
818            crate::reactive::IDisposable::dispose(disabled);
819        }
820    }
821}