use crate::gpu_handle_wrap_feature::GpuHandleWrapFeature;
use crate::helpers::ComResultExt;
use crate::interop::*;
use crate::top_level_impl::SurfaceTopLevel;
use ferroui_base::logging::{LogEventLevel, Logger};
use ferroui_base::platform::surfaces::{IPlatformRenderSurface, IPlatformRenderSurfaceRenderTarget};
use ferroui_base::platform::{
IExternalObjectsHandleWrapRenderInterfaceContextFeature, IOptionalFeatureProvider, IPlatformGraphics,
IPlatformGraphicsContext, IPlatformHandle, KnownPlatformGraphicsExternalImageHandleTypes,
KnownPlatformGraphicsExternalSemaphoreHandleTypes, PlatformGraphicsContextLostException,
PlatformGraphicsExternalImageFormat, PlatformGraphicsExternalImageProperties, PlatformRenderTargetState,
RenderTargetSceneInfo,
};
use ferroui_base::reactive::IDisposable;
use ferroui_base::rendering::composition::CompositionGpuImportedImageSynchronizationCapabilities;
use ferroui_base::threading::Dispatcher;
use ferroui_base::{PixelSize, RenderTargetNotReadyException};
use ferroui_microcom::{ComPtr, HResult, IUnknown};
use ferroui_opengl::gl_consts::*;
use ferroui_opengl::surfaces::{IGlPlatformSurface, IGlPlatformSurfaceRenderTarget, IGlPlatformSurfaceRenderingSession};
use ferroui_opengl::{
GetProcAddress, GlBasicInfoInterface, GlInterface, GlProfileType, GlVersion, IGlContext,
IGlContextExternalObjectsFeature, IGlExportableExternalImageTexture, IGlExternalImageTexture,
IGlExternalSemaphore, OpenGlException,
};
use std::any::{Any, TypeId};
use std::cell::{Cell, RefCell};
use std::ffi::{c_void, CString};
use std::rc::{Rc, Weak};
use std::sync::Arc;
pub struct FerroNativeGlPlatformGraphics {
display: ComPtr<IFrnGlDisplay>,
gl_display: Rc<GlDisplay>,
version: GlVersion,
shared_context: Rc<GlContext>,
}
unsafe impl Send for FerroNativeGlPlatformGraphics {}
unsafe impl Sync for FerroNativeGlPlatformGraphics {}
impl FerroNativeGlPlatformGraphics {
pub fn new(
display: ComPtr<IFrnGlDisplay>,
factory: &ComPtr<IFerroNativeFactory>,
) -> Result<FerroNativeGlPlatformGraphics, OpenGlException> {
let context = display
.create_context(None)
.map_err(native_error("CreateContext"))?
.ok_or_else(|| OpenGlException::new("CreateContext returned no context"))?;
let gl_interface;
let version;
{
let current = context.make_current().map_err(native_error("MakeCurrent"))?;
let get_proc_address = proc_address_of(&display);
let basic = unsafe { GlBasicInfoInterface::new(&get_proc_address) };
let major = basic.get_integerv(GL_MAJOR_VERSION);
let minor = basic.get_integerv(GL_MINOR_VERSION);
let profile_mask = basic.get_integerv(GL_CONTEXT_PROFILE_MASK);
let is_compatibility_profile =
(profile_mask & GL_CONTEXT_COMPATIBILITY_PROFILE_BIT) == GL_CONTEXT_COMPATIBILITY_PROFILE_BIT;
version = GlVersion::with_compatibility_profile(GlProfileType::OpenGL, major, minor, is_compatibility_profile);
gl_interface = Rc::new(unsafe { GlInterface::new(version, proc_address_of(&display)) });
drop(current);
}
let gl_display = Rc::new(GlDisplay::new(
display.clone(),
gl_interface,
context.get_sample_count(),
context.get_stencil_size(),
factory.clone(),
)?);
let shared_context = GlContext::new(
&gl_display,
None,
display
.create_context(None)
.map_err(native_error("CreateContext"))?
.ok_or_else(|| OpenGlException::new("CreateContext returned no context"))?,
version,
);
Ok(FerroNativeGlPlatformGraphics { display, gl_display, version, shared_context })
}
pub fn can_share_contexts(&self) -> bool {
true
}
pub fn can_create_contexts(&self) -> bool {
true
}
pub fn gl_display(&self) -> &Rc<GlDisplay> {
&self.gl_display
}
pub fn shared_context(&self) -> &Rc<GlContext> {
&self.shared_context
}
}
impl IPlatformGraphics for FerroNativeGlPlatformGraphics {
fn uses_shared_context(&self) -> bool {
true
}
fn create_context(&self) -> Rc<dyn IPlatformGraphicsContext> {
let native = self.display.create_context(None).check();
let Some(native) = native else {
panic!("CreateContext returned no context");
};
GlContext::new(&self.gl_display, None, native, self.version)
}
fn get_shared_context(&self) -> Rc<dyn IPlatformGraphicsContext> {
self.shared_context.clone()
}
}
fn proc_address_of(display: &ComPtr<IFrnGlDisplay>) -> GetProcAddress {
let display = display.clone();
Rc::new(move |name: &str| -> *const c_void {
let Ok(name) = CString::new(name) else {
return std::ptr::null();
};
display.get_proc_address(Some(&name)) as *const c_void
})
}
fn native_error(call: &'static str) -> impl Fn(HResult) -> OpenGlException {
move |error| OpenGlException::new(format!("{call} failed: {error:?}"))
}
pub struct GlDisplay {
display: ComPtr<IFrnGlDisplay>,
gl_interface: Rc<GlInterface>,
sample_count: i32,
stencil_size: i32,
factory: ComPtr<IFerroNativeFactory>,
memory_helper: ComPtr<IFrnNativeObjectsMemoryManagement>,
}
impl GlDisplay {
fn new(
display: ComPtr<IFrnGlDisplay>,
gl_interface: Rc<GlInterface>,
sample_count: i32,
stencil_size: i32,
factory: ComPtr<IFerroNativeFactory>,
) -> Result<GlDisplay, OpenGlException> {
let memory_helper = factory
.create_memory_management_helper()
.map_err(native_error("CreateMemoryManagementHelper"))?
.ok_or_else(|| OpenGlException::new("CreateMemoryManagementHelper returned no helper"))?;
Ok(GlDisplay { display, sample_count, stencil_size, factory, gl_interface, memory_helper })
}
pub fn gl_interface(&self) -> &Rc<GlInterface> {
&self.gl_interface
}
pub fn sample_count(&self) -> i32 {
self.sample_count
}
pub fn stencil_size(&self) -> i32 {
self.stencil_size
}
pub fn factory(&self) -> &ComPtr<IFerroNativeFactory> {
&self.factory
}
pub fn memory_helper(&self) -> &ComPtr<IFrnNativeObjectsMemoryManagement> {
&self.memory_helper
}
pub fn clear_context(&self) {
self.display.legacy_clear_current_context();
}
pub fn create_shared_context(self: &Rc<Self>, share: &Rc<GlContext>) -> Result<Rc<GlContext>, OpenGlException> {
let share_native = share.native();
let native = self
.display
.create_context(Some(&share_native))
.map_err(native_error("CreateContext"))?
.ok_or_else(|| OpenGlException::new("CreateContext returned no context"))?;
Ok(GlContext::new(self, Some(share.clone()), native, share.version()))
}
}
pub struct GlContext {
this: Weak<GlContext>,
display: Rc<GlDisplay>,
shared_with: Option<Rc<GlContext>>,
handle_wrap_feature: Rc<GpuHandleWrapFeature>,
external_objects: Rc<GlExternalObjectsFeature>,
context: RefCell<Option<ComPtr<IFrnGlContext>>>,
version: GlVersion,
}
impl GlContext {
pub(crate) fn new(
display: &Rc<GlDisplay>,
shared_with: Option<Rc<GlContext>>,
context: ComPtr<IFrnGlContext>,
version: GlVersion,
) -> Rc<GlContext> {
Rc::new_cyclic(|this| GlContext {
this: this.clone(),
display: display.clone(),
shared_with,
handle_wrap_feature: Rc::new(GpuHandleWrapFeature::new(display.memory_helper().clone())),
external_objects: Rc::new(GlExternalObjectsFeature::new(this.clone(), &context, display.clone())),
context: RefCell::new(Some(context)),
version,
})
}
pub fn context(&self) -> Option<ComPtr<IFrnGlContext>> {
self.context.borrow().clone()
}
#[track_caller]
fn native(&self) -> ComPtr<IFrnGlContext> {
self.throw_if_lost();
self.context().expect("the context is not lost")
}
#[track_caller]
pub fn throw_if_lost(&self) {
if self.is_lost() {
panic!("{}", PlatformGraphicsContextLostException);
}
}
fn try_make_current(&self) -> Result<Rc<dyn IDisposable>, HResult> {
let Some(native) = self.context() else {
return Err(HResult::FAIL);
};
let saved = native.make_current()?;
Ok(Rc::new(RestoreContext { saved: RefCell::new(saved) }))
}
}
struct RestoreContext {
saved: RefCell<Option<ComPtr<IUnknown>>>,
}
impl IDisposable for RestoreContext {
fn dispose(&self) {
let saved = self.saved.borrow_mut().take();
drop(saved);
}
}
impl Drop for RestoreContext {
fn drop(&mut self) {
self.dispose();
}
}
impl IOptionalFeatureProvider for GlContext {
fn try_get_feature(&self, feature_type: TypeId) -> Option<Rc<dyn Any>> {
if feature_type == TypeId::of::<dyn IExternalObjectsHandleWrapRenderInterfaceContextFeature>() {
let feature: Rc<dyn IExternalObjectsHandleWrapRenderInterfaceContextFeature> =
self.handle_wrap_feature.clone();
return Some(Rc::new(feature));
}
if feature_type == TypeId::of::<dyn IGlContextExternalObjectsFeature>() {
let feature: Rc<dyn IGlContextExternalObjectsFeature> = self.external_objects.clone();
return Some(Rc::new(feature));
}
if feature_type == TypeId::of::<dyn IGlContext>() {
let this: Rc<dyn IGlContext> = self.this.upgrade()?;
return Some(Rc::new(this));
}
None
}
}
impl IPlatformGraphicsContext for GlContext {
fn is_lost(&self) -> bool {
self.context.borrow().is_none()
}
fn ensure_current(&self) -> Rc<dyn IDisposable> {
IGlContext::make_current(self)
}
fn dispose(&self) {
let context = self.context.borrow_mut().take();
drop(context);
}
fn as_any(&self) -> &dyn Any {
self
}
}
impl IGlContext for GlContext {
fn version(&self) -> GlVersion {
self.version
}
fn gl_interface(&self) -> Rc<GlInterface> {
self.display.gl_interface().clone()
}
fn sample_count(&self) -> i32 {
self.display.sample_count()
}
fn stencil_size(&self) -> i32 {
self.display.stencil_size()
}
fn make_current(&self) -> Rc<dyn IDisposable> {
self.throw_if_lost();
self.try_make_current().check()
}
fn is_shared_with(&self, context: &dyn IGlContext) -> bool {
let Some(c) = context.as_any().downcast_ref::<GlContext>() else {
panic!("Unable to cast the context to type 'GlContext'.");
};
let same = |a: &GlContext, b: &GlContext| std::ptr::eq(a, b);
same(c, self)
|| c.shared_with.as_deref().is_some_and(|shared| same(shared, self))
|| self.shared_with.as_deref().is_some_and(|shared| same(shared, c))
|| match (&self.shared_with, &c.shared_with) {
(Some(a), Some(b)) => same(a, b),
_ => false,
}
}
fn can_create_shared_context(&self) -> bool {
true
}
fn create_shared_context(&self, _preferred_versions: Option<&[GlVersion]>) -> Option<Rc<dyn IGlContext>> {
let share = match &self.shared_with {
Some(shared_with) => shared_with.clone(),
None => self.this.upgrade()?,
};
match self.display.create_shared_context(&share) {
Ok(context) => Some(context),
Err(error) => {
if let Some(logger) = Logger::try_get(LogEventLevel::Error, "OpenGL") {
let source: &dyn Any = &"GlContext";
logger.log_with_values(Some(source), "Unable to create a shared context: {0}", &[&error]);
}
None
}
}
}
}
pub struct GlPlatformSurfaceRenderTarget {
target: RefCell<Option<ComPtr<IFrnGlSurfaceRenderTarget>>>,
context: Rc<dyn IGlContext>,
}
impl GlPlatformSurfaceRenderTarget {
pub(crate) fn new(target: ComPtr<IFrnGlSurfaceRenderTarget>, context: Rc<dyn IGlContext>) -> Self {
Self { target: RefCell::new(Some(target)), context }
}
}
impl IPlatformRenderSurfaceRenderTarget for GlPlatformSurfaceRenderTarget {
fn state(&self) -> PlatformRenderTargetState {
PlatformRenderTargetState::READY
}
}
impl IGlPlatformSurfaceRenderTarget for GlPlatformSurfaceRenderTarget {
fn begin_draw(&self, _scene_info: &RenderTargetSceneInfo) -> Rc<dyn IGlPlatformSurfaceRenderingSession> {
let Some(target) = self.target.borrow().clone() else {
panic!("Cannot access a disposed object: GlPlatformSurfaceRenderTarget");
};
let session = target.begin_drawing().check();
let Some(session) = session else {
panic!("BeginDrawing returned no session");
};
Rc::new(GlPlatformSurfaceRenderingSession::new(self.context.clone(), session))
}
fn dispose(&self) {
let target = self.target.borrow_mut().take();
drop(target);
}
}
pub struct GlPlatformSurfaceRenderingSession {
context: Rc<dyn IGlContext>,
session: RefCell<Option<ComPtr<IFrnGlSurfaceRenderingSession>>>,
}
impl GlPlatformSurfaceRenderingSession {
pub(crate) fn new(context: Rc<dyn IGlContext>, session: ComPtr<IFrnGlSurfaceRenderingSession>) -> Self {
Self { context, session: RefCell::new(Some(session)) }
}
#[track_caller]
fn with_session<R>(&self, f: impl FnOnce(&IFrnGlSurfaceRenderingSession) -> R) -> R {
match self.session.borrow().as_ref() {
Some(session) => f(session),
None => panic!("Cannot access a disposed object: GlPlatformSurfaceRenderingSession"),
}
}
}
impl IGlPlatformSurfaceRenderingSession for GlPlatformSurfaceRenderingSession {
fn context(&self) -> Rc<dyn IGlContext> {
self.context.clone()
}
fn size(&self) -> PixelSize {
let s = self.with_session(|session| session.get_pixel_size()).check();
PixelSize::new(s.width, s.height)
}
fn scaling(&self) -> f64 {
self.with_session(|session| session.get_scaling()).check()
}
fn is_y_flipped(&self) -> bool {
true
}
fn dispose(&self) {
let session = self.session.borrow_mut().take();
drop(session);
}
}
pub struct GlPlatformSurface {
weak_self: std::sync::Weak<GlPlatformSurface>,
top_level: SurfaceTopLevel,
}
impl GlPlatformSurface {
pub(crate) fn new(top_level: ComPtr<IFrnTopLevel>) -> Arc<GlPlatformSurface> {
Arc::new_cyclic(|weak_self| GlPlatformSurface {
weak_self: weak_self.clone(),
top_level: SurfaceTopLevel::new(top_level),
})
}
pub(crate) fn close(&self) {
self.top_level.release();
}
}
impl IPlatformRenderSurface for GlPlatformSurface {
fn is_ready(&self) -> bool {
self.top_level.get().is_some()
}
fn try_get_surface_kind(&self, kind: TypeId) -> Option<Rc<dyn Any>> {
if kind == TypeId::of::<dyn IGlPlatformSurface>() {
let this: Rc<dyn IGlPlatformSurface> = Rc::new(GlPlatformSurfaceView(self.weak_self.upgrade()?));
return Some(Rc::new(this));
}
None
}
fn as_any(&self) -> &dyn Any {
self
}
}
impl IGlPlatformSurface for GlPlatformSurface {
fn create_gl_render_target(&self, context: &Rc<dyn IGlContext>) -> Rc<dyn IGlPlatformSurfaceRenderTarget> {
if !Dispatcher::ui_thread().check_access() {
panic!("{}", RenderTargetNotReadyException::new());
}
let Some(top_level) = self.top_level.get() else {
panic!("{}", RenderTargetNotReadyException::new());
};
let Some(native_context) = context.as_any().downcast_ref::<GlContext>() else {
panic!("Unable to cast the context to type 'GlContext'.");
};
let native = native_context.context();
let target = top_level.create_gl_render_target(native.as_deref()).check();
let Some(target) = target else {
panic!("CreateGlRenderTarget returned no render target");
};
Rc::new(GlPlatformSurfaceRenderTarget::new(target, context.clone()))
}
}
struct GlPlatformSurfaceView(Arc<GlPlatformSurface>);
impl IPlatformRenderSurface for GlPlatformSurfaceView {
fn is_ready(&self) -> bool {
self.0.is_ready()
}
fn try_get_surface_kind(&self, kind: TypeId) -> Option<Rc<dyn Any>> {
self.0.try_get_surface_kind(kind)
}
fn as_any(&self) -> &dyn Any {
self.0.as_any()
}
}
impl IGlPlatformSurface for GlPlatformSurfaceView {
fn create_gl_render_target(&self, context: &Rc<dyn IGlContext>) -> Rc<dyn IGlPlatformSurfaceRenderTarget> {
self.0.create_gl_render_target(context)
}
}
pub(crate) struct GlExternalObjectsFeature {
context: Weak<GlContext>,
display: Rc<GlDisplay>,
device_luid: Option<Vec<u8>>,
}
impl GlExternalObjectsFeature {
fn new(context: Weak<GlContext>, native: &IFrnGlContext, display: Rc<GlDisplay>) -> GlExternalObjectsFeature {
let mut registry_id = 0u64;
let has_registry_id = unsafe { native.get_io_kit_registry_id(&mut registry_id) };
let device_luid = has_registry_id.then(|| registry_id.to_be_bytes().to_vec());
GlExternalObjectsFeature { context, display, device_luid }
}
#[track_caller]
fn context(&self) -> Rc<GlContext> {
match self.context.upgrade() {
Some(context) => context,
None => panic!("{}", PlatformGraphicsContextLostException),
}
}
}
impl IGlContextExternalObjectsFeature for GlExternalObjectsFeature {
fn supported_importable_external_image_types(&self) -> Vec<String> {
vec![KnownPlatformGraphicsExternalImageHandleTypes::IO_SURFACE_REF.to_string()]
}
fn supported_exportable_external_image_types(&self) -> Vec<String> {
Vec::new()
}
fn supported_importable_external_semaphore_types(&self) -> Vec<String> {
vec![KnownPlatformGraphicsExternalSemaphoreHandleTypes::METAL_SHARED_EVENT.to_string()]
}
fn supported_exportable_external_semaphore_types(&self) -> Vec<String> {
Vec::new()
}
fn get_supported_formats_for_external_memory_type(&self, _type_: &str) -> Vec<PlatformGraphicsExternalImageFormat> {
vec![PlatformGraphicsExternalImageFormat::B8G8R8A8UNorm]
}
fn create_image(
&self,
_type_: &str,
_size: PixelSize,
_format: PlatformGraphicsExternalImageFormat,
) -> Result<Rc<dyn IGlExportableExternalImageTexture>, OpenGlException> {
Err(OpenGlException::new("Specified method is not supported."))
}
fn create_semaphore(&self, _type_: &str) -> Result<Rc<dyn IGlExportableExternalImageTexture>, OpenGlException> {
Err(OpenGlException::new("Specified method is not supported."))
}
fn import_image(
&self,
handle: Rc<dyn IPlatformHandle>,
properties: PlatformGraphicsExternalImageProperties,
) -> Result<Rc<dyn IGlExternalImageTexture>, OpenGlException> {
if handle.handle_descriptor() == Some(KnownPlatformGraphicsExternalImageHandleTypes::IO_SURFACE_REF) {
if properties.format != PlatformGraphicsExternalImageFormat::B8G8R8A8UNorm {
return Err(OpenGlException::new("Only B8G8R8A8UNorm format is supported for IOSurfaceRef"));
}
let context = self.context();
let current = context.ensure_current();
let gl = context.gl_interface();
let old_texture = gl.get_integerv(GL_TEXTURE_BINDING_RECTANGLE);
let texture_id = gl.gen_texture();
gl.bind_texture(GL_TEXTURE_RECTANGLE, texture_id);
let error = unsafe {
context.native().tex_image_io_surface2d(
GL_TEXTURE_RECTANGLE,
GL_RGBA8,
properties.width,
properties.height,
GL_BGRA,
GL_UNSIGNED_INT_8_8_8_8_REV,
handle.handle() as *mut c_void,
0,
)
};
gl.bind_texture(GL_TEXTURE_RECTANGLE, old_texture);
if error != 0 {
gl.delete_texture(texture_id);
current.dispose();
return Err(OpenGlException::new(format!("CGLTexImageIOSurface2D returned {error}")));
}
current.dispose();
return Ok(Rc::new(ImportedTexture::new(
context,
GL_TEXTURE_RECTANGLE,
texture_id,
GL_RGBA8,
properties,
)));
}
Err(OpenGlException::new("This handle type is not supported"))
}
fn import_semaphore(&self, handle: Rc<dyn IPlatformHandle>) -> Result<Rc<dyn IGlExternalSemaphore>, OpenGlException> {
if handle.handle_descriptor() == Some(KnownPlatformGraphicsExternalSemaphoreHandleTypes::METAL_SHARED_EVENT) {
let imported = unsafe { self.display.factory().import_mtl_shared_event(handle.handle() as *mut c_void) }
.map_err(native_error("ImportMTLSharedEvent"))?
.ok_or_else(|| OpenGlException::new("ImportMTLSharedEvent returned no event"))?;
return Ok(Rc::new(MtlEventSemaphore::new(imported)));
}
Err(OpenGlException::new("This handle type is not supported"))
}
fn get_synchronization_capabilities(
&self,
_image_handle_type: &str,
) -> CompositionGpuImportedImageSynchronizationCapabilities {
CompositionGpuImportedImageSynchronizationCapabilities::AUTOMATIC
| CompositionGpuImportedImageSynchronizationCapabilities::TIMELINE_SEMAPHORES
}
fn device_luid(&self) -> Option<Vec<u8>> {
self.device_luid.clone()
}
fn device_uuid(&self) -> Option<Vec<u8>> {
None
}
}
pub(crate) struct MtlEventSemaphore {
inner: RefCell<Option<ComPtr<IFrnMTLSharedEvent>>>,
}
impl MtlEventSemaphore {
fn new(inner: ComPtr<IFrnMTLSharedEvent>) -> MtlEventSemaphore {
MtlEventSemaphore { inner: RefCell::new(Some(inner)) }
}
#[track_caller]
fn inner(&self) -> ComPtr<IFrnMTLSharedEvent> {
match self.inner.borrow().clone() {
Some(inner) => inner,
None => panic!("Cannot access a disposed object: MtlEventSemaphore"),
}
}
}
impl IGlExternalSemaphore for MtlEventSemaphore {
fn wait_semaphore(&self, _texture: &dyn IGlExternalImageTexture) {
panic!("This is a timeline semaphore");
}
fn signal_semaphore(&self, _texture: &dyn IGlExternalImageTexture) {
panic!("This is a timeline semaphore");
}
fn wait_timeline_semaphore(&self, _texture: &dyn IGlExternalImageTexture, value: u64) {
self.inner().wait(value, 1000);
}
fn signal_timeline_semaphore(&self, _texture: &dyn IGlExternalImageTexture, value: u64) {
self.inner().set_signaled_value(value);
}
fn dispose(&self) {
let inner = self.inner.borrow_mut().take();
drop(inner);
}
}
pub(crate) struct ImportedTexture {
context: Rc<GlContext>,
type_: i32,
id: i32,
internal_format: i32,
properties: PlatformGraphicsExternalImageProperties,
disposed: Cell<bool>,
}
impl ImportedTexture {
fn new(
context: Rc<GlContext>,
type_: i32,
id: i32,
internal_format: i32,
properties: PlatformGraphicsExternalImageProperties,
) -> ImportedTexture {
ImportedTexture { context, type_, id, internal_format, properties, disposed: Cell::new(false) }
}
}
impl IGlExternalImageTexture for ImportedTexture {
fn acquire_keyed_mutex(&self, _key: u32) {
panic!("Specified method is not supported.");
}
fn release_keyed_mutex(&self, _key: u32) {
panic!("Specified method is not supported.");
}
fn texture_id(&self) -> i32 {
self.id
}
fn internal_format(&self) -> i32 {
self.internal_format
}
fn texture_type(&self) -> i32 {
self.type_
}
fn properties(&self) -> PlatformGraphicsExternalImageProperties {
self.properties.clone()
}
fn dispose(&self) {
if self.disposed.replace(true) {
return;
}
if let Ok(current) = self.context.try_make_current() {
self.context.gl_interface().delete_texture(self.id);
current.dispose();
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::interop::create_ferro_native;
use ferroui_base::platform::PlatformHandle;
use std::ffi::c_char;
use std::panic::{catch_unwind, AssertUnwindSafe};
fn graphics() -> Option<FerroNativeGlPlatformGraphics> {
let factory = create_ferro_native().expect("factory");
let display = factory.obtain_gl_display().ok().flatten()?;
match FerroNativeGlPlatformGraphics::new(display, &factory) {
Ok(graphics) => Some(graphics),
Err(error) => {
eprintln!("skipped: OpenGL is not available: {error}");
None
}
}
}
fn gl_context(context: Rc<dyn IPlatformGraphicsContext>) -> Rc<dyn IGlContext> {
let features: &dyn IOptionalFeatureProvider = &*context;
features.try_get::<dyn IGlContext>().expect("an OpenGL context")
}
fn external_objects(context: &Rc<dyn IGlContext>) -> Rc<dyn IGlContextExternalObjectsFeature> {
let features: &dyn IOptionalFeatureProvider = &**context;
features.try_get::<dyn IGlContextExternalObjectsFeature>().expect("the feature")
}
fn panic_message(f: impl FnOnce()) -> String {
let payload = catch_unwind(AssertUnwindSafe(f)).expect_err("a panic");
match payload.downcast::<String>() {
Ok(message) => *message,
Err(payload) => payload.downcast::<&str>().map(|message| message.to_string()).unwrap_or_default(),
}
}
#[test]
fn the_graphics_hand_out_one_shared_context_of_the_core_profile() {
let Some(graphics) = graphics() else { return };
assert!(graphics.uses_shared_context());
assert!(graphics.can_share_contexts());
assert!(graphics.can_create_contexts());
let shared = gl_context(graphics.get_shared_context());
assert!(Rc::ptr_eq(&shared, &gl_context(graphics.get_shared_context())));
let version = shared.version();
assert_eq!(GlProfileType::OpenGL, version.type_());
assert!(version.major() > 3 || version.major() == 3 && version.minor() >= 2, "{version:?}");
assert!(!version.is_compatibility_profile());
let current = shared.make_current();
assert_eq!(version.major(), shared.gl_interface().get_integerv(GL_MAJOR_VERSION));
current.dispose();
assert!(shared.stencil_size() >= 8);
assert!(shared.sample_count() >= 0);
}
#[test]
fn contexts_share_with_the_context_they_were_created_from() {
let Some(graphics) = graphics() else { return };
let shared = gl_context(graphics.get_shared_context());
let other = gl_context(graphics.create_context());
assert!(shared.is_shared_with(&*shared));
assert!(!shared.is_shared_with(&*other));
assert!(!other.is_shared_with(&*shared));
assert!(shared.can_create_shared_context());
let child = shared.create_shared_context(None).expect("a shared context");
assert!(child.is_shared_with(&*shared));
assert!(shared.is_shared_with(&*child));
let grandchild = child.create_shared_context(None).expect("a shared context");
assert!(grandchild.is_shared_with(&*child));
assert!(grandchild.is_shared_with(&*shared));
assert!(!grandchild.is_shared_with(&*other));
child.dispose();
grandchild.dispose();
other.dispose();
}
#[test]
fn a_disposed_context_is_lost() {
let Some(graphics) = graphics() else { return };
let context = gl_context(graphics.create_context());
assert!(!context.is_lost());
context.dispose();
assert!(context.is_lost());
assert_eq!("The platform graphics context was lost.", panic_message(|| drop(context.make_current())));
}
#[test]
fn the_external_objects_feature_names_what_it_imports() {
let Some(graphics) = graphics() else { return };
let context = gl_context(graphics.get_shared_context());
let feature = external_objects(&context);
assert_eq!(vec!["IOSurfaceRef".to_string()], feature.supported_importable_external_image_types());
assert!(feature.supported_exportable_external_image_types().is_empty());
assert_eq!(vec!["MetalSharedEvent".to_string()], feature.supported_importable_external_semaphore_types());
assert!(feature.supported_exportable_external_semaphore_types().is_empty());
assert_eq!(
vec![PlatformGraphicsExternalImageFormat::B8G8R8A8UNorm],
feature.get_supported_formats_for_external_memory_type("IOSurfaceRef")
);
assert_eq!(
CompositionGpuImportedImageSynchronizationCapabilities::AUTOMATIC
| CompositionGpuImportedImageSynchronizationCapabilities::TIMELINE_SEMAPHORES,
feature.get_synchronization_capabilities("IOSurfaceRef")
);
assert_eq!(None, feature.device_uuid());
if let Some(luid) = feature.device_luid() {
assert_eq!(8, luid.len());
}
let size = PixelSize::new(4, 4);
let format = PlatformGraphicsExternalImageFormat::B8G8R8A8UNorm;
assert!(feature.create_image("IOSurfaceRef", size, format).is_err());
assert!(feature.create_semaphore("MetalSharedEvent").is_err());
let event =
Rc::new(PlatformHandle::new(1, Some(KnownPlatformGraphicsExternalSemaphoreHandleTypes::METAL_SHARED_EVENT)));
let error = feature.import_image(event, properties(format)).err().expect("refused");
assert_eq!("This handle type is not supported", error.to_string());
let surface =
Rc::new(PlatformHandle::new(1, Some(KnownPlatformGraphicsExternalImageHandleTypes::IO_SURFACE_REF)));
let error = feature
.import_image(surface.clone(), properties(PlatformGraphicsExternalImageFormat::R8G8B8A8UNorm))
.err()
.expect("refused");
assert_eq!("Only B8G8R8A8UNorm format is supported for IOSurfaceRef", error.to_string());
let error = feature.import_semaphore(surface).err().expect("refused");
assert_eq!("This handle type is not supported", error.to_string());
}
fn properties(format: PlatformGraphicsExternalImageFormat) -> PlatformGraphicsExternalImageProperties {
PlatformGraphicsExternalImageProperties { width: 16, height: 8, format, ..Default::default() }
}
type CFTypeRef = *const c_void;
#[link(name = "IOSurface", kind = "framework")]
extern "C" {
static kIOSurfaceWidth: CFTypeRef;
static kIOSurfaceHeight: CFTypeRef;
static kIOSurfaceBytesPerElement: CFTypeRef;
static kIOSurfacePixelFormat: CFTypeRef;
fn IOSurfaceCreate(properties: CFTypeRef) -> CFTypeRef;
}
#[link(name = "CoreFoundation", kind = "framework")]
extern "C" {
static kCFTypeDictionaryKeyCallBacks: c_void;
static kCFTypeDictionaryValueCallBacks: c_void;
fn CFDictionaryCreateMutable(
allocator: CFTypeRef,
capacity: isize,
key_callbacks: *const c_void,
value_callbacks: *const c_void,
) -> *mut c_void;
fn CFDictionarySetValue(dictionary: *mut c_void, key: CFTypeRef, value: CFTypeRef);
fn CFNumberCreate(allocator: CFTypeRef, type_: isize, value: *const c_void) -> CFTypeRef;
fn CFRelease(object: CFTypeRef);
}
fn io_surface() -> CFTypeRef {
const K_CF_NUMBER_SINT32_TYPE: isize = 3;
unsafe {
let dictionary = CFDictionaryCreateMutable(
std::ptr::null(),
0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks,
);
let pixel_format = u32::from_be_bytes(*b"BGRA") as i32;
for (key, value) in [
(kIOSurfaceWidth, 16i32),
(kIOSurfaceHeight, 8),
(kIOSurfaceBytesPerElement, 4),
(kIOSurfacePixelFormat, pixel_format),
] {
let number = CFNumberCreate(std::ptr::null(), K_CF_NUMBER_SINT32_TYPE, (&value as *const i32).cast());
CFDictionarySetValue(dictionary, key, number);
CFRelease(number);
}
let surface = IOSurfaceCreate(dictionary);
CFRelease(dictionary);
surface
}
}
fn import(feature: &Rc<dyn IGlContextExternalObjectsFeature>, surface: CFTypeRef) -> Rc<dyn IGlExternalImageTexture> {
let handle = Rc::new(PlatformHandle::new(
surface as isize,
Some(KnownPlatformGraphicsExternalImageHandleTypes::IO_SURFACE_REF),
));
feature
.import_image(handle, properties(PlatformGraphicsExternalImageFormat::B8G8R8A8UNorm))
.expect("the surface is imported")
}
#[test]
fn an_io_surface_is_imported_as_a_rectangle_texture() {
let Some(graphics) = graphics() else { return };
let context = gl_context(graphics.get_shared_context());
let feature = external_objects(&context);
let surface = io_surface();
assert!(!surface.is_null());
let texture = import(&feature, surface);
assert_eq!(GL_TEXTURE_RECTANGLE, texture.texture_type());
assert_eq!(GL_RGBA8, texture.internal_format());
assert!(texture.texture_id() > 0);
assert_eq!(16, texture.properties().width);
assert_eq!(8, texture.properties().height);
assert_eq!("Specified method is not supported.", panic_message(|| texture.acquire_keyed_mutex(0)));
assert_eq!("Specified method is not supported.", panic_message(|| texture.release_keyed_mutex(0)));
let current = context.make_current();
assert_eq!(0, context.gl_interface().get_integerv(GL_TEXTURE_BINDING_RECTANGLE));
current.dispose();
texture.dispose();
texture.dispose();
unsafe { CFRelease(surface) };
}
#[link(name = "Metal", kind = "framework")]
extern "C" {
fn MTLCreateSystemDefaultDevice() -> *mut c_void;
}
#[link(name = "objc")]
extern "C" {
fn sel_registerName(name: *const c_char) -> *mut c_void;
fn objc_msgSend();
fn objc_release(object: *mut c_void);
}
fn shared_event() -> Option<*mut c_void> {
unsafe {
let device = MTLCreateSystemDefaultDevice();
if device.is_null() {
return None;
}
let send: unsafe extern "C" fn(*mut c_void, *mut c_void) -> *mut c_void =
std::mem::transmute(objc_msgSend as unsafe extern "C" fn());
let event = send(device, sel_registerName(c"newSharedEvent".as_ptr()));
objc_release(device);
(!event.is_null()).then_some(event)
}
}
fn signaled_value(event: *mut c_void) -> u64 {
unsafe {
let send: unsafe extern "C" fn(*mut c_void, *mut c_void) -> u64 =
std::mem::transmute(objc_msgSend as unsafe extern "C" fn());
send(event, sel_registerName(c"signaledValue".as_ptr()))
}
}
#[test]
fn a_shared_event_is_imported_as_a_timeline_semaphore() {
let Some(graphics) = graphics() else { return };
let Some(event) = shared_event() else {
eprintln!("skipped: no Metal device");
return;
};
let context = gl_context(graphics.get_shared_context());
let feature = external_objects(&context);
let surface = io_surface();
let texture = import(&feature, surface);
let semaphore = feature
.import_semaphore(Rc::new(PlatformHandle::new(
event as isize,
Some(KnownPlatformGraphicsExternalSemaphoreHandleTypes::METAL_SHARED_EVENT),
)))
.expect("the event is imported");
semaphore.signal_timeline_semaphore(&*texture, 5);
assert_eq!(5, signaled_value(event));
semaphore.wait_timeline_semaphore(&*texture, 5);
assert_eq!("This is a timeline semaphore", panic_message(|| semaphore.wait_semaphore(&*texture)));
assert_eq!("This is a timeline semaphore", panic_message(|| semaphore.signal_semaphore(&*texture)));
semaphore.dispose();
assert_eq!(
"Cannot access a disposed object: MtlEventSemaphore",
panic_message(|| semaphore.signal_timeline_semaphore(&*texture, 6))
);
texture.dispose();
unsafe {
CFRelease(surface);
objc_release(event);
}
}
}