1use scirs2_core::gpu::{GpuBackend, GpuContext};
8
9pub fn align_size(size: usize, alignment: usize) -> usize {
14 if alignment == 0 || !alignment.is_power_of_two() {
15 return size;
16 }
17 (size + alignment - 1) & !(alignment - 1)
18}
19
20pub fn is_aligned(addr: usize, alignment: usize) -> bool {
22 if !alignment.is_power_of_two() {
23 return false;
24 }
25 addr & (alignment - 1) == 0
26}
27
28pub fn calculate_fragmentation(free_blocks: &[(usize, usize)]) -> f32 {
33 if free_blocks.is_empty() {
34 return 0.0;
35 }
36
37 let total_free: usize = free_blocks.iter().map(|(size, count)| size * count).sum();
38 let largest_block = free_blocks.iter().map(|(size, _)| *size).max().unwrap_or(0);
39
40 if total_free == 0 {
41 0.0
42 } else {
43 1.0 - (largest_block as f32 / total_free as f32)
44 }
45}
46
47pub fn format_bytes(bytes: usize) -> String {
49 const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB"];
50 let mut size = bytes as f64;
51 let mut unit_index = 0;
52
53 while size >= 1024.0 && unit_index < UNITS.len() - 1 {
54 size /= 1024.0;
55 unit_index += 1;
56 }
57
58 if unit_index == 0 {
59 format!("{} {}", bytes, UNITS[unit_index])
60 } else {
61 format!("{:.2} {}", size, UNITS[unit_index])
62 }
63}
64
65pub fn checked_next_power_of_two(n: usize) -> Option<usize> {
70 if n == 0 {
71 return Some(1);
72 }
73 if n.is_power_of_two() {
74 return Some(n);
75 }
76 let shift = usize::BITS - (n - 1).leading_zeros();
77 if shift >= usize::BITS {
78 None
79 } else {
80 Some(1usize << shift)
81 }
82}
83
84pub fn calculate_block_size(n: usize, max_threads: usize) -> (usize, usize) {
93 let block_size = crate::shaders::WORKGROUP_SIZE.min(max_threads.max(1));
94 let grid_size = n.div_ceil(block_size);
95 (grid_size, block_size)
96}
97
98pub fn get_optimal_backend() -> GpuBackend {
105 for backend in [GpuBackend::Wgpu, GpuBackend::Metal, GpuBackend::OpenCL] {
106 if GpuContext::new(backend).is_ok() {
107 return backend;
108 }
109 }
110 GpuBackend::Cpu
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116
117 #[test]
118 fn test_align_size() {
119 assert_eq!(align_size(100, 256), 256);
120 assert_eq!(align_size(256, 256), 256);
121 assert_eq!(align_size(300, 256), 512);
122 assert_eq!(align_size(300, 3), 300);
124 assert_eq!(align_size(300, 0), 300);
125 }
126
127 #[test]
128 fn test_is_aligned() {
129 assert!(is_aligned(0x1000, 256));
130 assert!(!is_aligned(0x1001, 256));
131 assert!(!is_aligned(0x1000, 3));
132 }
133
134 #[test]
135 fn test_format_bytes() {
136 assert_eq!(format_bytes(1024), "1.00 KB");
137 assert_eq!(format_bytes(1048576), "1.00 MB");
138 assert_eq!(format_bytes(512), "512 B");
139 }
140
141 #[test]
142 fn checked_next_power_of_two_handles_edges() {
143 assert_eq!(checked_next_power_of_two(0), Some(1));
144 assert_eq!(checked_next_power_of_two(1), Some(1));
145 assert_eq!(checked_next_power_of_two(100), Some(128));
146 assert_eq!(checked_next_power_of_two(128), Some(128));
147 let highest = 1usize << (usize::BITS - 1);
148 assert_eq!(checked_next_power_of_two(highest), Some(highest));
149 assert_eq!(checked_next_power_of_two(highest + 1), None);
151 assert_eq!(checked_next_power_of_two(usize::MAX), None);
152 }
153
154 #[test]
155 fn calculate_block_size_honours_max_threads_and_covers_the_tail() {
156 assert_eq!(calculate_block_size(1000, 64), (16, 64));
158 assert_eq!(calculate_block_size(1000, 1024), (4, 256));
160 let (grid, block) = calculate_block_size(257, 256);
162 assert_eq!((grid, block), (2, 256));
163 assert!(grid * block >= 257);
164 let (grid, block) = calculate_block_size(10, 0);
166 assert_eq!(block, 1);
167 assert_eq!(grid, 10);
168 }
169
170 #[test]
171 fn calculate_fragmentation_bounds() {
172 assert_eq!(calculate_fragmentation(&[]), 0.0);
173 assert_eq!(calculate_fragmentation(&[(1024, 1)]), 0.0);
174 let frag = calculate_fragmentation(&[(256, 4)]);
175 assert!(frag > 0.7 && frag < 0.8, "unexpected fragmentation {frag}");
176 }
177
178 #[test]
179 fn get_optimal_backend_returns_something_usable() {
180 let backend = get_optimal_backend();
181 assert!(
183 GpuContext::new(backend).is_ok(),
184 "get_optimal_backend returned unusable backend {backend}"
185 );
186 }
187}