use std::cell::RefCell;
use super::record_and_readback::binding_lookup::BindingLookup;
use vyre_driver::BackendError;
pub(crate) struct DispatchScratch {
pub(crate) clear_requests: Vec<(u32, u64, u64)>,
pub(crate) bind_group_buffer_ids: Vec<u64>,
pub(crate) bind_group_bound_indices: Vec<usize>,
pub(crate) input_idx_by_binding: BindingLookup,
pub(crate) gpu_idx_by_binding: BindingLookup,
pub(crate) output_idx_by_binding: BindingLookup,
}
impl DispatchScratch {
fn new() -> Self {
Self {
clear_requests: Vec::new(),
bind_group_buffer_ids: Vec::new(),
bind_group_bound_indices: Vec::new(),
input_idx_by_binding: BindingLookup::new(),
gpu_idx_by_binding: BindingLookup::new(),
output_idx_by_binding: BindingLookup::new(),
}
}
fn reset(&mut self) {
self.clear_requests.clear();
self.bind_group_buffer_ids.clear();
self.bind_group_bound_indices.clear();
self.input_idx_by_binding.clear();
self.gpu_idx_by_binding.clear();
self.output_idx_by_binding.clear();
}
}
thread_local! {
static SCRATCH: RefCell<DispatchScratch> = RefCell::new(DispatchScratch::new());
}
pub(crate) fn with_dispatch_scratch<F, R>(f: F) -> Result<R, BackendError>
where
F: FnOnce(&mut DispatchScratch) -> Result<R, BackendError>,
{
SCRATCH.with(|cell| {
let mut scratch = cell.try_borrow_mut().map_err(|_| {
BackendError::new(
"re-entrant wgpu dispatch scratch borrow. Fix: do not dispatch recursively on the same worker thread; submit nested work after the outer dispatch returns.",
)
})?;
scratch.reset();
f(&mut scratch)
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn scratch_retains_capacity_across_dispatches() {
with_dispatch_scratch(|scratch| {
for binding in 0..32u32 {
scratch.clear_requests.push((binding, 0, 4));
scratch.bind_group_buffer_ids.push(u64::from(binding));
scratch.bind_group_bound_indices.push(binding as usize);
}
Ok(())
})
.expect("Fix: dispatch scratch first borrow should succeed");
with_dispatch_scratch(|scratch| {
assert!(scratch.clear_requests.is_empty());
assert!(scratch.bind_group_buffer_ids.is_empty());
assert!(scratch.bind_group_bound_indices.is_empty());
assert!(
scratch.clear_requests.capacity() >= 32,
"Fix: dispatch scratch must retain capacity across calls. \
Got {} clear_requests capacity.",
scratch.clear_requests.capacity()
);
assert!(
scratch.bind_group_buffer_ids.capacity() >= 32,
"Fix: dispatch scratch must retain capacity across calls. \
Got {} bind_group_buffer_ids capacity.",
scratch.bind_group_buffer_ids.capacity()
);
assert!(
scratch.bind_group_bound_indices.capacity() >= 32,
"Fix: dispatch scratch must retain capacity across calls. \
Got {} bind_group_bound_indices capacity.",
scratch.bind_group_bound_indices.capacity()
);
Ok(())
})
.expect("Fix: dispatch scratch second borrow should succeed");
}
#[test]
fn nested_call_returns_structured_error() {
let error = with_dispatch_scratch(|outer| {
outer.clear_requests.push((1, 0, 4));
with_dispatch_scratch(|inner| {
assert!(inner.clear_requests.is_empty());
inner.clear_requests.push((2, 0, 8));
Ok(())
})
})
.expect_err("nested dispatch scratch borrow must return an error");
assert!(
error
.to_string()
.contains("re-entrant wgpu dispatch scratch borrow"),
"unexpected error: {error}"
);
}
}