#![allow(clippy::unwrap_used)]
#![allow(clippy::panic)]
use oxigdal_gpu::{
RasterTile, TiledConfig, auto_tile_size, execute_tiled, split_into_tiles, stitch_tiles,
vram_per_tile,
};
fn make_raster(w: usize, h: usize) -> Vec<f32> {
(0..w * h).map(|i| i as f32).collect()
}
fn passthrough(tile: &RasterTile) -> oxigdal_gpu::GpuResult<Vec<f32>> {
Ok(tile.data.clone())
}
#[test]
fn test_split_into_tiles_exact_fit() {
let raster = make_raster(1024, 1024);
let config = TiledConfig::default();
let tiles = split_into_tiles(&raster, 1024, 1024, &config);
assert_eq!(
tiles.len(),
4,
"Expected exactly 4 tiles for 1024×1024 / 512×512"
);
for tile in &tiles {
assert_eq!(tile.width, 512);
assert_eq!(tile.height, 512);
assert_eq!(tile.padded_width(), 512); assert_eq!(tile.padded_height(), 512);
}
let origins: Vec<(usize, usize)> = tiles.iter().map(|t| (t.origin_x, t.origin_y)).collect();
assert!(origins.contains(&(0, 0)));
assert!(origins.contains(&(512, 0)));
assert!(origins.contains(&(0, 512)));
assert!(origins.contains(&(512, 512)));
}
#[test]
fn test_split_into_tiles_non_exact_fit() {
let raster = make_raster(100, 100);
let config = TiledConfig::default().with_tile_size(64, 64);
let tiles = split_into_tiles(&raster, 100, 100, &config);
assert_eq!(tiles.len(), 4);
for tile in &tiles {
match (tile.origin_x, tile.origin_y) {
(0, 0) => {
assert_eq!(tile.width, 64);
assert_eq!(tile.height, 64);
}
(64, 0) => {
assert_eq!(tile.width, 36); assert_eq!(tile.height, 64);
}
(0, 64) => {
assert_eq!(tile.width, 64);
assert_eq!(tile.height, 36);
}
(64, 64) => {
assert_eq!(tile.width, 36);
assert_eq!(tile.height, 36);
}
other => panic!("Unexpected tile origin {:?}", other),
}
}
}
#[test]
fn test_split_overlap_adds_halo_pixels() {
let raster = make_raster(256, 256);
let config = TiledConfig::default()
.with_tile_size(128, 128)
.with_overlap(4);
let tiles = split_into_tiles(&raster, 256, 256, &config);
assert_eq!(tiles.len(), 4);
for tile in &tiles {
assert_eq!(tile.overlap_top, 4);
assert_eq!(tile.overlap_bottom, 4);
assert_eq!(tile.overlap_left, 4);
assert_eq!(tile.overlap_right, 4);
assert_eq!(tile.padded_width(), tile.width + 8);
assert_eq!(tile.padded_height(), tile.height + 8);
assert_eq!(tile.data.len(), tile.padded_len());
}
}
#[test]
fn test_split_overlap_edge_replication_at_corners() {
let raster = make_raster(64, 64);
let config = TiledConfig::default()
.with_tile_size(64, 64)
.with_overlap(8);
let tiles = split_into_tiles(&raster, 64, 64, &config);
assert_eq!(tiles.len(), 1);
let tile = &tiles[0];
assert_eq!(
tile.data[0], 0.0,
"Top-left halo corner must replicate raster pixel (0,0)"
);
let top_right_idx = tile.padded_width() - 1;
assert_eq!(
tile.data[top_right_idx], 63.0,
"Top-right halo corner must replicate raster pixel (63, 0)"
);
let bottom_left_idx = (tile.padded_height() - 1) * tile.padded_width();
assert_eq!(
tile.data[bottom_left_idx],
(63 * 64) as f32,
"Bottom-left halo corner must replicate raster pixel (0, 63)"
);
}
#[test]
fn test_stitch_reconstructs_identity() {
let width = 300;
let height = 200;
let raster = make_raster(width, height);
let config = TiledConfig::default().with_tile_size(128, 128);
let tiles = split_into_tiles(&raster, width, height, &config);
let stitched = stitch_tiles(&tiles, width, height);
assert_eq!(stitched.len(), raster.len());
for (i, (&orig, &got)) in raster.iter().zip(stitched.iter()).enumerate() {
assert_eq!(orig, got, "Pixel {i} mismatch: expected {orig}, got {got}");
}
}
#[test]
fn test_stitch_non_overlapping_tiles() {
let width = 200;
let height = 100;
let raster = make_raster(width, height);
let config = TiledConfig::default().with_tile_size(100, 100);
let tiles = split_into_tiles(&raster, width, height, &config);
assert_eq!(tiles.len(), 2);
let stitched = stitch_tiles(&tiles, width, height);
assert_eq!(stitched.len(), raster.len());
for (i, (&orig, &got)) in raster.iter().zip(stitched.iter()).enumerate() {
assert_eq!(orig, got, "Pixel {i}: expected {orig}, got {got}");
}
}
#[test]
fn test_vram_per_tile_formula() {
let tile = RasterTile {
data: vec![0.0; (64 + 4 + 4) * (64 + 4 + 4)], width: 64,
height: 64,
overlap_top: 4,
overlap_right: 4,
overlap_bottom: 4,
overlap_left: 4,
origin_x: 0,
origin_y: 0,
raster_width: 64,
raster_height: 64,
tile_index: 0,
};
let padded_len = tile.padded_len();
assert_eq!(padded_len, 72 * 72);
let expected = padded_len * 4 * 2 + 256;
assert_eq!(vram_per_tile(&tile), expected);
}
#[test]
fn test_auto_tile_size_halves_to_fit_budget() {
let (w, h) = auto_tile_size(512, 512, 0, 512, 0.0);
assert_eq!((w, h), (16, 16));
}
#[test]
fn test_auto_tile_size_preferred_fits_in_large_budget() {
let budget = 256 * 1024 * 1024; let (w, h) = auto_tile_size(512, 512, 0, budget, 0.1);
assert_eq!((w, h), (512, 512));
}
#[test]
fn test_execute_tiled_passthrough_matches_original() {
let width = 256;
let height = 256;
let raster = make_raster(width, height);
let config = TiledConfig::default().with_tile_size(128, 128);
let result = execute_tiled(&raster, width, height, &config, passthrough).unwrap();
assert_eq!(result.len(), raster.len());
for (i, (&orig, &got)) in raster.iter().zip(result.iter()).enumerate() {
assert_eq!(orig, got, "Pixel {i}: expected {orig}, got {got}");
}
}
#[test]
fn test_execute_tiled_scale_fn_applies_per_tile() {
let width = 200;
let height = 100;
let raster = make_raster(width, height);
let config = TiledConfig::default().with_tile_size(100, 100);
let scale_fn = |tile: &RasterTile| -> oxigdal_gpu::GpuResult<Vec<f32>> {
let scaled: Vec<f32> = tile.data.iter().map(|&v| v * 2.0).collect();
Ok(scaled)
};
let result = execute_tiled(&raster, width, height, &config, scale_fn).unwrap();
assert_eq!(result.len(), raster.len());
for (i, (&orig, &got)) in raster.iter().zip(result.iter()).enumerate() {
let expected = orig * 2.0;
assert_eq!(got, expected, "Pixel {i}: expected {expected}, got {got}");
}
}
#[test]
fn test_split_single_tile_covers_all() {
let width = 50;
let height = 30;
let raster = make_raster(width, height);
let config = TiledConfig::default().with_tile_size(200, 200);
let tiles = split_into_tiles(&raster, width, height, &config);
assert_eq!(tiles.len(), 1, "One tile should cover the entire raster");
let tile = &tiles[0];
assert_eq!(tile.width, width);
assert_eq!(tile.height, height);
assert_eq!(tile.origin_x, 0);
assert_eq!(tile.origin_y, 0);
assert_eq!(tile.data.len(), width * height);
for (i, (&orig, &got)) in raster.iter().zip(tile.data.iter()).enumerate() {
assert_eq!(orig, got, "Tile pixel {i}: expected {orig}, got {got}");
}
}
#[test]
fn test_split_empty_raster_returns_no_tiles() {
let config = TiledConfig::default();
let tiles = split_into_tiles(&[], 0, 0, &config);
assert!(tiles.is_empty());
}
#[test]
fn test_tile_indices_are_row_major() {
let raster = make_raster(200, 200);
let config = TiledConfig::default().with_tile_size(100, 100);
let tiles = split_into_tiles(&raster, 200, 200, &config);
assert_eq!(tiles.len(), 4);
let indices: Vec<usize> = tiles.iter().map(|t| t.tile_index).collect();
assert_eq!(indices, vec![0, 1, 2, 3]);
}
#[test]
fn test_stitch_empty_tiles_gives_zeros() {
let out = stitch_tiles(&[], 4, 4);
assert_eq!(out, vec![0.0_f32; 16]);
}
#[test]
fn test_execute_tiled_propagates_error() {
let raster = make_raster(64, 64);
let config = TiledConfig::default().with_tile_size(64, 64);
let error_fn = |_tile: &RasterTile| -> oxigdal_gpu::GpuResult<Vec<f32>> {
Err(oxigdal_gpu::GpuError::execution_failed(
"deliberate test error",
))
};
let result = execute_tiled(&raster, 64, 64, &config, error_fn);
assert!(result.is_err());
}
#[test]
fn test_stitch_with_overlap_recovers_original() {
let width = 128;
let height = 128;
let raster = make_raster(width, height);
let config = TiledConfig::default()
.with_tile_size(64, 64)
.with_overlap(4);
let tiles = split_into_tiles(&raster, width, height, &config);
let stitched = stitch_tiles(&tiles, width, height);
assert_eq!(stitched.len(), raster.len());
for (i, (&orig, &got)) in raster.iter().zip(stitched.iter()).enumerate() {
assert_eq!(orig, got, "Pixel {i}: expected {orig}, got {got}");
}
}
#[test]
fn test_auto_tile_size_never_exceeds_preferred() {
let budget = 1024 * 1024 * 1024; let (w, h) = auto_tile_size(512, 256, 0, budget, 0.05);
assert!(w <= 512);
assert!(h <= 256);
}