use crate::context::Context;
use crate::error::{to_option_error, SteamAudioError};
use std::string::ToString;
#[derive(Debug)]
pub struct OpenClDevice(audionimbus_sys::IPLOpenCLDevice);
impl OpenClDevice {
pub fn try_new(
context: &Context,
device_list: &OpenClDeviceList,
index: usize,
) -> Result<Self, SteamAudioError> {
let mut open_cl_device = Self(std::ptr::null_mut());
let status = unsafe {
audionimbus_sys::iplOpenCLDeviceCreate(
context.raw_ptr(),
**device_list,
index as i32,
open_cl_device.raw_ptr_mut(),
)
};
if let Some(error) = to_option_error(status) {
return Err(error);
}
Ok(open_cl_device)
}
pub unsafe fn from_existing(
context: &Context,
convolution_queue: *mut std::ffi::c_void,
ir_update_queue: *mut std::ffi::c_void,
) -> Result<Self, SteamAudioError> {
let mut open_cl_device = Self(std::ptr::null_mut());
let status = unsafe {
audionimbus_sys::iplOpenCLDeviceCreateFromExisting(
context.raw_ptr(),
convolution_queue,
ir_update_queue,
open_cl_device.raw_ptr_mut(),
)
};
if let Some(error) = to_option_error(status) {
return Err(error);
}
Ok(open_cl_device)
}
pub const fn raw_ptr(&self) -> audionimbus_sys::IPLOpenCLDevice {
self.0
}
pub const fn raw_ptr_mut(&mut self) -> &mut audionimbus_sys::IPLOpenCLDevice {
&mut self.0
}
}
impl Drop for OpenClDevice {
fn drop(&mut self) {
unsafe { audionimbus_sys::iplOpenCLDeviceRelease(&raw mut self.0) }
}
}
unsafe impl Send for OpenClDevice {}
unsafe impl Sync for OpenClDevice {}
impl Clone for OpenClDevice {
fn clone(&self) -> Self {
Self(unsafe { audionimbus_sys::iplOpenCLDeviceRetain(self.0) })
}
}
pub struct OpenClDeviceList(audionimbus_sys::IPLOpenCLDeviceList);
impl OpenClDeviceList {
pub fn try_new(
context: &Context,
open_cl_device_settings: &OpenClDeviceSettings,
) -> Result<Self, SteamAudioError> {
let mut open_cl_device_list: audionimbus_sys::IPLOpenCLDeviceList = std::ptr::null_mut();
let mut settings = audionimbus_sys::IPLOpenCLDeviceSettings::from(open_cl_device_settings);
let status = unsafe {
audionimbus_sys::iplOpenCLDeviceListCreate(
context.raw_ptr(),
&raw mut settings,
&raw mut open_cl_device_list,
)
};
if let Some(error) = to_option_error(status) {
return Err(error);
}
Ok(Self(open_cl_device_list))
}
pub fn num_devices(&self) -> usize {
unsafe { audionimbus_sys::iplOpenCLDeviceListGetNumDevices(self.raw_ptr()) as usize }
}
pub fn device_descriptor(
&self,
device_index: usize,
) -> Result<OpenClDeviceDescriptor, OpenClDeviceListError> {
let num_devices = self.num_devices();
if device_index >= num_devices {
return Err(OpenClDeviceListError::DeviceIndexOutOfBounds {
device_index,
num_devices,
});
}
let mut device_descriptor =
std::mem::MaybeUninit::<audionimbus_sys::IPLOpenCLDeviceDesc>::uninit();
unsafe {
audionimbus_sys::iplOpenCLDeviceListGetDeviceDesc(
self.raw_ptr(),
device_index as i32,
device_descriptor.as_mut_ptr(),
);
let device_descriptor = device_descriptor.assume_init();
Ok(OpenClDeviceDescriptor::try_from(&device_descriptor).expect("invalid C string"))
}
}
pub const fn raw_ptr(&self) -> audionimbus_sys::IPLOpenCLDeviceList {
self.0
}
pub const fn raw_ptr_mut(&mut self) -> &mut audionimbus_sys::IPLOpenCLDeviceList {
&mut self.0
}
}
impl std::ops::Deref for OpenClDeviceList {
type Target = audionimbus_sys::IPLOpenCLDeviceList;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl std::ops::DerefMut for OpenClDeviceList {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl Drop for OpenClDeviceList {
fn drop(&mut self) {
unsafe { audionimbus_sys::iplOpenCLDeviceListRelease(&raw mut self.0) }
}
}
unsafe impl Send for OpenClDeviceList {}
unsafe impl Sync for OpenClDeviceList {}
impl Clone for OpenClDeviceList {
fn clone(&self) -> Self {
Self(unsafe { audionimbus_sys::iplOpenCLDeviceListRetain(self.0) })
}
}
#[derive(Debug, PartialEq, Eq)]
pub enum OpenClDeviceListError {
DeviceIndexOutOfBounds {
device_index: usize,
num_devices: usize,
},
}
impl std::error::Error for OpenClDeviceListError {}
impl std::fmt::Display for OpenClDeviceListError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::DeviceIndexOutOfBounds {
device_index,
num_devices,
} => write!(
f,
"device index {device_index} out of bounds (num_devices: {num_devices})"
),
}
}
}
#[derive(Debug)]
pub struct OpenClDeviceSettings {
pub device_type: OpenClDeviceType,
pub num_compute_units_to_reserve: i32,
pub fraction_of_compute_units_for_impulse_response_update: f32,
pub requires_true_audio_next: bool,
}
impl Default for OpenClDeviceSettings {
fn default() -> Self {
Self {
device_type: OpenClDeviceType::Cpu,
num_compute_units_to_reserve: 0,
fraction_of_compute_units_for_impulse_response_update: 0.0,
requires_true_audio_next: false,
}
}
}
impl From<&OpenClDeviceSettings> for audionimbus_sys::IPLOpenCLDeviceSettings {
fn from(settings: &OpenClDeviceSettings) -> Self {
Self {
type_: settings.device_type.into(),
numCUsToReserve: settings.num_compute_units_to_reserve,
fractionCUsForIRUpdate: settings.fraction_of_compute_units_for_impulse_response_update,
requiresTAN: if settings.requires_true_audio_next {
audionimbus_sys::IPLbool::IPL_TRUE
} else {
audionimbus_sys::IPLbool::IPL_FALSE
},
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum OpenClDeviceType {
Any,
Cpu,
Gpu,
}
impl From<OpenClDeviceType> for audionimbus_sys::IPLOpenCLDeviceType {
fn from(device_type: OpenClDeviceType) -> Self {
match device_type {
OpenClDeviceType::Any => Self::IPL_OPENCLDEVICETYPE_ANY,
OpenClDeviceType::Cpu => Self::IPL_OPENCLDEVICETYPE_CPU,
OpenClDeviceType::Gpu => Self::IPL_OPENCLDEVICETYPE_GPU,
}
}
}
impl From<audionimbus_sys::IPLOpenCLDeviceType> for OpenClDeviceType {
fn from(device_type: audionimbus_sys::IPLOpenCLDeviceType) -> Self {
match device_type {
audionimbus_sys::IPLOpenCLDeviceType::IPL_OPENCLDEVICETYPE_ANY => Self::Any,
audionimbus_sys::IPLOpenCLDeviceType::IPL_OPENCLDEVICETYPE_CPU => Self::Cpu,
audionimbus_sys::IPLOpenCLDeviceType::IPL_OPENCLDEVICETYPE_GPU => Self::Gpu,
}
}
}
#[derive(Debug, PartialEq)]
pub struct OpenClDeviceDescriptor {
pub platform: *mut std::ffi::c_void,
pub platform_name: String,
pub platform_vendor: String,
pub platform_version: String,
pub device: *mut std::ffi::c_void,
pub device_name: String,
pub device_vendor: String,
pub device_version: String,
pub device_type: OpenClDeviceType,
pub num_convolution_cus: i32,
pub num_ir_update_cus: i32,
pub granularity: i32,
pub perf_score: f32,
}
unsafe fn cstr_to_string(ptr: *const std::ffi::c_char) -> Result<String, std::str::Utf8Error> {
if ptr.is_null() {
return Ok(String::new());
}
let c_str = std::ffi::CStr::from_ptr(ptr);
c_str.to_str().map(ToString::to_string)
}
impl TryFrom<&audionimbus_sys::IPLOpenCLDeviceDesc> for OpenClDeviceDescriptor {
type Error = std::str::Utf8Error;
fn try_from(
ipl_descriptor: &audionimbus_sys::IPLOpenCLDeviceDesc,
) -> Result<Self, Self::Error> {
Ok(Self {
platform: ipl_descriptor.platform,
platform_name: unsafe { cstr_to_string(ipl_descriptor.platformName)? },
platform_vendor: unsafe { cstr_to_string(ipl_descriptor.platformVendor)? },
platform_version: unsafe { cstr_to_string(ipl_descriptor.platformVersion)? },
device: ipl_descriptor.device,
device_name: unsafe { cstr_to_string(ipl_descriptor.deviceName)? },
device_vendor: unsafe { cstr_to_string(ipl_descriptor.deviceVendor)? },
device_version: unsafe { cstr_to_string(ipl_descriptor.deviceVersion)? },
device_type: ipl_descriptor.type_.into(),
num_convolution_cus: ipl_descriptor.numConvolutionCUs,
num_ir_update_cus: ipl_descriptor.numIRUpdateCUs,
granularity: ipl_descriptor.granularity,
perf_score: ipl_descriptor.perfScore,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::*;
mod open_cl_device {
use super::*;
mod device_descriptor {
use super::*;
#[test]
fn test_valid() {
let context = Context::default();
let settings = OpenClDeviceSettings::default();
let Ok(device_list) = OpenClDeviceList::try_new(&context, &settings) else {
return;
};
let num_devices = device_list.num_devices();
if num_devices > 0 {
assert!(device_list.device_descriptor(0).is_ok());
assert!(device_list.device_descriptor(num_devices - 1).is_ok());
}
}
#[test]
fn test_index_out_of_bounds() {
let context = Context::default();
let settings = OpenClDeviceSettings::default();
let Ok(device_list) = OpenClDeviceList::try_new(&context, &settings) else {
return;
};
let num_devices = device_list.num_devices();
assert_eq!(
device_list.device_descriptor(num_devices + 5),
Err(OpenClDeviceListError::DeviceIndexOutOfBounds {
device_index: num_devices + 5,
num_devices,
}),
);
}
}
#[test]
fn test_device_list_clone() {
let context = Context::default();
let settings = OpenClDeviceSettings::default();
let Ok(device_list) = OpenClDeviceList::try_new(&context, &settings) else {
return;
};
let clone = device_list.clone();
assert_eq!(device_list.raw_ptr(), clone.raw_ptr());
drop(device_list);
assert!(!clone.raw_ptr().is_null());
}
#[test]
fn test_device_clone() {
let context = Context::default();
let settings = OpenClDeviceSettings::default();
let Ok(device_list) = OpenClDeviceList::try_new(&context, &settings) else {
return;
};
let device = OpenClDevice::try_new(&context, &device_list, 0).unwrap();
let clone = device.clone();
assert_eq!(device.raw_ptr(), clone.raw_ptr());
drop(device);
assert!(!clone.raw_ptr().is_null());
}
}
}