diff --git a/docs/features.txt b/docs/features.txt index d244881e161..bffd00482e1 100644 --- a/docs/features.txt +++ b/docs/features.txt @@ -839,9 +839,9 @@ Rusticl extensions: cl_khr_external_memory_dma_buf not started cl_khr_external_memory_opaque_fd not started cl_khr_external_memory_win32 not started - cl_khr_external_semaphore not started + cl_khr_external_semaphore DONE (radeonsi, zink) cl_khr_external_semaphore_opaque_fd not started - cl_khr_external_semaphore_sync_fd not started + cl_khr_external_semaphore_sync_fd DONE (radeonsi, zink) cl_khr_external_semaphore_win32 not started cl_khr_fp16 DONE (asahi, freedreno, llvmpipe, panfrost, radeonsi, zink) cl_khr_gl_depth_images not started diff --git a/docs/relnotes/new_features.txt b/docs/relnotes/new_features.txt index 8729eda733c..a97498af246 100644 --- a/docs/relnotes/new_features.txt +++ b/docs/relnotes/new_features.txt @@ -12,3 +12,5 @@ VK_KHR_shader_untyped_pointers on anv and RADV VK_KHR_maintenance8 on NVK VK_KHR_maintenance9 on NVK cl_khr_semaphore on radeonsi and zink +cl_khr_external_semaphore on radeonsi and zink +cl_khr_external_semaphore_sync_fd on radeonsi and zink diff --git a/src/gallium/frontends/rusticl/api/device.rs b/src/gallium/frontends/rusticl/api/device.rs index d360ef1b642..98840dcf021 100644 --- a/src/gallium/frontends/rusticl/api/device.rs +++ b/src/gallium/frontends/rusticl/api/device.rs @@ -273,6 +273,20 @@ unsafe impl CLInfo for cl_device_id { (CL_QUEUE_PROFILING_ENABLE | CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE).into(), ), CL_DEVICE_REFERENCE_COUNT => v.write::(1), + CL_DEVICE_SEMAPHORE_EXPORT_HANDLE_TYPES_KHR + if dev.are_external_semaphores_supported() => + { + v.write::<&[cl_external_semaphore_handle_type_khr]>(&[ + CL_SEMAPHORE_HANDLE_SYNC_FD_KHR, + ]) + } + CL_DEVICE_SEMAPHORE_IMPORT_HANDLE_TYPES_KHR + if dev.are_external_semaphores_supported() => + { + v.write::<&[cl_external_semaphore_handle_type_khr]>(&[ + CL_SEMAPHORE_HANDLE_SYNC_FD_KHR, + ]) + } CL_DEVICE_SEMAPHORE_TYPES_KHR if dev.are_semaphores_supported() => { v.write::<&[cl_semaphore_type_khr]>(&[CL_SEMAPHORE_TYPE_BINARY_KHR]) } diff --git a/src/gallium/frontends/rusticl/api/platform.rs b/src/gallium/frontends/rusticl/api/platform.rs index 36d94d3cb33..63b5dea3b50 100644 --- a/src/gallium/frontends/rusticl/api/platform.rs +++ b/src/gallium/frontends/rusticl/api/platform.rs @@ -27,6 +27,24 @@ unsafe impl CLInfo for cl_platform_id { CL_PLATFORM_NAME => v.write::<&CStr>(c"rusticl"), CL_PLATFORM_NUMERIC_VERSION => v.write::(CLVersion::Cl3_0.into()), CL_PLATFORM_PROFILE => v.write::<&CStr>(c"FULL_PROFILE"), + CL_PLATFORM_SEMAPHORE_EXPORT_HANDLE_TYPES_KHR => { + v.write::<&[cl_external_semaphore_handle_type_khr]>( + if Platform::get().all_devs_have_external_semaphores() { + &[CL_SEMAPHORE_HANDLE_SYNC_FD_KHR] + } else { + &[] + }, + ) + } + CL_PLATFORM_SEMAPHORE_IMPORT_HANDLE_TYPES_KHR => { + v.write::<&[cl_external_semaphore_handle_type_khr]>( + if Platform::get().all_devs_have_external_semaphores() { + &[CL_SEMAPHORE_HANDLE_SYNC_FD_KHR] + } else { + &[] + }, + ) + } CL_PLATFORM_SEMAPHORE_TYPES_KHR => { v.write::<&[cl_semaphore_type_khr]>(if Platform::get().all_devs_have_semaphores() { &[CL_SEMAPHORE_TYPE_BINARY_KHR] diff --git a/src/gallium/frontends/rusticl/api/semaphore.rs b/src/gallium/frontends/rusticl/api/semaphore.rs index e3db0dd4203..81a9fa02024 100644 --- a/src/gallium/frontends/rusticl/api/semaphore.rs +++ b/src/gallium/frontends/rusticl/api/semaphore.rs @@ -8,13 +8,23 @@ use { icd::{ArcedCLObject, BaseCLObject, CLResult, ReferenceCountedAPIPointer}, util::{event_list_from_cl, CLInfo, CLInfoRes, CLInfoValue}, }, - core::{context::Context, device::Device, queue::Queue, semaphore::Semaphore}, + core::{ + context::Context, + device::Device, + queue::Queue, + semaphore::{Semaphore, SemaphoreHandle}, + }, + }, + mesa_rust_util::{ + conversion::TryIntoWithErr, + properties::{MultiValProperties, Properties}, + ptr::CheckedPtr, }, - mesa_rust_util::{conversion::TryIntoWithErr, properties::MultiValProperties}, rusticl_opencl_gen::*, rusticl_proc_macros::{cl_entrypoint, cl_info_entrypoint}, std::{ ffi::{c_int, c_void}, + mem, sync::Arc, }, }; @@ -36,7 +46,11 @@ unsafe impl CLInfo for cl_semaphore_khr { // is fine here. v.write::>(None) } - CL_SEMAPHORE_EXPORTABLE_KHR => v.write::(CL_FALSE), + // Exporting a semaphore handle from a semaphore that was created by importing an + // external semaphore handle is not permitted. + CL_SEMAPHORE_EXPORTABLE_KHR => { + v.write::((!sema.imported && sema.handle_type.is_some()).into()) + } CL_SEMAPHORE_PAYLOAD_KHR => { v.write::(sema.is_signalled().into()) } @@ -62,8 +76,10 @@ fn create_semaphore( return Err(CL_INVALID_VALUE); } + let mut handle = None; let mut sema_type = 0; let mut dev = None; + let mut handle_type = None; let sema_props = unsafe { MultiValProperties::new( sema_props, @@ -99,12 +115,29 @@ fn create_semaphore( dev = Some(dev_in); } CL_SEMAPHORE_EXPORT_HANDLE_TYPES_KHR => { - // CL_INVALID_VALUE if more than one semaphore handle type is specified in the - // CL_SEMAPHORE_EXPORT_HANDLE_TYPES_KHR list. - if vals.len() > 1 { - return Err(CL_INVALID_VALUE); + if let Some((&handle_type_in, remaining)) = vals.split_first() { + // CL_INVALID_VALUE if more than one semaphore handle type is specified in the + // CL_SEMAPHORE_EXPORT_HANDLE_TYPES_KHR list. + if !remaining.is_empty() { + return Err(CL_INVALID_VALUE); + } + + let handle_type_in = handle_type_in.try_into_with_err(CL_INVALID_PROPERTY)?; + if handle_type_in != CL_SEMAPHORE_HANDLE_SYNC_FD_KHR { + return Err(CL_INVALID_PROPERTY); + } + handle_type = Some(handle_type_in); } } + CL_SEMAPHORE_HANDLE_SYNC_FD_KHR => { + handle = Some(SemaphoreHandle::SyncFD( + // we cast to a signed int to be able to handle negative FDs such as -1. + // + // CL_INVALID_PROPERTY [..] if the value specified for a supported property name + // is not valid + (vals[0] as i64).try_into_with_err(CL_INVALID_PROPERTY)?, + )); + } CL_SEMAPHORE_TYPE_KHR => { // CL_INVALID_PROPERTY [..] if the value specified for a supported property name is // not valid @@ -141,11 +174,16 @@ fn create_semaphore( return Err(CL_INVALID_VALUE); } - Ok(Semaphore::new(context, sema_props, dev)?.into_cl()) + // CL_INVALID_OPERATION If props_list specifies a cl_external_semaphore_handle_type_khr followed + // by a handle as well as CL_SEMAPHORE_EXPORT_HANDLE_TYPES_KHR. Exporting a semaphore handle + // from a semaphore that was created by importing an external semaphore handle is not permitted. + if handle_type.is_some() && handle.is_some() { + return Err(CL_INVALID_OPERATION); + } + + Ok(Semaphore::new(context, sema_props, dev, handle_type, handle)?.into_cl()) // CL_INVALID_DEVICE if one or more devices identified by properties CL_SEMAPHORE_DEVICE_HANDLE_LIST_KHR cannot import the requested external semaphore handle type. - // CL_INVALID_OPERATION If props_list specifies a cl_external_semaphore_handle_type_khr followed by a handle as well as CL_SEMAPHORE_EXPORT_HANDLE_TYPES_KHR. Exporting a semaphore handle from a semaphore that was created by importing an external semaphore handle is not permitted. - // CL_INVALID_PROPERTY if sema_props includes more than one external semaphore handle. } #[cl_entrypoint(clEnqueueSignalSemaphoresKHR)] @@ -186,14 +224,8 @@ fn enqueue_signal_semaphores( } } - create_and_queue( - q, - CL_COMMAND_SEMAPHORE_SIGNAL_KHR, - evs, - event, - false, - Semaphore::gpu_signal(semas), - ) + let work = Semaphore::gpu_signal(semas, &q); + create_and_queue(q, CL_COMMAND_SEMAPHORE_SIGNAL_KHR, evs, event, false, work) // CL_INVALID_VALUE if any of the semaphore objects specified by sema_objects requires a semaphore payload and sema_payload_list is NULL. } @@ -255,14 +287,43 @@ fn enqueue_wait_semaphores( #[cl_entrypoint(clGetSemaphoreHandleForTypeKHR)] fn get_semaphore_handle_for_type( - _sema_object: cl_semaphore_khr, - _device: cl_device_id, - _handle_type: cl_external_semaphore_handle_type_khr, - _handle_size: usize, - _handle_ptr: *mut c_void, - _handle_size_ret: *mut usize, + sema_object: cl_semaphore_khr, + device: cl_device_id, + handle_type: cl_external_semaphore_handle_type_khr, + handle_size: usize, + handle_ptr: *mut c_void, + handle_size_ret: *mut usize, ) -> CLResult<()> { - Err(CL_INVALID_OPERATION) + let sema = Semaphore::ref_from_raw(sema_object)?; + let dev = Device::ref_from_raw(device)?; + + // CL_INVALID_DEVICE [..] if sema_object belongs to a context that is not associated with + // device. + if !sema.ctx.devs.contains(&dev) { + return Err(CL_INVALID_DEVICE); + } + + // CL_INVALID_VALUE if the requested external semaphore handle type was not specified when + // sema_object was created. + if sema.handle_type != Some(handle_type) { + return Err(CL_INVALID_VALUE); + } + + unsafe { handle_size_ret.write_checked(mem::size_of::()) }; + if !handle_ptr.is_null() { + // CL_INVALID_VALUE if the size in bytes specified by handle_size is less than size of the + // requested handle and handle_ptr is not NULL. + if handle_size < mem::size_of::() { + return Err(CL_INVALID_VALUE); + } + + let fd = sema.fd()?; + unsafe { + handle_ptr.cast::().write(fd); + } + } + + Ok(()) } #[cl_entrypoint(clReleaseSemaphoreKHR)] @@ -277,9 +338,28 @@ fn retain_semaphore(sema_object: cl_semaphore_khr) -> CLResult<()> { #[cl_entrypoint(clReImportSemaphoreSyncFdKHR)] fn re_import_semaphore_sync_fd( - _sema_object: cl_semaphore_khr, - _reimport_props: *mut cl_semaphore_reimport_properties_khr, - _fd: c_int, + sema_object: cl_semaphore_khr, + reimport_props: *mut cl_semaphore_reimport_properties_khr, + fd: c_int, ) -> CLResult<()> { - Err(CL_INVALID_OPERATION) + let sema = Semaphore::ref_from_raw(sema_object)?; + + // CL_INVALID_SEMAPHORE_KHR if a CL_SEMAPHORE_HANDLE_SYNC_FD_KHR handle was not imported when + // sema_object was created. + if !sema.imported { + return Err(CL_INVALID_SEMAPHORE_KHR); + } + + // reimport_props is an optional list of properties that affect the re-import behavior. [..] If + // no properties are required, reimport_props may be NULL. This extension does not define any + // optional properties. + // SAFETY: The list is terminated with the special property 0. + let props = unsafe { Properties::new(reimport_props) }.ok_or(CL_INVALID_PROPERTY)?; + if !props.is_empty() { + // We don't support any optional properties, so just throw an error for now. + return Err(CL_INVALID_PROPERTY); + } + + // CL_INVALID_VALUE if fd is invalid. + sema.reimport(fd) } diff --git a/src/gallium/frontends/rusticl/core/device.rs b/src/gallium/frontends/rusticl/core/device.rs index ac8b759a739..8770459e53a 100644 --- a/src/gallium/frontends/rusticl/core/device.rs +++ b/src/gallium/frontends/rusticl/core/device.rs @@ -771,6 +771,10 @@ impl DeviceBase { } if self.are_semaphores_supported() { + if self.are_external_semaphores_supported() { + add_ext(1, 0, 1, "cl_khr_external_semaphore"); + add_ext(1, 0, 1, "cl_khr_external_semaphore_sync_fd"); + } add_ext(1, 0, 1, "cl_khr_semaphore"); } @@ -1258,6 +1262,10 @@ impl DeviceBase { } } + pub fn are_external_semaphores_supported(&self) -> bool { + self.caps.has_create_fence_fd && self.screen().has_fence_get_fd() + } + pub fn are_semaphores_supported(&self) -> bool { self.screen().caps().fence_signal && self.screen().has_semaphore_create() } diff --git a/src/gallium/frontends/rusticl/core/platform.rs b/src/gallium/frontends/rusticl/core/platform.rs index 6a8c34026ea..5f2a3e704e4 100644 --- a/src/gallium/frontends/rusticl/core/platform.rs +++ b/src/gallium/frontends/rusticl/core/platform.rs @@ -281,6 +281,12 @@ impl Platform { pub fn all_devs_have_semaphores(&self) -> bool { self.devs.iter().all(|dev| dev.are_semaphores_supported()) } + + pub fn all_devs_have_external_semaphores(&self) -> bool { + self.devs + .iter() + .all(|dev| dev.are_external_semaphores_supported()) + } } impl Drop for Platform { diff --git a/src/gallium/frontends/rusticl/core/semaphore.rs b/src/gallium/frontends/rusticl/core/semaphore.rs index 73f888de2f2..55d78d9e886 100644 --- a/src/gallium/frontends/rusticl/core/semaphore.rs +++ b/src/gallium/frontends/rusticl/core/semaphore.rs @@ -4,42 +4,87 @@ use { crate::{ api::icd::{CLObjectBase, CLResult, RusticlTypes}, - core::{context::Context, device::Device, event::EventSig}, + core::{ + context::Context, + device::{Device, HelperContextWrapper}, + event::EventSig, + queue::Queue, + }, impl_cl_type_trait, }, - mesa_rust::pipe::{context::PipeContext, fence::PipeFence}, + mesa_rust::pipe::{ + context::PipeContext, + fence::{FenceFd, PipeFence}, + }, + mesa_rust_gen::pipe_fd_type, mesa_rust_util::properties::MultiValProperties, rusticl_opencl_gen::*, - std::sync::{Arc, Condvar, Mutex, MutexGuard}, + std::{ + ffi::c_int, + sync::{Arc, Condvar, Mutex, MutexGuard, Weak}, + }, }; +pub enum SemaphoreHandle { + SyncFD(c_int), +} + struct RealFence { fence: PipeFence, is_signalled: bool, } +enum Fence { + AlwaysSignalled, + Fence(RealFence), +} + +impl Fence { + fn from_fence(fence: PipeFence) -> Self { + Self::Fence(RealFence { + fence: fence, + is_signalled: false, + }) + } +} + struct SemaphoreState { - fence: RealFence, + fence: Fence, + last_queue: Option>, } enum SemaphoreWaitAction { Signal, Wait, + WaitImported, } impl SemaphoreState { fn gpu_signal(&mut self, ctx: &PipeContext) { - self.fence.fence.gpu_signal(ctx); - self.fence.is_signalled = true; + match &mut self.fence { + Fence::AlwaysSignalled => {} + Fence::Fence(fence) => { + fence.fence.gpu_signal(ctx); + fence.is_signalled = true; + } + } } fn gpu_wait(&mut self, ctx: &PipeContext) { - self.fence.fence.gpu_wait(ctx); - self.fence.is_signalled = false; + match &mut self.fence { + Fence::AlwaysSignalled => {} + Fence::Fence(fence) => { + fence.fence.gpu_wait(ctx); + fence.is_signalled = false + } + } } fn is_signalled(&self) -> bool { - self.fence.is_signalled + match &self.fence { + Fence::AlwaysSignalled => true, + Fence::Fence(fence) => fence.is_signalled, + } } fn do_action( @@ -48,12 +93,20 @@ impl SemaphoreState { action: SemaphoreWaitAction, ctx: &PipeContext, ) -> CLResult<()> { - // We need to wait until is_signalled gets set to the proper state. - let is_signalled_expected = !matches!(action, SemaphoreWaitAction::Signal); + // If we are always signalled we can just skip this call. + if matches!(this.fence, Fence::AlwaysSignalled) { + return Ok(()); + } - this = cv - .wait_while(this, |state| state.is_signalled() != is_signalled_expected) - .or(Err(CL_OUT_OF_HOST_MEMORY))?; + // On imported waits we ignore the state of this fence as it might have been waited on + // somewhere else and this is out of our control. + if !matches!(action, SemaphoreWaitAction::WaitImported) { + // We need to wait until is_signalled gets set to the proper state. + let is_signalled_expected = !matches!(action, SemaphoreWaitAction::Signal); + this = cv + .wait_while(this, |state| state.is_signalled() != is_signalled_expected) + .or(Err(CL_OUT_OF_HOST_MEMORY))?; + } match action { // If this semaphore was already signalled, we need to wait until something @@ -61,7 +114,7 @@ impl SemaphoreState { SemaphoreWaitAction::Signal => this.gpu_signal(ctx), // We need to wait until something signals this semaphore before we can wait on it. - SemaphoreWaitAction::Wait => this.gpu_wait(ctx), + SemaphoreWaitAction::Wait | SemaphoreWaitAction::WaitImported => this.gpu_wait(ctx), } drop(this); @@ -78,6 +131,8 @@ pub struct Semaphore { pub ctx: Arc, pub props: MultiValProperties, pub dev: &'static Device, + pub handle_type: Option, + pub imported: bool, state: Mutex, /// Condition Variable used for waiting on a gpu_signal or gpu_wait operation to executed on the /// queue thread. @@ -87,31 +142,100 @@ pub struct Semaphore { impl_cl_type_trait!(cl_semaphore_khr, Semaphore, CL_INVALID_SEMAPHORE_KHR); impl Semaphore { + fn create_semaphore(dev: &Device, handle: Option) -> CLResult { + Ok(match handle { + None => Fence::from_fence( + dev.screen() + .create_semaphore() + .ok_or(CL_OUT_OF_HOST_MEMORY)?, + ), + Some(SemaphoreHandle::SyncFD(fd)) => { + // The special value -1 for fd is treated like a valid sync file descriptor + // referring to an object that has already signaled. The import operation will + // succeed and the semaphore will have a temporarily imported payload as if a valid + // file descriptor had been provided. + if fd == -1 { + Fence::AlwaysSignalled + } else { + // Importing a semaphore payload from a file descriptor transfers ownership of + // the file descriptor from the application to the OpenCL implementation. The + // application must not perform any operations on the file descriptor after a + // successful import. + let fence_fd = FenceFd { fd: fd }; + let fence = dev + .helper_ctx() + .import_fence(&fence_fd, pipe_fd_type::PIPE_FD_TYPE_NATIVE_SYNC)?; + Fence::from_fence(fence) + } + } + }) + } + pub fn new( ctx: Arc, props: MultiValProperties, dev: &'static Device, + export_handle_type: Option, + handle: Option, ) -> CLResult> { Ok(Arc::new(Self { base: CLObjectBase::new(RusticlTypes::Semaphore), ctx: ctx, props: props, dev: dev, + handle_type: export_handle_type, + imported: handle.is_some(), state: Mutex::new(SemaphoreState { - fence: RealFence { - fence: dev - .screen() - .create_semaphore() - .ok_or(CL_OUT_OF_HOST_MEMORY)?, - is_signalled: false, - }, + fence: Self::create_semaphore(dev, handle)?, + last_queue: None, }), signal_cv: Condvar::new(), })) } + pub fn fd(&self) -> CLResult { + let mut state = self.state(); + if !state.is_signalled() { + // Some drivers might block until the semaphore was signalled, so we need to flush + // the last queue the semaphore was used on. + if let Some(queue) = state.last_queue.take() { + if let Some(queue) = queue.upgrade() { + queue.flush(false).ok().ok_or(CL_OUT_OF_RESOURCES)?; + } + } + + // Now we need to wait until the semaphore gets signalled. + state = self + .signal_cv + .wait_while(state, |state| !state.is_signalled()) + .or(Err(CL_OUT_OF_HOST_MEMORY))?; + } + + Ok(match &mut state.fence { + Fence::Fence(fence) => { + // Do the spinny + loop { + let fd = fence.fence.export_fd(); + if fd != -1 { + // After exporting a semaphore it's safe to re-signal it. + fence.is_signalled = false; + break fd; + } + } + } + Fence::AlwaysSignalled => -1, + }) + } + /// Makes the GPU signal the semaphore. - pub fn gpu_signal(semas: Vec>) -> EventSig { + pub fn gpu_signal(semas: Vec>, q: &Arc) -> EventSig { + for sema in &semas { + let mut state = sema.state(); + if !matches!(state.fence, Fence::AlwaysSignalled) { + state.last_queue = Some(Arc::downgrade(q)); + } + } + Box::new(move |_, ctx| { for sema in semas { SemaphoreState::do_action( @@ -130,7 +254,11 @@ impl Semaphore { SemaphoreState::do_action( self.state(), &self.signal_cv, - SemaphoreWaitAction::Wait, + if self.imported { + SemaphoreWaitAction::WaitImported + } else { + SemaphoreWaitAction::Wait + }, ctx, ) } @@ -139,6 +267,12 @@ impl Semaphore { self.state().is_signalled() } + pub fn reimport(&self, fd: c_int) -> CLResult<()> { + let new_fence = Self::create_semaphore(self.dev, Some(SemaphoreHandle::SyncFD(fd)))?; + self.state().fence = new_fence; + Ok(()) + } + fn state(&self) -> MutexGuard { self.state.lock().unwrap() } diff --git a/src/gallium/frontends/rusticl/mesa/pipe/fence.rs b/src/gallium/frontends/rusticl/mesa/pipe/fence.rs index c01eefc5381..9269283be92 100644 --- a/src/gallium/frontends/rusticl/mesa/pipe/fence.rs +++ b/src/gallium/frontends/rusticl/mesa/pipe/fence.rs @@ -7,7 +7,7 @@ use libc_rust_gen::close; use mesa_rust_gen::*; use mesa_rust_util::ptr::ThreadSafeCPtr; -use std::sync::Arc; +use std::{ffi::c_int, sync::Arc}; pub struct FenceFd { pub fd: i32, @@ -36,6 +36,10 @@ impl PipeFence { }) } + pub fn export_fd(&self) -> c_int { + self.screen.fence_get_fd(self.fence.as_ptr()) + } + pub fn gpu_signal(&self, ctx: &PipeContext) { debug_assert!(ctx.has_fence_server()); unsafe { diff --git a/src/gallium/frontends/rusticl/mesa/pipe/screen.rs b/src/gallium/frontends/rusticl/mesa/pipe/screen.rs index cbbb78bfffc..19ba2407a67 100644 --- a/src/gallium/frontends/rusticl/mesa/pipe/screen.rs +++ b/src/gallium/frontends/rusticl/mesa/pipe/screen.rs @@ -12,6 +12,7 @@ use mesa_rust_gen::*; use mesa_rust_util::has_required_feature; use mesa_rust_util::ptr::ThreadSafeCPtr; +use std::ffi::c_int; use std::ffi::CStr; use std::num::NonZeroU64; use std::os::raw::c_schar; @@ -469,6 +470,10 @@ impl PipeScreen { } } + pub(super) fn fence_get_fd(&self, fence: *mut pipe_fence_handle) -> c_int { + unsafe { self.screen().fence_get_fd.unwrap()(self.screen.as_ptr(), fence) } + } + pub fn query_memory_info(&self) -> Option { let mut info = pipe_memory_info::default(); unsafe { @@ -477,6 +482,10 @@ impl PipeScreen { Some(info) } + pub fn has_fence_get_fd(&self) -> bool { + self.screen().fence_get_fd.is_some() + } + pub fn has_semaphore_create(&self) -> bool { self.screen().semaphore_create.is_some() }