1mod error;
11pub use error::*;
12
13pub mod bvh;
14pub mod cell_list;
15pub mod compute;
16pub mod compute_pipeline;
17pub mod flux_compute;
18pub mod gpu_bench;
19pub mod grid_reduce;
20pub mod kernels;
21pub mod lbm_gpu;
22pub mod neural_compute;
23pub mod parallel;
24pub mod parallel_sort;
25pub mod particle_system;
26pub mod pipeline;
27pub mod sdf_compute;
28pub mod shader_registry;
29pub mod shaders;
30pub mod sparse_gpu;
31pub mod sph_gpu;
32
33pub use compute::{BufferHandle, ComputeBackend, ComputeKernel, CpuBackend};
34pub use neural_compute::*;
35pub use particle_system::functions::*;
36pub use particle_system::types::*;
37pub use sparse_gpu::*;
38
39pub fn dispatch_count(total: usize, group_size: usize) -> usize {
45 if group_size == 0 {
46 return 0;
47 }
48 total.div_ceil(group_size)
49}
50
51pub fn aligned_size(size: usize, alignment: usize) -> usize {
55 if alignment == 0 {
56 return size;
57 }
58 size.div_ceil(alignment) * alignment
59}
60
61pub fn linear_index_3d(x: usize, y: usize, z: usize, dim_x: usize, dim_y: usize) -> usize {
63 z * dim_x * dim_y + y * dim_x + x
64}
65
66pub fn index_3d_from_linear(index: usize, dim_x: usize, dim_y: usize) -> (usize, usize, usize) {
68 let z = index / (dim_x * dim_y);
69 let rem = index % (dim_x * dim_y);
70 let y = rem / dim_x;
71 let x = rem % dim_x;
72 (x, y, z)
73}
74
75#[derive(Debug, Clone)]
77pub struct DispatchTimer {
78 pub label: String,
80 pub elapsed_secs: f64,
82}
83
84impl DispatchTimer {
85 pub fn new(label: impl Into<String>) -> Self {
87 Self {
88 label: label.into(),
89 elapsed_secs: 0.0,
90 }
91 }
92
93 pub fn record(&mut self, elapsed: f64) {
95 self.elapsed_secs = elapsed;
96 }
97}
98
99pub fn bandwidth_gb_s(bytes_transferred: usize, elapsed_secs: f64) -> f64 {
104 if elapsed_secs <= 0.0 {
105 return 0.0;
106 }
107 (bytes_transferred as f64) / elapsed_secs / 1e9
108}
109
110pub fn elements_in_budget(budget_bytes: usize, element_size: usize) -> usize {
115 if element_size == 0 {
116 return 0;
117 }
118 budget_bytes / element_size
119}
120
121pub fn row_pitch(elements_per_row: usize, element_size: usize, alignment: usize) -> usize {
128 let raw = elements_per_row * element_size;
129 aligned_size(raw, alignment)
130}
131
132pub fn buffer_size_2d(
134 width: usize,
135 height: usize,
136 element_size: usize,
137 row_alignment: usize,
138) -> usize {
139 row_pitch(width, element_size, row_alignment) * height
140}
141
142pub fn next_power_of_two(value: usize) -> usize {
147 if value == 0 {
148 return 1;
149 }
150 let mut p = 1usize;
151 while p < value {
152 p <<= 1;
153 }
154 p
155}
156
157pub fn is_power_of_two(value: usize) -> bool {
159 value != 0 && (value & (value - 1)) == 0
160}
161
162pub fn log2_pow2(v: usize) -> u32 {
165 debug_assert!(is_power_of_two(v), "{v} is not a power of two");
166 v.trailing_zeros()
167}
168
169pub fn tile_count_2d(width: usize, height: usize, tw: usize, th: usize) -> (usize, usize) {
176 let tx = width.div_ceil(tw);
177 let ty = height.div_ceil(th);
178 (tx, ty)
179}
180
181pub fn total_tiles_2d(width: usize, height: usize, tw: usize, th: usize) -> usize {
183 let (tx, ty) = tile_count_2d(width, height, tw, th);
184 tx * ty
185}
186
187pub fn tile_index_to_2d(flat: usize, tiles_x: usize) -> (usize, usize) {
190 (flat % tiles_x, flat / tiles_x)
191}
192
193pub fn clamp_f64(v: f64, lo: f64, hi: f64) -> f64 {
197 v.max(lo).min(hi)
198}
199
200pub fn smoothstep(lo: f64, hi: f64, v: f64) -> f64 {
202 let t = clamp_f64((v - lo) / (hi - lo), 0.0, 1.0);
203 t * t * (3.0 - 2.0 * t)
204}
205
206pub fn smootherstep(lo: f64, hi: f64, v: f64) -> f64 {
208 let t = clamp_f64((v - lo) / (hi - lo), 0.0, 1.0);
209 t * t * t * (t * (t * 6.0 - 15.0) + 10.0)
210}
211
212pub fn lerp(a: f64, b: f64, t: f64) -> f64 {
214 a + t * (b - a)
215}
216
217pub fn inv_lerp(a: f64, b: f64, v: f64) -> f64 {
219 if (b - a).abs() < f64::EPSILON {
220 return 0.0;
221 }
222 (v - a) / (b - a)
223}
224
225pub fn safe_recip(x: f64, eps: f64) -> f64 {
229 if x.abs() > eps { 1.0 / x } else { 0.0 }
230}
231
232pub fn safe_sqrt(x: f64) -> f64 {
234 x.max(0.0).sqrt()
235}
236
237pub fn wrap_angle(theta: f64) -> f64 {
239 use std::f64::consts::PI;
240 let mut t = theta % (2.0 * PI);
241 if t > PI {
242 t -= 2.0 * PI;
243 }
244 if t <= -PI {
245 t += 2.0 * PI;
246 }
247 t
248}
249
250pub fn dot3(a: [f64; 3], b: [f64; 3]) -> f64 {
254 a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
255}
256
257pub fn cross3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
259 [
260 a[1] * b[2] - a[2] * b[1],
261 a[2] * b[0] - a[0] * b[2],
262 a[0] * b[1] - a[1] * b[0],
263 ]
264}
265
266pub fn length3(v: [f64; 3]) -> f64 {
268 dot3(v, v).sqrt()
269}
270
271pub fn normalize3(v: [f64; 3]) -> [f64; 3] {
273 let len = length3(v);
274 if len < 1e-15 {
275 return [0.0; 3];
276 }
277 [v[0] / len, v[1] / len, v[2] / len]
278}
279
280pub fn reflect3(d: [f64; 3], n: [f64; 3]) -> [f64; 3] {
282 let dn2 = 2.0 * dot3(d, n);
283 [d[0] - dn2 * n[0], d[1] - dn2 * n[1], d[2] - dn2 * n[2]]
284}
285
286pub fn exclusive_scan(data: &[f64]) -> Vec<f64> {
293 let mut result = Vec::with_capacity(data.len());
294 let mut acc = 0.0;
295 for &v in data {
296 result.push(acc);
297 acc += v;
298 }
299 result
300}
301
302pub fn inclusive_scan(data: &[f64]) -> Vec<f64> {
304 let mut result = Vec::with_capacity(data.len());
305 let mut acc = 0.0;
306 for &v in data {
307 acc += v;
308 result.push(acc);
309 }
310 result
311}
312
313pub fn reduce_sum(data: &[f64]) -> f64 {
315 data.iter().copied().sum()
316}
317
318pub fn reduce_max(data: &[f64]) -> f64 {
320 data.iter().copied().fold(f64::NEG_INFINITY, f64::max)
321}
322
323pub fn reduce_min(data: &[f64]) -> f64 {
325 data.iter().copied().fold(f64::INFINITY, f64::min)
326}
327
328#[cfg(test)]
329mod gpu_util_tests {
330 use super::*;
331 use std::f64::consts::PI;
332
333 #[test]
334 fn test_dispatch_count_exact() {
335 assert_eq!(dispatch_count(256, 64), 4);
336 }
337
338 #[test]
339 fn test_dispatch_count_remainder() {
340 assert_eq!(dispatch_count(257, 64), 5);
341 }
342
343 #[test]
344 fn test_dispatch_count_zero_group() {
345 assert_eq!(dispatch_count(100, 0), 0);
346 }
347
348 #[test]
349 fn test_aligned_size_exact() {
350 assert_eq!(aligned_size(256, 64), 256);
351 }
352
353 #[test]
354 fn test_aligned_size_pad() {
355 assert_eq!(aligned_size(257, 64), 320);
356 }
357
358 #[test]
359 fn test_aligned_size_zero_alignment() {
360 assert_eq!(aligned_size(100, 0), 100);
361 }
362
363 #[test]
364 fn test_linear_index_3d() {
365 assert_eq!(linear_index_3d(0, 0, 0, 4, 3), 0);
367 assert_eq!(linear_index_3d(3, 2, 1, 4, 3), 12 + 2 * 4 + 3);
368 }
369
370 #[test]
371 fn test_index_3d_roundtrip() {
372 let (dx, dy) = (4, 3);
373 for z in 0..2 {
374 for y in 0..dy {
375 for x in 0..dx {
376 let idx = linear_index_3d(x, y, z, dx, dy);
377 let (rx, ry, rz) = index_3d_from_linear(idx, dx, dy);
378 assert_eq!((rx, ry, rz), (x, y, z));
379 }
380 }
381 }
382 }
383
384 #[test]
385 fn test_dispatch_timer() {
386 let mut timer = DispatchTimer::new("test");
387 assert_eq!(timer.label, "test");
388 timer.record(0.5);
389 assert!((timer.elapsed_secs - 0.5).abs() < 1e-10);
390 }
391
392 #[test]
393 fn test_bandwidth_gb_s() {
394 let bw = bandwidth_gb_s(1_000_000_000, 1.0);
396 assert!((bw - 1.0).abs() < 1e-6);
397 }
398
399 #[test]
400 fn test_bandwidth_zero_time() {
401 assert!((bandwidth_gb_s(1000, 0.0)).abs() < 1e-10);
402 }
403
404 #[test]
405 fn test_elements_in_budget() {
406 assert_eq!(elements_in_budget(1024, 4), 256);
407 assert_eq!(elements_in_budget(1024, 0), 0);
408 }
409
410 #[test]
411 fn test_exclusive_scan() {
412 let data = [1.0, 2.0, 3.0, 4.0];
413 let result = exclusive_scan(&data);
414 assert_eq!(result, vec![0.0, 1.0, 3.0, 6.0]);
415 }
416
417 #[test]
418 fn test_inclusive_scan() {
419 let data = [1.0, 2.0, 3.0, 4.0];
420 let result = inclusive_scan(&data);
421 assert_eq!(result, vec![1.0, 3.0, 6.0, 10.0]);
422 }
423
424 #[test]
425 fn test_reduce_sum() {
426 assert!((reduce_sum(&[1.0, 2.0, 3.0]) - 6.0).abs() < 1e-10);
427 }
428
429 #[test]
430 fn test_reduce_max() {
431 assert!((reduce_max(&[1.0, 5.0, 3.0]) - 5.0).abs() < 1e-10);
432 }
433
434 #[test]
435 fn test_reduce_min() {
436 assert!((reduce_min(&[1.0, 5.0, 3.0]) - 1.0).abs() < 1e-10);
437 }
438
439 #[test]
440 fn test_exclusive_scan_empty() {
441 let result = exclusive_scan(&[]);
442 assert!(result.is_empty());
443 }
444
445 #[test]
446 fn test_inclusive_scan_single() {
447 let result = inclusive_scan(&[42.0]);
448 assert_eq!(result, vec![42.0]);
449 }
450
451 #[test]
454 fn test_row_pitch_aligned() {
455 assert_eq!(row_pitch(128, 4, 256), 512);
457 }
458
459 #[test]
460 fn test_row_pitch_needs_padding() {
461 assert_eq!(row_pitch(100, 4, 256), 512);
463 }
464
465 #[test]
466 fn test_buffer_size_2d() {
467 assert_eq!(buffer_size_2d(64, 4, 4, 256), 1024);
469 }
470
471 #[test]
472 fn test_next_power_of_two() {
473 assert_eq!(next_power_of_two(0), 1);
474 assert_eq!(next_power_of_two(1), 1);
475 assert_eq!(next_power_of_two(5), 8);
476 assert_eq!(next_power_of_two(8), 8);
477 assert_eq!(next_power_of_two(9), 16);
478 }
479
480 #[test]
481 fn test_is_power_of_two() {
482 assert!(is_power_of_two(1));
483 assert!(is_power_of_two(16));
484 assert!(!is_power_of_two(0));
485 assert!(!is_power_of_two(7));
486 }
487
488 #[test]
489 fn test_log2_pow2() {
490 assert_eq!(log2_pow2(1), 0);
491 assert_eq!(log2_pow2(2), 1);
492 assert_eq!(log2_pow2(256), 8);
493 }
494
495 #[test]
496 fn test_tile_count_2d_exact() {
497 let (tx, ty) = tile_count_2d(64, 64, 16, 16);
498 assert_eq!(tx, 4);
499 assert_eq!(ty, 4);
500 }
501
502 #[test]
503 fn test_tile_count_2d_remainder() {
504 let (tx, ty) = tile_count_2d(65, 65, 16, 16);
505 assert_eq!(tx, 5);
506 assert_eq!(ty, 5);
507 }
508
509 #[test]
510 fn test_total_tiles_2d() {
511 assert_eq!(total_tiles_2d(64, 64, 16, 16), 16);
512 }
513
514 #[test]
515 fn test_tile_index_to_2d() {
516 assert_eq!(tile_index_to_2d(5, 4), (1, 1));
518 assert_eq!(tile_index_to_2d(0, 4), (0, 0));
519 }
520
521 #[test]
524 fn test_smoothstep_edges() {
525 assert!((smoothstep(0.0, 1.0, 0.0) - 0.0).abs() < 1e-12);
526 assert!((smoothstep(0.0, 1.0, 1.0) - 1.0).abs() < 1e-12);
527 }
528
529 #[test]
530 fn test_smoothstep_midpoint() {
531 assert!((smoothstep(0.0, 1.0, 0.5) - 0.5).abs() < 1e-12);
533 }
534
535 #[test]
536 fn test_smootherstep_edges() {
537 assert!((smootherstep(0.0, 1.0, 0.0)).abs() < 1e-12);
538 assert!((smootherstep(0.0, 1.0, 1.0) - 1.0).abs() < 1e-12);
539 }
540
541 #[test]
542 fn test_lerp_inv_lerp_roundtrip() {
543 let a = 10.0;
544 let b = 20.0;
545 let t = 0.3;
546 let v = lerp(a, b, t);
547 assert!((inv_lerp(a, b, v) - t).abs() < 1e-12);
548 }
549
550 #[test]
551 fn test_safe_recip_normal() {
552 assert!((safe_recip(2.0, 1e-9) - 0.5).abs() < 1e-12);
553 }
554
555 #[test]
556 fn test_safe_recip_near_zero() {
557 assert!((safe_recip(1e-15, 1e-9)).abs() < 1e-12);
558 }
559
560 #[test]
561 fn test_safe_sqrt_positive() {
562 assert!((safe_sqrt(9.0) - 3.0).abs() < 1e-12);
563 }
564
565 #[test]
566 fn test_safe_sqrt_negative() {
567 assert!((safe_sqrt(-1.0)).abs() < 1e-12);
568 }
569
570 #[test]
571 fn test_wrap_angle_in_range() {
572 let wrapped = wrap_angle(3.0 * PI);
573 assert!(wrapped.abs() <= PI + 1e-12, "wrapped = {wrapped}");
574 }
575
576 #[test]
579 fn test_dot3() {
580 let a = [1.0, 2.0, 3.0];
581 let b = [4.0, 5.0, 6.0];
582 assert!((dot3(a, b) - 32.0).abs() < 1e-12);
583 }
584
585 #[test]
586 fn test_cross3() {
587 let i = [1.0, 0.0, 0.0];
588 let j = [0.0, 1.0, 0.0];
589 let k = cross3(i, j);
590 assert!((k[0]).abs() < 1e-12);
591 assert!((k[1]).abs() < 1e-12);
592 assert!((k[2] - 1.0).abs() < 1e-12);
593 }
594
595 #[test]
596 fn test_length3() {
597 let v = [3.0, 4.0, 0.0];
598 assert!((length3(v) - 5.0).abs() < 1e-12);
599 }
600
601 #[test]
602 fn test_normalize3() {
603 let v = [0.0, 0.0, 5.0];
604 let n = normalize3(v);
605 assert!((length3(n) - 1.0).abs() < 1e-12);
606 assert!((n[2] - 1.0).abs() < 1e-12);
607 }
608
609 #[test]
610 fn test_normalize3_zero_vec() {
611 let n = normalize3([0.0; 3]);
612 assert_eq!(n, [0.0; 3]);
613 }
614
615 #[test]
616 fn test_reflect3() {
617 let d = [0.0, -1.0, 0.0]; let n = [0.0, 1.0, 0.0]; let r = reflect3(d, n);
621 assert!((r[1] - 1.0).abs() < 1e-12);
623 }
624}
625pub mod collision_gpu;
626pub mod deformable_gpu;
627pub mod fluid_gpu;
628pub mod fluid_sim_gpu;
629pub mod gpu_cloth;
630pub mod gpu_collision_detection;
631pub mod gpu_collision_ext;
632pub mod gpu_fem_assembly;
633pub mod gpu_fluid;
634pub mod gpu_fluid_euler;
635pub mod gpu_lbm;
636pub mod gpu_md_solver;
637pub mod gpu_mesh_processing;
638pub mod gpu_neural_solver;
639pub mod gpu_nn;
640pub mod gpu_particle_system;
641pub mod gpu_particles;
642pub mod gpu_ray_tracing;
643pub mod gpu_reduction;
644pub mod gpu_rigid;
645pub mod gpu_sdf;
646pub mod gpu_sort;
647pub mod gpu_sparse_solver;
648pub mod gpu_sph_density;
649pub mod gpu_sph_pressure;
650pub mod gpu_sph_solver;
651pub mod gpu_thermal;
652pub mod gpu_voxel;
653pub mod memory;
654pub mod neural_physics;
655pub mod path_tracer;
656pub mod ray_marching;
657pub mod ray_tracing_gpu;
658pub mod raytracing;
659pub mod scheduler;