1use crate::gpu_handle_wrap_feature::GpuHandleWrapFeature;
11use crate::helpers::ComResultExt;
12use crate::interop::*;
13use crate::top_level_impl::SurfaceTopLevel;
14use ferroui_base::logging::{LogEventLevel, Logger};
15use ferroui_base::platform::surfaces::{IPlatformRenderSurface, IPlatformRenderSurfaceRenderTarget};
16use ferroui_base::platform::{
17 IExternalObjectsHandleWrapRenderInterfaceContextFeature, IOptionalFeatureProvider, IPlatformGraphics,
18 IPlatformGraphicsContext, IPlatformHandle, KnownPlatformGraphicsExternalImageHandleTypes,
19 KnownPlatformGraphicsExternalSemaphoreHandleTypes, PlatformGraphicsContextLostException,
20 PlatformGraphicsExternalImageFormat, PlatformGraphicsExternalImageProperties, PlatformRenderTargetState,
21 RenderTargetSceneInfo,
22};
23use ferroui_base::reactive::IDisposable;
24use ferroui_base::rendering::composition::CompositionGpuImportedImageSynchronizationCapabilities;
25use ferroui_base::threading::Dispatcher;
26use ferroui_base::{PixelSize, RenderTargetNotReadyException};
27use ferroui_microcom::{ComPtr, HResult, IUnknown};
28use ferroui_opengl::gl_consts::*;
29use ferroui_opengl::surfaces::{IGlPlatformSurface, IGlPlatformSurfaceRenderTarget, IGlPlatformSurfaceRenderingSession};
30use ferroui_opengl::{
31 GetProcAddress, GlBasicInfoInterface, GlInterface, GlProfileType, GlVersion, IGlContext,
32 IGlContextExternalObjectsFeature, IGlExportableExternalImageTexture, IGlExternalImageTexture,
33 IGlExternalSemaphore, OpenGlException,
34};
35use std::any::{Any, TypeId};
36use std::cell::{Cell, RefCell};
37use std::ffi::{c_void, CString};
38use std::rc::{Rc, Weak};
39use std::sync::Arc;
40
41pub struct FerroNativeGlPlatformGraphics {
44 display: ComPtr<IFrnGlDisplay>,
45 gl_display: Rc<GlDisplay>,
46 version: GlVersion,
47 shared_context: Rc<GlContext>,
48}
49
50unsafe impl Send for FerroNativeGlPlatformGraphics {}
69unsafe impl Sync for FerroNativeGlPlatformGraphics {}
71
72impl FerroNativeGlPlatformGraphics {
73 pub fn new(
81 display: ComPtr<IFrnGlDisplay>,
82 factory: &ComPtr<IFerroNativeFactory>,
83 ) -> Result<FerroNativeGlPlatformGraphics, OpenGlException> {
84 let context = display
85 .create_context(None)
86 .map_err(native_error("CreateContext"))?
87 .ok_or_else(|| OpenGlException::new("CreateContext returned no context"))?;
88
89 let gl_interface;
90 let version;
91 {
92 let current = context.make_current().map_err(native_error("MakeCurrent"))?;
93 let get_proc_address = proc_address_of(&display);
94 let basic = unsafe { GlBasicInfoInterface::new(&get_proc_address) };
99 let major = basic.get_integerv(GL_MAJOR_VERSION);
100 let minor = basic.get_integerv(GL_MINOR_VERSION);
101 let profile_mask = basic.get_integerv(GL_CONTEXT_PROFILE_MASK);
102 let is_compatibility_profile =
103 (profile_mask & GL_CONTEXT_COMPATIBILITY_PROFILE_BIT) == GL_CONTEXT_COMPATIBILITY_PROFILE_BIT;
104
105 version = GlVersion::with_compatibility_profile(GlProfileType::OpenGL, major, minor, is_compatibility_profile);
106 gl_interface = Rc::new(unsafe { GlInterface::new(version, proc_address_of(&display)) });
108 drop(current);
109 }
110
111 let gl_display = Rc::new(GlDisplay::new(
112 display.clone(),
113 gl_interface,
114 context.get_sample_count(),
115 context.get_stencil_size(),
116 factory.clone(),
117 )?);
118 let shared_context = GlContext::new(
119 &gl_display,
120 None,
121 display
122 .create_context(None)
123 .map_err(native_error("CreateContext"))?
124 .ok_or_else(|| OpenGlException::new("CreateContext returned no context"))?,
125 version,
126 );
127
128 Ok(FerroNativeGlPlatformGraphics { display, gl_display, version, shared_context })
129 }
130
131 pub fn can_share_contexts(&self) -> bool {
133 true
134 }
135
136 pub fn can_create_contexts(&self) -> bool {
138 true
139 }
140
141 pub fn gl_display(&self) -> &Rc<GlDisplay> {
143 &self.gl_display
144 }
145
146 pub fn shared_context(&self) -> &Rc<GlContext> {
148 &self.shared_context
149 }
150}
151
152impl IPlatformGraphics for FerroNativeGlPlatformGraphics {
153 fn uses_shared_context(&self) -> bool {
154 true
155 }
156
157 fn create_context(&self) -> Rc<dyn IPlatformGraphicsContext> {
163 let native = self.display.create_context(None).check();
164 let Some(native) = native else {
165 panic!("CreateContext returned no context");
166 };
167 GlContext::new(&self.gl_display, None, native, self.version)
168 }
169
170 fn get_shared_context(&self) -> Rc<dyn IPlatformGraphicsContext> {
171 self.shared_context.clone()
172 }
173}
174
175fn proc_address_of(display: &ComPtr<IFrnGlDisplay>) -> GetProcAddress {
178 let display = display.clone();
179 Rc::new(move |name: &str| -> *const c_void {
180 let Ok(name) = CString::new(name) else {
181 return std::ptr::null();
182 };
183 display.get_proc_address(Some(&name)) as *const c_void
184 })
185}
186
187fn native_error(call: &'static str) -> impl Fn(HResult) -> OpenGlException {
189 move |error| OpenGlException::new(format!("{call} failed: {error:?}"))
190}
191
192pub struct GlDisplay {
196 display: ComPtr<IFrnGlDisplay>,
197 gl_interface: Rc<GlInterface>,
198 sample_count: i32,
199 stencil_size: i32,
200 factory: ComPtr<IFerroNativeFactory>,
201 memory_helper: ComPtr<IFrnNativeObjectsMemoryManagement>,
202}
203
204impl GlDisplay {
205 fn new(
216 display: ComPtr<IFrnGlDisplay>,
217 gl_interface: Rc<GlInterface>,
218 sample_count: i32,
219 stencil_size: i32,
220 factory: ComPtr<IFerroNativeFactory>,
221 ) -> Result<GlDisplay, OpenGlException> {
222 let memory_helper = factory
223 .create_memory_management_helper()
224 .map_err(native_error("CreateMemoryManagementHelper"))?
225 .ok_or_else(|| OpenGlException::new("CreateMemoryManagementHelper returned no helper"))?;
226 Ok(GlDisplay { display, sample_count, stencil_size, factory, gl_interface, memory_helper })
227 }
228
229 pub fn gl_interface(&self) -> &Rc<GlInterface> {
230 &self.gl_interface
231 }
232
233 pub fn sample_count(&self) -> i32 {
234 self.sample_count
235 }
236
237 pub fn stencil_size(&self) -> i32 {
238 self.stencil_size
239 }
240
241 pub fn factory(&self) -> &ComPtr<IFerroNativeFactory> {
242 &self.factory
243 }
244
245 pub fn memory_helper(&self) -> &ComPtr<IFrnNativeObjectsMemoryManagement> {
246 &self.memory_helper
247 }
248
249 pub fn clear_context(&self) {
251 self.display.legacy_clear_current_context();
252 }
253
254 pub fn create_shared_context(self: &Rc<Self>, share: &Rc<GlContext>) -> Result<Rc<GlContext>, OpenGlException> {
259 let share_native = share.native();
260 let native = self
261 .display
262 .create_context(Some(&share_native))
263 .map_err(native_error("CreateContext"))?
264 .ok_or_else(|| OpenGlException::new("CreateContext returned no context"))?;
265 Ok(GlContext::new(self, Some(share.clone()), native, share.version()))
266 }
267}
268
269pub struct GlContext {
271 this: Weak<GlContext>,
272 display: Rc<GlDisplay>,
273 shared_with: Option<Rc<GlContext>>,
274 handle_wrap_feature: Rc<GpuHandleWrapFeature>,
275 external_objects: Rc<GlExternalObjectsFeature>,
276 context: RefCell<Option<ComPtr<IFrnGlContext>>>,
277 version: GlVersion,
278}
279
280impl GlContext {
281 pub(crate) fn new(
282 display: &Rc<GlDisplay>,
283 shared_with: Option<Rc<GlContext>>,
284 context: ComPtr<IFrnGlContext>,
285 version: GlVersion,
286 ) -> Rc<GlContext> {
287 Rc::new_cyclic(|this| GlContext {
288 this: this.clone(),
289 display: display.clone(),
290 shared_with,
291 handle_wrap_feature: Rc::new(GpuHandleWrapFeature::new(display.memory_helper().clone())),
292 external_objects: Rc::new(GlExternalObjectsFeature::new(this.clone(), &context, display.clone())),
293 context: RefCell::new(Some(context)),
294 version,
295 })
296 }
297
298 pub fn context(&self) -> Option<ComPtr<IFrnGlContext>> {
300 self.context.borrow().clone()
301 }
302
303 #[track_caller]
308 fn native(&self) -> ComPtr<IFrnGlContext> {
309 self.throw_if_lost();
310 self.context().expect("the context is not lost")
311 }
312
313 #[track_caller]
317 pub fn throw_if_lost(&self) {
318 if self.is_lost() {
319 panic!("{}", PlatformGraphicsContextLostException);
320 }
321 }
322
323 fn try_make_current(&self) -> Result<Rc<dyn IDisposable>, HResult> {
326 let Some(native) = self.context() else {
327 return Err(HResult::FAIL);
328 };
329 let saved = native.make_current()?;
330 Ok(Rc::new(RestoreContext { saved: RefCell::new(saved) }))
331 }
332}
333
334struct RestoreContext {
338 saved: RefCell<Option<ComPtr<IUnknown>>>,
339}
340
341impl IDisposable for RestoreContext {
342 fn dispose(&self) {
343 let saved = self.saved.borrow_mut().take();
344 drop(saved);
345 }
346}
347
348impl Drop for RestoreContext {
349 fn drop(&mut self) {
350 self.dispose();
351 }
352}
353
354impl IOptionalFeatureProvider for GlContext {
355 fn try_get_feature(&self, feature_type: TypeId) -> Option<Rc<dyn Any>> {
356 if feature_type == TypeId::of::<dyn IExternalObjectsHandleWrapRenderInterfaceContextFeature>() {
357 let feature: Rc<dyn IExternalObjectsHandleWrapRenderInterfaceContextFeature> =
358 self.handle_wrap_feature.clone();
359 return Some(Rc::new(feature));
360 }
361 if feature_type == TypeId::of::<dyn IGlContextExternalObjectsFeature>() {
362 let feature: Rc<dyn IGlContextExternalObjectsFeature> = self.external_objects.clone();
363 return Some(Rc::new(feature));
364 }
365 if feature_type == TypeId::of::<dyn IGlContext>() {
368 let this: Rc<dyn IGlContext> = self.this.upgrade()?;
369 return Some(Rc::new(this));
370 }
371 None
372 }
373}
374
375impl IPlatformGraphicsContext for GlContext {
376 fn is_lost(&self) -> bool {
377 self.context.borrow().is_none()
378 }
379
380 fn ensure_current(&self) -> Rc<dyn IDisposable> {
381 IGlContext::make_current(self)
382 }
383
384 fn dispose(&self) {
385 let context = self.context.borrow_mut().take();
386 drop(context);
387 }
388
389 fn as_any(&self) -> &dyn Any {
390 self
391 }
392}
393
394impl IGlContext for GlContext {
395 fn version(&self) -> GlVersion {
396 self.version
397 }
398
399 fn gl_interface(&self) -> Rc<GlInterface> {
400 self.display.gl_interface().clone()
401 }
402
403 fn sample_count(&self) -> i32 {
404 self.display.sample_count()
405 }
406
407 fn stencil_size(&self) -> i32 {
408 self.display.stencil_size()
409 }
410
411 fn make_current(&self) -> Rc<dyn IDisposable> {
415 self.throw_if_lost();
416 self.try_make_current().check()
417 }
418
419 fn is_shared_with(&self, context: &dyn IGlContext) -> bool {
423 let Some(c) = context.as_any().downcast_ref::<GlContext>() else {
424 panic!("Unable to cast the context to type 'GlContext'.");
425 };
426 let same = |a: &GlContext, b: &GlContext| std::ptr::eq(a, b);
427 same(c, self)
428 || c.shared_with.as_deref().is_some_and(|shared| same(shared, self))
429 || self.shared_with.as_deref().is_some_and(|shared| same(shared, c))
430 || match (&self.shared_with, &c.shared_with) {
431 (Some(a), Some(b)) => same(a, b),
432 _ => false,
433 }
434 }
435
436 fn can_create_shared_context(&self) -> bool {
437 true
438 }
439
440 fn create_shared_context(&self, _preferred_versions: Option<&[GlVersion]>) -> Option<Rc<dyn IGlContext>> {
445 let share = match &self.shared_with {
446 Some(shared_with) => shared_with.clone(),
447 None => self.this.upgrade()?,
448 };
449 match self.display.create_shared_context(&share) {
450 Ok(context) => Some(context),
451 Err(error) => {
452 if let Some(logger) = Logger::try_get(LogEventLevel::Error, "OpenGL") {
453 let source: &dyn Any = &"GlContext";
454 logger.log_with_values(Some(source), "Unable to create a shared context: {0}", &[&error]);
455 }
456 None
457 }
458 }
459 }
460}
461
462pub struct GlPlatformSurfaceRenderTarget {
464 target: RefCell<Option<ComPtr<IFrnGlSurfaceRenderTarget>>>,
465 context: Rc<dyn IGlContext>,
466}
467
468impl GlPlatformSurfaceRenderTarget {
469 pub(crate) fn new(target: ComPtr<IFrnGlSurfaceRenderTarget>, context: Rc<dyn IGlContext>) -> Self {
470 Self { target: RefCell::new(Some(target)), context }
471 }
472}
473
474impl IPlatformRenderSurfaceRenderTarget for GlPlatformSurfaceRenderTarget {
475 fn state(&self) -> PlatformRenderTargetState {
476 PlatformRenderTargetState::READY
477 }
478}
479
480impl IGlPlatformSurfaceRenderTarget for GlPlatformSurfaceRenderTarget {
481 fn begin_draw(&self, _scene_info: &RenderTargetSceneInfo) -> Rc<dyn IGlPlatformSurfaceRenderingSession> {
485 let Some(target) = self.target.borrow().clone() else {
487 panic!("Cannot access a disposed object: GlPlatformSurfaceRenderTarget");
488 };
489 let session = target.begin_drawing().check();
490 let Some(session) = session else {
491 panic!("BeginDrawing returned no session");
492 };
493 Rc::new(GlPlatformSurfaceRenderingSession::new(self.context.clone(), session))
494 }
495
496 fn dispose(&self) {
497 let target = self.target.borrow_mut().take();
498 drop(target);
499 }
500}
501
502pub struct GlPlatformSurfaceRenderingSession {
506 context: Rc<dyn IGlContext>,
507 session: RefCell<Option<ComPtr<IFrnGlSurfaceRenderingSession>>>,
508}
509
510impl GlPlatformSurfaceRenderingSession {
511 pub(crate) fn new(context: Rc<dyn IGlContext>, session: ComPtr<IFrnGlSurfaceRenderingSession>) -> Self {
512 Self { context, session: RefCell::new(Some(session)) }
513 }
514
515 #[track_caller]
520 fn with_session<R>(&self, f: impl FnOnce(&IFrnGlSurfaceRenderingSession) -> R) -> R {
521 match self.session.borrow().as_ref() {
522 Some(session) => f(session),
523 None => panic!("Cannot access a disposed object: GlPlatformSurfaceRenderingSession"),
524 }
525 }
526}
527
528impl IGlPlatformSurfaceRenderingSession for GlPlatformSurfaceRenderingSession {
529 fn context(&self) -> Rc<dyn IGlContext> {
530 self.context.clone()
531 }
532
533 fn size(&self) -> PixelSize {
534 let s = self.with_session(|session| session.get_pixel_size()).check();
535 PixelSize::new(s.width, s.height)
536 }
537
538 fn scaling(&self) -> f64 {
539 self.with_session(|session| session.get_scaling()).check()
540 }
541
542 fn is_y_flipped(&self) -> bool {
543 true
544 }
545
546 fn dispose(&self) {
547 let session = self.session.borrow_mut().take();
548 drop(session);
549 }
550}
551
552pub struct GlPlatformSurface {
560 weak_self: std::sync::Weak<GlPlatformSurface>,
561 top_level: SurfaceTopLevel,
562}
563
564impl GlPlatformSurface {
565 pub(crate) fn new(top_level: ComPtr<IFrnTopLevel>) -> Arc<GlPlatformSurface> {
566 Arc::new_cyclic(|weak_self| GlPlatformSurface {
567 weak_self: weak_self.clone(),
568 top_level: SurfaceTopLevel::new(top_level),
569 })
570 }
571
572 pub(crate) fn close(&self) {
575 self.top_level.release();
576 }
577}
578
579impl IPlatformRenderSurface for GlPlatformSurface {
580 fn is_ready(&self) -> bool {
585 self.top_level.get().is_some()
586 }
587
588 fn try_get_surface_kind(&self, kind: TypeId) -> Option<Rc<dyn Any>> {
589 if kind == TypeId::of::<dyn IGlPlatformSurface>() {
590 let this: Rc<dyn IGlPlatformSurface> = Rc::new(GlPlatformSurfaceView(self.weak_self.upgrade()?));
593 return Some(Rc::new(this));
594 }
595 None
596 }
597
598 fn as_any(&self) -> &dyn Any {
599 self
600 }
601}
602
603impl IGlPlatformSurface for GlPlatformSurface {
604 fn create_gl_render_target(&self, context: &Rc<dyn IGlContext>) -> Rc<dyn IGlPlatformSurfaceRenderTarget> {
610 if !Dispatcher::ui_thread().check_access() {
611 panic!("{}", RenderTargetNotReadyException::new());
612 }
613 let Some(top_level) = self.top_level.get() else {
614 panic!("{}", RenderTargetNotReadyException::new());
615 };
616 let Some(native_context) = context.as_any().downcast_ref::<GlContext>() else {
617 panic!("Unable to cast the context to type 'GlContext'.");
618 };
619 let native = native_context.context();
620 let target = top_level.create_gl_render_target(native.as_deref()).check();
621 let Some(target) = target else {
622 panic!("CreateGlRenderTarget returned no render target");
623 };
624 Rc::new(GlPlatformSurfaceRenderTarget::new(target, context.clone()))
625 }
626}
627
628struct GlPlatformSurfaceView(Arc<GlPlatformSurface>);
631
632impl IPlatformRenderSurface for GlPlatformSurfaceView {
633 fn is_ready(&self) -> bool {
634 self.0.is_ready()
635 }
636
637 fn try_get_surface_kind(&self, kind: TypeId) -> Option<Rc<dyn Any>> {
638 self.0.try_get_surface_kind(kind)
639 }
640
641 fn as_any(&self) -> &dyn Any {
642 self.0.as_any()
643 }
644}
645
646impl IGlPlatformSurface for GlPlatformSurfaceView {
647 fn create_gl_render_target(&self, context: &Rc<dyn IGlContext>) -> Rc<dyn IGlPlatformSurfaceRenderTarget> {
648 self.0.create_gl_render_target(context)
649 }
650}
651
652pub(crate) struct GlExternalObjectsFeature {
657 context: Weak<GlContext>,
658 display: Rc<GlDisplay>,
659 device_luid: Option<Vec<u8>>,
660}
661
662impl GlExternalObjectsFeature {
663 fn new(context: Weak<GlContext>, native: &IFrnGlContext, display: Rc<GlDisplay>) -> GlExternalObjectsFeature {
664 let mut registry_id = 0u64;
665 let has_registry_id = unsafe { native.get_io_kit_registry_id(&mut registry_id) };
668 let device_luid = has_registry_id.then(|| registry_id.to_be_bytes().to_vec());
672 GlExternalObjectsFeature { context, display, device_luid }
673 }
674
675 #[track_caller]
678 fn context(&self) -> Rc<GlContext> {
679 match self.context.upgrade() {
680 Some(context) => context,
681 None => panic!("{}", PlatformGraphicsContextLostException),
682 }
683 }
684}
685
686impl IGlContextExternalObjectsFeature for GlExternalObjectsFeature {
687 fn supported_importable_external_image_types(&self) -> Vec<String> {
688 vec![KnownPlatformGraphicsExternalImageHandleTypes::IO_SURFACE_REF.to_string()]
689 }
690
691 fn supported_exportable_external_image_types(&self) -> Vec<String> {
692 Vec::new()
693 }
694
695 fn supported_importable_external_semaphore_types(&self) -> Vec<String> {
696 vec![KnownPlatformGraphicsExternalSemaphoreHandleTypes::METAL_SHARED_EVENT.to_string()]
697 }
698
699 fn supported_exportable_external_semaphore_types(&self) -> Vec<String> {
700 Vec::new()
701 }
702
703 fn get_supported_formats_for_external_memory_type(&self, _type_: &str) -> Vec<PlatformGraphicsExternalImageFormat> {
704 vec![PlatformGraphicsExternalImageFormat::B8G8R8A8UNorm]
705 }
706
707 fn create_image(
708 &self,
709 _type_: &str,
710 _size: PixelSize,
711 _format: PlatformGraphicsExternalImageFormat,
712 ) -> Result<Rc<dyn IGlExportableExternalImageTexture>, OpenGlException> {
713 Err(OpenGlException::new("Specified method is not supported."))
714 }
715
716 fn create_semaphore(&self, _type_: &str) -> Result<Rc<dyn IGlExportableExternalImageTexture>, OpenGlException> {
717 Err(OpenGlException::new("Specified method is not supported."))
718 }
719
720 fn import_image(
721 &self,
722 handle: Rc<dyn IPlatformHandle>,
723 properties: PlatformGraphicsExternalImageProperties,
724 ) -> Result<Rc<dyn IGlExternalImageTexture>, OpenGlException> {
725 if handle.handle_descriptor() == Some(KnownPlatformGraphicsExternalImageHandleTypes::IO_SURFACE_REF) {
726 if properties.format != PlatformGraphicsExternalImageFormat::B8G8R8A8UNorm {
727 return Err(OpenGlException::new("Only B8G8R8A8UNorm format is supported for IOSurfaceRef"));
728 }
729 let context = self.context();
730 let current = context.ensure_current();
731 let gl = context.gl_interface();
732 let old_texture = gl.get_integerv(GL_TEXTURE_BINDING_RECTANGLE);
733 let texture_id = gl.gen_texture();
734 gl.bind_texture(GL_TEXTURE_RECTANGLE, texture_id);
735 let error = unsafe {
740 context.native().tex_image_io_surface2d(
741 GL_TEXTURE_RECTANGLE,
742 GL_RGBA8,
743 properties.width,
744 properties.height,
745 GL_BGRA,
746 GL_UNSIGNED_INT_8_8_8_8_REV,
747 handle.handle() as *mut c_void,
748 0,
749 )
750 };
751 gl.bind_texture(GL_TEXTURE_RECTANGLE, old_texture);
752
753 if error != 0 {
754 gl.delete_texture(texture_id);
755 current.dispose();
756 return Err(OpenGlException::new(format!("CGLTexImageIOSurface2D returned {error}")));
757 }
758 current.dispose();
759 return Ok(Rc::new(ImportedTexture::new(
760 context,
761 GL_TEXTURE_RECTANGLE,
762 texture_id,
763 GL_RGBA8,
764 properties,
765 )));
766 }
767 Err(OpenGlException::new("This handle type is not supported"))
768 }
769
770 fn import_semaphore(&self, handle: Rc<dyn IPlatformHandle>) -> Result<Rc<dyn IGlExternalSemaphore>, OpenGlException> {
771 if handle.handle_descriptor() == Some(KnownPlatformGraphicsExternalSemaphoreHandleTypes::METAL_SHARED_EVENT) {
772 let imported = unsafe { self.display.factory().import_mtl_shared_event(handle.handle() as *mut c_void) }
777 .map_err(native_error("ImportMTLSharedEvent"))?
778 .ok_or_else(|| OpenGlException::new("ImportMTLSharedEvent returned no event"))?;
779 return Ok(Rc::new(MtlEventSemaphore::new(imported)));
780 }
781
782 Err(OpenGlException::new("This handle type is not supported"))
783 }
784
785 fn get_synchronization_capabilities(
786 &self,
787 _image_handle_type: &str,
788 ) -> CompositionGpuImportedImageSynchronizationCapabilities {
789 CompositionGpuImportedImageSynchronizationCapabilities::AUTOMATIC
790 | CompositionGpuImportedImageSynchronizationCapabilities::TIMELINE_SEMAPHORES
791 }
792
793 fn device_luid(&self) -> Option<Vec<u8>> {
794 self.device_luid.clone()
795 }
796
797 fn device_uuid(&self) -> Option<Vec<u8>> {
798 None
799 }
800}
801
802pub(crate) struct MtlEventSemaphore {
804 inner: RefCell<Option<ComPtr<IFrnMTLSharedEvent>>>,
805}
806
807impl MtlEventSemaphore {
808 fn new(inner: ComPtr<IFrnMTLSharedEvent>) -> MtlEventSemaphore {
809 MtlEventSemaphore { inner: RefCell::new(Some(inner)) }
810 }
811
812 #[track_caller]
815 fn inner(&self) -> ComPtr<IFrnMTLSharedEvent> {
816 match self.inner.borrow().clone() {
817 Some(inner) => inner,
818 None => panic!("Cannot access a disposed object: MtlEventSemaphore"),
819 }
820 }
821}
822
823impl IGlExternalSemaphore for MtlEventSemaphore {
824 fn wait_semaphore(&self, _texture: &dyn IGlExternalImageTexture) {
827 panic!("This is a timeline semaphore");
828 }
829
830 fn signal_semaphore(&self, _texture: &dyn IGlExternalImageTexture) {
833 panic!("This is a timeline semaphore");
834 }
835
836 fn wait_timeline_semaphore(&self, _texture: &dyn IGlExternalImageTexture, value: u64) {
837 self.inner().wait(value, 1000);
838 }
839
840 fn signal_timeline_semaphore(&self, _texture: &dyn IGlExternalImageTexture, value: u64) {
841 self.inner().set_signaled_value(value);
842 }
843
844 fn dispose(&self) {
845 let inner = self.inner.borrow_mut().take();
846 drop(inner);
847 }
848}
849
850pub(crate) struct ImportedTexture {
852 context: Rc<GlContext>,
853 type_: i32,
854 id: i32,
855 internal_format: i32,
856 properties: PlatformGraphicsExternalImageProperties,
857 disposed: Cell<bool>,
858}
859
860impl ImportedTexture {
861 fn new(
862 context: Rc<GlContext>,
863 type_: i32,
864 id: i32,
865 internal_format: i32,
866 properties: PlatformGraphicsExternalImageProperties,
867 ) -> ImportedTexture {
868 ImportedTexture { context, type_, id, internal_format, properties, disposed: Cell::new(false) }
869 }
870}
871
872impl IGlExternalImageTexture for ImportedTexture {
873 fn acquire_keyed_mutex(&self, _key: u32) {
876 panic!("Specified method is not supported.");
877 }
878
879 fn release_keyed_mutex(&self, _key: u32) {
882 panic!("Specified method is not supported.");
883 }
884
885 fn texture_id(&self) -> i32 {
886 self.id
887 }
888
889 fn internal_format(&self) -> i32 {
890 self.internal_format
891 }
892
893 fn texture_type(&self) -> i32 {
894 self.type_
895 }
896
897 fn properties(&self) -> PlatformGraphicsExternalImageProperties {
898 self.properties.clone()
899 }
900
901 fn dispose(&self) {
902 if self.disposed.replace(true) {
903 return;
904 }
905 if let Ok(current) = self.context.try_make_current() {
908 self.context.gl_interface().delete_texture(self.id);
909 current.dispose();
910 }
911 }
912}
913
914#[cfg(test)]
915mod tests {
916 use super::*;
920 use crate::interop::create_ferro_native;
921 use ferroui_base::platform::PlatformHandle;
922 use std::ffi::c_char;
923 use std::panic::{catch_unwind, AssertUnwindSafe};
924
925 fn graphics() -> Option<FerroNativeGlPlatformGraphics> {
926 let factory = create_ferro_native().expect("factory");
927 let display = factory.obtain_gl_display().ok().flatten()?;
928 match FerroNativeGlPlatformGraphics::new(display, &factory) {
929 Ok(graphics) => Some(graphics),
930 Err(error) => {
931 eprintln!("skipped: OpenGL is not available: {error}");
932 None
933 }
934 }
935 }
936
937 fn gl_context(context: Rc<dyn IPlatformGraphicsContext>) -> Rc<dyn IGlContext> {
938 let features: &dyn IOptionalFeatureProvider = &*context;
939 features.try_get::<dyn IGlContext>().expect("an OpenGL context")
940 }
941
942 fn external_objects(context: &Rc<dyn IGlContext>) -> Rc<dyn IGlContextExternalObjectsFeature> {
943 let features: &dyn IOptionalFeatureProvider = &**context;
944 features.try_get::<dyn IGlContextExternalObjectsFeature>().expect("the feature")
945 }
946
947 fn panic_message(f: impl FnOnce()) -> String {
949 let payload = catch_unwind(AssertUnwindSafe(f)).expect_err("a panic");
950 match payload.downcast::<String>() {
951 Ok(message) => *message,
952 Err(payload) => payload.downcast::<&str>().map(|message| message.to_string()).unwrap_or_default(),
953 }
954 }
955
956 #[test]
957 fn the_graphics_hand_out_one_shared_context_of_the_core_profile() {
958 let Some(graphics) = graphics() else { return };
959
960 assert!(graphics.uses_shared_context());
961 assert!(graphics.can_share_contexts());
962 assert!(graphics.can_create_contexts());
963 let shared = gl_context(graphics.get_shared_context());
964 assert!(Rc::ptr_eq(&shared, &gl_context(graphics.get_shared_context())));
965
966 let version = shared.version();
967 assert_eq!(GlProfileType::OpenGL, version.type_());
968 assert!(version.major() > 3 || version.major() == 3 && version.minor() >= 2, "{version:?}");
969 assert!(!version.is_compatibility_profile());
970
971 let current = shared.make_current();
973 assert_eq!(version.major(), shared.gl_interface().get_integerv(GL_MAJOR_VERSION));
974 current.dispose();
975 assert!(shared.stencil_size() >= 8);
976 assert!(shared.sample_count() >= 0);
977 }
978
979 #[test]
980 fn contexts_share_with_the_context_they_were_created_from() {
981 let Some(graphics) = graphics() else { return };
982 let shared = gl_context(graphics.get_shared_context());
983 let other = gl_context(graphics.create_context());
984
985 assert!(shared.is_shared_with(&*shared));
986 assert!(!shared.is_shared_with(&*other));
987 assert!(!other.is_shared_with(&*shared));
988
989 assert!(shared.can_create_shared_context());
990 let child = shared.create_shared_context(None).expect("a shared context");
991 assert!(child.is_shared_with(&*shared));
992 assert!(shared.is_shared_with(&*child));
993 let grandchild = child.create_shared_context(None).expect("a shared context");
995 assert!(grandchild.is_shared_with(&*child));
996 assert!(grandchild.is_shared_with(&*shared));
997 assert!(!grandchild.is_shared_with(&*other));
998
999 child.dispose();
1000 grandchild.dispose();
1001 other.dispose();
1002 }
1003
1004 #[test]
1005 fn a_disposed_context_is_lost() {
1006 let Some(graphics) = graphics() else { return };
1007 let context = gl_context(graphics.create_context());
1008 assert!(!context.is_lost());
1009 context.dispose();
1010 assert!(context.is_lost());
1011 assert_eq!("The platform graphics context was lost.", panic_message(|| drop(context.make_current())));
1012 }
1013
1014 #[test]
1015 fn the_external_objects_feature_names_what_it_imports() {
1016 let Some(graphics) = graphics() else { return };
1017 let context = gl_context(graphics.get_shared_context());
1018 let feature = external_objects(&context);
1019
1020 assert_eq!(vec!["IOSurfaceRef".to_string()], feature.supported_importable_external_image_types());
1021 assert!(feature.supported_exportable_external_image_types().is_empty());
1022 assert_eq!(vec!["MetalSharedEvent".to_string()], feature.supported_importable_external_semaphore_types());
1023 assert!(feature.supported_exportable_external_semaphore_types().is_empty());
1024 assert_eq!(
1025 vec![PlatformGraphicsExternalImageFormat::B8G8R8A8UNorm],
1026 feature.get_supported_formats_for_external_memory_type("IOSurfaceRef")
1027 );
1028 assert_eq!(
1029 CompositionGpuImportedImageSynchronizationCapabilities::AUTOMATIC
1030 | CompositionGpuImportedImageSynchronizationCapabilities::TIMELINE_SEMAPHORES,
1031 feature.get_synchronization_capabilities("IOSurfaceRef")
1032 );
1033 assert_eq!(None, feature.device_uuid());
1034 if let Some(luid) = feature.device_luid() {
1035 assert_eq!(8, luid.len());
1036 }
1037
1038 let size = PixelSize::new(4, 4);
1039 let format = PlatformGraphicsExternalImageFormat::B8G8R8A8UNorm;
1040 assert!(feature.create_image("IOSurfaceRef", size, format).is_err());
1041 assert!(feature.create_semaphore("MetalSharedEvent").is_err());
1042
1043 let event =
1044 Rc::new(PlatformHandle::new(1, Some(KnownPlatformGraphicsExternalSemaphoreHandleTypes::METAL_SHARED_EVENT)));
1045 let error = feature.import_image(event, properties(format)).err().expect("refused");
1046 assert_eq!("This handle type is not supported", error.to_string());
1047 let surface =
1048 Rc::new(PlatformHandle::new(1, Some(KnownPlatformGraphicsExternalImageHandleTypes::IO_SURFACE_REF)));
1049 let error = feature
1050 .import_image(surface.clone(), properties(PlatformGraphicsExternalImageFormat::R8G8B8A8UNorm))
1051 .err()
1052 .expect("refused");
1053 assert_eq!("Only B8G8R8A8UNorm format is supported for IOSurfaceRef", error.to_string());
1054 let error = feature.import_semaphore(surface).err().expect("refused");
1055 assert_eq!("This handle type is not supported", error.to_string());
1056 }
1057
1058 fn properties(format: PlatformGraphicsExternalImageFormat) -> PlatformGraphicsExternalImageProperties {
1059 PlatformGraphicsExternalImageProperties { width: 16, height: 8, format, ..Default::default() }
1060 }
1061
1062 type CFTypeRef = *const c_void;
1064 #[link(name = "IOSurface", kind = "framework")]
1065 extern "C" {
1066 static kIOSurfaceWidth: CFTypeRef;
1067 static kIOSurfaceHeight: CFTypeRef;
1068 static kIOSurfaceBytesPerElement: CFTypeRef;
1069 static kIOSurfacePixelFormat: CFTypeRef;
1070 fn IOSurfaceCreate(properties: CFTypeRef) -> CFTypeRef;
1071 }
1072 #[link(name = "CoreFoundation", kind = "framework")]
1073 extern "C" {
1074 static kCFTypeDictionaryKeyCallBacks: c_void;
1075 static kCFTypeDictionaryValueCallBacks: c_void;
1076 fn CFDictionaryCreateMutable(
1077 allocator: CFTypeRef,
1078 capacity: isize,
1079 key_callbacks: *const c_void,
1080 value_callbacks: *const c_void,
1081 ) -> *mut c_void;
1082 fn CFDictionarySetValue(dictionary: *mut c_void, key: CFTypeRef, value: CFTypeRef);
1083 fn CFNumberCreate(allocator: CFTypeRef, type_: isize, value: *const c_void) -> CFTypeRef;
1084 fn CFRelease(object: CFTypeRef);
1085 }
1086
1087 fn io_surface() -> CFTypeRef {
1089 const K_CF_NUMBER_SINT32_TYPE: isize = 3;
1090 unsafe {
1094 let dictionary = CFDictionaryCreateMutable(
1095 std::ptr::null(),
1096 0,
1097 &kCFTypeDictionaryKeyCallBacks,
1098 &kCFTypeDictionaryValueCallBacks,
1099 );
1100 let pixel_format = u32::from_be_bytes(*b"BGRA") as i32;
1101 for (key, value) in [
1102 (kIOSurfaceWidth, 16i32),
1103 (kIOSurfaceHeight, 8),
1104 (kIOSurfaceBytesPerElement, 4),
1105 (kIOSurfacePixelFormat, pixel_format),
1106 ] {
1107 let number = CFNumberCreate(std::ptr::null(), K_CF_NUMBER_SINT32_TYPE, (&value as *const i32).cast());
1108 CFDictionarySetValue(dictionary, key, number);
1109 CFRelease(number);
1110 }
1111 let surface = IOSurfaceCreate(dictionary);
1112 CFRelease(dictionary);
1113 surface
1114 }
1115 }
1116
1117 fn import(feature: &Rc<dyn IGlContextExternalObjectsFeature>, surface: CFTypeRef) -> Rc<dyn IGlExternalImageTexture> {
1118 let handle = Rc::new(PlatformHandle::new(
1119 surface as isize,
1120 Some(KnownPlatformGraphicsExternalImageHandleTypes::IO_SURFACE_REF),
1121 ));
1122 feature
1123 .import_image(handle, properties(PlatformGraphicsExternalImageFormat::B8G8R8A8UNorm))
1124 .expect("the surface is imported")
1125 }
1126
1127 #[test]
1128 fn an_io_surface_is_imported_as_a_rectangle_texture() {
1129 let Some(graphics) = graphics() else { return };
1130 let context = gl_context(graphics.get_shared_context());
1131 let feature = external_objects(&context);
1132 let surface = io_surface();
1133 assert!(!surface.is_null());
1134
1135 let texture = import(&feature, surface);
1136 assert_eq!(GL_TEXTURE_RECTANGLE, texture.texture_type());
1137 assert_eq!(GL_RGBA8, texture.internal_format());
1138 assert!(texture.texture_id() > 0);
1139 assert_eq!(16, texture.properties().width);
1140 assert_eq!(8, texture.properties().height);
1141 assert_eq!("Specified method is not supported.", panic_message(|| texture.acquire_keyed_mutex(0)));
1142 assert_eq!("Specified method is not supported.", panic_message(|| texture.release_keyed_mutex(0)));
1143
1144 let current = context.make_current();
1146 assert_eq!(0, context.gl_interface().get_integerv(GL_TEXTURE_BINDING_RECTANGLE));
1147 current.dispose();
1148
1149 texture.dispose();
1150 texture.dispose();
1151 unsafe { CFRelease(surface) };
1153 }
1154
1155 #[link(name = "Metal", kind = "framework")]
1157 extern "C" {
1158 fn MTLCreateSystemDefaultDevice() -> *mut c_void;
1159 }
1160 #[link(name = "objc")]
1161 extern "C" {
1162 fn sel_registerName(name: *const c_char) -> *mut c_void;
1163 fn objc_msgSend();
1164 fn objc_release(object: *mut c_void);
1165 }
1166
1167 fn shared_event() -> Option<*mut c_void> {
1169 unsafe {
1174 let device = MTLCreateSystemDefaultDevice();
1175 if device.is_null() {
1176 return None;
1177 }
1178 let send: unsafe extern "C" fn(*mut c_void, *mut c_void) -> *mut c_void =
1179 std::mem::transmute(objc_msgSend as unsafe extern "C" fn());
1180 let event = send(device, sel_registerName(c"newSharedEvent".as_ptr()));
1181 objc_release(device);
1182 (!event.is_null()).then_some(event)
1183 }
1184 }
1185
1186 fn signaled_value(event: *mut c_void) -> u64 {
1188 unsafe {
1190 let send: unsafe extern "C" fn(*mut c_void, *mut c_void) -> u64 =
1191 std::mem::transmute(objc_msgSend as unsafe extern "C" fn());
1192 send(event, sel_registerName(c"signaledValue".as_ptr()))
1193 }
1194 }
1195
1196 #[test]
1197 fn a_shared_event_is_imported_as_a_timeline_semaphore() {
1198 let Some(graphics) = graphics() else { return };
1199 let Some(event) = shared_event() else {
1200 eprintln!("skipped: no Metal device");
1201 return;
1202 };
1203 let context = gl_context(graphics.get_shared_context());
1204 let feature = external_objects(&context);
1205 let surface = io_surface();
1206 let texture = import(&feature, surface);
1207
1208 let semaphore = feature
1209 .import_semaphore(Rc::new(PlatformHandle::new(
1210 event as isize,
1211 Some(KnownPlatformGraphicsExternalSemaphoreHandleTypes::METAL_SHARED_EVENT),
1212 )))
1213 .expect("the event is imported");
1214 semaphore.signal_timeline_semaphore(&*texture, 5);
1215 assert_eq!(5, signaled_value(event));
1216 semaphore.wait_timeline_semaphore(&*texture, 5);
1218
1219 assert_eq!("This is a timeline semaphore", panic_message(|| semaphore.wait_semaphore(&*texture)));
1220 assert_eq!("This is a timeline semaphore", panic_message(|| semaphore.signal_semaphore(&*texture)));
1221
1222 semaphore.dispose();
1223 assert_eq!(
1224 "Cannot access a disposed object: MtlEventSemaphore",
1225 panic_message(|| semaphore.signal_timeline_semaphore(&*texture, 6))
1226 );
1227 texture.dispose();
1228 unsafe {
1230 CFRelease(surface);
1231 objc_release(event);
1232 }
1233 }
1234}