Skip to main content

oidn_wgpu/
device.rs

1//! OIDN logical device (CPU or GPU backend) and physical device queries.
2//! Full API: type-based and physical-ID/UUID/LUID/PCI/CUDA/HIP/Metal device creation.
3
4use crate::sys;
5use crate::Error;
6use std::ffi::{CStr, CString};
7use std::ptr;
8use std::sync::Arc;
9
10/// Number of physical devices supported by OIDN. Valid IDs are `0 .. num_physical_devices()`.
11pub fn num_physical_devices() -> i32 {
12    unsafe { sys::oidnGetNumPhysicalDevices() }
13}
14
15/// Returns a boolean parameter of the physical device. `name` is e.g. `"type"`, `"name"`.
16pub fn get_physical_device_bool(physical_device_id: i32, name: &str) -> bool {
17    let c_name = CString::new(name).unwrap();
18    unsafe { sys::oidnGetPhysicalDeviceBool(physical_device_id, c_name.as_ptr()) }
19}
20
21/// Returns an integer parameter of the physical device.
22pub fn get_physical_device_int(physical_device_id: i32, name: &str) -> i32 {
23    let c_name = CString::new(name).unwrap();
24    unsafe { sys::oidnGetPhysicalDeviceInt(physical_device_id, c_name.as_ptr()) }
25}
26
27/// Returns a string parameter of the physical device. Pointer valid until next OIDN call.
28pub fn get_physical_device_string(physical_device_id: i32, name: &str) -> Option<String> {
29    let c_name = CString::new(name).unwrap();
30    let p = unsafe { sys::oidnGetPhysicalDeviceString(physical_device_id, c_name.as_ptr()) };
31    if p.is_null() {
32        return None;
33    }
34    Some(unsafe { CStr::from_ptr(p).to_string_lossy().into_owned() })
35}
36
37/// Returns opaque data and its size for the physical device. Pointer valid until next OIDN call.
38pub fn get_physical_device_data(physical_device_id: i32, name: &str) -> Option<(*const std::ffi::c_void, usize)> {
39    let c_name = CString::new(name).unwrap();
40    let mut size = 0usize;
41    let p = unsafe { sys::oidnGetPhysicalDeviceData(physical_device_id, c_name.as_ptr(), &mut size) };
42    if p.is_null() {
43        None
44    } else {
45        Some((p, size))
46    }
47}
48
49/// Whether the CPU device is supported.
50pub fn is_cpu_device_supported() -> bool {
51    unsafe { sys::oidnIsCPUDeviceSupported() }
52}
53
54/// Whether the given CUDA device ID is supported.
55pub fn is_cuda_device_supported(device_id: i32) -> bool {
56    unsafe { sys::oidnIsCUDADeviceSupported(device_id) }
57}
58
59/// Whether the given HIP device ID is supported.
60pub fn is_hip_device_supported(device_id: i32) -> bool {
61    unsafe { sys::oidnIsHIPDeviceSupported(device_id) }
62}
63
64/// Whether the given Metal device (MTLDevice, passed as raw pointer) is supported.
65pub unsafe fn is_metal_device_supported(device: *mut std::ffi::c_void) -> bool {
66    sys::oidnIsMetalDeviceSupported(device)
67}
68
69/// Returns the first unqueried error for the current thread (e.g. from a failed device creation)
70/// and clears it. Can be called without a device to check why `OidnDevice::new()` or similar failed.
71pub fn take_global_error() -> Option<Error> {
72    let mut msg_ptr: *const std::ffi::c_char = ptr::null();
73    let code = unsafe { sys::oidnGetDeviceError(ptr::null_mut(), &mut msg_ptr) };
74    if code == sys::OIDNError::None {
75        return None;
76    }
77    let message = if msg_ptr.is_null() {
78        String::new()
79    } else {
80        unsafe { CStr::from_ptr(msg_ptr).to_string_lossy().into_owned() }
81    };
82    Some(Error::OidnError { code: code as u32, message })
83}
84
85/// Open Image Denoise logical device.
86///
87/// Prefer creating one per application and reusing it; filter creation is relatively expensive.
88/// See [`Self::new`], [`Self::cpu`], and backend-specific constructors.
89#[derive(Clone)]
90pub struct OidnDevice {
91    pub(crate) raw: sys::OIDNDevice,
92    _refcount: Arc<()>,
93}
94
95impl std::fmt::Debug for OidnDevice {
96    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
97        f.debug_struct("OidnDevice").finish_non_exhaustive()
98    }
99}
100
101impl OidnDevice {
102    /// Creates a device using the default backend (auto-selects CPU or GPU when available).
103    ///
104    /// # Errors
105    ///
106    /// Returns [`Error::DeviceCreationFailed`] if no backend is available. Use [`take_global_error()`]
107    /// to retrieve the underlying OIDN message.
108    pub fn new() -> Result<Self, Error> {
109        Self::with_type(OidnDeviceType::Default)
110    }
111
112    /// Creates a CPU-only device (most portable).
113    pub fn cpu() -> Result<Self, Error> {
114        Self::with_type(OidnDeviceType::Cpu)
115    }
116
117    /// Creates a CUDA device for NVIDIA GPU-accelerated denoising.
118    /// Requires OIDN built with CUDA support; returns `DeviceCreationFailed` otherwise.
119    pub fn cuda() -> Result<Self, Error> {
120        Self::with_type(OidnDeviceType::Cuda)
121    }
122
123    /// Creates a SYCL device (Intel GPU/CPU via oneAPI). Requires OIDN built with SYCL.
124    pub fn sycl() -> Result<Self, Error> {
125        Self::with_type(OidnDeviceType::Sycl)
126    }
127
128    /// Creates a HIP device (AMD GPU). Requires OIDN built with HIP.
129    pub fn hip() -> Result<Self, Error> {
130        Self::with_type(OidnDeviceType::Hip)
131    }
132
133    /// Creates a Metal device (Apple GPU). Requires OIDN built with Metal.
134    pub fn metal() -> Result<Self, Error> {
135        Self::with_type(OidnDeviceType::Metal)
136    }
137
138    /// Creates a device of the given type.
139    pub fn with_type(device_type: OidnDeviceType) -> Result<Self, Error> {
140        let raw = unsafe { sys::oidnNewDevice(device_type.to_raw()) };
141        if raw.is_null() {
142            return Err(Error::DeviceCreationFailed);
143        }
144        unsafe { sys::oidnCommitDevice(raw) };
145        Ok(Self {
146            raw,
147            _refcount: Arc::new(()),
148        })
149    }
150
151    /// Creates a device from a physical device ID (0 to `num_physical_devices()` - 1).
152    pub fn new_by_id(physical_device_id: i32) -> Result<Self, Error> {
153        let raw = unsafe { sys::oidnNewDeviceByID(physical_device_id) };
154        if raw.is_null() {
155            return Err(Error::DeviceCreationFailed);
156        }
157        unsafe { sys::oidnCommitDevice(raw) };
158        Ok(Self { raw, _refcount: Arc::new(()) })
159    }
160
161    /// Creates a device from a physical device UUID (16 bytes; see [`crate::OIDN_UUID_SIZE`]).
162    pub fn new_by_uuid(uuid: &[u8; sys::OIDN_UUID_SIZE]) -> Result<Self, Error> {
163        let raw = unsafe { sys::oidnNewDeviceByUUID(uuid.as_ptr() as *const std::ffi::c_void) };
164        if raw.is_null() {
165            return Err(Error::DeviceCreationFailed);
166        }
167        unsafe { sys::oidnCommitDevice(raw) };
168        Ok(Self { raw, _refcount: Arc::new(()) })
169    }
170
171    /// Creates a device from a physical device LUID (8 bytes; see [`crate::OIDN_LUID_SIZE`]).
172    pub fn new_by_luid(luid: &[u8; sys::OIDN_LUID_SIZE]) -> Result<Self, Error> {
173        let raw = unsafe { sys::oidnNewDeviceByLUID(luid.as_ptr() as *const std::ffi::c_void) };
174        if raw.is_null() {
175            return Err(Error::DeviceCreationFailed);
176        }
177        unsafe { sys::oidnCommitDevice(raw) };
178        Ok(Self { raw, _refcount: Arc::new(()) })
179    }
180
181    /// Creates a device from a PCI address (domain, bus, device, function).
182    pub fn new_by_pci_address(
183        pci_domain: i32,
184        pci_bus: i32,
185        pci_device: i32,
186        pci_function: i32,
187    ) -> Result<Self, Error> {
188        let raw = unsafe {
189            sys::oidnNewDeviceByPCIAddress(pci_domain, pci_bus, pci_device, pci_function)
190        };
191        if raw.is_null() {
192            return Err(Error::DeviceCreationFailed);
193        }
194        unsafe { sys::oidnCommitDevice(raw) };
195        Ok(Self { raw, _refcount: Arc::new(()) })
196    }
197
198    /// Creates a CUDA device for the given device ID and optional stream.
199    /// `stream`: `None` = default stream; otherwise a valid `cudaStream_t` (e.g. from rust CUDA bindings).
200    /// Currently only one (device_id, stream) pair is supported.
201    pub unsafe fn new_cuda_device(
202        device_id: i32,
203        stream: Option<*mut std::ffi::c_void>,
204    ) -> Result<Self, Error> {
205        let stream_ptr = stream.unwrap_or(ptr::null_mut());
206        let raw = sys::oidnNewCUDADevice(&device_id, &stream_ptr, 1);
207        if raw.is_null() {
208            return Err(Error::DeviceCreationFailed);
209        }
210        sys::oidnCommitDevice(raw);
211        Ok(Self { raw, _refcount: Arc::new(()) })
212    }
213
214    /// Creates a HIP device for the given device ID and optional stream.
215    /// `stream`: `None` = default stream. Currently only one pair is supported.
216    pub unsafe fn new_hip_device(
217        device_id: i32,
218        stream: Option<*mut std::ffi::c_void>,
219    ) -> Result<Self, Error> {
220        let stream_ptr = stream.unwrap_or(ptr::null_mut());
221        let raw = sys::oidnNewHIPDevice(&device_id, &stream_ptr, 1);
222        if raw.is_null() {
223            return Err(Error::DeviceCreationFailed);
224        }
225        sys::oidnCommitDevice(raw);
226        Ok(Self { raw, _refcount: Arc::new(()) })
227    }
228
229    /// Creates a Metal device from an array of Metal command queues (MTLCommandQueue).
230    /// Currently only one queue is supported. Pass a single pointer.
231    pub unsafe fn new_metal_device(command_queues: &[*mut std::ffi::c_void]) -> Result<Self, Error> {
232        let raw = sys::oidnNewMetalDevice(command_queues.as_ptr(), command_queues.len() as i32);
233        if raw.is_null() {
234            return Err(Error::DeviceCreationFailed);
235        }
236        sys::oidnCommitDevice(raw);
237        Ok(Self { raw, _refcount: Arc::new(()) })
238    }
239
240    /// Sets a boolean device parameter. Must call `commit()` before first use if you change parameters.
241    pub fn set_bool(&self, name: &str, value: bool) {
242        let c_name = CString::new(name).unwrap();
243        unsafe { sys::oidnSetDeviceBool(self.raw, c_name.as_ptr(), value) };
244    }
245
246    /// Sets an integer device parameter.
247    pub fn set_int(&self, name: &str, value: i32) {
248        let c_name = CString::new(name).unwrap();
249        unsafe { sys::oidnSetDeviceInt(self.raw, c_name.as_ptr(), value) };
250    }
251
252    /// Gets a boolean device parameter.
253    pub fn get_bool(&self, name: &str) -> bool {
254        let c_name = CString::new(name).unwrap();
255        unsafe { sys::oidnGetDeviceBool(self.raw, c_name.as_ptr()) }
256    }
257
258    /// Gets an integer device parameter.
259    pub fn get_int(&self, name: &str) -> i32 {
260        let c_name = CString::new(name).unwrap();
261        unsafe { sys::oidnGetDeviceInt(self.raw, c_name.as_ptr()) }
262    }
263
264    /// Gets an unsigned integer device parameter (OIDN exposes this as cast of get_int).
265    pub fn get_uint(&self, name: &str) -> u32 {
266        self.get_int(name) as u32
267    }
268
269    /// Commits all previous device parameter changes. Must be called before first filter creation.
270    pub fn commit(&self) {
271        unsafe { sys::oidnCommitDevice(self.raw) };
272    }
273
274    /// Sets the error callback. The callback is invoked from OIDN; it must not panic.
275    /// `user_ptr` is passed to the callback. Must remain valid until device is released or callback is cleared.
276    pub unsafe fn set_error_function_raw(
277        &self,
278        func: sys::OIDNErrorFunction,
279        user_ptr: *mut std::ffi::c_void,
280    ) {
281        sys::oidnSetDeviceErrorFunction(self.raw, func, user_ptr);
282    }
283
284    /// Returns the first unqueried error and clears it.
285    pub fn take_error(&self) -> Option<Error> {
286        let mut msg_ptr: *const std::ffi::c_char = ptr::null();
287        let code = unsafe { sys::oidnGetDeviceError(self.raw, &mut msg_ptr) };
288        if code == sys::OIDNError::None {
289            return None;
290        }
291        let message = if msg_ptr.is_null() {
292            String::new()
293        } else {
294            unsafe { CStr::from_ptr(msg_ptr).to_string_lossy().into_owned() }
295        };
296        Some(Error::OidnError { code: code as u32, message })
297    }
298
299    /// Waits for all async operations on this device to complete.
300    pub fn sync(&self) {
301        unsafe { sys::oidnSyncDevice(self.raw) };
302    }
303
304    /// Retains the device (increments OIDN reference count). For advanced interop only; our `Clone` uses Arc.
305    pub fn retain(&self) {
306        unsafe { sys::oidnRetainDevice(self.raw) };
307    }
308
309    pub(crate) fn raw(&self) -> sys::OIDNDevice {
310        self.raw
311    }
312}
313
314impl Drop for OidnDevice {
315    fn drop(&mut self) {
316        unsafe { sys::oidnReleaseDevice(self.raw) }
317    }
318}
319
320unsafe impl Send for OidnDevice {}
321unsafe impl Sync for OidnDevice {}
322
323/// OIDN device type (CPU, GPU backends, or default auto-select).
324#[derive(Clone, Copy, Debug, Default)]
325pub enum OidnDeviceType {
326    /// Auto-select best available (e.g. CUDA if built and available).
327    #[default]
328    Default,
329    /// CPU only (most portable).
330    Cpu,
331    /// Intel GPU/CPU via SYCL (oneAPI). Requires OIDN built with SYCL.
332    Sycl,
333    /// NVIDIA GPU via CUDA. Requires OIDN built with CUDA.
334    Cuda,
335    /// AMD GPU via HIP. Requires OIDN built with HIP.
336    Hip,
337    /// Apple GPU via Metal. Requires OIDN built with Metal.
338    Metal,
339}
340
341impl OidnDeviceType {
342    fn to_raw(self) -> sys::OIDNDeviceType {
343        match self {
344            OidnDeviceType::Default => sys::OIDNDeviceType::Default,
345            OidnDeviceType::Cpu => sys::OIDNDeviceType::CPU,
346            OidnDeviceType::Sycl => sys::OIDNDeviceType::SYCL,
347            OidnDeviceType::Cuda => sys::OIDNDeviceType::CUDA,
348            OidnDeviceType::Hip => sys::OIDNDeviceType::HIP,
349            OidnDeviceType::Metal => sys::OIDNDeviceType::Metal,
350        }
351    }
352}