Galactica/crates/render/src/texturearray.rs

181 lines
4.7 KiB
Rust

use crate::globaluniform::{AtlasArray, AtlasImageLocation};
use anyhow::Result;
use bytemuck::Zeroable;
use galactica_content::Content;
use galactica_packer::SpriteAtlasImage;
use galactica_util::constants::ASSET_CACHE;
use image::GenericImageView;
use log::info;
use std::{fs::File, io::Read, num::NonZeroU32, path::Path};
use wgpu::BindGroupLayout;
pub(crate) struct RawTexture {
pub(crate) view: wgpu::TextureView,
}
impl RawTexture {
pub(crate) fn from_bytes(
device: &wgpu::Device,
queue: &wgpu::Queue,
bytes: &[u8],
label: &str,
) -> Result<Self> {
let img = image::load_from_memory(bytes)?;
Self::from_image(device, queue, &img, Some(label))
}
pub(crate) fn from_image(
device: &wgpu::Device,
queue: &wgpu::Queue,
img: &image::DynamicImage,
label: Option<&str>,
) -> Result<Self> {
let rgba = img.to_rgba8();
let dimensions = img.dimensions();
let size = wgpu::Extent3d {
width: dimensions.0,
height: dimensions.1,
depth_or_array_layers: 1,
};
let texture = device.create_texture(&wgpu::TextureDescriptor {
label,
size,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8UnormSrgb,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[],
});
let view = texture.create_view(&Default::default());
queue.write_texture(
wgpu::ImageCopyTexture {
aspect: wgpu::TextureAspect::All,
texture: &texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
},
&rgba,
wgpu::ImageDataLayout {
offset: 0,
bytes_per_row: Some(4 * dimensions.0),
rows_per_image: Some(dimensions.1),
},
size,
);
Ok(Self { view })
}
}
#[derive(Debug, Clone)]
pub struct Texture {
pub index: u32, // Index in texture array
pub len: u32, // Number of frames
pub frame_duration: f32, // Frames per second
pub aspect: f32, // width / height
pub repeat: u32, // How to re-play this texture
pub location: Vec<SpriteAtlasImage>,
}
pub struct TextureArray {
pub bind_group: wgpu::BindGroup,
pub bind_group_layout: BindGroupLayout,
pub texture_atlas: AtlasArray,
}
impl TextureArray {
pub fn new(device: &wgpu::Device, queue: &wgpu::Queue, ct: &Content) -> Result<Self> {
// Load all textures
let mut texture_data = Vec::new();
for file in ct.atlas_files() {
info!("opening {file}");
let p = Path::new(ASSET_CACHE);
let mut f = File::open(p.join(file))?;
let mut bytes = Vec::new();
f.read_to_end(&mut bytes)?;
texture_data.push(RawTexture::from_bytes(
&device,
&queue,
&bytes,
&format!("Atlas `{file}`"),
)?);
}
let mut image_locations = AtlasArray::zeroed();
info!("sending to gpu");
for image in ct.get_atlas().iter_images() {
image_locations.data[image.idx.get() as usize] = AtlasImageLocation {
xpos: image.x,
ypos: image.y,
width: image.w,
height: image.h,
atlas_texture: image.atlas,
_padding: Default::default(),
};
}
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Nearest,
mipmap_filter: wgpu::FilterMode::Nearest,
..Default::default()
});
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("TextureArray bind group layout"),
entries: &[
// Texture data
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
ty: wgpu::BindingType::Texture {
multisampled: false,
view_dimension: wgpu::TextureViewDimension::D2,
sample_type: wgpu::TextureSampleType::Float { filterable: true },
},
count: NonZeroU32::new(texture_data.len() as u32),
},
// Texture sampler
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::VERTEX_FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: NonZeroU32::new(1),
},
],
});
let views: Vec<&wgpu::TextureView> = texture_data.iter().map(|x| &x.view).collect();
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("TextureArray bind group"),
layout: &bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureViewArray(&views),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::SamplerArray(&[&sampler]),
},
],
});
return Ok(Self {
bind_group,
bind_group_layout,
texture_atlas: image_locations,
});
}
}