use crate::{ content, globaluniform::{ImageLocation, ImageLocations}, }; use anyhow::Result; use bytemuck::Zeroable; use galactica_packer::SpriteAtlasImage; use image::GenericImageView; use std::{collections::HashMap, fs::File, io::Read, num::NonZeroU32}; 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(&wgpu::TextureViewDescriptor::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 fps: 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, starfield_handle: content::SpriteHandle, sprites: HashMap, pub data: ImageLocations, } impl TextureArray { pub fn get_starfield_texture(&self) -> &Texture { self.sprites.get(&self.starfield_handle).unwrap() } pub fn get_texture(&self, handle: content::SpriteHandle) -> &Texture { match self.sprites.get(&handle) { Some(x) => x, None => unreachable!("Tried to get a texture that doesn't exist"), } } pub fn new(device: &wgpu::Device, queue: &wgpu::Queue, ct: &content::Content) -> Result { // Load all textures let mut texture_data = Vec::new(); let mut sprites = HashMap::new(); println!("opening image"); let mut f = File::open("atlas-0.bmp")?; let mut bytes = Vec::new(); f.read_to_end(&mut bytes)?; texture_data.push(RawTexture::from_bytes(&device, &queue, &bytes, "Atlas")?); let mut tx = ImageLocations::zeroed(); println!("sending to gpu"); let mut i = 0; for t in &ct.sprites { let loc = ct.get_image(&t.frames[0]); sprites.insert( t.handle, Texture { index: i, aspect: t.aspect, fps: t.fps, len: t.frames.len() as u32, repeat: t.repeat.as_int(), location: vec![loc.clone()], }, ); // Insert texture location data for path in &t.frames { let image = ct.get_image(&path); tx.data[i as usize] = ImageLocation { x: image.x, y: image.y, w: image.w, h: image.h, }; i += 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, sprites, starfield_handle: ct.get_starfield_handle(), data: tx, }); } }