rusticl: implement cl_khr_external_semaphore

Acked-by: Alyssa Rosenzweig <alyssa@rosenzweig.io>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/36007>
This commit is contained in:
Karol Herbst
2025-08-27 17:09:37 +02:00
committed by Marge Bot
parent daf777df8c
commit 24c7e247af
10 changed files with 331 additions and 56 deletions
+2 -2
View File
@@ -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
+2
View File
@@ -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
@@ -273,6 +273,20 @@ unsafe impl CLInfo<cl_device_info> for cl_device_id {
(CL_QUEUE_PROFILING_ENABLE | CL_QUEUE_OUT_OF_ORDER_EXEC_MODE_ENABLE).into(),
),
CL_DEVICE_REFERENCE_COUNT => v.write::<cl_uint>(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])
}
@@ -27,6 +27,24 @@ unsafe impl CLInfo<cl_platform_info> for cl_platform_id {
CL_PLATFORM_NAME => v.write::<&CStr>(c"rusticl"),
CL_PLATFORM_NUMERIC_VERSION => v.write::<cl_version>(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]
+109 -29
View File
@@ -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<cl_semaphore_info_khr> for cl_semaphore_khr {
// is fine here.
v.write::<Option<cl_external_semaphore_handle_type_khr>>(None)
}
CL_SEMAPHORE_EXPORTABLE_KHR => v.write::<cl_bool>(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::<cl_bool>((!sema.imported && sema.handle_type.is_some()).into())
}
CL_SEMAPHORE_PAYLOAD_KHR => {
v.write::<cl_semaphore_payload_khr>(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::<c_int>()) };
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::<c_int>() {
return Err(CL_INVALID_VALUE);
}
let fd = sema.fd()?;
unsafe {
handle_ptr.cast::<c_int>().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)
}
@@ -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()
}
@@ -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 {
+158 -24
View File
@@ -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<Weak<Queue>>,
}
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<Context>,
pub props: MultiValProperties<cl_semaphore_properties_khr>,
pub dev: &'static Device,
pub handle_type: Option<cl_external_semaphore_handle_type_khr>,
pub imported: bool,
state: Mutex<SemaphoreState>,
/// 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<SemaphoreHandle>) -> CLResult<Fence> {
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<Context>,
props: MultiValProperties<cl_semaphore_properties_khr>,
dev: &'static Device,
export_handle_type: Option<cl_external_semaphore_handle_type_khr>,
handle: Option<SemaphoreHandle>,
) -> CLResult<Arc<Self>> {
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<c_int> {
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<Arc<Self>>) -> EventSig {
pub fn gpu_signal(semas: Vec<Arc<Self>>, q: &Arc<Queue>) -> 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<SemaphoreState> {
self.state.lock().unwrap()
}
@@ -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 {
@@ -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<pipe_memory_info> {
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()
}