Skip to main content

oxicuda_levelzero/
device.rs

1//! Level Zero device wrapper.
2//!
3//! Loads `libze_loader.so` (Linux) or `ze_loader.dll` (Windows) at runtime via
4//! `libloading` and exposes a safe Rust handle.  On macOS every constructor
5//! returns [`LevelZeroError::UnsupportedPlatform`].
6
7use crate::error::{LevelZeroError, LevelZeroResult};
8
9// ─── Platform-specific imports and type definitions ──────────────────────────
10
11#[cfg(any(target_os = "linux", target_os = "windows"))]
12use std::{ffi::c_void, sync::Arc};
13
14#[cfg(any(target_os = "linux", target_os = "windows"))]
15use libloading::Library;
16
17// ─── Level Zero opaque handle types (Linux + Windows only) ───────────────────
18
19#[cfg(any(target_os = "linux", target_os = "windows"))]
20type ZeDriverHandle = *mut c_void;
21
22#[cfg(any(target_os = "linux", target_os = "windows"))]
23type ZeDeviceHandle = *mut c_void;
24
25#[cfg(any(target_os = "linux", target_os = "windows"))]
26type ZeContextHandle = *mut c_void;
27
28#[cfg(any(target_os = "linux", target_os = "windows"))]
29pub(crate) type ZeCommandQueueHandle = *mut c_void;
30
31#[cfg(any(target_os = "linux", target_os = "windows"))]
32pub(crate) type ZeCommandListHandle = *mut c_void;
33
34#[cfg(any(target_os = "linux", target_os = "windows"))]
35pub(crate) type ZeModuleHandle = *mut c_void;
36
37#[cfg(any(target_os = "linux", target_os = "windows"))]
38pub(crate) type ZeKernelHandle = *mut c_void;
39
40// ─── Level Zero result / type constants ──────────────────────────────────────
41
42#[cfg(any(target_os = "linux", target_os = "windows"))]
43const ZE_RESULT_SUCCESS: u32 = 0;
44
45#[cfg(any(target_os = "linux", target_os = "windows"))]
46const ZE_DEVICE_TYPE_GPU: u32 = 1;
47
48#[cfg(any(target_os = "linux", target_os = "windows"))]
49const ZE_STRUCTURE_TYPE_CONTEXT_DESC: u32 = 0xb;
50
51#[cfg(any(target_os = "linux", target_os = "windows"))]
52const ZE_STRUCTURE_TYPE_COMMAND_QUEUE_DESC: u32 = 0xf;
53
54#[cfg(any(target_os = "linux", target_os = "windows"))]
55pub(crate) const ZE_STRUCTURE_TYPE_COMMAND_LIST_DESC: u32 = 0x9;
56
57#[cfg(any(target_os = "linux", target_os = "windows"))]
58pub(crate) const ZE_STRUCTURE_TYPE_DEVICE_MEM_ALLOC_DESC: u32 = 0x1;
59
60#[cfg(any(target_os = "linux", target_os = "windows"))]
61pub(crate) const ZE_STRUCTURE_TYPE_HOST_MEM_ALLOC_DESC: u32 = 0x2;
62
63#[cfg(any(target_os = "linux", target_os = "windows"))]
64const ZE_STRUCTURE_TYPE_DEVICE_PROPERTIES: u32 = 0x3;
65
66#[cfg(any(target_os = "linux", target_os = "windows"))]
67pub(crate) const ZE_STRUCTURE_TYPE_MODULE_DESC: u32 = 0x18;
68
69#[cfg(any(target_os = "linux", target_os = "windows"))]
70pub(crate) const ZE_STRUCTURE_TYPE_KERNEL_DESC: u32 = 0x1a;
71
72#[cfg(any(target_os = "linux", target_os = "windows"))]
73pub(crate) const ZE_MODULE_FORMAT_IL_SPIRV: u32 = 0;
74
75// ─── Level Zero descriptor and property structs (Linux + Windows only) ───────
76
77#[cfg(any(target_os = "linux", target_os = "windows"))]
78#[repr(C)]
79pub(crate) struct ZeContextDesc {
80    pub(crate) stype: u32,
81    pub(crate) p_next: *const c_void,
82}
83
84#[cfg(any(target_os = "linux", target_os = "windows"))]
85#[repr(C)]
86pub(crate) struct ZeCommandQueueDesc {
87    stype: u32,
88    p_next: *const c_void,
89    ordinal: u32,
90    index: u32,
91    flags: u32,
92    mode: u32,
93    priority: u32,
94}
95
96#[cfg(any(target_os = "linux", target_os = "windows"))]
97#[repr(C)]
98pub(crate) struct ZeCommandListDesc {
99    pub stype: u32,
100    pub p_next: *const c_void,
101    pub command_queue_group_ordinal: u32,
102    pub flags: u32,
103}
104
105#[cfg(any(target_os = "linux", target_os = "windows"))]
106#[repr(C)]
107pub(crate) struct ZeDeviceMemAllocDesc {
108    pub stype: u32,
109    pub p_next: *const c_void,
110    pub flags: u32,
111    pub ordinal: u32,
112}
113
114#[cfg(any(target_os = "linux", target_os = "windows"))]
115#[repr(C)]
116pub(crate) struct ZeHostMemAllocDesc {
117    pub stype: u32,
118    pub p_next: *const c_void,
119    pub flags: u32,
120}
121
122#[cfg(any(target_os = "linux", target_os = "windows"))]
123#[repr(C)]
124pub(crate) struct ZeDeviceProperties {
125    stype: u32,
126    p_next: *const c_void,
127    device_type: u32,
128    vendor_id: u32,
129    device_id: u32,
130    _flags: u32,
131    _sub_device_ids: [u32; 64],
132    _timer_resolution: u64,
133    _timestamp_valid_bits: u32,
134    _kernel_timestamp_valid_bits: u32,
135    name: [u8; 256],
136    _max_mem_alloc_size: u64,
137    _num_threads_per_eu: u32,
138    _physical_eu_simd_width: u32,
139    _num_eu_per_sub_slice: u32,
140    _num_sub_slices_per_slice: u32,
141    _num_slices: u32,
142    _timer_resolution_ns: u64,
143    _uuid: [u8; 16],
144}
145
146#[cfg(any(target_os = "linux", target_os = "windows"))]
147#[repr(C)]
148pub(crate) struct ZeModuleDesc {
149    pub stype: u32,
150    pub p_next: *const c_void,
151    pub format: u32,
152    pub input_size: usize,
153    pub p_input_module: *const u8,
154    pub p_build_flags: *const u8,
155    pub p_constants: *const c_void,
156}
157
158#[cfg(any(target_os = "linux", target_os = "windows"))]
159#[repr(C)]
160pub(crate) struct ZeKernelDesc {
161    pub stype: u32,
162    pub p_next: *const c_void,
163    pub flags: u32,
164    pub p_kernel_name: *const u8,
165}
166
167#[cfg(any(target_os = "linux", target_os = "windows"))]
168#[repr(C)]
169pub(crate) struct ZeGroupCount {
170    pub group_count_x: u32,
171    pub group_count_y: u32,
172    pub group_count_z: u32,
173}
174
175// ─── Level Zero function pointer types (Linux + Windows only) ────────────────
176
177#[cfg(any(target_os = "linux", target_os = "windows"))]
178type ZeInitFn = unsafe extern "C" fn(flags: u32) -> u32;
179
180#[cfg(any(target_os = "linux", target_os = "windows"))]
181type ZeDriverGetFn = unsafe extern "C" fn(count: *mut u32, drivers: *mut ZeDriverHandle) -> u32;
182
183#[cfg(any(target_os = "linux", target_os = "windows"))]
184type ZeDeviceGetFn = unsafe extern "C" fn(
185    driver: ZeDriverHandle,
186    count: *mut u32,
187    devices: *mut ZeDeviceHandle,
188) -> u32;
189
190#[cfg(any(target_os = "linux", target_os = "windows"))]
191type ZeDeviceGetPropertiesFn =
192    unsafe extern "C" fn(device: ZeDeviceHandle, props: *mut ZeDeviceProperties) -> u32;
193
194#[cfg(any(target_os = "linux", target_os = "windows"))]
195type ZeContextCreateFn = unsafe extern "C" fn(
196    driver: ZeDriverHandle,
197    desc: *const ZeContextDesc,
198    context: *mut ZeContextHandle,
199) -> u32;
200
201#[cfg(any(target_os = "linux", target_os = "windows"))]
202type ZeContextDestroyFn = unsafe extern "C" fn(context: ZeContextHandle) -> u32;
203
204#[cfg(any(target_os = "linux", target_os = "windows"))]
205type ZeCommandQueueCreateFn = unsafe extern "C" fn(
206    context: ZeContextHandle,
207    device: ZeDeviceHandle,
208    desc: *const ZeCommandQueueDesc,
209    queue: *mut ZeCommandQueueHandle,
210) -> u32;
211
212#[cfg(any(target_os = "linux", target_os = "windows"))]
213type ZeCommandQueueDestroyFn = unsafe extern "C" fn(queue: ZeCommandQueueHandle) -> u32;
214
215#[cfg(any(target_os = "linux", target_os = "windows"))]
216type ZeCommandQueueSynchronizeFn =
217    unsafe extern "C" fn(queue: ZeCommandQueueHandle, timeout: u64) -> u32;
218
219#[cfg(any(target_os = "linux", target_os = "windows"))]
220type ZeCommandQueueExecuteCommandListsFn = unsafe extern "C" fn(
221    queue: ZeCommandQueueHandle,
222    count: u32,
223    lists: *const ZeCommandListHandle,
224    fence: usize,
225) -> u32;
226
227#[cfg(any(target_os = "linux", target_os = "windows"))]
228type ZeCommandListCreateFn = unsafe extern "C" fn(
229    context: ZeContextHandle,
230    device: ZeDeviceHandle,
231    desc: *const ZeCommandListDesc,
232    list: *mut ZeCommandListHandle,
233) -> u32;
234
235#[cfg(any(target_os = "linux", target_os = "windows"))]
236type ZeCommandListDestroyFn = unsafe extern "C" fn(list: ZeCommandListHandle) -> u32;
237
238#[cfg(any(target_os = "linux", target_os = "windows"))]
239type ZeCommandListCloseFn = unsafe extern "C" fn(list: ZeCommandListHandle) -> u32;
240
241#[cfg(any(target_os = "linux", target_os = "windows"))]
242type ZeCommandListResetFn = unsafe extern "C" fn(list: ZeCommandListHandle) -> u32;
243
244#[cfg(any(target_os = "linux", target_os = "windows"))]
245type ZeCommandListAppendMemoryCopyFn = unsafe extern "C" fn(
246    list: ZeCommandListHandle,
247    dst: *mut c_void,
248    src: *const c_void,
249    size: usize,
250    signal_event: usize,
251    wait_count: u32,
252    wait_events: *const usize,
253) -> u32;
254
255#[cfg(any(target_os = "linux", target_os = "windows"))]
256type ZeMemAllocDeviceFn = unsafe extern "C" fn(
257    context: ZeContextHandle,
258    desc: *const ZeDeviceMemAllocDesc,
259    size: usize,
260    alignment: usize,
261    device: ZeDeviceHandle,
262    ptr: *mut *mut c_void,
263) -> u32;
264
265#[cfg(any(target_os = "linux", target_os = "windows"))]
266type ZeMemAllocHostFn = unsafe extern "C" fn(
267    context: ZeContextHandle,
268    desc: *const ZeHostMemAllocDesc,
269    size: usize,
270    alignment: usize,
271    ptr: *mut *mut c_void,
272) -> u32;
273
274#[cfg(any(target_os = "linux", target_os = "windows"))]
275type ZeMemFreeFn = unsafe extern "C" fn(context: ZeContextHandle, ptr: *mut c_void) -> u32;
276
277#[cfg(any(target_os = "linux", target_os = "windows"))]
278type ZeModuleCreateFn = unsafe extern "C" fn(
279    context: ZeContextHandle,
280    device: ZeDeviceHandle,
281    desc: *const ZeModuleDesc,
282    module: *mut ZeModuleHandle,
283    build_log: *mut *mut c_void,
284) -> u32;
285
286#[cfg(any(target_os = "linux", target_os = "windows"))]
287type ZeModuleDestroyFn = unsafe extern "C" fn(module: ZeModuleHandle) -> u32;
288
289#[cfg(any(target_os = "linux", target_os = "windows"))]
290type ZeKernelCreateFn = unsafe extern "C" fn(
291    module: ZeModuleHandle,
292    desc: *const ZeKernelDesc,
293    kernel: *mut ZeKernelHandle,
294) -> u32;
295
296#[cfg(any(target_os = "linux", target_os = "windows"))]
297type ZeKernelDestroyFn = unsafe extern "C" fn(kernel: ZeKernelHandle) -> u32;
298
299#[cfg(any(target_os = "linux", target_os = "windows"))]
300type ZeKernelSetGroupSizeFn =
301    unsafe extern "C" fn(kernel: ZeKernelHandle, x: u32, y: u32, z: u32) -> u32;
302
303#[cfg(any(target_os = "linux", target_os = "windows"))]
304type ZeKernelSetArgumentValueFn = unsafe extern "C" fn(
305    kernel: ZeKernelHandle,
306    arg_index: u32,
307    arg_size: usize,
308    p_arg_value: *const c_void,
309) -> u32;
310
311#[cfg(any(target_os = "linux", target_os = "windows"))]
312type ZeCommandListAppendLaunchKernelFn = unsafe extern "C" fn(
313    list: ZeCommandListHandle,
314    kernel: ZeKernelHandle,
315    launch_func_args: *const ZeGroupCount,
316    signal_event: usize,
317    wait_count: u32,
318    wait_events: *const usize,
319) -> u32;
320
321// ─── L0Api — dynamically-loaded function table (Linux + Windows only) ─────────
322
323/// Holds the loaded `libze_loader` library and all extracted function pointers.
324///
325/// The `Library` field keeps the shared object alive for the lifetime of this struct.
326#[cfg(any(target_os = "linux", target_os = "windows"))]
327pub(crate) struct L0Api {
328    /// The loaded shared library — must outlive all function pointer calls.
329    _lib: Library,
330    pub ze_init: ZeInitFn,
331    pub ze_driver_get: ZeDriverGetFn,
332    pub ze_device_get: ZeDeviceGetFn,
333    pub ze_device_get_properties: ZeDeviceGetPropertiesFn,
334    pub ze_context_create: ZeContextCreateFn,
335    pub ze_context_destroy: ZeContextDestroyFn,
336    pub ze_command_queue_create: ZeCommandQueueCreateFn,
337    pub ze_command_queue_destroy: ZeCommandQueueDestroyFn,
338    pub ze_command_queue_synchronize: ZeCommandQueueSynchronizeFn,
339    pub ze_command_queue_execute_command_lists: ZeCommandQueueExecuteCommandListsFn,
340    pub ze_command_list_create: ZeCommandListCreateFn,
341    pub ze_command_list_destroy: ZeCommandListDestroyFn,
342    pub ze_command_list_close: ZeCommandListCloseFn,
343    #[allow(dead_code)]
344    pub ze_command_list_reset: ZeCommandListResetFn,
345    pub ze_command_list_append_memory_copy: ZeCommandListAppendMemoryCopyFn,
346    pub ze_mem_alloc_device: ZeMemAllocDeviceFn,
347    pub ze_mem_alloc_host: ZeMemAllocHostFn,
348    pub ze_mem_free: ZeMemFreeFn,
349    pub ze_module_create: ZeModuleCreateFn,
350    pub ze_module_destroy: ZeModuleDestroyFn,
351    pub ze_kernel_create: ZeKernelCreateFn,
352    pub ze_kernel_destroy: ZeKernelDestroyFn,
353    pub ze_kernel_set_group_size: ZeKernelSetGroupSizeFn,
354    pub ze_kernel_set_argument_value: ZeKernelSetArgumentValueFn,
355    pub ze_command_list_append_launch_kernel: ZeCommandListAppendLaunchKernelFn,
356}
357
358#[cfg(any(target_os = "linux", target_os = "windows"))]
359impl L0Api {
360    /// Load the Level Zero loader library and extract all function pointers.
361    ///
362    /// # Safety
363    ///
364    /// The returned `L0Api` must not outlive the process image that loaded it.
365    /// All function pointers are valid only as long as the `Library` is alive
366    /// (which is guaranteed by the `_lib` field).
367    unsafe fn load() -> LevelZeroResult<Self> {
368        #[cfg(target_os = "linux")]
369        let lib_name = "libze_loader.so.1";
370        #[cfg(target_os = "windows")]
371        let lib_name = "ze_loader.dll";
372
373        // SAFETY: We are loading a well-known system library by its standard
374        // filename. The resulting Library keeps the shared object alive.
375        let lib = unsafe {
376            Library::new(lib_name)
377                .map_err(|e| LevelZeroError::LibraryNotFound(format!("{lib_name}: {e}")))?
378        };
379
380        macro_rules! sym {
381            ($name:literal, $ty:ty) => {{
382                // SAFETY: We are loading a symbol from the Level Zero loader
383                // that we know to exist with the correct type signature.
384                // The symbol lifetime is bounded by `lib`.
385                *unsafe {
386                    lib.get::<$ty>($name).map_err(|e| {
387                        LevelZeroError::LibraryNotFound(format!(
388                            "symbol {}: {e}",
389                            stringify!($name)
390                        ))
391                    })?
392                }
393            }};
394        }
395
396        let ze_init = sym!(b"zeInit\0", ZeInitFn);
397        let ze_driver_get = sym!(b"zeDriverGet\0", ZeDriverGetFn);
398        let ze_device_get = sym!(b"zeDeviceGet\0", ZeDeviceGetFn);
399        let ze_device_get_properties = sym!(b"zeDeviceGetProperties\0", ZeDeviceGetPropertiesFn);
400        let ze_context_create = sym!(b"zeContextCreate\0", ZeContextCreateFn);
401        let ze_context_destroy = sym!(b"zeContextDestroy\0", ZeContextDestroyFn);
402        let ze_command_queue_create = sym!(b"zeCommandQueueCreate\0", ZeCommandQueueCreateFn);
403        let ze_command_queue_destroy = sym!(b"zeCommandQueueDestroy\0", ZeCommandQueueDestroyFn);
404        let ze_command_queue_synchronize =
405            sym!(b"zeCommandQueueSynchronize\0", ZeCommandQueueSynchronizeFn);
406        let ze_command_queue_execute_command_lists = sym!(
407            b"zeCommandQueueExecuteCommandLists\0",
408            ZeCommandQueueExecuteCommandListsFn
409        );
410        let ze_command_list_create = sym!(b"zeCommandListCreate\0", ZeCommandListCreateFn);
411        let ze_command_list_destroy = sym!(b"zeCommandListDestroy\0", ZeCommandListDestroyFn);
412        let ze_command_list_close = sym!(b"zeCommandListClose\0", ZeCommandListCloseFn);
413        let ze_command_list_reset = sym!(b"zeCommandListReset\0", ZeCommandListResetFn);
414        let ze_command_list_append_memory_copy = sym!(
415            b"zeCommandListAppendMemoryCopy\0",
416            ZeCommandListAppendMemoryCopyFn
417        );
418        let ze_mem_alloc_device = sym!(b"zeMemAllocDevice\0", ZeMemAllocDeviceFn);
419        let ze_mem_alloc_host = sym!(b"zeMemAllocHost\0", ZeMemAllocHostFn);
420        let ze_mem_free = sym!(b"zeMemFree\0", ZeMemFreeFn);
421        let ze_module_create = sym!(b"zeModuleCreate\0", ZeModuleCreateFn);
422        let ze_module_destroy = sym!(b"zeModuleDestroy\0", ZeModuleDestroyFn);
423        let ze_kernel_create = sym!(b"zeKernelCreate\0", ZeKernelCreateFn);
424        let ze_kernel_destroy = sym!(b"zeKernelDestroy\0", ZeKernelDestroyFn);
425        let ze_kernel_set_group_size = sym!(b"zeKernelSetGroupSize\0", ZeKernelSetGroupSizeFn);
426        let ze_kernel_set_argument_value =
427            sym!(b"zeKernelSetArgumentValue\0", ZeKernelSetArgumentValueFn);
428        let ze_command_list_append_launch_kernel = sym!(
429            b"zeCommandListAppendLaunchKernel\0",
430            ZeCommandListAppendLaunchKernelFn
431        );
432
433        Ok(Self {
434            _lib: lib,
435            ze_init,
436            ze_driver_get,
437            ze_device_get,
438            ze_device_get_properties,
439            ze_context_create,
440            ze_context_destroy,
441            ze_command_queue_create,
442            ze_command_queue_destroy,
443            ze_command_queue_synchronize,
444            ze_command_queue_execute_command_lists,
445            ze_command_list_create,
446            ze_command_list_destroy,
447            ze_command_list_close,
448            ze_command_list_reset,
449            ze_command_list_append_memory_copy,
450            ze_mem_alloc_device,
451            ze_mem_alloc_host,
452            ze_mem_free,
453            ze_module_create,
454            ze_module_destroy,
455            ze_kernel_create,
456            ze_kernel_destroy,
457            ze_kernel_set_group_size,
458            ze_kernel_set_argument_value,
459            ze_command_list_append_launch_kernel,
460        })
461    }
462}
463
464// ─── LevelZeroDevice ─────────────────────────────────────────────────────────
465
466/// An Intel GPU device accessed via the Level Zero API.
467///
468/// On non-Linux/Windows platforms, [`LevelZeroDevice::new`] always returns
469/// [`LevelZeroError::UnsupportedPlatform`].
470pub struct LevelZeroDevice {
471    /// The loaded Level Zero API (Linux and Windows only).
472    #[cfg(any(target_os = "linux", target_os = "windows"))]
473    pub(crate) api: Arc<L0Api>,
474    /// The Level Zero context handle (Linux and Windows only).
475    #[cfg(any(target_os = "linux", target_os = "windows"))]
476    pub(crate) context: ZeContextHandle,
477    /// The selected GPU device handle (Linux and Windows only).
478    #[cfg(any(target_os = "linux", target_os = "windows"))]
479    pub(crate) device: ZeDeviceHandle,
480    /// The command queue handle (Linux and Windows only).
481    #[cfg(any(target_os = "linux", target_os = "windows"))]
482    pub(crate) queue: ZeCommandQueueHandle,
483    /// Human-readable device name.
484    device_name: String,
485}
486
487impl LevelZeroDevice {
488    /// Open the first Intel GPU found via the Level Zero loader.
489    ///
490    /// Returns [`LevelZeroError::UnsupportedPlatform`] on macOS.
491    pub fn new() -> LevelZeroResult<Self> {
492        #[cfg(any(target_os = "linux", target_os = "windows"))]
493        {
494            // SAFETY: L0Api::load performs dlopen/LoadLibrary and symbol
495            // resolution. All further calls are guarded by result checks.
496            let api = Arc::new(unsafe { L0Api::load()? });
497
498            // Step 1: Initialize Level Zero runtime.
499            // SAFETY: ze_init is a valid function pointer from the loaded library.
500            let rc = unsafe { (api.ze_init)(0) };
501            if rc != ZE_RESULT_SUCCESS {
502                return Err(LevelZeroError::ZeError(rc, "zeInit failed".into()));
503            }
504
505            // Step 2: Enumerate drivers.
506            let mut driver_count: u32 = 0;
507            // SAFETY: Passing null for the driver array to query the count.
508            let rc =
509                unsafe { (api.ze_driver_get)(&mut driver_count as *mut u32, std::ptr::null_mut()) };
510            if rc != ZE_RESULT_SUCCESS {
511                return Err(LevelZeroError::ZeError(
512                    rc,
513                    "zeDriverGet (count) failed".into(),
514                ));
515            }
516            if driver_count == 0 {
517                return Err(LevelZeroError::NoSuitableDevice);
518            }
519
520            let mut drivers: Vec<ZeDriverHandle> =
521                vec![std::ptr::null_mut(); driver_count as usize];
522            // SAFETY: `drivers` is allocated for exactly `driver_count` elements.
523            let rc =
524                unsafe { (api.ze_driver_get)(&mut driver_count as *mut u32, drivers.as_mut_ptr()) };
525            if rc != ZE_RESULT_SUCCESS {
526                return Err(LevelZeroError::ZeError(
527                    rc,
528                    "zeDriverGet (enumerate) failed".into(),
529                ));
530            }
531
532            let driver = drivers[0];
533
534            // Step 3: Enumerate devices and find the first GPU.
535            let mut device_count: u32 = 0;
536            // SAFETY: Passing null to query the device count for this driver.
537            let rc = unsafe {
538                (api.ze_device_get)(driver, &mut device_count as *mut u32, std::ptr::null_mut())
539            };
540            if rc != ZE_RESULT_SUCCESS {
541                return Err(LevelZeroError::ZeError(
542                    rc,
543                    "zeDeviceGet (count) failed".into(),
544                ));
545            }
546            if device_count == 0 {
547                return Err(LevelZeroError::NoSuitableDevice);
548            }
549
550            let mut devices: Vec<ZeDeviceHandle> =
551                vec![std::ptr::null_mut(); device_count as usize];
552            // SAFETY: `devices` is allocated for exactly `device_count` elements.
553            let rc = unsafe {
554                (api.ze_device_get)(driver, &mut device_count as *mut u32, devices.as_mut_ptr())
555            };
556            if rc != ZE_RESULT_SUCCESS {
557                return Err(LevelZeroError::ZeError(
558                    rc,
559                    "zeDeviceGet (enumerate) failed".into(),
560                ));
561            }
562
563            // Step 4: Find the first GPU device and read its properties.
564            let mut chosen_device: Option<ZeDeviceHandle> = None;
565            let mut device_name = String::from("Intel GPU");
566
567            for &dev in &devices {
568                // SAFETY: ZeDeviceProperties is #[repr(C)] and fully initialized
569                // (zeroed) before passing to the API.
570                let mut props =
571                    unsafe { std::mem::MaybeUninit::<ZeDeviceProperties>::zeroed().assume_init() };
572                props.stype = ZE_STRUCTURE_TYPE_DEVICE_PROPERTIES;
573                props.p_next = std::ptr::null();
574
575                // SAFETY: `dev` is a valid device handle; `props` is properly
576                // initialized with the correct stype field.
577                let rc = unsafe {
578                    (api.ze_device_get_properties)(dev, &mut props as *mut ZeDeviceProperties)
579                };
580                if rc != ZE_RESULT_SUCCESS {
581                    continue;
582                }
583
584                if props.device_type == ZE_DEVICE_TYPE_GPU {
585                    // Extract null-terminated name string.
586                    let name_len = props
587                        .name
588                        .iter()
589                        .position(|&b| b == 0)
590                        .unwrap_or(props.name.len());
591                    device_name = String::from_utf8_lossy(&props.name[..name_len]).into_owned();
592                    chosen_device = Some(dev);
593                    break;
594                }
595            }
596
597            let device = chosen_device.ok_or(LevelZeroError::NoSuitableDevice)?;
598
599            // Step 5: Create a context.
600            let ctx_desc = ZeContextDesc {
601                stype: ZE_STRUCTURE_TYPE_CONTEXT_DESC,
602                p_next: std::ptr::null(),
603            };
604            let mut context: ZeContextHandle = std::ptr::null_mut();
605            // SAFETY: `ctx_desc` is valid; `context` is a valid output pointer.
606            let rc = unsafe {
607                (api.ze_context_create)(driver, &ctx_desc, &mut context as *mut ZeContextHandle)
608            };
609            if rc != ZE_RESULT_SUCCESS {
610                return Err(LevelZeroError::ZeError(rc, "zeContextCreate failed".into()));
611            }
612
613            // Step 6: Create a command queue.
614            let queue_desc = ZeCommandQueueDesc {
615                stype: ZE_STRUCTURE_TYPE_COMMAND_QUEUE_DESC,
616                p_next: std::ptr::null(),
617                ordinal: 0,
618                index: 0,
619                flags: 0,
620                mode: 0, // default mode
621                priority: 0,
622            };
623            let mut queue: ZeCommandQueueHandle = std::ptr::null_mut();
624            // SAFETY: `context`, `device`, and `queue_desc` are valid; `queue`
625            // is a valid output pointer.
626            let rc = unsafe {
627                (api.ze_command_queue_create)(
628                    context,
629                    device,
630                    &queue_desc,
631                    &mut queue as *mut ZeCommandQueueHandle,
632                )
633            };
634            if rc != ZE_RESULT_SUCCESS {
635                // Clean up context before returning error.
636                // SAFETY: context was successfully created above.
637                unsafe { (api.ze_context_destroy)(context) };
638                return Err(LevelZeroError::ZeError(
639                    rc,
640                    "zeCommandQueueCreate failed".into(),
641                ));
642            }
643
644            tracing::info!("Level Zero device selected: {device_name}");
645
646            Ok(Self {
647                api,
648                context,
649                device,
650                queue,
651                device_name,
652            })
653        }
654
655        #[cfg(not(any(target_os = "linux", target_os = "windows")))]
656        {
657            Err(LevelZeroError::UnsupportedPlatform)
658        }
659    }
660
661    /// Human-readable device name (e.g. `"Intel UHD Graphics 770"`).
662    pub fn name(&self) -> &str {
663        &self.device_name
664    }
665}
666
667// ─── Drop ────────────────────────────────────────────────────────────────────
668
669impl Drop for LevelZeroDevice {
670    fn drop(&mut self) {
671        #[cfg(any(target_os = "linux", target_os = "windows"))]
672        {
673            // SAFETY: `queue` and `context` were successfully created in `new()`
674            // and have not been freed yet.  We destroy in reverse creation order.
675            unsafe {
676                (self.api.ze_command_queue_destroy)(self.queue);
677                (self.api.ze_context_destroy)(self.context);
678            }
679        }
680    }
681}
682
683// ─── Send + Sync ─────────────────────────────────────────────────────────────
684
685// SAFETY: `LevelZeroDevice` holds raw Level Zero opaque pointers that are
686// conceptually owned by this struct.  The Level Zero specification states that
687// context and command-queue handles may be used from any thread as long as
688// external synchronization is provided.  We guarantee exclusive ownership via
689// `Arc<LevelZeroDevice>` and `Mutex` in the memory manager.
690unsafe impl Send for LevelZeroDevice {}
691// SAFETY: See `Send` impl above.  Shared immutable access is safe because all
692// mutable operations go through synchronised command lists.
693unsafe impl Sync for LevelZeroDevice {}
694
695// ─── Debug ───────────────────────────────────────────────────────────────────
696
697impl std::fmt::Debug for LevelZeroDevice {
698    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
699        write!(f, "LevelZeroDevice({})", self.device_name)
700    }
701}
702
703// ─── Tests ───────────────────────────────────────────────────────────────────
704
705#[cfg(test)]
706mod tests {
707    use super::*;
708
709    #[test]
710    #[cfg(any(target_os = "linux", target_os = "windows"))]
711    fn level_zero_device_graceful_init() {
712        match LevelZeroDevice::new() {
713            Ok(dev) => {
714                assert!(!dev.name().is_empty());
715                let dbg = format!("{dev:?}");
716                assert!(dbg.contains("LevelZeroDevice"));
717            }
718            Err(LevelZeroError::LibraryNotFound(_)) => {
719                // Acceptable: Level Zero loader not installed on this machine.
720            }
721            Err(LevelZeroError::NoSuitableDevice) => {
722                // Acceptable: no Intel GPU present.
723            }
724            Err(LevelZeroError::ZeError(_, _)) => {
725                // Acceptable: Level Zero runtime error (e.g. missing driver).
726            }
727            Err(e) => {
728                // Any other error must not panic — just log it.
729                let _ = format!("Level Zero device init error (non-fatal): {e}");
730            }
731        }
732    }
733
734    #[test]
735    #[cfg(not(any(target_os = "linux", target_os = "windows")))]
736    fn level_zero_device_unsupported_on_macos() {
737        let result = LevelZeroDevice::new();
738        assert!(matches!(result, Err(LevelZeroError::UnsupportedPlatform)));
739    }
740}