Visitar URL original
. · processing/libprocessing@695b87a · GitHub
Skip to content

Commit 695b87a

Browse files
committed
.
1 parent f6a9c4b commit 695b87a

12 files changed

Lines changed: 1251 additions & 0 deletions

‎Cargo.toml‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,10 +212,54 @@ path = "examples/camera_controllers.rs"
212212
name = "compute_readback"
213213
path = "examples/compute_readback.rs"
214214

215+
[[example]]
216+
name = "scan_test"
217+
path = "examples/scan_test.rs"
218+
219+
[[example]]
220+
name = "grid_test"
221+
path = "examples/grid_test.rs"
222+
223+
[[example]]
224+
name = "flock_grid_test"
225+
path = "examples/flock_grid_test.rs"
226+
215227
[[example]]
216228
name = "alias_spike"
217229
path = "examples/alias_spike.rs"
218230

231+
[[example]]
232+
name = "map_test"
233+
path = "examples/map_test.rs"
234+
235+
[[example]]
236+
name = "combine_mix_test"
237+
path = "examples/combine_mix_test.rs"
238+
239+
[[example]]
240+
name = "lookup_test"
241+
path = "examples/lookup_test.rs"
242+
243+
[[example]]
244+
name = "glue_test"
245+
path = "examples/glue_test.rs"
246+
247+
[[example]]
248+
name = "sort_test"
249+
path = "examples/sort_test.rs"
250+
251+
[[example]]
252+
name = "field_falloff_test"
253+
path = "examples/field_falloff_test.rs"
254+
255+
[[example]]
256+
name = "group_compact_test"
257+
path = "examples/group_compact_test.rs"
258+
259+
[[example]]
260+
name = "reduce_test"
261+
path = "examples/reduce_test.rs"
262+
219263
[[example]]
220264
name = "particles_basic"
221265
path = "examples/particles_basic.rs"

‎examples/combine_mix_test.rs‎

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
//! Validation harness for the `combine` and `mix` algebra verbs (task #5),
2+
//! including the alias guard (a `dst` that aliases a read-only operand must be
3+
//! rejected in Rust, NOT reach wgpu — where it would be a fatal validation
4+
//! error, see `examples/alias_spike.rs`).
5+
//!
6+
//! Run: `cargo run --example combine_mix_test`. Exits non-zero on mismatch.
7+
8+
use processing::prelude::*;
9+
10+
fn f32s(bytes: &[u8]) -> Vec<f32> {
11+
bytes
12+
.chunks_exact(4)
13+
.map(|c| f32::from_le_bytes([c[0], c[1], c[2], c[3]]))
14+
.collect()
15+
}
16+
fn to_bytes(v: &[f32]) -> Vec<u8> {
17+
v.iter().flat_map(|f| f.to_le_bytes()).collect()
18+
}
19+
fn approx(a: &[f32], b: &[f32]) -> bool {
20+
a.len() == b.len() && a.iter().zip(b).all(|(x, y)| (x - y).abs() < 1e-5)
21+
}
22+
23+
fn sketch() -> error::Result<bool> {
24+
init(Config::default())?;
25+
let surface = surface_create_offscreen(1, 1, 1.0, TextureFormat::Rgba8Unorm)?;
26+
let _graphics = graphics_create(surface, 1, 1, TextureFormat::Rgba8Unorm)?;
27+
28+
let mut ok = true;
29+
30+
// --- combine in-place: a += b (Float3, 4 particles) ---
31+
let av: Vec<f32> = (0..12).map(|i| i as f32).collect();
32+
let bv: Vec<f32> = (0..12).map(|i| (i * 10) as f32).collect();
33+
let a = buffer_create_with_data(to_bytes(&av))?;
34+
let b = buffer_create_with_data(to_bytes(&bv))?;
35+
combine(a, a, b, 3, COMBINE_ADD, 1.0, 0.0)?;
36+
let got = f32s(&buffer_read(a)?);
37+
let want: Vec<f32> = av.iter().zip(&bv).map(|(x, y)| x + y).collect();
38+
if approx(&got, &want) {
39+
println!(" PASS combine in-place a+=b");
40+
} else {
41+
ok = false;
42+
println!(" FAIL combine in-place: got {got:?}, want {want:?}");
43+
}
44+
45+
// --- combine out-of-place: dst = a * b, inputs preserved ---
46+
let dst = buffer_create_with_data(to_bytes(&vec![0.0; 12]))?;
47+
combine(dst, a, b, 3, COMBINE_MUL, 1.0, 0.0)?;
48+
let got_dst = f32s(&buffer_read(dst)?);
49+
let cur_a = f32s(&buffer_read(a)?);
50+
let want_dst: Vec<f32> = want.iter().zip(&bv).map(|(x, y)| x * y).collect();
51+
if approx(&got_dst, &want_dst) && approx(&cur_a, &want) {
52+
println!(" PASS combine out-of-place dst=a*b (inputs preserved)");
53+
} else {
54+
ok = false;
55+
println!(" FAIL combine out-of-place: dst={got_dst:?} want {want_dst:?}");
56+
}
57+
58+
// --- alias guard: combine(dst=b, a, b) must be rejected in Rust ---
59+
match combine(b, a, b, 3, COMBINE_ADD, 1.0, 0.0) {
60+
Err(_) => println!(" PASS combine alias guard (dst==b rejected before dispatch)"),
61+
Ok(()) => {
62+
ok = false;
63+
println!(" FAIL combine alias guard: expected Err, got Ok");
64+
}
65+
}
66+
67+
buffer_destroy(a)?;
68+
buffer_destroy(b)?;
69+
buffer_destroy(dst)?;
70+
71+
// --- mix out-of-place: dst = lerp(a, b, t), t per-particle broadcast ---
72+
// 3 particles x 4 components (color-like); t = [0, 0.5, 1].
73+
let comps = 4u32;
74+
let a2: Vec<f32> = vec![1.0; 12]; // all-ones
75+
let b2: Vec<f32> = vec![3.0; 12]; // all-threes
76+
let tv = vec![0.0f32, 0.5, 1.0];
77+
let am = buffer_create_with_data(to_bytes(&a2))?;
78+
let bm = buffer_create_with_data(to_bytes(&b2))?;
79+
let tm = buffer_create_with_data(to_bytes(&tv))?;
80+
let dm = buffer_create_with_data(to_bytes(&vec![0.0; 12]))?;
81+
mix(dm, am, bm, tm, comps, 1.0, 0.0, true)?;
82+
let got_mix = f32s(&buffer_read(dm)?);
83+
// particle 0 -> a(1.0), particle 1 -> midpoint(2.0), particle 2 -> b(3.0)
84+
let mut want_mix = vec![1.0f32; 4];
85+
want_mix.extend([2.0f32; 4]);
86+
want_mix.extend([3.0f32; 4]);
87+
if approx(&got_mix, &want_mix) {
88+
println!(" PASS mix out-of-place (per-particle t broadcast across components)");
89+
} else {
90+
ok = false;
91+
println!(" FAIL mix out-of-place: got {got_mix:?}, want {want_mix:?}");
92+
}
93+
94+
// --- mix in-place: a = lerp(a, b, t) ---
95+
mix(am, am, bm, tm, comps, 1.0, 0.0, true)?;
96+
let got_mix_ip = f32s(&buffer_read(am)?);
97+
if approx(&got_mix_ip, &want_mix) {
98+
println!(" PASS mix in-place");
99+
} else {
100+
ok = false;
101+
println!(" FAIL mix in-place: got {got_mix_ip:?}, want {want_mix:?}");
102+
}
103+
104+
buffer_destroy(am)?;
105+
buffer_destroy(bm)?;
106+
buffer_destroy(tm)?;
107+
buffer_destroy(dm)?;
108+
109+
Ok(ok)
110+
}
111+
112+
fn main() {
113+
let ok = sketch().unwrap();
114+
if ok {
115+
println!("combine_mix_test: ALL PASS");
116+
exit(0).unwrap();
117+
} else {
118+
println!("combine_mix_test: FAILURES");
119+
exit(1).unwrap();
120+
}
121+
}

‎examples/field_falloff_test.rs‎

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
//! Validates the shared `falloff` WESL helper (task #10) end-to-end: dispatches
2+
//! the `field` kernel (which now `import`s `processing::particles::falloff`) over
3+
//! particles at known distances and checks the weights against CPU falloff.
4+
//!
5+
//! Run: `cargo run --example field_falloff_test`. Exits non-zero on mismatch.
6+
7+
use bevy::prelude::Entity;
8+
use processing::prelude::*;
9+
10+
const RADIUS: f32 = 2.0;
11+
12+
fn f32s(b: &[u8]) -> Vec<f32> {
13+
b.chunks_exact(4)
14+
.map(|c| f32::from_le_bytes([c[0], c[1], c[2], c[3]]))
15+
.collect()
16+
}
17+
fn to_bytes(v: &[f32]) -> Vec<u8> {
18+
v.iter().flat_map(|f| f.to_le_bytes()).collect()
19+
}
20+
fn approx(a: &[f32], b: &[f32]) -> bool {
21+
a.len() == b.len() && a.iter().zip(b).all(|(x, y)| (x - y).abs() < 1e-5)
22+
}
23+
24+
// Particles along +x at distances 0, 0.5, 1.0, 1.5 from the origin.
25+
const DISTS: [f32; 4] = [0.0, 0.5, 1.0, 1.5];
26+
27+
fn cpu_falloff(d: f32, mode: u32) -> f32 {
28+
let n = 1.0 - d / RADIUS;
29+
match mode {
30+
1 => n,
31+
2 => n * n * (3.0 - 2.0 * n),
32+
3 => n * n,
33+
4 => n * n * n,
34+
5 => RADIUS / (d + RADIUS),
35+
_ => 1.0,
36+
}
37+
}
38+
39+
fn run_mode(field: Entity, pos: Entity, weight: Entity, mode: u32) -> error::Result<bool> {
40+
compute_set(field, "falloff_mode", shader_value::ShaderValue::UInt(mode))?;
41+
compute_dispatch(field, 1, 1, 1)?;
42+
let got = f32s(&buffer_read(weight)?);
43+
let want: Vec<f32> = DISTS.iter().map(|&d| cpu_falloff(d, mode)).collect();
44+
if approx(&got, &want) {
45+
println!(" PASS falloff mode {mode}: {got:?}");
46+
Ok(true)
47+
} else {
48+
println!(" FAIL falloff mode {mode}: got {got:?}, want {want:?}");
49+
Ok(false)
50+
}
51+
}
52+
53+
fn sketch() -> error::Result<bool> {
54+
init(Config::default())?;
55+
let surface = surface_create_offscreen(1, 1, 1.0, TextureFormat::Rgba8Unorm)?;
56+
let _graphics = graphics_create(surface, 1, 1, TextureFormat::Rgba8Unorm)?;
57+
58+
let positions: Vec<f32> = DISTS.iter().flat_map(|&d| [d, 0.0, 0.0]).collect();
59+
let pos = buffer_create_with_data(to_bytes(&positions))?;
60+
let weight = buffer_create_with_data(to_bytes(&vec![0.0; DISTS.len()]))?;
61+
62+
let field = particles_kernel_field()?;
63+
compute_set(field, "position", shader_value::ShaderValue::Buffer(pos))?;
64+
compute_set(field, "weight", shader_value::ShaderValue::Buffer(weight))?;
65+
compute_set(field, "center", shader_value::ShaderValue::Float3([0.0, 0.0, 0.0]))?;
66+
compute_set(field, "radius", shader_value::ShaderValue::Float(RADIUS))?;
67+
68+
let mut ok = true;
69+
for mode in [0u32, 1, 2, 3, 4, 5] {
70+
ok &= run_mode(field, pos, weight, mode)?;
71+
}
72+
73+
// The other falloff users share the same import — instantiate them so their
74+
// WESL actually compiles (compute_create errors if the import is broken).
75+
for (name, r) in [
76+
("attract", particles_kernel_attract()),
77+
("vortex", particles_kernel_vortex()),
78+
("impulse", particles_kernel_impulse()),
79+
] {
80+
match r {
81+
Ok(_) => println!(" PASS {name} compiles (falloff import)"),
82+
Err(e) => {
83+
ok = false;
84+
println!(" FAIL {name} compile: {e}");
85+
}
86+
}
87+
}
88+
Ok(ok)
89+
}
90+
91+
fn main() {
92+
let ok = sketch().unwrap();
93+
if ok {
94+
println!("field_falloff_test: ALL PASS");
95+
exit(0).unwrap();
96+
} else {
97+
println!("field_falloff_test: FAILURES");
98+
exit(1).unwrap();
99+
}
100+
}

0 commit comments

Comments
 (0)