Skip to main content

ferroui_base/rendering/composition/server/
server_compositor.rs

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