use crate::globaluniform::{AtlasArray, AtlasImageLocation, SpriteData, SpriteDataArray}; use anyhow::Result; use bytemuck::Zeroable; use galactica_content::Content; use galactica_packer::SpriteAtlasImage; use galactica_util::constants::ASSET_CACHE; use image::GenericImageView; 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 { 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 { 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, } pub struct TextureArray { pub bind_group: wgpu::BindGroup, pub bind_group_layout: BindGroupLayout, pub image_locations: AtlasArray, pub sprite_data: SpriteDataArray, } impl TextureArray { pub fn new(device: &wgpu::Device, queue: &wgpu::Queue, ct: &Content) -> Result { // Load all textures let mut texture_data = Vec::new(); for a in ct.atlas_files() { println!("opening {a}"); let p = Path::new(ASSET_CACHE); let mut f = File::open(p.join(a))?; let mut bytes = Vec::new(); f.read_to_end(&mut bytes)?; texture_data.push(RawTexture::from_bytes( &device, &queue, &bytes, &format!("Atlas `{a}`"), )?); } let mut image_locations = AtlasArray::zeroed(); let mut sprite_data = SpriteDataArray::zeroed(); println!("sending to gpu"); let mut image_counter = 0; let mut sprite_counter = 0; for t in &ct.sprites { sprite_data.data[sprite_counter] = SpriteData { frame_count: t.frames.len() as u32, repeatmode: t.repeat.as_int(), aspect: t.aspect, frame_duration: t.frame_duration, first_frame: image_counter, _padding: Default::default(), }; sprite_counter += 1; // Insert texture location data for path in &t.frames { let image = ct.get_image(&path); image_locations.data[image_counter as usize] = AtlasImageLocation { xpos: image.x, ypos: image.y, width: image.w, height: image.h, atlas_texture: image.atlas, _padding: Default::default(), }; image_counter += 1; } } 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, image_locations, sprite_data, }); } }