use cubecl_common::bytes::Bytes;
use cubecl_core as cubecl;
use cubecl_core::prelude::*;
use cubecl_core::runtime_tests::capture_status;
use cubecl_core::server::Handle;
use cubecl_environment::stream::StreamId;
use cubecl_server::config::{CubeClRuntimeConfig, RuntimeConfig};
use cubecl_server::runtime::Runtime;
use cubecl_wgpu::WgpuRuntime;
use std::sync::Mutex;
static CAPTURE_LOCK: Mutex<()> = Mutex::new(());
fn sharing_one_pooled_stream(seed: u64) -> (StreamId, StreamId) {
let max_streams = CubeClRuntimeConfig::get().streaming.max_streams as u64;
(
StreamId { value: seed },
StreamId {
value: seed + max_streams,
},
)
}
#[cube(launch)]
fn add_one(input: &[f32], output: &mut [f32]) {
if ABSOLUTE_POS < output.len() {
output[ABSOLUTE_POS] = input[ABSOLUTE_POS] + 1.0;
}
}
#[cube(launch)]
fn mul_two(input: &[f32], output: &mut [f32]) {
if ABSOLUTE_POS < output.len() {
output[ABSOLUTE_POS] = input[ABSOLUTE_POS] * 2.0;
}
}
#[test]
fn wgpu_graph_capture_replay() {
let _guard = CAPTURE_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let client = <WgpuRuntime>::client(&Default::default());
let n = 4usize;
let input = client.create_from_slice(f32::as_bytes(&[1.0, 2.0, 3.0, 4.0]));
let output = client.empty(n * core::mem::size_of::<f32>());
let launch = |client: &Client| {
add_one::launch(
client,
CubeCount::Static(1, 1, 1),
CubeDim::new(client, n),
unsafe { BufferArg::from_raw_parts(input.clone(), n) },
unsafe { BufferArg::from_raw_parts(output.clone(), n) },
);
};
client.graph_prepare().expect("graph_prepare");
launch(&client);
let _ = client.read_one(output.clone()).unwrap();
client.start_capture().expect("start_capture");
launch(&client);
let graph = client.stop_capture().expect("stop_capture");
unsafe { graph.replay() }.expect("replay enqueues");
let out = client.read_one(output.clone()).unwrap();
assert_eq!(f32::from_bytes(&out), &[2.0, 3.0, 4.0, 5.0]);
unsafe { graph.replay() }.expect("replay enqueues");
let out = client.read_one(output).unwrap();
assert_eq!(f32::from_bytes(&out), &[2.0, 3.0, 4.0, 5.0]);
}
#[test]
fn wgpu_graph_mid_capture_allocation_is_rejected() {
let _guard = CAPTURE_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let client = <WgpuRuntime>::client(&Default::default());
let n = 4usize;
let input = client.create_from_slice(f32::as_bytes(&[1.0, 2.0, 3.0, 4.0]));
let output = client.empty(n * core::mem::size_of::<f32>());
let launch = |client: &Client| {
add_one::launch(
client,
CubeCount::Static(1, 1, 1),
CubeDim::new(client, n),
unsafe { BufferArg::from_raw_parts(input.clone(), n) },
unsafe { BufferArg::from_raw_parts(output.clone(), n) },
);
};
client.graph_prepare().expect("graph_prepare");
launch(&client);
let _ = client.read_one(output.clone()).unwrap();
client.start_capture().expect("start_capture");
launch(&client);
const UNPOOLED_BYTES: usize = 3_145_733;
let grown = client.empty(UNPOOLED_BYTES);
assert!(
client.stop_capture().is_err(),
"a capture that needed a fresh allocation must not seal"
);
drop(grown);
}
#[test]
fn wgpu_graph_input_rewrite() {
let _guard = CAPTURE_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let client = <WgpuRuntime>::client(&Default::default());
let n = 4usize;
let input = client.create_from_slice(f32::as_bytes(&[1.0, 2.0, 3.0, 4.0]));
let output = client.empty(n * core::mem::size_of::<f32>());
let launch = |client: &Client| {
add_one::launch(
client,
CubeCount::Static(1, 1, 1),
CubeDim::new(client, n),
unsafe { BufferArg::from_raw_parts(input.clone(), n) },
unsafe { BufferArg::from_raw_parts(output.clone(), n) },
);
};
client.graph_prepare().expect("graph_prepare");
launch(&client);
let _ = client.read_one(output.clone()).unwrap();
client.start_capture().expect("start_capture");
launch(&client);
let graph = client.stop_capture().expect("stop_capture");
unsafe { graph.replay() }.expect("replay enqueues");
let out = client.read_one(output.clone()).unwrap();
assert_eq!(f32::from_bytes(&out), &[2.0, 3.0, 4.0, 5.0]);
client.write(
&input,
Bytes::from_bytes_vec(f32::as_bytes(&[10.0, 20.0, 30.0, 40.0]).to_vec()),
);
unsafe { graph.replay() }.expect("replay enqueues");
let out = client.read_one(output).unwrap();
assert_eq!(f32::from_bytes(&out), &[11.0, 21.0, 31.0, 41.0]);
}
#[test]
fn wgpu_graph_intermediate_recycling() {
let _guard = CAPTURE_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let client = <WgpuRuntime>::client(&Default::default());
let n = 4usize;
let bytes = n * core::mem::size_of::<f32>();
let input = client.create_from_slice(f32::as_bytes(&[1.0, 2.0, 3.0, 4.0]));
let output = client.empty(bytes);
let run = |client: &Client, tmp: &Handle| {
add_one::launch(
client,
CubeCount::Static(1, 1, 1),
CubeDim::new(client, n),
unsafe { BufferArg::from_raw_parts(input.clone(), n) },
unsafe { BufferArg::from_raw_parts(tmp.clone(), n) },
);
mul_two::launch(
client,
CubeCount::Static(1, 1, 1),
CubeDim::new(client, n),
unsafe { BufferArg::from_raw_parts(tmp.clone(), n) },
unsafe { BufferArg::from_raw_parts(output.clone(), n) },
);
};
client.graph_prepare().expect("graph_prepare");
{
let tmp = client.empty(bytes);
run(&client, &tmp);
let _ = client.read_one(output.clone()).unwrap();
}
client.start_capture().expect("start_capture");
let tmp = client.empty(bytes);
run(&client, &tmp);
let graph = client.stop_capture().expect("stop_capture");
drop(tmp);
let sentinels: Vec<Handle> = (0..8)
.map(|_| client.create_from_slice(f32::as_bytes(&[999.0; 4])))
.collect();
unsafe { graph.replay() }.expect("replay enqueues");
let out_bytes = client.read_one(output).unwrap();
let out = f32::from_bytes(&out_bytes);
assert_eq!(
out,
&[4.0, 6.0, 8.0, 10.0],
"the graph's own output is corrupted, before the retention question"
);
let clobbered = sentinels.iter().any(|h| {
let bytes = client.read_one(h.clone()).unwrap();
f32::from_bytes(&bytes) == [2.0, 3.0, 4.0, 5.0]
});
assert!(
!clobbered,
"replay wrote into a live external buffer that reused the graph's \
intermediate slice — buffer retention failed to pin it"
);
}
#[cube(launch)]
fn add_one_tensor(input: &Tensor<f32>, output: &mut Tensor<f32>) {
if ABSOLUTE_POS < input.shape(0) {
output[ABSOLUTE_POS] = input[ABSOLUTE_POS] + 1.0;
}
}
#[test]
fn wgpu_graph_many_launches_dynamic_metadata() {
const N: usize = 64; const PASS_LAUNCHES: usize = 150;
fn run_pass(client: &Client, a: &Handle, b: &Handle) {
for i in 0..PASS_LAUNCHES {
let (src, dst) = if i % 2 == 0 { (a, b) } else { (b, a) };
add_one_tensor::launch(
client,
CubeCount::Static(1, 1, 1),
CubeDim::new_1d(N as u32),
unsafe { TensorArg::from_raw_parts(src.clone(), [1].into(), [N].into()) },
unsafe { TensorArg::from_raw_parts(dst.clone(), [1].into(), [N].into()) },
);
}
}
fn simulate(start: f32, passes: usize) -> (Vec<f32>, Vec<f32>) {
let (mut a, mut b) = (vec![start; N], vec![start; N]);
for _ in 0..passes {
for i in 0..PASS_LAUNCHES {
if i % 2 == 0 {
for j in 0..N {
b[j] = a[j] + 1.0;
}
} else {
for j in 0..N {
a[j] = b[j] + 1.0;
}
}
}
}
(a, b)
}
let _guard = CAPTURE_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let client = <WgpuRuntime>::client(&Default::default());
let a = client.create_from_slice(f32::as_bytes(&vec![0.0f32; N]));
let b = client.create_from_slice(f32::as_bytes(&vec![0.0f32; N]));
client.graph_prepare().expect("graph_prepare");
run_pass(&client, &a, &b);
let (exp_a, exp_b) = simulate(0.0, 1);
assert_eq!(
f32::from_bytes(&client.read_one(a.clone()).unwrap()),
&exp_a[..]
);
assert_eq!(
f32::from_bytes(&client.read_one(b.clone()).unwrap()),
&exp_b[..]
);
client.start_capture().expect("start_capture");
run_pass(&client, &a, &b);
let graph = client.stop_capture().expect("stop_capture");
unsafe { graph.replay() }.expect("replay enqueues");
unsafe { graph.replay() }.expect("replay enqueues");
let (exp_a, exp_b) = simulate(0.0, 3);
assert_eq!(
f32::from_bytes(&client.read_one(a.clone()).unwrap()),
&exp_a[..]
);
assert_eq!(
f32::from_bytes(&client.read_one(b.clone()).unwrap()),
&exp_b[..]
);
let fresh = f32::as_bytes(&[100.0f32; N]).to_vec();
client.write(&a, Bytes::from_bytes_vec(fresh.clone()));
client.write(&b, Bytes::from_bytes_vec(fresh));
unsafe { graph.replay() }.expect("replay enqueues");
let (exp_a, exp_b) = simulate(100.0, 1);
assert_eq!(
f32::from_bytes(&client.read_one(a.clone()).unwrap()),
&exp_a[..]
);
assert_eq!(
f32::from_bytes(&client.read_one(b.clone()).unwrap()),
&exp_b[..]
);
}
#[test]
fn wgpu_graph_lifecycle_state_errors() {
let _guard = CAPTURE_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let client = <WgpuRuntime>::client(&Default::default());
assert!(
client.start_capture().is_err(),
"start_capture without graph_prepare must be rejected"
);
assert!(
client.stop_capture().is_err(),
"stop_capture without a recording capture must be rejected"
);
client.graph_prepare().expect("graph_prepare");
assert!(
client.graph_prepare().is_err(),
"a second graph_prepare on a prepared stream must be rejected"
);
let n = 4usize;
let input = client.create_from_slice(f32::as_bytes(&[1.0, 2.0, 3.0, 4.0]));
let output = client.empty(n * core::mem::size_of::<f32>());
let launch = |client: &Client| {
add_one::launch(
client,
CubeCount::Static(1, 1, 1),
CubeDim::new(client, n),
unsafe { BufferArg::from_raw_parts(input.clone(), n) },
unsafe { BufferArg::from_raw_parts(output.clone(), n) },
);
};
launch(&client);
let _ = client.read_one(output.clone()).unwrap();
client.start_capture().expect("start_capture");
launch(&client);
let graph = client.stop_capture().expect("stop_capture");
unsafe { graph.replay() }.expect("replay enqueues");
let out = client.read_one(output).unwrap();
assert_eq!(f32::from_bytes(&out), &[2.0, 3.0, 4.0, 5.0]);
}
#[test]
fn wgpu_graph_read_rejected_while_recording() {
let _guard = CAPTURE_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let client = <WgpuRuntime>::client(&Default::default());
let n = 4usize;
let input = client.create_from_slice(f32::as_bytes(&[1.0, 2.0, 3.0, 4.0]));
let output = client.empty(n * core::mem::size_of::<f32>());
let launch = |client: &Client| {
add_one::launch(
client,
CubeCount::Static(1, 1, 1),
CubeDim::new(client, n),
unsafe { BufferArg::from_raw_parts(input.clone(), n) },
unsafe { BufferArg::from_raw_parts(output.clone(), n) },
);
};
client.graph_prepare().expect("graph_prepare");
launch(&client);
let _ = client.read_one(output.clone()).unwrap();
client.start_capture().expect("start_capture");
launch(&client);
assert!(
client.read_one(output.clone()).is_err(),
"a read inside a capture window must be rejected"
);
let graph = client.stop_capture().expect("stop_capture");
unsafe { graph.replay() }.expect("replay enqueues");
let out = client.read_one(output).unwrap();
assert_eq!(f32::from_bytes(&out), &[2.0, 3.0, 4.0, 5.0]);
}
#[test]
fn wgpu_graph_write_rejected_while_recording() {
let _guard = CAPTURE_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let client = <WgpuRuntime>::client(&Default::default());
let n = 4usize;
let input = client.create_from_slice(f32::as_bytes(&[1.0, 2.0, 3.0, 4.0]));
let output = client.empty(n * core::mem::size_of::<f32>());
let launch = |client: &Client| {
add_one::launch(
client,
CubeCount::Static(1, 1, 1),
CubeDim::new(client, n),
unsafe { BufferArg::from_raw_parts(input.clone(), n) },
unsafe { BufferArg::from_raw_parts(output.clone(), n) },
);
};
client.graph_prepare().expect("graph_prepare");
launch(&client);
let _ = client.read_one(output.clone()).unwrap();
client.start_capture().expect("start_capture");
launch(&client);
client.write(
&input,
Bytes::from_bytes_vec(f32::as_bytes(&[9.0, 9.0, 9.0, 9.0]).to_vec()),
);
assert!(
client.stop_capture().is_err(),
"a capture window containing a write must be rejected: the recording \
is missing that operation"
);
client.write(
&input,
Bytes::from_bytes_vec(f32::as_bytes(&[1.0, 2.0, 3.0, 4.0]).to_vec()),
);
launch(&client);
let out = client.read_one(output).unwrap();
assert_eq!(f32::from_bytes(&out), &[2.0, 3.0, 4.0, 5.0]);
}
#[test]
fn wgpu_graph_write_rejected_while_recording_is_not_read_as_written() {
let _guard = CAPTURE_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let client = <WgpuRuntime>::client(&Default::default());
let capturing = StreamId { value: 3_000_001 };
let reader = StreamId { value: 3_000_002 };
let n = 4usize;
let target = reader.executes(|| client.create_from_slice(f32::as_bytes(&[1.0, 2.0, 3.0, 4.0])));
let (input, output) = capturing.executes(|| {
(
client.create_from_slice(f32::as_bytes(&[1.0, 2.0, 3.0, 4.0])),
client.empty(n * core::mem::size_of::<f32>()),
)
});
let launch = |client: &Client| {
add_one::launch(
client,
CubeCount::Static(1, 1, 1),
CubeDim::new(client, n),
unsafe { BufferArg::from_raw_parts(input.clone(), n) },
unsafe { BufferArg::from_raw_parts(output.clone(), n) },
);
};
capturing.executes(|| {
client.graph_prepare().expect("graph_prepare");
launch(&client);
let _ = client.read_one(output.clone()).unwrap();
client.start_capture().expect("start_capture");
launch(&client);
client.write(
&target,
Bytes::from_bytes_vec(f32::as_bytes(&[9.0, 9.0, 9.0, 9.0]).to_vec()),
);
});
assert!(
reader.executes(|| client.read_one(target.clone())).is_err(),
"reading a buffer whose write was rejected must fail, not hand back \
the bytes that were there before it"
);
assert!(
capturing.executes(|| client.stop_capture()).is_err(),
"a capture window containing a write must be rejected"
);
let out = capturing.executes(|| {
launch(&client);
client.read_one(output.clone()).unwrap()
});
assert_eq!(f32::from_bytes(&out), &[2.0, 3.0, 4.0, 5.0]);
}
#[test]
fn wgpu_graph_destroy_leaves_an_enqueued_replay_intact() {
let _guard = CAPTURE_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let client = <WgpuRuntime>::client(&Default::default());
let n = 4usize;
let bytes = n * core::mem::size_of::<f32>();
let input = client.create_from_slice(f32::as_bytes(&[1.0, 2.0, 3.0, 4.0]));
let output = client.empty(bytes);
let run = |client: &Client, tmp: &Handle| {
add_one::launch(
client,
CubeCount::Static(1, 1, 1),
CubeDim::new(client, n),
unsafe { BufferArg::from_raw_parts(input.clone(), n) },
unsafe { BufferArg::from_raw_parts(tmp.clone(), n) },
);
mul_two::launch(
client,
CubeCount::Static(1, 1, 1),
CubeDim::new(client, n),
unsafe { BufferArg::from_raw_parts(tmp.clone(), n) },
unsafe { BufferArg::from_raw_parts(output.clone(), n) },
);
};
client.graph_prepare().expect("graph_prepare");
{
let tmp = client.empty(bytes);
run(&client, &tmp);
let _ = client.read_one(output.clone()).unwrap();
}
client.start_capture().expect("start_capture");
let tmp = client.empty(bytes);
run(&client, &tmp);
let graph = client.stop_capture().expect("stop_capture");
drop(tmp);
unsafe { graph.replay() }.expect("replay enqueues");
drop(graph);
let _sentinels: Vec<Handle> = (0..8)
.map(|_| client.create_from_slice(f32::as_bytes(&[999.0; 4])))
.collect();
let out_bytes = client.read_one(output).unwrap();
assert_eq!(
f32::from_bytes(&out_bytes),
&[4.0, 6.0, 8.0, 10.0],
"the enqueued replay read sentinel bytes: destroying the graph let a \
later write reach its memory first"
);
}
#[test]
fn wgpu_graph_a_capture_that_never_sealed_is_not_read_as_run() {
let _guard = CAPTURE_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let client = <WgpuRuntime>::client(&Default::default());
let capturing = StreamId { value: 4_000_001 };
let reader = StreamId { value: 4_000_002 };
let n = 4usize;
let (input, output, target) = capturing.executes(|| {
(
client.create_from_slice(f32::as_bytes(&[1.0, 2.0, 3.0, 4.0])),
client.empty(n * core::mem::size_of::<f32>()),
client.create_from_slice(f32::as_bytes(&[1.0, 2.0, 3.0, 4.0])),
)
});
let launch = |client: &Client| {
add_one::launch(
client,
CubeCount::Static(1, 1, 1),
CubeDim::new(client, n),
unsafe { BufferArg::from_raw_parts(input.clone(), n) },
unsafe { BufferArg::from_raw_parts(output.clone(), n) },
);
};
capturing.executes(|| {
client.graph_prepare().expect("graph_prepare");
launch(&client);
let _ = client.read_one(output.clone()).unwrap();
client.start_capture().expect("start_capture");
launch(&client);
client.write(
&target,
Bytes::from_bytes_vec(f32::as_bytes(&[9.0, 9.0, 9.0, 9.0]).to_vec()),
);
});
assert!(
capturing.executes(|| client.stop_capture()).is_err(),
"a capture window containing a write must be rejected"
);
assert!(
reader.executes(|| client.read_one(output.clone())).is_err(),
"the recorded launch never ran, so reading its output must fail rather \
than hand back what the warmup left there"
);
let out = capturing.executes(|| {
launch(&client);
client.read_one(output.clone()).unwrap()
});
assert_eq!(f32::from_bytes(&out), &[2.0, 3.0, 4.0, 5.0]);
let out = reader.executes(|| client.read_one(output.clone()).unwrap());
assert_eq!(f32::from_bytes(&out), &[2.0, 3.0, 4.0, 5.0]);
}
#[test]
fn wgpu_graph_capture_is_sealed_by_the_stream_that_opened_it() {
let _guard = CAPTURE_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let client = <WgpuRuntime>::client(&Default::default());
let (owner, neighbour) = sharing_one_pooled_stream(2_000_001);
let n = 4usize;
let (input, output) = owner.executes(|| {
(
client.create_from_slice(f32::as_bytes(&[1.0, 2.0, 3.0, 4.0])),
client.empty(n * core::mem::size_of::<f32>()),
)
});
let launch = |client: &Client| {
add_one::launch(
client,
CubeCount::Static(1, 1, 1),
CubeDim::new(client, n),
unsafe { BufferArg::from_raw_parts(input.clone(), n) },
unsafe { BufferArg::from_raw_parts(output.clone(), n) },
);
};
owner.executes(|| {
client.graph_prepare().expect("graph_prepare");
launch(&client);
let _ = client.read_one(output.clone()).unwrap();
client.start_capture().expect("start_capture");
launch(&client);
});
let refused = neighbour
.executes(|| client.stop_capture())
.expect_err("a stream that did not open the window must not seal it into a graph")
.to_string();
assert!(
refused.contains("the capture belongs to logical stream"),
"the neighbour has to reach the owner's window to be refused it, got: {refused}"
);
assert!(
owner.executes(|| client.stop_capture()).is_err(),
"the discarded window must not still be recording"
);
let out = owner.executes(|| {
launch(&client);
client.read_one(output.clone()).unwrap()
});
assert_eq!(f32::from_bytes(&out), &[2.0, 3.0, 4.0, 5.0]);
}
#[test]
fn wgpu_graph_capture_is_isolated_from_another_stream() {
let _guard = CAPTURE_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let client = <WgpuRuntime>::client(&Default::default());
let n = 4usize;
let input = client.create_from_slice(f32::as_bytes(&[1.0, 2.0, 3.0, 4.0]));
let output = client.empty(n * core::mem::size_of::<f32>());
let launch = |client: &Client| {
add_one::launch(
client,
CubeCount::Static(1, 1, 1),
CubeDim::new(client, n),
unsafe { BufferArg::from_raw_parts(input.clone(), n) },
unsafe { BufferArg::from_raw_parts(output.clone(), n) },
);
};
client.graph_prepare().expect("graph_prepare");
launch(&client);
let _ = client.read_one(output.clone()).unwrap();
client.start_capture().expect("start_capture");
launch(&client);
let other = {
let other_client = client.clone();
std::thread::spawn(move || {
let a = other_client.create_from_slice(f32::as_bytes(&[7.0, 7.0, 7.0, 7.0]));
let b = other_client.empty(n * core::mem::size_of::<f32>());
mul_two::launch(
&other_client,
CubeCount::Static(1, 1, 1),
CubeDim::new(&other_client, n),
unsafe { BufferArg::from_raw_parts(a.clone(), n) },
unsafe { BufferArg::from_raw_parts(b.clone(), n) },
);
let bytes = other_client.read_one(b).unwrap();
f32::from_bytes(&bytes).to_vec()
})
};
let other = other.join().expect("the other stream's work");
assert_eq!(
other,
&[14.0, 14.0, 14.0, 14.0],
"the other stream's result"
);
let graph = client.stop_capture().expect("stop_capture");
unsafe { graph.replay() }.expect("replay enqueues");
let out = client.read_one(output).unwrap();
assert_eq!(f32::from_bytes(&out), &[2.0, 3.0, 4.0, 5.0]);
}
#[cube(launch_unchecked)]
fn poisoned(out: &mut [f32], #[comptime] reason: String) {
push_validation_error(reason);
out[0] = 1f32;
}
#[test]
fn wgpu_graph_capture_refuses_a_tainted_input() {
let _guard = CAPTURE_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let client = <WgpuRuntime>::client(&Default::default());
let n = 4usize;
let input = client.create_from_slice(f32::as_bytes(&[1.0, 2.0, 3.0, 4.0]));
let output = client.empty(n * core::mem::size_of::<f32>());
unsafe {
poisoned::launch_unchecked(
&client,
CubeCount::Static(1, 1, 1),
CubeDim::new(&client, n),
BufferArg::from_raw_parts(input.clone(), n),
"doomed-window".to_string(),
)
};
let _ = client.flush();
client.graph_prepare().expect("graph_prepare");
client.start_capture().expect("start_capture");
add_one::launch(
&client,
CubeCount::Static(1, 1, 1),
CubeDim::new(&client, n),
unsafe { BufferArg::from_raw_parts(input.clone(), n) },
unsafe { BufferArg::from_raw_parts(output.clone(), n) },
);
let err = client
.stop_capture()
.expect_err("the recording is missing an operation and must not seal")
.to_string();
assert!(
err.contains("missing an operation"),
"the refusal names the hole in the recording, got: {err}"
);
}
#[test]
fn wgpu_graph_a_launch_that_fails_in_the_window_dooms_the_capture() {
let _guard = CAPTURE_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let client = <WgpuRuntime>::client(&Default::default());
let n = 4usize;
let output = client.empty(n * core::mem::size_of::<f32>());
client.graph_prepare().expect("graph_prepare");
client.start_capture().expect("start_capture");
unsafe {
poisoned::launch_unchecked(
&client,
CubeCount::Static(1, 1, 1),
CubeDim::new(&client, n),
BufferArg::from_raw_parts(output.clone(), n),
"failed-in-window".to_string(),
)
};
let err = client
.stop_capture()
.expect_err("the recording is missing an operation and must not seal")
.to_string();
assert!(
err.contains("missing an operation"),
"the refusal names the hole in the recording, got: {err}"
);
client
.read_one(output)
.expect_err("nothing wrote the output");
}
#[test]
fn wgpu_graph_replay_settles_after_a_failed_enqueue() {
let _guard = CAPTURE_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let client = <WgpuRuntime>::client(&Default::default());
let n = 4usize;
let input = client.create_from_slice(f32::as_bytes(&[1.0, 2.0, 3.0, 4.0]));
let output = client.empty(n * core::mem::size_of::<f32>());
let launch = |client: &Client| {
add_one::launch(
client,
CubeCount::Static(1, 1, 1),
CubeDim::new(client, n),
unsafe { BufferArg::from_raw_parts(input.clone(), n) },
unsafe { BufferArg::from_raw_parts(output.clone(), n) },
);
};
client.graph_prepare().expect("graph_prepare");
launch(&client);
let _ = client.read_one(output.clone()).unwrap();
client.start_capture().expect("start_capture");
launch(&client);
let graph = client.stop_capture().expect("stop_capture");
client.graph_prepare().expect("graph_prepare");
client.start_capture().expect("start_capture");
unsafe { graph.replay() }.expect_err("a replay cannot be recorded into a capture");
let _ = client.stop_capture();
client
.read_one(output.clone())
.expect_err("the failed replay never wrote the graph's output");
unsafe { graph.replay() }.expect("replay enqueues");
let out = client
.read_one(output)
.expect("a replay that lands settles the write set");
assert_eq!(f32::from_bytes(&out), &[2.0, 3.0, 4.0, 5.0]);
}
#[test]
fn wgpu_graph_capture_is_seen_by_every_client_of_its_stream() {
let _guard = CAPTURE_LOCK.lock().unwrap_or_else(|e| e.into_inner());
capture_status::a_capture_is_seen_by_every_client_of_its_stream::<WgpuRuntime>();
}
#[test]
fn wgpu_graph_prepared_capture_ends_at_stop_capture() {
let _guard = CAPTURE_LOCK.lock().unwrap_or_else(|e| e.into_inner());
capture_status::a_prepared_capture_ends_at_stop_capture::<WgpuRuntime>();
}