mirror of
https://github.com/storytold/effectcraft.git
synced 2026-11-11 16:33:01 -06:00
GPU: the keyed Birth Rate test tells birth timing from birth count and checks the GPU ran (#1373)
The two histories now bear the same 180 particles by 0.9 s at different steps (3/s from 0 to 0.5 s, and from 0.25 to 0.75 s). So a checkpoint check by birth count alone fails the test, not only no check. A test-only dispatch counter asserts each GPU state really came from the GPU rather than the CPU fallback.
This commit is contained in:
@@ -121,6 +121,10 @@ fn uniforms(req: &SimRequest, n: u32, s0: u64, prev_n: u32) -> Vec<u8> {
|
||||
out
|
||||
}
|
||||
|
||||
/// Simulations dispatched on the GPU (tests check the GPU path really ran).
|
||||
#[cfg(test)]
|
||||
pub(crate) static DISPATCHES: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
|
||||
|
||||
/// Simulate `req` (see [`effectcraft_effects::psim::ParticleSim`]).
|
||||
///
|
||||
/// With deferred readbacks (a browser worker) the state's readback is keyed by the request
|
||||
@@ -202,6 +206,8 @@ pub(crate) fn simulate(g: &GpuContext, req: &SimRequest) -> Option<Vec<SimPartic
|
||||
}
|
||||
enc.copy_buffer_to_buffer(&next, 0, &read, 0, size);
|
||||
g.queue.submit([enc.finish()]);
|
||||
#[cfg(test)]
|
||||
DISPATCHES.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||
if let (Some(d), Some(k)) = (&deferred, key) {
|
||||
let row = usize::try_from(size).ok()?;
|
||||
let done = d.start(k, n, 1, [0.0; 2], 1.0);
|
||||
|
||||
@@ -122,23 +122,25 @@ fn keyed_state(p: &Params, t: f64, host: &Keyed) -> Vec<SimParticle> {
|
||||
|
||||
/// #1373: with a keyed Birth Rate the GPU is given the births of each moment, as the CPU
|
||||
/// simulates them: a burst lives on after the rate drops to 0, and a GPU checkpoint of one
|
||||
/// history is not resumed for another (the burst's start moved, so the same ids are born at
|
||||
/// other steps; same system key).
|
||||
/// history is not resumed for another with the same system key, even when both have born as
|
||||
/// many particles (180 by 0.9 s here) at other steps.
|
||||
#[test]
|
||||
fn particle_world_with_a_keyed_birth_rate_matches_the_cpu() {
|
||||
let Some(g) = gpu() else { return };
|
||||
fn long(t: f64) -> f64 {
|
||||
if t < 0.5 { 4.0 } else { 0.0 }
|
||||
fn early(t: f64) -> f64 {
|
||||
if t < 0.5 { 3.0 } else { 0.0 }
|
||||
}
|
||||
fn late(t: f64) -> f64 {
|
||||
if (0.2..0.5).contains(&t) { 4.0 } else { 0.0 }
|
||||
if (0.25..0.75).contains(&t) { 3.0 } else { 0.0 }
|
||||
}
|
||||
let p = params("ec.sim.ccparticleworld", &[("birthRate", n(0.0)), ("longevity", n(1.2)), ("extras/randomSeed", n(21.0))]);
|
||||
for (rate, times) in [(long as fn(f64) -> f64, [0.4, 0.9, 1.2]), (late, [0.9, 1.2, 0.6])] {
|
||||
for (rate, times) in [(early as fn(f64) -> f64, [0.4, 0.9, 1.2]), (late, [0.9, 1.2, 0.6])] {
|
||||
let (cpu_host, gpu_host) = (Keyed { gpu: None, rate }, Keyed { gpu: Some(g), rate });
|
||||
for t in times {
|
||||
let cpu = keyed_state(&p, t, &cpu_host);
|
||||
let before = crate::particles::DISPATCHES.load(std::sync::atomic::Ordering::Relaxed);
|
||||
let gpu = keyed_state(&p, t, &gpu_host);
|
||||
assert!(crate::particles::DISPATCHES.load(std::sync::atomic::Ordering::Relaxed) > before, "t {t}: simulated on the GPU");
|
||||
agree(&format!("keyed birth rate t {t}"), &cpu, &gpu, 1e-4, cpu.len() / 100 + 1);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user