use anyhow::Result; use galactica_content::{Content, TextureHandle}; use image::GenericImageView; use std::{collections::HashMap, fs::File, io::Read, num::NonZeroU32}; use wgpu::BindGroupLayout; pub(super) struct RawTexture { pub(super) view: wgpu::TextureView, } impl RawTexture { pub(super) 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(super) 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, Copy)] pub struct Texture { pub index: u32, // Index in texture array pub aspect: f32, // width / height } pub struct TextureArray { pub bind_group: wgpu::BindGroup, pub bind_group_layout: BindGroupLayout, starfield_handle: TextureHandle, textures: HashMap, } impl TextureArray { pub fn get_starfield_texture(&self) -> Texture { *self.textures.get(&self.starfield_handle).unwrap() } pub fn get_texture(&self, handle: TextureHandle) -> Texture { match self.textures.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) -> Result { // Load all textures let mut texture_data = Vec::new(); let mut textures = HashMap::new(); for t in &ct.textures { let mut f = File::open(&t.path)?; let mut bytes = Vec::new(); f.read_to_end(&mut bytes)?; textures.insert( t.handle, Texture { index: texture_data.len() as u32, aspect: t.handle.aspect, }, ); texture_data.push(RawTexture::from_bytes(&device, &queue, &bytes, &t.name)?); } 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::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::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, // Array of all views resource: wgpu::BindingResource::TextureViewArray(&views), }, wgpu::BindGroupEntry { binding: 1, resource: wgpu::BindingResource::SamplerArray(&[&sampler]), }, ], }); return Ok(Self { bind_group, bind_group_layout, textures: textures, starfield_handle: ct.get_starfield_handle(), }); } }