use scirs2_core::gpu::{GpuBackend, GpuContext};
pub fn align_size(size: usize, alignment: usize) -> usize {
if alignment == 0 || !alignment.is_power_of_two() {
return size;
}
(size + alignment - 1) & !(alignment - 1)
}
pub fn is_aligned(addr: usize, alignment: usize) -> bool {
if !alignment.is_power_of_two() {
return false;
}
addr & (alignment - 1) == 0
}
pub fn calculate_fragmentation(free_blocks: &[(usize, usize)]) -> f32 {
if free_blocks.is_empty() {
return 0.0;
}
let total_free: usize = free_blocks.iter().map(|(size, count)| size * count).sum();
let largest_block = free_blocks.iter().map(|(size, _)| *size).max().unwrap_or(0);
if total_free == 0 {
0.0
} else {
1.0 - (largest_block as f32 / total_free as f32)
}
}
pub fn format_bytes(bytes: usize) -> String {
const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB"];
let mut size = bytes as f64;
let mut unit_index = 0;
while size >= 1024.0 && unit_index < UNITS.len() - 1 {
size /= 1024.0;
unit_index += 1;
}
if unit_index == 0 {
format!("{} {}", bytes, UNITS[unit_index])
} else {
format!("{:.2} {}", size, UNITS[unit_index])
}
}
pub fn checked_next_power_of_two(n: usize) -> Option<usize> {
if n == 0 {
return Some(1);
}
if n.is_power_of_two() {
return Some(n);
}
let shift = usize::BITS - (n - 1).leading_zeros();
if shift >= usize::BITS {
None
} else {
Some(1usize << shift)
}
}
pub fn calculate_block_size(n: usize, max_threads: usize) -> (usize, usize) {
let block_size = crate::shaders::WORKGROUP_SIZE.min(max_threads.max(1));
let grid_size = n.div_ceil(block_size);
(grid_size, block_size)
}
pub fn get_optimal_backend() -> GpuBackend {
for backend in [GpuBackend::Wgpu, GpuBackend::Metal, GpuBackend::OpenCL] {
if GpuContext::new(backend).is_ok() {
return backend;
}
}
GpuBackend::Cpu
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_align_size() {
assert_eq!(align_size(100, 256), 256);
assert_eq!(align_size(256, 256), 256);
assert_eq!(align_size(300, 256), 512);
assert_eq!(align_size(300, 3), 300);
assert_eq!(align_size(300, 0), 300);
}
#[test]
fn test_is_aligned() {
assert!(is_aligned(0x1000, 256));
assert!(!is_aligned(0x1001, 256));
assert!(!is_aligned(0x1000, 3));
}
#[test]
fn test_format_bytes() {
assert_eq!(format_bytes(1024), "1.00 KB");
assert_eq!(format_bytes(1048576), "1.00 MB");
assert_eq!(format_bytes(512), "512 B");
}
#[test]
fn checked_next_power_of_two_handles_edges() {
assert_eq!(checked_next_power_of_two(0), Some(1));
assert_eq!(checked_next_power_of_two(1), Some(1));
assert_eq!(checked_next_power_of_two(100), Some(128));
assert_eq!(checked_next_power_of_two(128), Some(128));
let highest = 1usize << (usize::BITS - 1);
assert_eq!(checked_next_power_of_two(highest), Some(highest));
assert_eq!(checked_next_power_of_two(highest + 1), None);
assert_eq!(checked_next_power_of_two(usize::MAX), None);
}
#[test]
fn calculate_block_size_honours_max_threads_and_covers_the_tail() {
assert_eq!(calculate_block_size(1000, 64), (16, 64));
assert_eq!(calculate_block_size(1000, 1024), (4, 256));
let (grid, block) = calculate_block_size(257, 256);
assert_eq!((grid, block), (2, 256));
assert!(grid * block >= 257);
let (grid, block) = calculate_block_size(10, 0);
assert_eq!(block, 1);
assert_eq!(grid, 10);
}
#[test]
fn calculate_fragmentation_bounds() {
assert_eq!(calculate_fragmentation(&[]), 0.0);
assert_eq!(calculate_fragmentation(&[(1024, 1)]), 0.0);
let frag = calculate_fragmentation(&[(256, 4)]);
assert!(frag > 0.7 && frag < 0.8, "unexpected fragmentation {frag}");
}
#[test]
fn get_optimal_backend_returns_something_usable() {
let backend = get_optimal_backend();
assert!(
GpuContext::new(backend).is_ok(),
"get_optimal_backend returned unusable backend {backend}"
);
}
}