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
15pub struct PlatformRenderInterfaceContextManager {
21 weak_self: Weak<PlatformRenderInterfaceContextManager>,
22 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 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 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 pub fn has_platform_render_interface(&self) -> bool {
80 self.render_interface.borrow().is_some()
81 }
82
83 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 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 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 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 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 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 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 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 pub fn existing_backend_context(&self) -> Option<Rc<dyn IPlatformRenderInterfaceContext>> {
203 self.backend.borrow().clone()
204 }
205
206 pub fn gpu_context(&self) -> Option<Rc<dyn IPlatformGraphicsContext>> {
208 self.gpu_context.borrow().as_ref().map(|context| context.value())
209 }
210
211 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 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 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 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 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 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 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 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 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}