use half::f16;
use volren_core::volume::{DynVolume, VolumeInfo};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GpuTextureFormat {
R16Float,
}
impl GpuTextureFormat {
#[must_use]
pub fn to_wgpu(self) -> wgpu::TextureFormat {
match self {
Self::R16Float => wgpu::TextureFormat::R16Float,
}
}
}
#[allow(dead_code)]
pub(crate) struct GpuVolumeTexture {
pub texture: wgpu::Texture,
pub view: wgpu::TextureView,
pub sampler: wgpu::Sampler,
pub format: wgpu::TextureFormat,
pub dimensions: glam::UVec3,
}
impl GpuVolumeTexture {
pub fn allocate_empty(
device: &wgpu::Device,
dimensions: glam::UVec3,
linear: bool,
) -> Self {
let format = GpuTextureFormat::R16Float.to_wgpu();
let texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("volren_volume_3d"),
size: wgpu::Extent3d {
width: dimensions.x,
height: dimensions.y,
depth_or_array_layers: dimensions.z,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D3,
format,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[],
});
let view = texture.create_view(&wgpu::TextureViewDescriptor {
dimension: Some(wgpu::TextureViewDimension::D3),
..Default::default()
});
let filter = if linear {
wgpu::FilterMode::Linear
} else {
wgpu::FilterMode::Nearest
};
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
label: Some("volren_volume_sampler"),
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: filter,
min_filter: filter,
mipmap_filter: wgpu::FilterMode::Nearest,
..Default::default()
});
Self {
texture,
view,
sampler,
format,
dimensions,
}
}
pub fn upload(
device: &wgpu::Device,
queue: &wgpu::Queue,
volume: &DynVolume,
linear: bool,
) -> Self {
let dims = volume.dimensions();
let texture = Self::allocate_empty(device, dims, linear);
let voxels = to_f16_bits(volume);
write_f16_bits(queue, &texture.texture, texture.dimensions, 0, dims.z, &voxels);
texture
}
pub fn update_i16_slice(&self, queue: &wgpu::Queue, z_index: u32, pixels: &[i16]) {
let converted: Vec<u16> = pixels
.iter()
.map(|&value| f16::from_f32(f32::from(value)).to_bits())
.collect();
write_f16_bits(queue, &self.texture, self.dimensions, z_index, 1, &converted);
}
}
fn write_f16_bits(
queue: &wgpu::Queue,
texture: &wgpu::Texture,
dimensions: glam::UVec3,
start_z: u32,
depth: u32,
voxels: &[u16],
) {
let slice_len = (dimensions.x * dimensions.y) as usize;
let max_chunk_depth =
((4 * 1024 * 1024) / (slice_len.max(1) * std::mem::size_of::<u16>())).max(1) as u32;
let bytes_per_row = dimensions.x * std::mem::size_of::<u16>() as u32;
for chunk_start in (0..depth).step_by(max_chunk_depth as usize) {
let chunk_depth = (depth - chunk_start).min(max_chunk_depth);
let start = chunk_start as usize * slice_len;
let end = start + chunk_depth as usize * slice_len;
queue.write_texture(
wgpu::TexelCopyTextureInfo {
texture,
mip_level: 0,
origin: wgpu::Origin3d {
x: 0,
y: 0,
z: start_z + chunk_start,
},
aspect: wgpu::TextureAspect::All,
},
bytemuck::cast_slice(&voxels[start..end]),
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(bytes_per_row),
rows_per_image: Some(dimensions.y),
},
wgpu::Extent3d {
width: dimensions.x,
height: dimensions.y,
depth_or_array_layers: chunk_depth,
},
);
}
}
fn to_f16_bits(volume: &DynVolume) -> Vec<u16> {
match volume {
DynVolume::U8(v) => v
.data()
.iter()
.map(|&value| f16::from_f32(f32::from(value)).to_bits())
.collect(),
DynVolume::I8(v) => v
.data()
.iter()
.map(|&value| f16::from_f32(f32::from(value)).to_bits())
.collect(),
DynVolume::U16(v) => v
.data()
.iter()
.map(|&value| f16::from_f32(value as f32).to_bits())
.collect(),
DynVolume::I16(v) => v
.data()
.iter()
.map(|&value| f16::from_f32(f32::from(value)).to_bits())
.collect(),
DynVolume::U32(v) => v
.data()
.iter()
.map(|&value| f16::from_f32(value as f32).to_bits())
.collect(),
DynVolume::I32(v) => v
.data()
.iter()
.map(|&value| f16::from_f32(value as f32).to_bits())
.collect(),
DynVolume::F32(v) => v
.data()
.iter()
.map(|&value| f16::from_f32(value).to_bits())
.collect(),
DynVolume::F64(v) => v
.data()
.iter()
.map(|&value| f16::from_f32(value as f32).to_bits())
.collect(),
_ => unreachable!("all current DynVolume variants are handled"),
}
}