use crate::{
assets::upload::Asset,
ecs::system::Res,
wgpu::{
backend::WGPUBackend,
gpu_context::GpuContext,
mipmap::MipmapGenerator,
textures::{bytes_per_pixel, decode_file, write_texture_level0},
},
};
pub struct CubemapDescriptor {
pub size: u32,
pub format: wgpu::TextureFormat,
pub faces: Option<[Vec<u8>; 6]>,
pub face_files: Option<[&'static str; 6]>,
pub generate_mips: bool,
}
impl CubemapDescriptor {
pub fn from_files(size: u32, files: [&'static str; 6]) -> Self {
Self {
size,
format: wgpu::TextureFormat::Rgba8UnormSrgb,
faces: None,
face_files: Some(files),
generate_mips: false,
}
}
pub fn from_faces(size: u32, format: wgpu::TextureFormat, faces: [Vec<u8>; 6]) -> Self {
Self {
size,
format,
faces: Some(faces),
face_files: None,
generate_mips: false,
}
}
pub fn empty(size: u32, format: wgpu::TextureFormat) -> Self {
Self {
size,
format,
faces: None,
face_files: None,
generate_mips: false,
}
}
pub fn with_format(mut self, format: wgpu::TextureFormat) -> Self {
self.format = format;
self
}
pub fn with_mips(mut self) -> Self {
self.generate_mips = true;
self
}
fn wgpu_descriptor(&self, mip_count: u32, render_target: bool) -> wgpu::TextureDescriptor<'_> {
let mut usage = super::mipmap::texture_usage(mip_count);
if render_target {
usage |= wgpu::TextureUsages::RENDER_ATTACHMENT;
}
wgpu::TextureDescriptor {
label: None,
size: wgpu::Extent3d {
width: self.size,
height: self.size,
depth_or_array_layers: 6,
},
mip_level_count: mip_count,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: self.format,
usage,
view_formats: &[],
}
}
}
pub struct GPUCubemap {
texture: wgpu::Texture,
view: wgpu::TextureView,
size: u32,
format: wgpu::TextureFormat,
ctx: GpuContext,
}
impl GPUCubemap {
pub fn write_face(&self, face: u32, pixels: &[u8]) {
write_texture_level0(self.ctx.queue(), &self.texture, face, self.format, self.size, self.size, pixels);
}
pub fn size(&self) -> u32 {
self.size
}
pub(crate) fn view(&self) -> &wgpu::TextureView {
&self.view
}
}
impl Asset<WGPUBackend> for GPUCubemap {
type Source = CubemapDescriptor;
type Deps<'a> = Res<'a, MipmapGenerator>;
fn upload<'a>(
source: &CubemapDescriptor,
backend: &WGPUBackend,
mipmap_generator: &Res<'a, MipmapGenerator>,
) -> Option<Self> {
let faces: Option<[Vec<u8>; 6]> = if let Some(files) = &source.face_files {
let mut out: [Vec<u8>; 6] = Default::default();
for (i, path) in files.iter().enumerate() {
let (w, h, data) = decode_file(path, source.format)?;
if w != source.size || h != source.size {
tracing::error!(
"CubemapSpec: face {i} ('{path}') is {w}x{h}, expected {0}x{0}",
source.size
);
return None;
}
out[i] = data;
}
Some(out)
} else {
source.faces.clone()
};
let mip_count = super::mipmap::mip_count(source.size, source.generate_mips);
let texture = backend
.device
.create_texture(&source.wgpu_descriptor(mip_count, faces.is_none()));
if let Some(faces) = &faces {
for (face, data) in faces.iter().enumerate() {
backend.queue.write_texture(
wgpu::TexelCopyTextureInfo {
texture: &texture,
mip_level: 0,
origin: wgpu::Origin3d {
x: 0,
y: 0,
z: face as u32,
},
aspect: wgpu::TextureAspect::All,
},
data,
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(bytes_per_pixel(source.format) * source.size),
rows_per_image: Some(source.size),
},
wgpu::Extent3d {
width: source.size,
height: source.size,
depth_or_array_layers: 1,
},
);
}
if mip_count > 1 {
mipmap_generator.generate_mips(
&backend.device,
&backend.queue,
&texture,
source.format,
mip_count,
6,
);
}
}
let view = texture.create_view(&wgpu::TextureViewDescriptor {
dimension: Some(wgpu::TextureViewDimension::Cube),
..Default::default()
});
Some(Self {
texture,
view,
size: source.size,
format: source.format,
ctx: GpuContext::from_backend(backend),
})
}
}
crate::wgpu::plugin_macros::mipmap_asset_plugin! {
CubemapPlugin, GPUCubemap
}