Skip to main content

vyre_driver_wgpu/runtime/
indirect.rs

1//! Indirect dispatch path (C-B4).
2//!
3//! Submits `ComputePass::dispatch_workgroups_indirect(buffer, 0)`
4//! on a GPU-resident `[u32; 3]` workgroup-count buffer, so the
5//! upstream kernel can decide the downstream dispatch shape
6//! without a round-trip to host.
7//!
8//! The `core.indirect_dispatch` op (registered in
9//! `vyre-core/src/dialect/core_indirect.rs`) names this operation
10//! in vyre IR; this module is the wgpu-side implementation.
11
12use std::sync::Arc;
13use vyre_driver::BackendError;
14
15use crate::buffer::GpuBufferHandle;
16
17/// Minimum size of an indirect workgroup-count buffer: three
18/// little-endian `u32`s, or 12 bytes.
19pub const INDIRECT_ARGS_BYTES: u64 = 12;
20
21/// Handle + offset identifying where in GPU memory the `[u32; 3]`
22/// workgroup count lives.
23pub struct IndirectArgs {
24    /// GPU buffer containing the workgroup count at the given byte
25    /// offset.
26    pub buffer: Arc<wgpu::Buffer>,
27    /// Byte offset within `buffer`. Must be 4-byte aligned per
28    /// wgpu's contract.
29    pub offset: u64,
30}
31
32impl IndirectArgs {
33    /// Build an `IndirectArgs` from a `GpuBufferHandle` + offset.
34    ///
35    /// # Errors
36    ///
37    /// Returns a `BackendError` when:
38    ///
39    /// * `offset` is not 4-byte aligned (wgpu rejects unaligned
40    ///   indirect dispatches).
41    /// * `offset + INDIRECT_ARGS_BYTES` would exceed the buffer's
42    ///   byte length.
43    /// * The underlying buffer does not carry
44    ///   `wgpu::BufferUsages::INDIRECT`.
45    pub fn from_handle(handle: &GpuBufferHandle, offset: u64) -> Result<Self, BackendError> {
46        if offset & 0b11 != 0 {
47            return Err(BackendError::new(format!(
48                "indirect dispatch offset {offset} is not 4-byte aligned. Fix: align to a u32 boundary."
49            )));
50        }
51        if offset
52            .checked_add(INDIRECT_ARGS_BYTES)
53            .map(|end| end > handle.byte_len())
54            .unwrap_or(true)
55        {
56            return Err(BackendError::new(format!(
57                "indirect dispatch would read past buffer end (offset={offset}, args={INDIRECT_ARGS_BYTES}, buffer byte_len={}). Fix: grow the buffer or lower the offset.",
58                handle.byte_len()
59            )));
60        }
61        if !handle.usage().contains(wgpu::BufferUsages::INDIRECT) {
62            return Err(BackendError::new(
63                "indirect dispatch requires buffer with `wgpu::BufferUsages::INDIRECT`. Fix: allocate the workgroup-count buffer with INDIRECT usage.",
64            ));
65        }
66        Ok(Self {
67            buffer: handle.buffer_arc(),
68            offset,
69        })
70    }
71}
72
73/// Record an indirect dispatch into an existing compute pass.
74///
75/// The caller sets the pipeline + bind group before calling this;
76/// we just submit the `dispatch_workgroups_indirect`.
77pub fn dispatch_indirect<'a>(pass: &mut wgpu::ComputePass<'a>, args: &'a IndirectArgs) {
78    pass.dispatch_workgroups_indirect(&args.buffer, args.offset);
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84
85    #[test]
86    fn args_bytes_is_twelve() {
87        assert_eq!(INDIRECT_ARGS_BYTES, 12);
88    }
89
90    // Note: tests that actually construct IndirectArgs require a
91    // real wgpu::Buffer and hence a GPU. The full dispatch path is
92    // exercised from vyre-wgpu integration tests (`tests/indirect_dispatch.rs`).
93}