1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
//! [`ComputeDevice`] — the one compute-submission surface shared by every
//! backend that owns a device handle.
//!
//! [`Backend`](crate::Backend) answers *what hardware is here and what can it
//! do*. `ComputeDevice` answers the next question — *get bytes onto it, run a
//! shader I compiled, get bytes back* — with a single trait the Vulkan, D3D12,
//! OpenGL, Metal, and Level Zero backends all implement. Code written against
//! `C: ComputeDevice` runs unchanged on any of them:
//!
//! ```no_run
//! use ironaccelerator_core::ComputeDevice;
//!
//! fn double_in_place<C: ComputeDevice>(dev: &C, code: &[u8]) -> Result<Vec<f32>, C::Error> {
//! let input: Vec<u8> = (0..256u32).flat_map(|i| (i as f32).to_le_bytes()).collect();
//! let buf = dev.upload(&input)?;
//! let pipe = dev.pipeline(code, 1)?; // one storage buffer at slot 0
//! dev.dispatch(&pipe, &[&buf], [256 / 64, 1, 1])?;
//! let mut out = vec![0u8; input.len()];
//! dev.download(&buf, &mut out)?;
//! Ok(out.chunks_exact(4).map(|c| f32::from_le_bytes(c.try_into().unwrap())).collect())
//! }
//! ```
//!
//! # It stays at the driver line
//!
//! The trait moves bytes and launches caller-supplied bytecode. It does not
//! compile shaders, choose work sizes, or describe workloads — those are
//! consumer concerns, exactly as with [`Backend`](crate::Backend). The shape is
//! deliberately the least-common-denominator of the three backends: flat
//! storage buffers bound at consecutive slots `0..bindings`, one entry point,
//! host-blocking submission.
//!
//! # Bytecode is backend-native
//!
//! [`pipeline`](ComputeDevice::pipeline) takes the format the backend's driver
//! consumes — there is no translation layer here:
//!
//! | backend | `code` is | entry point |
//! |---------|-----------|-------------|
//! | Vulkan | SPIR-V (a little-endian `u32` word stream as bytes; length a multiple of 4) | `main` |
//! | D3D12 | a signed DXIL container (`dxc -T cs_6_0 …`) | shader's own |
//! | OpenGL | GLSL compute-shader source, UTF-8 (`#version 430`+) | `main` |
//! | Metal | a compiled `.metallib` (`xcrun metal … && xcrun metallib …`) | first kernel (MSL reserves `main`) |
//! | Level Zero | OpenCL/SYCL-flavored SPIR-V — `Kernel` model, pointer args (not Vulkan's `GLCompute` SPIR-V) | `main` |
//!
//! The `bindings` count is how many storage buffers the shader declares at
//! slots `0..bindings` (`binding = N` in SPIR-V/GLSL, `register(uN)` in HLSL,
//! `[[buffer(N)]]` in Metal); [`dispatch`](ComputeDevice::dispatch) binds
//! exactly that many in order.
//!
//! One wrinkle: Metal and Level Zero set the group size at dispatch, where the
//! others declare it in the shader (`local_size` / `numthreads`). The trait
//! carries only threadgroup counts, so those two impls assume a 1-D group of
//! 64; each exposes a native call taking an explicit size for other cases.
//!
//! # Why WebGPU is not here
//!
//! `ironaccelerator-webgpu` does not implement this trait. WebGPU's `GPUDevice`
//! is owned by the host page and its submission API is asynchronous and
//! JS-side; wrapping it would mean re-exporting a binding crate's types and
//! taking on the very dependency that backend was built to avoid. There is no
//! device handle for the crate to hang an impl on, so it stays a host-driven
//! adapter survey.
/// A device you can allocate on, submit a compute shader to, and read back
/// from — one uniform surface over Vulkan, D3D12, and OpenGL.
///
/// Implemented on the backend's owned device/context type (Vulkan `Context`,
/// D3D12 `Context`, OpenGL `GlDevice`, Metal `Context`, Level Zero `Context`).
/// Every method blocks until the GPU has finished the operation, matching the
/// one-shot submission model the backends already use; batching and async are
/// concrete-type concerns layered on top.
///
/// The associated types keep this zero-cost — there is no boxing and no
/// vtable. The trait is therefore not `dyn`-safe by design; use it as a generic
/// bound (`fn run<C: ComputeDevice>(dev: &C)`), not a trait object.