Added texture loader
parent
08dd0e1c64
commit
3d173b7486
|
@ -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],
|
||||||
}]),
|
}]),
|
||||||
);
|
);
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,
|
||||||
|
}
|
|
@ -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",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue