use crate::error::{Error, Result};
use crate::surface::Surface;
use crate::vk_format::FormatExt;
pub enum CubemapInput {
SeparateFaces(Box<[Surface; 6]>),
Cross(Surface),
Strip(Surface),
}
pub fn split_cubemap(input: CubemapInput) -> Result<[Surface; 6]> {
match input {
CubemapInput::SeparateFaces(faces) => {
log::debug!("Splitting cubemap: separate faces input");
validate_uniform_faces(&faces).map(|()| *faces)
}
CubemapInput::Cross(surface) => {
log::debug!("Splitting cubemap: cross input");
log::debug!("Cross source: {}x{}", surface.width, surface.height);
split_cross(&surface)
}
CubemapInput::Strip(surface) => {
log::debug!("Splitting cubemap: strip input");
log::debug!("Strip source: {}x{}", surface.width, surface.height);
split_strip(&surface)
}
}
}
fn validate_uniform_faces(faces: &[Surface; 6]) -> Result<()> {
let (w, h) = (faces[0].width, faces[0].height);
for face in &faces[1..] {
if face.width != w || face.height != h {
return Err(Error::CubemapNonUniformFaces);
}
}
Ok(())
}
fn split_cross(surface: &Surface) -> Result<[Surface; 6]> {
profiling::scope!("split_cross");
let face_w = surface.width / 4;
let face_h = surface.height / 3;
if face_w == 0 || face_h == 0 {
return Err(Error::InvalidDimensions(
"cross layout image too small".into(),
));
}
let positions = [
(2, 1), (0, 1), (1, 0), (1, 2), (1, 1), (3, 1), ];
let faces: Vec<Surface> = positions
.iter()
.map(|&(col, row)| extract_region(surface, col * face_w, row * face_h, face_w, face_h))
.collect();
Ok(std::array::from_fn(|i| faces[i].clone()))
}
fn split_strip(surface: &Surface) -> Result<[Surface; 6]> {
profiling::scope!("split_strip");
let face_w = surface.width / 6;
let face_h = surface.height;
if face_w == 0 {
return Err(Error::InvalidDimensions(
"strip layout image too small".into(),
));
}
let faces: Vec<Surface> = (0..6)
.map(|i| extract_region(surface, i * face_w, 0, face_w, face_h))
.collect();
Ok(std::array::from_fn(|i| faces[i].clone()))
}
fn extract_region(src: &Surface, src_x: u32, src_y: u32, width: u32, height: u32) -> Surface {
profiling::scope!("extract_region");
let bpp = src
.format
.bytes_per_pixel()
.expect("cubemap requires uncompressed format");
let new_stride = width * bpp as u32;
let mut data = Vec::with_capacity((new_stride * height) as usize);
for row in 0..height {
let src_offset = ((src_y + row) * src.stride + src_x * bpp as u32) as usize;
let row_bytes = &src.data[src_offset..src_offset + new_stride as usize];
data.extend_from_slice(row_bytes);
}
Surface {
data,
width,
height,
depth: 1,
stride: new_stride,
slice_stride: 0,
format: src.format,
color_space: src.color_space,
alpha: src.alpha,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::alpha::AlphaMode;
use crate::surface::ColorSpace;
fn make_face(width: u32, height: u32, fill: u8) -> Surface {
let stride = width * 4;
Surface {
data: vec![fill; (stride * height) as usize],
width,
height,
depth: 1,
stride,
slice_stride: 0,
format: ktx2::Format::R8G8B8A8_UNORM,
color_space: ColorSpace::Srgb,
alpha: AlphaMode::Straight,
}
}
#[test]
fn separate_faces_passthrough() {
let faces = std::array::from_fn(|i| make_face(64, 64, i as u8));
let result = split_cubemap(CubemapInput::SeparateFaces(Box::new(faces))).unwrap();
for (i, face) in result.iter().enumerate() {
assert_eq!(face.width, 64);
assert_eq!(face.height, 64);
assert_eq!(face.data[0], i as u8);
}
}
#[test]
fn non_uniform_faces_error() {
let mut faces = std::array::from_fn(|_| make_face(64, 64, 0));
faces[3] = make_face(32, 32, 0);
let result = split_cubemap(CubemapInput::SeparateFaces(Box::new(faces)));
assert!(result.is_err());
}
}