Added texture loader

master
Mark 2023-12-24 16:18:36 -08:00
parent 08dd0e1c64
commit 3d173b7486
Signed by: Mark
GPG Key ID: C6D63995FE72FD80
9 changed files with 303 additions and 165 deletions

View File

@ -376,7 +376,7 @@ impl GPUState {
self.window_size.height as f32, self.window_size.height as f32,
], ],
window_aspect: [self.window_aspect, 0.0], 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], starfield_tile_size: [STARFIELD_SIZE as f32, 0.0],
}]), }]),
); );

View File

@ -1,12 +1,10 @@
mod globaldata; mod globaldata;
mod gpustate; mod gpustate;
mod pipeline; mod pipeline;
mod rawtexture;
mod texturearray; mod texturearray;
mod vertexbuffer; mod vertexbuffer;
pub use gpustate::GPUState; pub use gpustate::GPUState;
pub use texturearray::Texture;
// API correction matrix. // API correction matrix.
// cgmath uses OpenGL's matrix format, which // cgmath uses OpenGL's matrix format, which

View File

@ -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<String, Texture>,
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<Tex>,
error: Tex,
}
#[derive(Deserialize)]
struct Tex {
name: String,
path: String,
}
impl Tex {
pub fn read(&self, device: &wgpu::Device, queue: &wgpu::Queue) -> Result<RawTexture> {
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<Self> {
// Load all textures
let (error_texture, textures, texture_data) = {
let mut texture_data: Vec<RawTexture> = Vec::new();
let mut textures: HashMap<String, Texture> = 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,
});
}
}

View File

@ -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<String, Texture>,
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<Self> {
// 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,
});
}
}

View File

@ -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<Vec<String>>,
pub texture: Vec<Texture>,
}
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<Vec<String>>,
pub texture: Vec<Texture>,
}
#[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<RawTexture> {
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<String, Texture>,
pub texture_data: Vec<RawTexture>,
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<Self> {
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<HashMap<String, RawTexture>> {
// 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);
}
}

View File

@ -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,
}

View File

@ -9,7 +9,7 @@ pub enum ShipKind {
impl ShipKind { impl ShipKind {
fn sprite(&self) -> &'static str { fn sprite(&self) -> &'static str {
match self { match self {
Self::Gypsum => "gypsum", Self::Gypsum => "ship::gypsum",
} }
} }

View File

@ -48,7 +48,7 @@ impl System {
s.bodies.push(Doodad { s.bodies.push(Doodad {
pos: (0.0, 0.0).into(), pos: (0.0, 0.0).into(),
sprite: "a0".to_owned(), sprite: "star::star".to_owned(),
parallax: 10.0, parallax: 10.0,
height: 80.0, height: 80.0,
}); });
@ -60,23 +60,11 @@ impl System {
angle: Deg { 0: 31.0 }, angle: Deg { 0: 31.0 },
} }
.to_cartesian(), .to_cartesian(),
sprite: "earth".to_owned(), sprite: "planet::earth".to_owned(),
parallax: 5.0, parallax: 5.0,
height: 120.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; return s;
} }