1use crate::gpu_handle_wrap_feature::GpuHandleWrapFeature;
6use crate::helpers::ComResultExt;
7use crate::interop::*;
8use crate::top_level_impl::SurfaceTopLevel;
9use ferroui_base::platform::surfaces::{IPlatformRenderSurface, IPlatformRenderSurfaceRenderTarget};
10use ferroui_base::platform::{
11 IExternalObjectsHandleWrapRenderInterfaceContextFeature, IOptionalFeatureProvider, IPlatformGraphics,
12 IPlatformGraphicsContext, IPlatformHandle,
13 KnownPlatformGraphicsExternalImageHandleTypes, KnownPlatformGraphicsExternalSemaphoreHandleTypes,
14 PlatformGraphicsExternalImageFormat, PlatformGraphicsExternalImageProperties,
15};
16use ferroui_base::reactive::IDisposable;
17use ferroui_base::rendering::composition::CompositionGpuImportedImageSynchronizationCapabilities;
18use ferroui_base::threading::Dispatcher;
19use ferroui_base::PixelSize;
20use ferroui_microcom::{ComPtr, HResult};
21use ferroui_metal::{
22 IMetalDevice, IMetalExternalObjectsFeature, IMetalExternalTexture, IMetalPlatformSurface,
23 IMetalPlatformSurfaceRenderTarget, IMetalPlatformSurfaceRenderingSession, IMetalSharedEvent,
24};
25use std::any::{Any, TypeId};
26use std::cell::RefCell;
27use std::ffi::c_void;
28use std::rc::Rc;
29use std::sync::{Arc, Weak};
30
31pub struct MetalPlatformGraphics {
39 display: ComPtr<IFrnMetalDisplay>,
40 memory_helper: ComPtr<IFrnNativeObjectsMemoryManagement>,
41}
42
43unsafe impl Send for MetalPlatformGraphics {}
64unsafe impl Sync for MetalPlatformGraphics {}
66
67impl MetalPlatformGraphics {
68 pub fn new(factory: &IFerroNativeFactory) -> Result<MetalPlatformGraphics, HResult> {
71 let display = factory.obtain_metal_display()?.ok_or(HResult::POINTER)?;
72 let memory_helper = factory.create_memory_management_helper()?.ok_or(HResult::POINTER)?;
73 Ok(MetalPlatformGraphics { display, memory_helper })
74 }
75
76 pub fn try_create_context(&self) -> Result<Rc<MetalDevice>, HResult> {
78 let native = self.display.create_device()?.ok_or(HResult::POINTER)?;
79 Ok(MetalDevice::new(self.memory_helper.clone(), native))
80 }
81}
82
83const _: fn() = || {
84 fn assert_send_sync<T: Send + Sync>() {}
85 assert_send_sync::<MetalPlatformGraphics>();
86};
87
88impl IPlatformGraphics for MetalPlatformGraphics {
89 fn uses_shared_context(&self) -> bool {
90 false
91 }
92
93 fn create_context(&self) -> Rc<dyn IPlatformGraphicsContext> {
94 self.try_create_context().check()
95 }
96
97 fn get_shared_context(&self) -> Rc<dyn IPlatformGraphicsContext> {
98 panic!("Specified method is not supported.");
99 }
100}
101
102pub struct MetalDevice {
104 weak_self: std::rc::Weak<MetalDevice>,
105 sync_root: ferroui_base::utilities::DisposableLock,
106 handle_wrap_feature: Rc<GpuHandleWrapFeature>,
107 external_objects_feature: Rc<MetalExternalObjectsFeature>,
108 native: RefCell<Option<ComPtr<IFrnMetalDevice>>>,
109}
110
111impl MetalDevice {
112 fn new(
113 memory_helper: ComPtr<IFrnNativeObjectsMemoryManagement>,
114 native: ComPtr<IFrnMetalDevice>,
115 ) -> Rc<MetalDevice> {
116 Rc::new_cyclic(|weak_self| MetalDevice {
117 weak_self: weak_self.clone(),
118 sync_root: ferroui_base::utilities::DisposableLock::new(),
119 handle_wrap_feature: Rc::new(GpuHandleWrapFeature::new(memory_helper)),
120 external_objects_feature: Rc::new(MetalExternalObjectsFeature::new(&native, weak_self.clone())),
121 native: RefCell::new(Some(native)),
122 })
123 }
124
125 #[track_caller]
130 pub fn native(&self) -> ComPtr<IFrnMetalDevice> {
131 match self.native.borrow().clone() {
132 Some(native) => native,
133 None => panic!("Cannot access a disposed object: MetalDevice"),
134 }
135 }
136
137 #[track_caller]
144 fn with_native<R>(&self, f: impl FnOnce(&IFrnMetalDevice) -> R) -> R {
145 match self.native.borrow().as_ref() {
146 Some(native) => f(&**native),
147 None => panic!("Cannot access a disposed object: MetalDevice"),
148 }
149 }
150}
151
152impl IOptionalFeatureProvider for MetalDevice {
153 fn try_get_feature(&self, feature_type: TypeId) -> Option<Rc<dyn Any>> {
158 if feature_type == TypeId::of::<dyn IMetalDevice>() {
159 let this: Rc<dyn IMetalDevice> = self.weak_self.upgrade()?;
160 return Some(Rc::new(this));
161 }
162 if feature_type == TypeId::of::<dyn IExternalObjectsHandleWrapRenderInterfaceContextFeature>() {
163 let feature: Rc<dyn IExternalObjectsHandleWrapRenderInterfaceContextFeature> =
164 self.handle_wrap_feature.clone();
165 return Some(Rc::new(feature));
166 }
167 if feature_type == TypeId::of::<dyn IMetalExternalObjectsFeature>() {
168 let feature: Rc<dyn IMetalExternalObjectsFeature> = self.external_objects_feature.clone();
169 return Some(Rc::new(feature));
170 }
171 None
172 }
173}
174
175impl IPlatformGraphicsContext for MetalDevice {
176 fn is_lost(&self) -> bool {
177 false
178 }
179
180 fn ensure_current(&self) -> Rc<dyn IDisposable> {
188 self.sync_root.lock()
189 }
190
191 fn dispose(&self) {
192 let native = self.native.borrow_mut().take();
193 drop(native);
194 }
195
196 fn as_any(&self) -> &dyn Any {
197 self
198 }
199}
200
201impl IMetalDevice for MetalDevice {
202 fn device(&self) -> *mut c_void {
203 self.with_native(|native| native.get_device())
204 }
205
206 fn command_queue(&self) -> *mut c_void {
207 self.with_native(|native| native.get_queue())
208 }
209}
210
211pub struct MetalPlatformSurface {
222 weak_self: Weak<MetalPlatformSurface>,
223 top_level: SurfaceTopLevel,
224}
225
226impl MetalPlatformSurface {
227 pub(crate) fn new(top_level: ComPtr<IFrnTopLevel>) -> Arc<MetalPlatformSurface> {
228 Arc::new_cyclic(|weak_self| MetalPlatformSurface {
229 weak_self: weak_self.clone(),
230 top_level: SurfaceTopLevel::new(top_level),
231 })
232 }
233
234 pub(crate) fn close(&self) {
237 self.top_level.release();
238 }
239}
240
241impl IPlatformRenderSurface for MetalPlatformSurface {
242 fn is_ready(&self) -> bool {
248 self.top_level.get().is_some()
249 }
250
251 fn try_get_surface_kind(&self, kind: TypeId) -> Option<Rc<dyn Any>> {
252 if kind == TypeId::of::<dyn IMetalPlatformSurface>() {
253 let this: Rc<dyn IMetalPlatformSurface> = Rc::new(MetalPlatformSurfaceView(self.weak_self.upgrade()?));
256 return Some(Rc::new(this));
257 }
258 None
259 }
260
261 fn as_any(&self) -> &dyn Any {
262 self
263 }
264}
265
266impl IMetalPlatformSurface for MetalPlatformSurface {
267 fn create_metal_render_target(&self, device: Rc<dyn IMetalDevice>) -> Rc<dyn IMetalPlatformSurfaceRenderTarget> {
272 if !Dispatcher::ui_thread().check_access() {
273 panic!("The render target is not ready.");
274 }
275
276 let Some(top_level) = self.top_level.get() else {
277 panic!("The render target is not ready.");
278 };
279
280 let Some(dev) = device.as_any().downcast_ref::<MetalDevice>() else {
281 panic!("The Metal device belongs to a different platform backend.");
282 };
283 let native_device = dev.native();
284 let target = top_level.create_metal_render_target(Some(&native_device)).check();
285 Rc::new(MetalRenderTarget { native: RefCell::new(target) })
286 }
287}
288
289struct MetalPlatformSurfaceView(Arc<MetalPlatformSurface>);
292
293impl IPlatformRenderSurface for MetalPlatformSurfaceView {
294 fn is_ready(&self) -> bool {
295 self.0.is_ready()
296 }
297
298 fn try_get_surface_kind(&self, kind: TypeId) -> Option<Rc<dyn Any>> {
299 self.0.try_get_surface_kind(kind)
300 }
301
302 fn as_any(&self) -> &dyn Any {
303 self.0.as_any()
304 }
305}
306
307impl IMetalPlatformSurface for MetalPlatformSurfaceView {
308 fn create_metal_render_target(&self, device: Rc<dyn IMetalDevice>) -> Rc<dyn IMetalPlatformSurfaceRenderTarget> {
309 self.0.create_metal_render_target(device)
310 }
311}
312
313pub(crate) struct MetalExternalObjectsFeature {
325 device: std::rc::Weak<MetalDevice>,
326 device_luid: Option<Vec<u8>>,
327}
328
329impl MetalExternalObjectsFeature {
330 fn new(native: &IFrnMetalDevice, device: std::rc::Weak<MetalDevice>) -> MetalExternalObjectsFeature {
331 let mut registry_id = 0u64;
332 let has_registry_id = unsafe { native.get_io_kit_registry_id(&mut registry_id) };
335 let device_luid = has_registry_id.then(|| registry_id.to_be_bytes().to_vec());
337 MetalExternalObjectsFeature { device, device_luid }
338 }
339
340 #[track_caller]
345 fn native(&self) -> ComPtr<IFrnMetalDevice> {
346 match self.device.upgrade() {
347 Some(device) => device.native(),
348 None => panic!("Cannot access a disposed object: MetalDevice"),
349 }
350 }
351}
352
353impl IMetalExternalObjectsFeature for MetalExternalObjectsFeature {
354 fn supported_image_handle_types(&self) -> Vec<String> {
355 vec![KnownPlatformGraphicsExternalImageHandleTypes::IO_SURFACE_REF.to_string()]
356 }
357
358 fn supported_semaphore_types(&self) -> Vec<String> {
359 vec![KnownPlatformGraphicsExternalSemaphoreHandleTypes::METAL_SHARED_EVENT.to_string()]
360 }
361
362 fn device_luid(&self) -> Option<Vec<u8>> {
363 self.device_luid.clone()
364 }
365
366 fn get_synchronization_capabilities(
367 &self,
368 _image_handle_type: &str,
369 ) -> CompositionGpuImportedImageSynchronizationCapabilities {
370 CompositionGpuImportedImageSynchronizationCapabilities::TIMELINE_SEMAPHORES
371 }
372
373 fn import_image(
377 &self,
378 handle: Rc<dyn IPlatformHandle>,
379 properties: PlatformGraphicsExternalImageProperties,
380 ) -> Rc<dyn IMetalExternalTexture> {
381 let format = match properties.format {
384 PlatformGraphicsExternalImageFormat::R8G8B8A8UNorm => FrnPixelFormat::kFrnRgba8888,
385 PlatformGraphicsExternalImageFormat::B8G8R8A8UNorm => FrnPixelFormat::kFrnBgra8888,
386 };
387
388 if handle.handle_descriptor() != Some(KnownPlatformGraphicsExternalImageHandleTypes::IO_SURFACE_REF) {
389 panic!("Specified method is not supported.");
390 }
391
392 let texture = unsafe { self.native().import_io_surface(handle.handle() as *mut c_void, format) }
398 .and_then(|texture| texture.ok_or(HResult::POINTER))
399 .check();
400 Rc::new(ImportedTexture { texture: RefCell::new(Some(texture)) })
401 }
402
403 fn import_shared_event(&self, handle: Rc<dyn IPlatformHandle>) -> Rc<dyn IMetalSharedEvent> {
407 if handle.handle_descriptor() != Some(KnownPlatformGraphicsExternalSemaphoreHandleTypes::METAL_SHARED_EVENT) {
408 panic!("Specified method is not supported.");
409 }
410 let inner = unsafe { self.native().import_shared_event(handle.handle() as *mut c_void) }
416 .and_then(|event| event.ok_or(HResult::POINTER))
417 .check();
418 Rc::new(SharedEvent { inner: RefCell::new(Some(inner)) })
419 }
420
421 fn submit_wait(&self, event: &dyn IMetalSharedEvent, wait_for_value: u64) {
424 let event = SharedEvent::from_contract(event).native();
425 self.native().submit_wait(Some(&*event), wait_for_value).check();
426 }
427
428 fn submit_signal(&self, event: &dyn IMetalSharedEvent, signal_value: u64) {
431 let event = SharedEvent::from_contract(event).native();
432 self.native().submit_signal(Some(&*event), signal_value).check();
433 }
434}
435
436struct ImportedTexture {
438 texture: RefCell<Option<ComPtr<IFrnMetalTexture>>>,
439}
440
441impl ImportedTexture {
442 #[track_caller]
447 fn with_texture<R>(&self, f: impl FnOnce(&IFrnMetalTexture) -> R) -> R {
448 match self.texture.borrow().as_ref() {
449 Some(texture) => f(&**texture),
450 None => panic!("Cannot access a disposed object: ImportedTexture"),
451 }
452 }
453}
454
455impl IMetalExternalTexture for ImportedTexture {
456 fn width(&self) -> i32 {
457 self.with_texture(|texture| texture.get_width())
458 }
459
460 fn height(&self) -> i32 {
461 self.with_texture(|texture| texture.get_height())
462 }
463
464 fn samples(&self) -> i32 {
465 self.with_texture(|texture| texture.get_sample_count())
466 }
467
468 fn handle(&self) -> *mut c_void {
469 self.with_texture(|texture| texture.get_native_handle())
470 }
471
472 fn dispose(&self) {
473 let texture = self.texture.borrow_mut().take();
474 drop(texture);
475 }
476}
477
478struct SharedEvent {
480 inner: RefCell<Option<ComPtr<IFrnMTLSharedEvent>>>,
481}
482
483impl SharedEvent {
484 #[track_caller]
486 fn from_contract(event: &dyn IMetalSharedEvent) -> &SharedEvent {
487 match event.as_any().downcast_ref::<SharedEvent>() {
488 Some(event) => event,
489 None => panic!("The shared event belongs to a different platform backend."),
490 }
491 }
492
493 #[track_caller]
498 fn native(&self) -> ComPtr<IFrnMTLSharedEvent> {
499 match self.inner.borrow().clone() {
500 Some(inner) => inner,
501 None => panic!("Cannot access a disposed object: SharedEvent"),
502 }
503 }
504}
505
506impl IMetalSharedEvent for SharedEvent {
507 fn handle(&self) -> *mut c_void {
508 self.native().get_native_handle()
509 }
510
511 fn dispose(&self) {
512 let inner = self.inner.borrow_mut().take();
513 drop(inner);
514 }
515
516 fn as_any(&self) -> &dyn Any {
517 self
518 }
519}
520
521pub struct MetalRenderTarget {
523 native: RefCell<Option<ComPtr<IFrnMetalRenderTarget>>>,
524}
525
526impl MetalRenderTarget {
527 #[track_caller]
528 fn native(&self) -> ComPtr<IFrnMetalRenderTarget> {
529 match self.native.borrow().clone() {
530 Some(native) => native,
531 None => panic!("Cannot access a disposed object: MetalRenderTarget"),
532 }
533 }
534}
535
536impl IPlatformRenderSurfaceRenderTarget for MetalRenderTarget {}
537
538impl IMetalPlatformSurfaceRenderTarget for MetalRenderTarget {
539 fn begin_rendering(&self) -> Rc<dyn IMetalPlatformSurfaceRenderingSession> {
540 let session = self.native().begin_drawing().check();
541 Rc::new(MetalDrawingSession { session: RefCell::new(session) })
542 }
543
544 fn dispose(&self) {
545 let native = self.native.borrow_mut().take();
546 drop(native);
547 }
548}
549
550pub struct MetalDrawingSession {
553 session: RefCell<Option<ComPtr<IFrnMetalRenderingSession>>>,
554}
555
556impl MetalDrawingSession {
557 #[track_caller]
564 fn with_session<R>(&self, f: impl FnOnce(&IFrnMetalRenderingSession) -> R) -> R {
565 match self.session.borrow().as_ref() {
566 Some(session) => f(&**session),
567 None => panic!("Cannot access a disposed object: MetalDrawingSession"),
568 }
569 }
570}
571
572impl IMetalPlatformSurfaceRenderingSession for MetalDrawingSession {
573 fn texture(&self) -> *mut c_void {
574 self.with_session(|session| session.get_texture())
575 }
576
577 fn size(&self) -> PixelSize {
578 let size = self.with_session(|session| session.get_pixel_size()).check();
579 PixelSize::new(size.width, size.height)
580 }
581
582 fn scaling(&self) -> f64 {
583 self.with_session(|session| session.get_scaling())
584 }
585
586 fn is_y_flipped(&self) -> bool {
587 false
588 }
589
590 fn dispose(&self) {
591 let session = self.session.borrow_mut().take();
592 drop(session);
593 }
594}
595
596#[cfg(test)]
597mod tests {
598 use super::*;
601 use crate::gpu_handle_wrap_feature::tests::helper;
602 use ferroui_base::platform::PlatformHandle;
603
604 type Log = Rc<RefCell<Vec<String>>>;
605
606 struct FakeNativeDevice {
607 log: Log,
608 registry_id: Option<u64>,
609 }
610
611 impl IFrnMetalDeviceImpl for FakeNativeDevice {
612 fn get_device(&self) -> *mut c_void {
613 std::ptr::null_mut()
614 }
615
616 fn get_queue(&self) -> *mut c_void {
617 std::ptr::null_mut()
618 }
619
620 fn get_io_kit_registry_id(&self, value: *mut u64) -> bool {
621 match self.registry_id {
622 Some(registry_id) => {
623 unsafe { *value = registry_id };
625 true
626 }
627 None => false,
628 }
629 }
630
631 fn import_io_surface(
632 &self,
633 handle: *mut c_void,
634 pixel_format: FrnPixelFormat,
635 ) -> Result<Option<ComPtr<IFrnMetalTexture>>, HResult> {
636 self.log.borrow_mut().push(format!("ImportIOSurface({}, {pixel_format:?})", handle as usize));
637 if handle.is_null() {
638 return Err(HResult::FAIL);
639 }
640 Ok(Some(IFrnMetalTexture::from_impl(FakeNativeTexture { log: self.log.clone() })))
641 }
642
643 fn import_shared_event(
644 &self,
645 mtl_shared_event_instance: *mut c_void,
646 ) -> Result<Option<ComPtr<IFrnMTLSharedEvent>>, HResult> {
647 let handle = mtl_shared_event_instance as usize;
648 self.log.borrow_mut().push(format!("ImportSharedEvent({handle})"));
649 Ok(Some(IFrnMTLSharedEvent::from_impl(FakeNativeEvent { log: self.log.clone(), handle })))
650 }
651
652 fn submit_wait(&self, ev: Option<&IFrnMTLSharedEvent>, value: u64) -> Result<(), HResult> {
653 let handle = ev.map(|ev| ev.get_native_handle() as usize);
654 self.log.borrow_mut().push(format!("SubmitWait({handle:?}, {value})"));
655 Ok(())
656 }
657
658 fn submit_signal(&self, ev: Option<&IFrnMTLSharedEvent>, value: u64) -> Result<(), HResult> {
659 let handle = ev.map(|ev| ev.get_native_handle() as usize);
660 self.log.borrow_mut().push(format!("SubmitSignal({handle:?}, {value})"));
661 Ok(())
662 }
663 }
664
665 struct FakeNativeTexture {
666 log: Log,
667 }
668
669 impl IFrnMetalTextureImpl for FakeNativeTexture {
670 fn get_native_handle(&self) -> *mut c_void {
671 77 as *mut c_void
672 }
673
674 fn get_width(&self) -> i32 {
675 30
676 }
677
678 fn get_height(&self) -> i32 {
679 20
680 }
681
682 fn get_sample_count(&self) -> i32 {
683 1
684 }
685 }
686
687 impl Drop for FakeNativeTexture {
688 fn drop(&mut self) {
689 self.log.borrow_mut().push("the texture is released".to_string());
690 }
691 }
692
693 struct FakeNativeEvent {
694 log: Log,
695 handle: usize,
696 }
697
698 impl IFrnMTLSharedEventImpl for FakeNativeEvent {
699 fn get_native_handle(&self) -> *mut c_void {
700 (self.handle + 1000) as *mut c_void
702 }
703
704 fn wait(&self, _value: u64, _timeout_ms: u64) -> bool {
705 false
706 }
707
708 fn set_signaled_value(&self, _value: u64) {}
709
710 fn get_signaled_value(&self) -> u64 {
711 0
712 }
713 }
714
715 impl Drop for FakeNativeEvent {
716 fn drop(&mut self) {
717 self.log.borrow_mut().push("the event is released".to_string());
718 }
719 }
720
721 fn device(registry_id: Option<u64>) -> (Rc<MetalDevice>, Log) {
722 let log = Log::default();
723 let native = IFrnMetalDevice::from_impl(FakeNativeDevice { log: log.clone(), registry_id });
724 (MetalDevice::new(helper(&log), native), log)
725 }
726
727 fn feature(device: &Rc<MetalDevice>) -> Rc<dyn IMetalExternalObjectsFeature> {
728 let features: &dyn IOptionalFeatureProvider = &**device;
729 features.try_get::<dyn IMetalExternalObjectsFeature>().expect("the device has the feature")
730 }
731
732 fn take(log: &Log) -> Vec<String> {
733 std::mem::take(&mut *log.borrow_mut())
734 }
735
736 fn io_surface(handle: isize) -> Rc<dyn IPlatformHandle> {
737 Rc::new(PlatformHandle::new(handle, Some(KnownPlatformGraphicsExternalImageHandleTypes::IO_SURFACE_REF)))
738 }
739
740 fn shared_event(handle: isize) -> Rc<dyn IPlatformHandle> {
741 Rc::new(PlatformHandle::new(
742 handle,
743 Some(KnownPlatformGraphicsExternalSemaphoreHandleTypes::METAL_SHARED_EVENT),
744 ))
745 }
746
747 fn properties(format: PlatformGraphicsExternalImageFormat) -> PlatformGraphicsExternalImageProperties {
748 PlatformGraphicsExternalImageProperties { width: 30, height: 20, format, ..Default::default() }
749 }
750
751 #[test]
752 fn the_device_announces_what_it_imports_and_how_it_is_synchronized() {
753 let (device, _) = device(Some(0x0102_0304_0506_0708));
754 let feature = feature(&device);
755
756 assert_eq!(vec!["IOSurfaceRef".to_string()], feature.supported_image_handle_types());
757 assert_eq!(vec!["MetalSharedEvent".to_string()], feature.supported_semaphore_types());
758 assert_eq!(
759 CompositionGpuImportedImageSynchronizationCapabilities::TIMELINE_SEMAPHORES,
760 feature.get_synchronization_capabilities("IOSurfaceRef")
761 );
762 assert_eq!(Some(vec![1, 2, 3, 4, 5, 6, 7, 8]), feature.device_luid());
764 }
765
766 #[test]
767 fn a_device_without_a_registry_identifier_has_no_luid() {
768 let (device, _) = device(None);
769
770 assert_eq!(None, feature(&device).device_luid());
771 }
772
773 #[test]
774 fn an_io_surface_is_imported_with_the_format_of_the_image() {
775 let (device, log) = device(None);
776 let feature = feature(&device);
777
778 let texture = feature.import_image(io_surface(5), properties(PlatformGraphicsExternalImageFormat::R8G8B8A8UNorm));
779 assert_eq!(vec!["ImportIOSurface(5, kFrnRgba8888)"], take(&log));
780 assert_eq!(30, texture.width());
781 assert_eq!(20, texture.height());
782 assert_eq!(1, texture.samples());
783 assert_eq!(77, texture.handle() as usize);
784
785 texture.dispose();
786 assert_eq!(vec!["the texture is released"], take(&log));
787 texture.dispose();
788 assert!(take(&log).is_empty());
789
790 let texture = feature.import_image(io_surface(6), properties(PlatformGraphicsExternalImageFormat::B8G8R8A8UNorm));
791 assert_eq!(vec!["ImportIOSurface(6, kFrnBgra8888)"], take(&log));
792 drop(texture);
793 }
794
795 #[test]
796 #[should_panic(expected = "Cannot access a disposed object: ImportedTexture")]
797 fn a_disposed_texture_cannot_be_read() {
798 let (device, _) = device(None);
799 let texture = feature(&device).import_image(io_surface(5), properties(Default::default()));
800 texture.dispose();
801 let _ = texture.width();
802 }
803
804 #[test]
805 #[should_panic(expected = "Specified method is not supported.")]
806 fn a_handle_that_is_not_an_io_surface_is_not_imported_as_an_image() {
807 let (device, _) = device(None);
808 let _ = feature(&device).import_image(shared_event(5), properties(Default::default()));
809 }
810
811 #[test]
812 #[should_panic(expected = "Native call failed")]
813 fn a_failed_import_of_the_native_device_fails_the_import() {
814 let (device, _) = device(None);
815 let _ = feature(&device).import_image(io_surface(0), properties(Default::default()));
816 }
817
818 #[test]
819 #[should_panic(expected = "Specified method is not supported.")]
820 fn a_handle_that_is_not_a_shared_event_is_not_imported_as_an_event() {
821 let (device, _) = device(None);
822 let _ = feature(&device).import_shared_event(io_surface(5));
823 }
824
825 #[test]
826 fn the_commands_wait_for_and_signal_the_imported_event() {
827 let (device, log) = device(None);
828 let feature = feature(&device);
829
830 let event = feature.import_shared_event(shared_event(9));
831 assert_eq!(vec!["ImportSharedEvent(9)"], take(&log));
832 assert_eq!(1009, event.handle() as usize);
833
834 feature.submit_wait(&*event, 3);
835 feature.submit_signal(&*event, 4);
836 assert_eq!(vec!["SubmitWait(Some(1009), 3)", "SubmitSignal(Some(1009), 4)"], take(&log));
837
838 event.dispose();
839 assert_eq!(vec!["the event is released"], take(&log));
840 event.dispose();
841 assert!(take(&log).is_empty());
842 }
843
844 #[test]
845 #[should_panic(expected = "Cannot access a disposed object: SharedEvent")]
846 fn a_disposed_event_cannot_be_waited_for() {
847 let (device, _) = device(None);
848 let feature = feature(&device);
849 let event = feature.import_shared_event(shared_event(9));
850 event.dispose();
851 feature.submit_wait(&*event, 3);
852 }
853
854 #[test]
855 #[should_panic(expected = "The shared event belongs to a different platform backend.")]
856 fn an_event_of_another_backend_is_refused() {
857 struct ForeignEvent;
858
859 impl IMetalSharedEvent for ForeignEvent {
860 fn handle(&self) -> *mut c_void {
861 std::ptr::null_mut()
862 }
863
864 fn dispose(&self) {}
865
866 fn as_any(&self) -> &dyn Any {
867 self
868 }
869 }
870
871 let (device, _) = device(None);
872 feature(&device).submit_signal(&ForeignEvent, 1);
873 }
874
875 #[test]
876 fn the_device_wraps_the_handles_it_imports() {
877 let (device, log) = device(None);
878 let features: &dyn IOptionalFeatureProvider = &*device;
879 let wrap = features
880 .try_get::<dyn IExternalObjectsHandleWrapRenderInterfaceContextFeature>()
881 .expect("the device has the feature");
882 let feature = feature(&device);
883
884 let surface = io_surface(5);
887 let wrapped = wrap
888 .wrap_image_handle_on_any_thread(&surface, properties(Default::default()))
889 .expect("an IOSurface is wrapped");
890 drop(surface);
891 let texture = feature.import_image(wrapped.clone().as_platform_handle(), properties(Default::default()));
892 wrapped.dispose();
893 assert_eq!(
894 vec!["RetainCFObject(5)", "ImportIOSurface(5, kFrnRgba8888)", "ReleaseCFObject(5)"],
895 take(&log)
896 );
897 drop(texture);
898 let _ = take(&log);
899
900 let event = shared_event(9);
902 let wrapped = wrap.wrap_semaphore_handle_on_any_thread(&event).expect("a shared event is wrapped");
903 drop(event);
904 let imported = feature.import_shared_event(wrapped.clone().as_platform_handle());
905 wrapped.dispose();
906 assert_eq!(vec!["RetainNSObject(9)", "ImportSharedEvent(9)", "ReleaseNSObject(9)"], take(&log));
907 drop(imported);
908 }
909
910 #[test]
911 fn the_wrapping_feature_outlives_a_disposed_device() {
912 let (device, log) = device(None);
915 let features: &dyn IOptionalFeatureProvider = &*device;
916 let wrap = features
917 .try_get::<dyn IExternalObjectsHandleWrapRenderInterfaceContextFeature>()
918 .expect("the device has the feature");
919 let wrapped = wrap.wrap_semaphore_handle_on_any_thread(&shared_event(9)).expect("a shared event is wrapped");
920
921 device.dispose();
922 wrapped.dispose();
923 assert_eq!(vec!["RetainNSObject(9)", "ReleaseNSObject(9)"], take(&log));
924 }
925
926 #[test]
927 #[should_panic(expected = "Cannot access a disposed object: MetalDevice")]
928 fn the_feature_of_a_disposed_device_imports_nothing() {
929 let (device, _) = device(None);
930 let feature = feature(&device);
931 device.dispose();
932 let _ = feature.import_shared_event(shared_event(9));
933 }
934}