use crate::error::{GpuError, GpuResult};
#[derive(Debug, Clone)]
pub struct TiledConfig {
pub tile_width: usize,
pub tile_height: usize,
pub overlap_pixels: usize,
pub vram_safety_margin: f64,
}
impl Default for TiledConfig {
fn default() -> Self {
Self {
tile_width: 512,
tile_height: 512,
overlap_pixels: 0,
vram_safety_margin: 0.1,
}
}
}
impl TiledConfig {
pub fn with_tile_size(mut self, w: usize, h: usize) -> Self {
self.tile_width = w;
self.tile_height = h;
self
}
pub fn with_overlap(mut self, pixels: usize) -> Self {
self.overlap_pixels = pixels;
self
}
pub fn with_vram_safety_margin(mut self, margin: f64) -> Self {
self.vram_safety_margin = margin.clamp(0.0, 0.99);
self
}
}
#[derive(Debug, Clone)]
pub struct RasterTile {
pub data: Vec<f32>,
pub width: usize,
pub height: usize,
pub overlap_top: usize,
pub overlap_right: usize,
pub overlap_bottom: usize,
pub overlap_left: usize,
pub origin_x: usize,
pub origin_y: usize,
pub raster_width: usize,
pub raster_height: usize,
pub tile_index: usize,
}
impl RasterTile {
#[inline]
pub fn padded_width(&self) -> usize {
self.width + self.overlap_left + self.overlap_right
}
#[inline]
pub fn padded_height(&self) -> usize {
self.height + self.overlap_top + self.overlap_bottom
}
#[inline]
pub fn padded_len(&self) -> usize {
self.padded_width() * self.padded_height()
}
}
pub fn split_into_tiles(
raster: &[f32],
raster_width: usize,
raster_height: usize,
config: &TiledConfig,
) -> Vec<RasterTile> {
if raster_width == 0 || raster_height == 0 {
return Vec::new();
}
let tile_w = if config.tile_width == 0 {
raster_width
} else {
config.tile_width
};
let tile_h = if config.tile_height == 0 {
raster_height
} else {
config.tile_height
};
let overlap = config.overlap_pixels;
let tiles_x = raster_width.div_ceil(tile_w);
let tiles_y = raster_height.div_ceil(tile_h);
let mut result = Vec::with_capacity(tiles_x * tiles_y);
for ty in 0..tiles_y {
for tx in 0..tiles_x {
let core_x0 = tx * tile_w;
let core_y0 = ty * tile_h;
let core_w = tile_w.min(raster_width - core_x0);
let core_h = tile_h.min(raster_height - core_y0);
let halo_top = overlap.min(core_y0);
let halo_left = overlap.min(core_x0);
let halo_bottom = overlap.min(raster_height.saturating_sub(core_y0 + core_h));
let halo_right = overlap.min(raster_width.saturating_sub(core_x0 + core_w));
let pad_top = overlap;
let pad_left = overlap;
let pad_bottom = overlap;
let pad_right = overlap;
let padded_w = core_w + pad_left + pad_right;
let padded_h = core_h + pad_top + pad_bottom;
let mut data = vec![0.0_f32; padded_w * padded_h];
for row in 0..padded_h {
let raster_row = (core_y0 as isize - overlap as isize + row as isize)
.clamp(0, raster_height as isize - 1) as usize;
for col in 0..padded_w {
let raster_col = (core_x0 as isize - overlap as isize + col as isize)
.clamp(0, raster_width as isize - 1)
as usize;
let src_idx = raster_row * raster_width + raster_col;
let dst_idx = row * padded_w + col;
data[dst_idx] = if src_idx < raster.len() {
raster[src_idx]
} else {
0.0
};
}
}
result.push(RasterTile {
data,
width: core_w,
height: core_h,
overlap_top: pad_top,
overlap_right: pad_right,
overlap_bottom: pad_bottom,
overlap_left: pad_left,
origin_x: core_x0,
origin_y: core_y0,
raster_width,
raster_height,
tile_index: ty * tiles_x + tx,
});
let _ = (halo_top, halo_left, halo_bottom, halo_right);
}
}
result
}
pub fn stitch_tiles(tiles: &[RasterTile], raster_width: usize, raster_height: usize) -> Vec<f32> {
let mut output = vec![0.0_f32; raster_width * raster_height];
for tile in tiles {
let padded_w = tile.padded_width();
let core_w = tile.width;
let core_h = tile.height;
let halo_left = tile.overlap_left;
let halo_top = tile.overlap_top;
for row in 0..core_h {
for col in 0..core_w {
let src_row = halo_top + row;
let src_col = halo_left + col;
let src_idx = src_row * padded_w + src_col;
let dst_row = tile.origin_y + row;
let dst_col = tile.origin_x + col;
if dst_row < raster_height && dst_col < raster_width {
let dst_idx = dst_row * raster_width + dst_col;
if src_idx < tile.data.len() {
output[dst_idx] = tile.data[src_idx];
}
}
}
}
}
output
}
pub fn vram_per_tile(tile: &RasterTile) -> usize {
tile.padded_len() * 4 * 2 + 256
}
pub fn auto_tile_size(
preferred_w: usize,
preferred_h: usize,
overlap: usize,
vram_budget_bytes: usize,
safety_margin: f64,
) -> (usize, usize) {
let effective_budget =
(vram_budget_bytes as f64 * (1.0 - safety_margin.clamp(0.0, 0.99))) as usize;
let mut w = preferred_w.max(16);
let mut h = preferred_h.max(16);
const MIN: usize = 16;
loop {
let synthetic = RasterTile {
data: Vec::new(),
width: w,
height: h,
overlap_top: overlap,
overlap_right: overlap,
overlap_bottom: overlap,
overlap_left: overlap,
origin_x: 0,
origin_y: 0,
raster_width: w,
raster_height: h,
tile_index: 0,
};
if vram_per_tile(&synthetic) <= effective_budget {
return (w, h);
}
if w > MIN || h > MIN {
if w >= h {
w = (w / 2).max(MIN);
} else {
h = (h / 2).max(MIN);
}
} else {
return (MIN, MIN);
}
}
}
pub fn execute_tiled<F>(
raster: &[f32],
width: usize,
height: usize,
config: &TiledConfig,
tile_fn: F,
) -> GpuResult<Vec<f32>>
where
F: Fn(&RasterTile) -> GpuResult<Vec<f32>>,
{
if width == 0 || height == 0 {
return Ok(Vec::new());
}
let tiles = split_into_tiles(raster, width, height, config);
let mut processed_tiles: Vec<RasterTile> = Vec::with_capacity(tiles.len());
for tile in &tiles {
let processed_data = tile_fn(tile).map_err(|e| {
GpuError::execution_failed(format!(
"tile {} (origin {},{}) failed: {}",
tile.tile_index, tile.origin_x, tile.origin_y, e
))
})?;
if processed_data.len() != tile.padded_len() {
return Err(GpuError::execution_failed(format!(
"tile_fn for tile {} returned {} elements but expected {} (padded_len)",
tile.tile_index,
processed_data.len(),
tile.padded_len(),
)));
}
processed_tiles.push(RasterTile {
data: processed_data,
width: tile.width,
height: tile.height,
overlap_top: tile.overlap_top,
overlap_right: tile.overlap_right,
overlap_bottom: tile.overlap_bottom,
overlap_left: tile.overlap_left,
origin_x: tile.origin_x,
origin_y: tile.origin_y,
raster_width: tile.raster_width,
raster_height: tile.raster_height,
tile_index: tile.tile_index,
});
}
Ok(stitch_tiles(&processed_tiles, width, height))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tiled_config_default() {
let cfg = TiledConfig::default();
assert_eq!(cfg.tile_width, 512);
assert_eq!(cfg.tile_height, 512);
assert_eq!(cfg.overlap_pixels, 0);
assert!((cfg.vram_safety_margin - 0.1).abs() < f64::EPSILON);
}
#[test]
fn test_tiled_config_builder() {
let cfg = TiledConfig::default()
.with_tile_size(256, 128)
.with_overlap(4)
.with_vram_safety_margin(0.2);
assert_eq!(cfg.tile_width, 256);
assert_eq!(cfg.tile_height, 128);
assert_eq!(cfg.overlap_pixels, 4);
assert!((cfg.vram_safety_margin - 0.2).abs() < f64::EPSILON);
}
#[test]
fn test_raster_tile_padded_dimensions() {
let tile = RasterTile {
data: vec![0.0; (10 + 2 + 2) * (8 + 3 + 3)],
width: 10,
height: 8,
overlap_top: 3,
overlap_right: 2,
overlap_bottom: 3,
overlap_left: 2,
origin_x: 0,
origin_y: 0,
raster_width: 100,
raster_height: 100,
tile_index: 0,
};
assert_eq!(tile.padded_width(), 14);
assert_eq!(tile.padded_height(), 14);
assert_eq!(tile.padded_len(), 196);
}
}