From 3d173b74865856d360ab522c0fcfa6842ab719af Mon Sep 17 00:00:00 2001 From: Mark Date: Sun, 24 Dec 2023 16:18:36 -0800 Subject: [PATCH] Added texture loader --- src/render/gpustate.rs | 2 +- src/render/mod.rs | 2 - src/render/texturearray.rs | 147 --------------- src/render/texturearray/array.rs | 90 +++++++++ src/render/texturearray/loader.rs | 191 ++++++++++++++++++++ src/render/texturearray/mod.rs | 18 ++ src/render/{ => texturearray}/rawtexture.rs | 0 src/ship.rs | 2 +- src/system.rs | 16 +- 9 files changed, 303 insertions(+), 165 deletions(-) delete mode 100644 src/render/texturearray.rs create mode 100644 src/render/texturearray/array.rs create mode 100644 src/render/texturearray/loader.rs create mode 100644 src/render/texturearray/mod.rs rename src/render/{ => texturearray}/rawtexture.rs (100%) diff --git a/src/render/gpustate.rs b/src/render/gpustate.rs index f095996..1baceac 100644 --- a/src/render/gpustate.rs +++ b/src/render/gpustate.rs @@ -376,7 +376,7 @@ impl GPUState { self.window_size.height as f32, ], window_aspect: [self.window_aspect, 0.0], - starfield_texture: [1, 0], + starfield_texture: [self.texture_array.get_starfield().index, 0], starfield_tile_size: [STARFIELD_SIZE as f32, 0.0], }]), ); diff --git a/src/render/mod.rs b/src/render/mod.rs index 6877b1a..5e56cc4 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -1,12 +1,10 @@ mod globaldata; mod gpustate; mod pipeline; -mod rawtexture; mod texturearray; mod vertexbuffer; pub use gpustate::GPUState; -pub use texturearray::Texture; // API correction matrix. // cgmath uses OpenGL's matrix format, which diff --git a/src/render/texturearray.rs b/src/render/texturearray.rs deleted file mode 100644 index 58fd1c0..0000000 --- a/src/render/texturearray.rs +++ /dev/null @@ -1,147 +0,0 @@ -use anyhow::Result; -use serde::Deserialize; -use std::{collections::HashMap, fs::File, io::Read, num::NonZeroU32}; -use wgpu::BindGroupLayout; - -use super::rawtexture::RawTexture; - -pub struct TextureArray { - pub bind_group: wgpu::BindGroup, - pub bind_group_layout: BindGroupLayout, - textures: HashMap, - error_texture: Texture, -} - -#[derive(Debug, Clone, Copy)] -pub struct Texture { - pub index: u32, // Index in texture array - pub aspect: f32, // width / height -} - -#[derive(Deserialize)] -struct Textures { - texture: Vec, - error: Tex, -} - -#[derive(Deserialize)] -struct Tex { - name: String, - path: String, -} - -impl Tex { - pub fn read(&self, device: &wgpu::Device, queue: &wgpu::Queue) -> Result { - let p = format!("{}/{}", TextureArray::INDEX_ROOT, self.path); - let mut f = File::open(&p)?; - let mut bytes = Vec::new(); - f.read_to_end(&mut bytes)?; - RawTexture::from_bytes(&device, &queue, &bytes, &self.name) - } -} - -impl TextureArray { - const INDEX_PATH: &'static str = "assets.ignore/index.toml"; - const INDEX_ROOT: &'static str = "assets.ignore"; - - pub fn get_texture(&self, name: &str) -> Texture { - match self.textures.get(name) { - Some(x) => *x, - None => self.error_texture, - } - } - - pub fn new(device: &wgpu::Device, queue: &wgpu::Queue) -> Result { - // Load all textures - - let (error_texture, textures, texture_data) = { - let mut texture_data: Vec = Vec::new(); - let mut textures: HashMap = HashMap::new(); - - let mut texture_index_string = String::new(); - let _ = File::open(Self::INDEX_PATH)?.read_to_string(&mut texture_index_string); - let texture_index: Textures = toml::from_str(&texture_index_string)?; - - // First texture is always error - let raw = texture_index.error.read(device, queue)?; - let error_texture = Texture { - index: 0, - aspect: raw.dimensions.0 as f32 / raw.dimensions.1 as f32, - }; - texture_data.push(raw); - - // Load the rest - let mut i = 1; - for t in texture_index.texture { - let raw = t.read(device, queue)?; - let tx = Texture { - index: i, - aspect: raw.dimensions.0 as f32 / raw.dimensions.1 as f32, - }; - textures.insert(t.name.clone(), tx); - texture_data.push(raw); - i += 1; - } - - (error_texture, textures, texture_data) - }; - - 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("texture_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(texture_data.len() as u32), - }, - ], - }); - - let views: Vec<&wgpu::TextureView> = texture_data.iter().map(|x| &x.view).collect(); - let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("texture_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].repeat(views.len())), - }, - ], - }); - - return Ok(Self { - bind_group, - bind_group_layout, - textures, - error_texture, - }); - } -} diff --git a/src/render/texturearray/array.rs b/src/render/texturearray/array.rs new file mode 100644 index 0000000..017098a --- /dev/null +++ b/src/render/texturearray/array.rs @@ -0,0 +1,90 @@ +use anyhow::Result; +use std::{collections::HashMap, num::NonZeroU32, path::PathBuf}; +use wgpu::BindGroupLayout; + +use super::{loader::TextureLoader, Texture, TextureArrayConfig}; + +pub struct TextureArray { + pub bind_group: wgpu::BindGroup, + pub bind_group_layout: BindGroupLayout, + textures: HashMap, + config: TextureArrayConfig, +} + +impl TextureArray { + const INDEX_PATH: &'static str = "assets.ignore"; + + pub fn get_starfield(&self) -> Texture { + return self.get_texture(&self.config.starfield); + } + + pub fn get_texture(&self, name: &str) -> Texture { + match self.textures.get(name) { + Some(x) => *x, + None => self.get_texture(&self.config.error), + } + } + + pub fn new(device: &wgpu::Device, queue: &wgpu::Queue) -> Result { + // Load all textures + let loader = TextureLoader::load(device, queue, &PathBuf::from(Self::INDEX_PATH))?; + + 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("texture_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(loader.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(loader.texture_data.len() as u32), + }, + ], + }); + + let views: Vec<&wgpu::TextureView> = loader.texture_data.iter().map(|x| &x.view).collect(); + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("texture_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].repeat(views.len())), + }, + ], + }); + + return Ok(Self { + bind_group, + bind_group_layout, + textures: loader.textures, + config: loader.config, + }); + } +} diff --git a/src/render/texturearray/loader.rs b/src/render/texturearray/loader.rs new file mode 100644 index 0000000..64b6f6c --- /dev/null +++ b/src/render/texturearray/loader.rs @@ -0,0 +1,191 @@ +use anyhow::Result; +use std::{collections::HashMap, fs::File, io::Read, path::Path}; + +use super::{rawtexture::RawTexture, Texture, TextureArrayConfig}; + +mod fields { + use super::{File, Path, RawTexture, Read, Result, TextureArrayConfig}; + use serde::Deserialize; + + // Config is used outside of this file, + // so it is defined in mod.rs. + + #[derive(Deserialize)] + pub struct Index { + pub config: TextureArrayConfig, + pub include: Option>, + pub texture: Vec, + } + + impl Index { + pub fn to_sub(self) -> (TextureArrayConfig, SubIndex) { + ( + self.config, + SubIndex { + include: self.include, + texture: self.texture, + }, + ) + } + } + + #[derive(Deserialize)] + pub struct SubIndex { + pub include: Option>, + pub texture: Vec, + } + + #[derive(Deserialize)] + pub struct Texture { + pub name: String, + pub path: String, + } + + impl Texture { + pub fn read( + &self, + device: &wgpu::Device, + queue: &wgpu::Queue, + root: &Path, + ) -> Result { + let mut f = File::open(root.join(&self.path))?; + let mut bytes = Vec::new(); + f.read_to_end(&mut bytes)?; + RawTexture::from_bytes(&device, &queue, &bytes, &self.name) + } + } +} + +pub struct TextureLoader { + pub textures: HashMap, + pub texture_data: Vec, + pub config: TextureArrayConfig, +} + +impl TextureLoader { + const INDEX_NAME: &'static str = "_index.toml"; + + pub fn load(device: &wgpu::Device, queue: &wgpu::Queue, index_path: &Path) -> Result { + let index: fields::Index = { + let mut texture_index_string = String::new(); + let _ = File::open(index_path.join(Self::INDEX_NAME))? + .read_to_string(&mut texture_index_string); + toml::from_str(&texture_index_string)? + }; + + let (config, index) = index.to_sub(); + let texture_raw = + Self::inner_load_textures(device, queue, index, index_path, "".to_string())?; + + // TODO: validate configuration + + let mut textures = HashMap::new(); + let mut texture_data = Vec::new(); + let mut i = 0; + for (name, data) in texture_raw { + textures.insert( + name, + Texture { + index: i, + aspect: data.dimensions.0 as f32 / data.dimensions.1 as f32, + }, + ); + texture_data.push(data); + i += 1; + } + + return Ok(Self { + textures, + texture_data, + config, + }); + } + + fn inner_load_textures( + device: &wgpu::Device, + queue: &wgpu::Queue, + index: fields::SubIndex, + texture_root: &Path, + prefix: String, + ) -> Result> { + // Load all textures + let mut texture_raw = HashMap::new(); + for t in index.texture { + let data = match t.read(device, queue, &texture_root) { + Ok(r) => r, + Err(_) => { + println!( + "[WARNING] Failed to load texture `{}` from `{}`: file doesn't exist.", + if prefix.is_empty() { + t.name + } else { + format!("{prefix}::{}", t.name) + }, + texture_root.display(), + ); + continue; + } + }; + + if texture_raw.contains_key(&t.name) { + println!( + "[WARNING] Subindex in `{}` has a duplicate texture `{}`, skipping.", + texture_root.display(), + if prefix.is_empty() { + t.name + } else { + format!("{prefix}::{}", t.name) + }, + ); + continue; + } + + texture_raw.insert( + if prefix.is_empty() { + t.name + } else { + format!("{prefix}::{}", t.name) + }, + data, + ); + } + + // Load included files + if let Some(include) = index.include { + for i in include { + let sub_root = texture_root.join(&i); + let index: fields::SubIndex = { + let mut texture_index_string = String::new(); + let _ = File::open(sub_root.join(Self::INDEX_NAME))? + .read_to_string(&mut texture_index_string); + toml::from_str(&texture_index_string)? + }; + + let sub_textures = Self::inner_load_textures( + device, + queue, + index, + &sub_root, + if prefix.is_empty() { + i + } else { + format!("{prefix}::{i}") + }, + )?; + for (s, data) in sub_textures { + if texture_raw.contains_key(&s) { + println!( + "[WARNING] Subindex in `{}` has a duplicate texture `{}`, skipping.", + sub_root.display(), + &s, + ); + continue; + } + texture_raw.insert(s, data); + } + } + } + + return Ok(texture_raw); + } +} diff --git a/src/render/texturearray/mod.rs b/src/render/texturearray/mod.rs new file mode 100644 index 0000000..9da07c3 --- /dev/null +++ b/src/render/texturearray/mod.rs @@ -0,0 +1,18 @@ +mod array; +mod loader; +mod rawtexture; + +pub use array::TextureArray; +use serde::Deserialize; + +#[derive(Debug, Clone, Copy)] +pub struct Texture { + pub index: u32, // Index in texture array + pub aspect: f32, // width / height +} + +#[derive(Deserialize)] +pub struct TextureArrayConfig { + pub error: String, + pub starfield: String, +} diff --git a/src/render/rawtexture.rs b/src/render/texturearray/rawtexture.rs similarity index 100% rename from src/render/rawtexture.rs rename to src/render/texturearray/rawtexture.rs diff --git a/src/ship.rs b/src/ship.rs index 65f7efc..f87c842 100644 --- a/src/ship.rs +++ b/src/ship.rs @@ -9,7 +9,7 @@ pub enum ShipKind { impl ShipKind { fn sprite(&self) -> &'static str { match self { - Self::Gypsum => "gypsum", + Self::Gypsum => "ship::gypsum", } } diff --git a/src/system.rs b/src/system.rs index b522b0f..ce337f2 100644 --- a/src/system.rs +++ b/src/system.rs @@ -48,7 +48,7 @@ impl System { s.bodies.push(Doodad { pos: (0.0, 0.0).into(), - sprite: "a0".to_owned(), + sprite: "star::star".to_owned(), parallax: 10.0, height: 80.0, }); @@ -60,23 +60,11 @@ impl System { angle: Deg { 0: 31.0 }, } .to_cartesian(), - sprite: "earth".to_owned(), + sprite: "planet::earth".to_owned(), parallax: 5.0, height: 120.0, }); - s.bodies.push(Doodad { - pos: Polar { - center: (0.0, 0.0).into(), - radius: 200.0, - angle: Deg { 0: 270.0 }, - } - .to_cartesian(), - sprite: "small".to_owned(), - parallax: 0.5, - height: 50.0, - }); - return s; }