Skip to main content

ferroui_base/rendering/
platform_render_interface_context_manager.rs

1use std::cell::RefCell;
2use std::rc::{Rc, Weak};
3use std::sync::Arc;
4
5use super::OwnedDisposable;
6use crate::platform::surfaces::IPlatformRenderSurface;
7use crate::platform::{
8    IPlatformGraphics, IPlatformGraphicsContext, IPlatformGraphicsReadyStateFeature, IPlatformRenderInterface,
9    IPlatformRenderInterfaceContext, IRenderTarget,
10};
11use crate::reactive::{Disposable, IDisposable};
12use crate::utilities::HandlerList;
13use crate::{FerroLocator, LocatorExtensions};
14
15/// Owns the backend render context of a renderer and recreates it (together
16/// with the graphics context it is bound to) when it is lost.
17///
18/// Like the platform contracts it manages, it is confined to the thread it
19/// was created on.
20pub struct PlatformRenderInterfaceContextManager {
21    weak_self: Weak<PlatformRenderInterfaceContextManager>,
22    /// The platform graphics and their ready state feature: shared with the
23    /// thread of the platform by their contracts (`Arc`, `Send + Sync`), so
24    /// the thread that renders may hold, call and drop these handles. In
25    /// cells because [`release_platform_handles`](Self::release_platform_handles)
26    /// empties them with the render interface below.
27    graphics: RefCell<Option<Arc<dyn IPlatformGraphics>>>,
28    backend: RefCell<Option<Rc<dyn IPlatformRenderInterfaceContext>>>,
29    gpu_context: RefCell<Option<OwnedDisposable<dyn IPlatformGraphicsContext>>>,
30    ready_state_feature: RefCell<Option<Arc<dyn IPlatformGraphicsReadyStateFeature>>>,
31    /// The render interface of the platform, looked up where the manager is
32    /// created: the service locator belongs to a thread, and the context may
33    /// be created by another one (the render thread).
34    ///
35    /// The one handle the manager shares with its thread whose count is not
36    /// atomic. Nothing outside this type reaches the field: the handle is
37    /// cloned from the locator by the thread that has the services (`new`,
38    /// `capture_platform_render_interface`), lent by reference
39    /// (`with_platform_render_interface`, the creation of the backend
40    /// context), and dropped by that thread
41    /// (`release_platform_handles`, or the release of the server compositor
42    /// where its thread renders too).
43    render_interface: RefCell<Option<Rc<dyn IPlatformRenderInterface>>>,
44    context_disposed: HandlerList<dyn Fn()>,
45    context_created: HandlerList<dyn Fn(&Rc<dyn IPlatformRenderInterfaceContext>)>,
46}
47
48impl PlatformRenderInterfaceContextManager {
49    pub fn new(graphics: Option<Arc<dyn IPlatformGraphics>>) -> Rc<PlatformRenderInterfaceContextManager> {
50        let ready_state_feature = graphics
51            .as_ref()
52            .and_then(|graphics| graphics.as_feature_provider())
53            .and_then(|features| features.try_get_shared::<dyn IPlatformGraphicsReadyStateFeature>());
54
55        Rc::new_cyclic(|weak_self| PlatformRenderInterfaceContextManager {
56            weak_self: weak_self.clone(),
57            graphics: RefCell::new(graphics),
58            backend: RefCell::new(None),
59            gpu_context: RefCell::new(None),
60            ready_state_feature: RefCell::new(ready_state_feature),
61            render_interface: RefCell::new(FerroLocator::current().get_service::<dyn IPlatformRenderInterface>()),
62            context_disposed: HandlerList::new(),
63            context_created: HandlerList::new(),
64        })
65    }
66
67    /// Looks the render interface of the platform up in the service locator
68    /// of the calling thread, if it is not known yet. Called on the thread
69    /// that has the services (the UI thread), before a frame of another
70    /// thread needs the render interface.
71    pub fn capture_platform_render_interface(&self) {
72        let mut render_interface = self.render_interface.borrow_mut();
73        if render_interface.is_none() {
74            *render_interface = FerroLocator::current().get_service::<dyn IPlatformRenderInterface>();
75        }
76    }
77
78    /// Whether the render interface of the platform is known.
79    pub fn has_platform_render_interface(&self) -> bool {
80        self.render_interface.borrow().is_some()
81    }
82
83    /// Runs `f` with the render interface of the platform, if it is known.
84    ///
85    /// The render interface is lent, not handed out: its handle is shared
86    /// with the service locator of the UI thread, whose count is not atomic,
87    /// so a frame of the render thread must neither clone nor release it.
88    pub fn with_platform_render_interface<R>(&self, f: impl FnOnce(&dyn IPlatformRenderInterface) -> R) -> Option<R> {
89        let render_interface = self.render_interface.borrow();
90        render_interface.as_deref().map(f)
91    }
92
93    /// Lets go of the handles the manager shares with the thread that
94    /// created it: the platform graphics, its ready state feature and the
95    /// render interface. The count of the render interface is not atomic, so
96    /// its handle is dropped here, by that thread, when the rest of the
97    /// manager is about to be released by the render thread (a compositor
98    /// that is confined to it). The other two may be dropped by any thread
99    /// and go with it: the manager creates no backend context afterwards.
100    pub fn release_platform_handles(&self) {
101        let graphics = self.graphics.borrow_mut().take();
102        let ready_state_feature = self.ready_state_feature.borrow_mut().take();
103        let render_interface = self.render_interface.borrow_mut().take();
104        drop((graphics, ready_state_feature, render_interface));
105    }
106
107    /// Raised after a lost graphics context has been released.
108    pub fn context_disposed(&self, handler: impl Fn() + 'static) -> Rc<dyn IDisposable> {
109        let token = self.context_disposed.add(Rc::new(handler));
110        let this = self.weak_self.clone();
111        Disposable::create(move || {
112            if let Some(this) = this.upgrade() {
113                this.context_disposed.remove(token);
114            }
115        })
116    }
117
118    /// Raised after a backend context has been created.
119    pub fn context_created(
120        &self,
121        handler: impl Fn(&Rc<dyn IPlatformRenderInterfaceContext>) + 'static,
122    ) -> Rc<dyn IDisposable> {
123        let token = self.context_created.add(Rc::new(handler));
124        let this = self.weak_self.clone();
125        Disposable::create(move || {
126            if let Some(this) = this.upgrade() {
127                this.context_created.remove(token);
128            }
129        })
130    }
131
132    pub fn is_ready(&self) -> bool {
133        // A backend context that exists does not need the render interface
134        // again. Without one, a thread that cannot find the render interface
135        // (the render thread, before the UI thread has looked it up) waits.
136        if self.backend.borrow().is_none() && !self.has_platform_render_interface() {
137            return false;
138        }
139        self.ready_state_feature.borrow().as_ref().is_none_or(|feature| feature.is_ready())
140    }
141
142    /// Makes sure there is a backend context that is not lost.
143    ///
144    /// # Panics
145    /// Panics when the platform graphics are not ready yet.
146    pub fn ensure_valid_backend_context(&self) {
147        if !self.is_ready() {
148            panic!("Platform graphics isn't ready yet");
149        }
150
151        let gpu_context_is_lost = self.gpu_context().is_some_and(|context| context.is_lost());
152        if self.backend.borrow().is_none() || gpu_context_is_lost {
153            let backend = self.backend.borrow_mut().take();
154            if let Some(backend) = backend {
155                backend.dispose();
156            }
157
158            let gpu_context = self.gpu_context.borrow_mut().take();
159            if let Some(mut gpu_context) = gpu_context {
160                gpu_context.dispose();
161                for (_, handler) in self.context_disposed.snapshot().iter() {
162                    handler();
163                }
164            }
165
166            if let Some(graphics) = &*self.graphics.borrow() {
167                if self.ready_state_feature.borrow().as_ref().is_none_or(|feature| feature.uses_contexts()) {
168                    let gpu_context = if graphics.uses_shared_context() {
169                        OwnedDisposable::new(graphics.get_shared_context(), false)
170                    } else {
171                        OwnedDisposable::new(graphics.create_context(), true)
172                    };
173                    *self.gpu_context.borrow_mut() = Some(gpu_context);
174                }
175            }
176
177            // Lent from the cell: see `with_platform_render_interface`.
178            let backend = {
179                let render_interface = self.render_interface.borrow();
180                match render_interface.as_deref() {
181                    Some(render_interface) => render_interface.create_backend_context(self.gpu_context()),
182                    None => panic!("Unable to locate the platform render interface."),
183                }
184            };
185            *self.backend.borrow_mut() = Some(backend.clone());
186            for (_, handler) in self.context_created.snapshot().iter() {
187                handler(&backend);
188            }
189        }
190    }
191
192    /// The backend context; created (or recreated) when needed.
193    pub fn value(&self) -> Rc<dyn IPlatformRenderInterfaceContext> {
194        self.ensure_valid_backend_context();
195        self.backend.borrow().clone().expect("the backend context was just ensured")
196    }
197
198    /// The backend context if there is one, as it is: nothing is created,
199    /// and a lost context is not replaced. For a thread that may look at the
200    /// context but must not create one (the thread of a compositor that is
201    /// confined to its render thread).
202    pub fn existing_backend_context(&self) -> Option<Rc<dyn IPlatformRenderInterfaceContext>> {
203        self.backend.borrow().clone()
204    }
205
206    /// The graphics context the backend context is bound to, if any.
207    pub fn gpu_context(&self) -> Option<Rc<dyn IPlatformGraphicsContext>> {
208        self.gpu_context.borrow().as_ref().map(|context| context.value())
209    }
210
211    /// Makes the graphics context current. Disposing the result restores the
212    /// previously current context.
213    pub fn ensure_current(&self) -> Rc<dyn IDisposable> {
214        self.ensure_valid_backend_context();
215        match self.gpu_context() {
216            Some(gpu_context) => gpu_context.ensure_current(),
217            None => Disposable::empty(),
218        }
219    }
220
221    pub fn create_render_target(&self, surfaces: &[std::sync::Arc<dyn IPlatformRenderSurface>]) -> Rc<dyn IRenderTarget> {
222        self.value().create_render_target(surfaces)
223    }
224
225    pub fn is_ready_to_create_render_target(&self, surfaces: &[std::sync::Arc<dyn IPlatformRenderSurface>]) -> bool {
226        let backend = self.backend.borrow().clone();
227        match backend {
228            None => self.is_ready(),
229            Some(backend) => backend.is_ready_to_create_render_target(surfaces),
230        }
231    }
232
233    /// Releases the backend context; the next use creates a new one.
234    pub fn reset(&self) {
235        let backend = self.backend.borrow_mut().take();
236        if let Some(backend) = backend {
237            backend.dispose();
238        }
239    }
240}
241
242#[cfg(test)]
243mod tests {
244    use super::*;
245    use crate::utilities::ThreadBound;
246    use std::sync::atomic::{AtomicBool, Ordering};
247    use crate::media::imaging::BitmapInterpolationMode;
248    use crate::media::{FillRule, GeometryCombineMode};
249    use crate::platform::{
250        AlphaFormat, IBitmapImpl, IDrawingContextLayerImpl, IGeometryImpl, IOptionalFeatureProvider,
251        IPlatformRenderInterfaceRegion, IRenderTargetBitmapImpl, IStreamGeometryBuilder, IWriteableBitmapImpl,
252        PixelFormat,
253    };
254    use crate::{PixelSize, Point, Rect, Vector};
255    use std::any::{Any, TypeId};
256    use std::cell::Cell;
257    use std::io::Read;
258
259    type Log = Rc<RefCell<Vec<String>>>;
260
261    fn record(log: &Log, entry: impl Into<String>) {
262        log.borrow_mut().push(entry.into());
263    }
264
265    fn take(log: &Log) -> Vec<String> {
266        std::mem::take(&mut *log.borrow_mut())
267    }
268
269    struct GraphicsContext {
270        id: i32,
271        log: Log,
272        is_lost: Cell<bool>,
273    }
274
275    impl IOptionalFeatureProvider for GraphicsContext {
276        fn try_get_feature(&self, _feature_type: TypeId) -> Option<Rc<dyn Any>> {
277            None
278        }
279    }
280
281    impl IPlatformGraphicsContext for GraphicsContext {
282        fn is_lost(&self) -> bool {
283            self.is_lost.get()
284        }
285
286        fn ensure_current(&self) -> Rc<dyn IDisposable> {
287            record(&self.log, format!("gpu{}.ensure_current", self.id));
288            let log = self.log.clone();
289            let id = self.id;
290            Disposable::create(move || record(&log, format!("gpu{id}.restore")))
291        }
292
293        fn dispose(&self) {
294            record(&self.log, format!("gpu{}.dispose", self.id));
295        }
296
297        fn as_any(&self) -> &dyn Any {
298            self
299        }
300    }
301
302    struct ReadyState {
303        is_ready: AtomicBool,
304        uses_contexts: AtomicBool,
305    }
306
307    impl IPlatformGraphicsReadyStateFeature for ReadyState {
308        fn is_ready(&self) -> bool {
309            self.is_ready.load(Ordering::SeqCst)
310        }
311
312        fn uses_contexts(&self) -> bool {
313            self.uses_contexts.load(Ordering::SeqCst)
314        }
315    }
316
317    /// The platform graphics of these tests: the log and the contexts it
318    /// hands out are objects of the test thread, so they are bound to it,
319    /// as the display of an implementation that belongs to one thread is.
320    struct Graphics {
321        state: ThreadBound<GraphicsState>,
322        ready_state: Option<Arc<ReadyState>>,
323    }
324
325    struct GraphicsState {
326        log: Log,
327        uses_shared_context: bool,
328        shared: Rc<GraphicsContext>,
329        created: RefCell<Vec<Rc<GraphicsContext>>>,
330    }
331
332    impl std::ops::Deref for Graphics {
333        type Target = GraphicsState;
334
335        fn deref(&self) -> &GraphicsState {
336            self.state.get()
337        }
338    }
339
340    impl Graphics {
341        fn new(log: &Log, uses_shared_context: bool, ready_state: Option<Arc<ReadyState>>) -> Arc<Graphics> {
342            Arc::new(Graphics {
343                state: ThreadBound::new(GraphicsState {
344                    log: log.clone(),
345                    uses_shared_context,
346                    shared: Rc::new(GraphicsContext { id: 0, log: log.clone(), is_lost: Cell::new(false) }),
347                    created: RefCell::new(Vec::new()),
348                }),
349                ready_state,
350            })
351        }
352    }
353
354    impl IOptionalFeatureProvider for Graphics {
355        fn try_get_feature(&self, feature_type: TypeId) -> Option<Rc<dyn Any>> {
356            if feature_type == TypeId::of::<dyn IPlatformGraphicsReadyStateFeature>() {
357                let feature: Arc<dyn IPlatformGraphicsReadyStateFeature> = self.ready_state.clone()?;
358                return Some(Rc::new(feature));
359            }
360            None
361        }
362    }
363
364    impl IPlatformGraphics for Graphics {
365        fn uses_shared_context(&self) -> bool {
366            self.state.get().uses_shared_context
367        }
368
369        fn create_context(&self) -> Rc<dyn IPlatformGraphicsContext> {
370            let context = Rc::new(GraphicsContext {
371                id: self.created.borrow().len() as i32 + 1,
372                log: self.log.clone(),
373                is_lost: Cell::new(false),
374            });
375            record(&self.log, format!("graphics.create_context -> gpu{}", context.id));
376            self.created.borrow_mut().push(context.clone());
377            context
378        }
379
380        fn get_shared_context(&self) -> Rc<dyn IPlatformGraphicsContext> {
381            record(&self.log, "graphics.get_shared_context");
382            self.shared.clone()
383        }
384
385        fn as_feature_provider(&self) -> Option<&dyn IOptionalFeatureProvider> {
386            Some(self)
387        }
388    }
389
390    struct RenderTarget;
391
392    impl IRenderTarget for RenderTarget {
393        fn properties(&self) -> crate::platform::RenderTargetProperties {
394            Default::default()
395        }
396
397        fn create_drawing_context(
398            &self,
399            _scene_info: &crate::platform::RenderTargetSceneInfo,
400        ) -> (Box<dyn crate::platform::IDrawingContextImpl>, crate::platform::RenderTargetDrawingContextProperties)
401        {
402            unimplemented!()
403        }
404
405        fn dispose(&self) {}
406    }
407
408    struct BackendContext {
409        id: i32,
410        log: Log,
411        ready_to_create_render_target: Cell<bool>,
412    }
413
414    impl IOptionalFeatureProvider for BackendContext {
415        fn try_get_feature(&self, _feature_type: TypeId) -> Option<Rc<dyn Any>> {
416            None
417        }
418    }
419
420    impl IPlatformRenderInterfaceContext for BackendContext {
421        fn create_render_target(&self, surfaces: &[std::sync::Arc<dyn IPlatformRenderSurface>]) -> Rc<dyn IRenderTarget> {
422            record(&self.log, format!("backend{}.create_render_target({})", self.id, surfaces.len()));
423            Rc::new(RenderTarget)
424        }
425
426        fn create_offscreen_render_target(
427            &self,
428            _pixel_size: PixelSize,
429            _scaling: Vector,
430            _enable_text_antialiasing: bool,
431        ) -> Rc<dyn IDrawingContextLayerImpl> {
432            unimplemented!()
433        }
434
435        fn is_lost(&self) -> bool {
436            false
437        }
438
439        fn max_offscreen_render_target_pixel_size(&self) -> Option<PixelSize> {
440            None
441        }
442
443        fn is_ready_to_create_render_target(&self, _surfaces: &[std::sync::Arc<dyn IPlatformRenderSurface>]) -> bool {
444            self.ready_to_create_render_target.get()
445        }
446
447        fn dispose(&self) {
448            record(&self.log, format!("backend{}.dispose", self.id));
449        }
450    }
451
452    struct RenderInterface {
453        log: Log,
454        backends: RefCell<Vec<Rc<BackendContext>>>,
455    }
456
457    impl IPlatformRenderInterface for RenderInterface {
458        fn create_backend_context(
459            &self,
460            graphics_api_context: Option<Rc<dyn IPlatformGraphicsContext>>,
461        ) -> Rc<dyn IPlatformRenderInterfaceContext> {
462            let backend = Rc::new(BackendContext {
463                id: self.backends.borrow().len() as i32 + 1,
464                log: self.log.clone(),
465                ready_to_create_render_target: Cell::new(true),
466            });
467            let gpu = match &graphics_api_context {
468                Some(context) => {
469                    format!("gpu{}", context.as_any().downcast_ref::<GraphicsContext>().unwrap().id)
470                }
471                None => "software".to_string(),
472            };
473            record(&self.log, format!("create_backend_context({gpu}) -> backend{}", backend.id));
474            self.backends.borrow_mut().push(backend.clone());
475            backend
476        }
477
478        fn build_glyph_run_geometry(&self, _glyph_run: &crate::media::GlyphRun) -> Arc<dyn IGeometryImpl> {
479            unimplemented!()
480        }
481
482        fn create_glyph_run(
483            &self,
484            _glyph_typeface: &Rc<crate::media::GlyphTypeface>,
485            _font_rendering_em_size: f64,
486            _glyph_infos: &[crate::media::text_formatting::GlyphInfo],
487            _baseline_origin: Point,
488        ) -> std::sync::Arc<dyn crate::platform::IGlyphRunImpl> {
489            unimplemented!()
490        }
491
492        fn create_ellipse_geometry(&self, _rect: Rect) -> Arc<dyn IGeometryImpl> {
493            unimplemented!()
494        }
495
496        fn create_line_geometry(&self, _p1: Point, _p2: Point) -> Arc<dyn IGeometryImpl> {
497            unimplemented!()
498        }
499
500        fn create_rectangle_geometry(&self, _rect: Rect) -> Arc<dyn IGeometryImpl> {
501            unimplemented!()
502        }
503
504        fn create_stream_geometry_builder(&self, _source: Option<&dyn IGeometryImpl>) -> Box<dyn IStreamGeometryBuilder> {
505            unimplemented!()
506        }
507
508        fn create_geometry_group(
509            &self,
510            _fill_rule: FillRule,
511            _children: &[Arc<dyn IGeometryImpl>],
512        ) -> Arc<dyn IGeometryImpl> {
513            unimplemented!()
514        }
515
516        fn create_combined_geometry(
517            &self,
518            _combine_mode: GeometryCombineMode,
519            _g1: Arc<dyn IGeometryImpl>,
520            _g2: Arc<dyn IGeometryImpl>,
521        ) -> Arc<dyn IGeometryImpl> {
522            unimplemented!()
523        }
524
525        fn create_render_target_bitmap(&self, _size: PixelSize, _dpi: Vector) -> std::sync::Arc<dyn IRenderTargetBitmapImpl> {
526            unimplemented!()
527        }
528
529        fn create_writeable_bitmap(
530            &self,
531            _size: PixelSize,
532            _dpi: Vector,
533            _format: PixelFormat,
534            _alpha_format: AlphaFormat,
535        ) -> std::sync::Arc<dyn IWriteableBitmapImpl> {
536            unimplemented!()
537        }
538
539        fn load_bitmap_from_file(&self, _file_name: &str) -> std::io::Result<std::sync::Arc<crate::platform::SharedBitmapImpl>> {
540            unimplemented!()
541        }
542
543        fn load_bitmap(&self, _stream: &mut dyn Read) -> std::io::Result<std::sync::Arc<crate::platform::SharedBitmapImpl>> {
544            unimplemented!()
545        }
546
547        fn load_writeable_bitmap_to_width(
548            &self,
549            _stream: &mut dyn Read,
550            _width: i32,
551            _interpolation_mode: BitmapInterpolationMode,
552        ) -> std::io::Result<std::sync::Arc<dyn IWriteableBitmapImpl>> {
553            unimplemented!()
554        }
555
556        fn load_writeable_bitmap_to_height(
557            &self,
558            _stream: &mut dyn Read,
559            _height: i32,
560            _interpolation_mode: BitmapInterpolationMode,
561        ) -> std::io::Result<std::sync::Arc<dyn IWriteableBitmapImpl>> {
562            unimplemented!()
563        }
564
565        fn load_writeable_bitmap_from_file(&self, _file_name: &str) -> std::io::Result<std::sync::Arc<dyn IWriteableBitmapImpl>> {
566            unimplemented!()
567        }
568
569        fn load_writeable_bitmap(&self, _stream: &mut dyn Read) -> std::io::Result<std::sync::Arc<dyn IWriteableBitmapImpl>> {
570            unimplemented!()
571        }
572
573        fn load_bitmap_to_width(
574            &self,
575            _stream: &mut dyn Read,
576            _width: i32,
577            _interpolation_mode: BitmapInterpolationMode,
578        ) -> std::io::Result<std::sync::Arc<crate::platform::SharedBitmapImpl>> {
579            unimplemented!()
580        }
581
582        fn load_bitmap_to_height(
583            &self,
584            _stream: &mut dyn Read,
585            _height: i32,
586            _interpolation_mode: BitmapInterpolationMode,
587        ) -> std::io::Result<std::sync::Arc<crate::platform::SharedBitmapImpl>> {
588            unimplemented!()
589        }
590
591        fn resize_bitmap(
592            &self,
593            _bitmap_impl: &dyn IBitmapImpl,
594            _destination_size: PixelSize,
595            _interpolation_mode: BitmapInterpolationMode,
596        ) -> std::sync::Arc<crate::platform::SharedBitmapImpl> {
597            unimplemented!()
598        }
599
600        fn load_bitmap_from_pixels(
601            &self,
602            _format: PixelFormat,
603            _alpha_format: AlphaFormat,
604            _data: &[u8],
605            _size: PixelSize,
606            _dpi: Vector,
607            _stride: i32,
608        ) -> std::sync::Arc<crate::platform::SharedBitmapImpl> {
609            unimplemented!()
610        }
611
612        fn supports_individual_round_rects(&self) -> bool {
613            false
614        }
615
616        fn default_alpha_format(&self) -> AlphaFormat {
617            unimplemented!()
618        }
619
620        fn default_pixel_format(&self) -> PixelFormat {
621            unimplemented!()
622        }
623
624        fn is_supported_bitmap_pixel_format(&self, _format: PixelFormat) -> bool {
625            false
626        }
627
628        fn supports_regions(&self) -> bool {
629            false
630        }
631
632        fn create_region(&self) -> Rc<dyn IPlatformRenderInterfaceRegion> {
633            unimplemented!()
634        }
635    }
636
637    /// Registers a render interface for the test and restores the locator
638    /// afterwards.
639    struct Fixture {
640        log: Log,
641        render_interface: Rc<RenderInterface>,
642        scope: Rc<dyn IDisposable>,
643    }
644
645    impl Fixture {
646        fn new() -> Fixture {
647            let log: Log = Rc::new(RefCell::new(Vec::new()));
648            let scope = FerroLocator::enter_scope();
649            let render_interface = Rc::new(RenderInterface { log: log.clone(), backends: RefCell::new(Vec::new()) });
650            FerroLocator::current_mutable().bind::<dyn IPlatformRenderInterface>().to_constant(render_interface.clone());
651            Fixture { log, render_interface, scope }
652        }
653
654        fn manager(&self, graphics: Option<Arc<Graphics>>) -> Rc<PlatformRenderInterfaceContextManager> {
655            let manager =
656                PlatformRenderInterfaceContextManager::new(graphics.map(|graphics| graphics as Arc<dyn IPlatformGraphics>));
657            let log = self.log.clone();
658            manager.context_disposed(move || record(&log, "ContextDisposed"));
659            let log = self.log.clone();
660            let render_interface = Rc::downgrade(&self.render_interface);
661            manager.context_created(move |context| {
662                let render_interface = render_interface.upgrade().unwrap();
663                let backends = render_interface.backends.borrow();
664                let created = backends.last().unwrap();
665                assert!(std::ptr::eq(Rc::as_ptr(context) as *const (), Rc::as_ptr(created) as *const ()));
666                record(&log, format!("ContextCreated(backend{})", created.id));
667            });
668            manager
669        }
670    }
671
672    impl Drop for Fixture {
673        fn drop(&mut self) {
674            self.scope.dispose();
675        }
676    }
677
678    #[test]
679    fn software_backend_is_created_once_without_a_graphics_context() {
680        let fixture = Fixture::new();
681        let manager = fixture.manager(None);
682        assert!(manager.is_ready());
683        assert!(manager.gpu_context().is_none());
684        assert!(take(&fixture.log).is_empty());
685
686        let value = manager.value();
687        assert_eq!(take(&fixture.log), ["create_backend_context(software) -> backend1", "ContextCreated(backend1)"]);
688        assert!(manager.gpu_context().is_none());
689
690        assert!(Rc::ptr_eq(&value, &manager.value()));
691        manager.ensure_valid_backend_context();
692        // Without a graphics context there is nothing to make current.
693        manager.ensure_current().dispose();
694        assert!(take(&fixture.log).is_empty());
695    }
696
697    #[test]
698    fn owned_graphics_context_is_recreated_when_lost() {
699        let fixture = Fixture::new();
700        let graphics = Graphics::new(&fixture.log, false, None);
701        let manager = fixture.manager(Some(graphics.clone()));
702
703        manager.ensure_valid_backend_context();
704        assert_eq!(
705            take(&fixture.log),
706            [
707                "graphics.create_context -> gpu1",
708                "create_backend_context(gpu1) -> backend1",
709                "ContextCreated(backend1)"
710            ]
711        );
712        let gpu_context = manager.gpu_context().unwrap();
713        assert!(std::ptr::eq(
714            Rc::as_ptr(&gpu_context) as *const (),
715            Rc::as_ptr(&graphics.created.borrow()[0]) as *const ()
716        ));
717
718        let current = manager.ensure_current();
719        current.dispose();
720        assert_eq!(take(&fixture.log), ["gpu1.ensure_current", "gpu1.restore"]);
721
722        graphics.created.borrow()[0].is_lost.set(true);
723        let value = manager.value();
724        assert_eq!(
725            take(&fixture.log),
726            [
727                "backend1.dispose",
728                "gpu1.dispose",
729                "ContextDisposed",
730                "graphics.create_context -> gpu2",
731                "create_backend_context(gpu2) -> backend2",
732                "ContextCreated(backend2)"
733            ]
734        );
735        assert!(std::ptr::eq(
736            Rc::as_ptr(&value) as *const (),
737            Rc::as_ptr(&fixture.render_interface.backends.borrow()[1]) as *const ()
738        ));
739    }
740
741    #[test]
742    fn shared_graphics_context_is_not_disposed() {
743        let fixture = Fixture::new();
744        let graphics = Graphics::new(&fixture.log, true, None);
745        let manager = fixture.manager(Some(graphics.clone()));
746
747        manager.ensure_valid_backend_context();
748        assert_eq!(
749            take(&fixture.log),
750            ["graphics.get_shared_context", "create_backend_context(gpu0) -> backend1", "ContextCreated(backend1)"]
751        );
752
753        graphics.shared.is_lost.set(true);
754        // The lost shared context is handed out again by the graphics, so
755        // the backend is recreated on every use while it stays lost.
756        manager.ensure_valid_backend_context();
757        assert_eq!(
758            take(&fixture.log),
759            [
760                "backend1.dispose",
761                "ContextDisposed",
762                "graphics.get_shared_context",
763                "create_backend_context(gpu0) -> backend2",
764                "ContextCreated(backend2)"
765            ]
766        );
767    }
768
769    #[test]
770    fn reset_releases_the_backend_but_keeps_the_graphics_context() {
771        let fixture = Fixture::new();
772        let graphics = Graphics::new(&fixture.log, false, None);
773        let manager = fixture.manager(Some(graphics));
774        manager.ensure_valid_backend_context();
775        take(&fixture.log);
776
777        manager.reset();
778        manager.reset();
779        assert_eq!(take(&fixture.log), ["backend1.dispose"]);
780        assert!(manager.gpu_context().is_some());
781
782        // The graphics context is released and recreated with the backend.
783        manager.ensure_valid_backend_context();
784        assert_eq!(
785            take(&fixture.log),
786            [
787                "gpu1.dispose",
788                "ContextDisposed",
789                "graphics.create_context -> gpu2",
790                "create_backend_context(gpu2) -> backend2",
791                "ContextCreated(backend2)"
792            ]
793        );
794    }
795
796    #[test]
797    fn create_render_target_uses_the_backend() {
798        let fixture = Fixture::new();
799        let manager = fixture.manager(None);
800        // Before a backend exists, readiness is the readiness of the graphics.
801        assert!(manager.is_ready_to_create_render_target(&[]));
802        assert!(take(&fixture.log).is_empty());
803
804        manager.create_render_target(&[]);
805        assert_eq!(
806            take(&fixture.log),
807            [
808                "create_backend_context(software) -> backend1",
809                "ContextCreated(backend1)",
810                "backend1.create_render_target(0)"
811            ]
812        );
813
814        fixture.render_interface.backends.borrow()[0].ready_to_create_render_target.set(false);
815        assert!(!manager.is_ready_to_create_render_target(&[]));
816    }
817
818    #[test]
819    fn ready_state_feature_controls_readiness_and_context_use() {
820        let fixture = Fixture::new();
821        let ready_state =
822            Arc::new(ReadyState { is_ready: AtomicBool::new(false), uses_contexts: AtomicBool::new(false) });
823        let graphics = Graphics::new(&fixture.log, false, Some(ready_state.clone()));
824        let manager = fixture.manager(Some(graphics));
825
826        assert!(!manager.is_ready());
827        assert!(!manager.is_ready_to_create_render_target(&[]));
828        let failed = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| manager.ensure_valid_backend_context()));
829        assert!(failed.is_err());
830        assert!(take(&fixture.log).is_empty());
831
832        ready_state.is_ready.store(true, Ordering::SeqCst);
833        assert!(manager.is_ready());
834        manager.ensure_valid_backend_context();
835        // The graphics do not use contexts: the backend is created without one.
836        assert_eq!(take(&fixture.log), ["create_backend_context(software) -> backend1", "ContextCreated(backend1)"]);
837        assert!(manager.gpu_context().is_none());
838
839        manager.reset();
840        ready_state.uses_contexts.store(true, Ordering::SeqCst);
841        manager.ensure_valid_backend_context();
842        assert_eq!(
843            take(&fixture.log),
844            [
845                "backend1.dispose",
846                "graphics.create_context -> gpu1",
847                "create_backend_context(gpu1) -> backend2",
848                "ContextCreated(backend2)"
849            ]
850        );
851    }
852
853    #[test]
854    fn event_subscriptions_can_be_disposed() {
855        let fixture = Fixture::new();
856        let manager = PlatformRenderInterfaceContextManager::new(None);
857        let created = Rc::new(Cell::new(0));
858        let c = created.clone();
859        let subscription = manager.context_created(move |_| c.set(c.get() + 1));
860        manager.ensure_valid_backend_context();
861        assert_eq!(created.get(), 1);
862
863        subscription.dispose();
864        manager.reset();
865        manager.ensure_valid_backend_context();
866        assert_eq!(created.get(), 1);
867        drop(fixture);
868    }
869}