Compare commits

...

15 Commits

Author SHA1 Message Date
SeanOMik e6da582ee3
Merge branch 'feature/gltf-loading'
ci/woodpecker/push/woodpecker Pipeline failed Details
2023-10-21 22:30:21 -04:00
SeanOMik 970e3ac8a3
Add woodpecker ci pipeline
ci/woodpecker/push/woodpecker Pipeline failed Details
2023-10-21 22:28:08 -04:00
SeanOMik 7ae59c0415
Loading textures from gltf blob and gltf.bin's, fix loading multiple meshses in a single model 2023-10-21 22:19:34 -04:00
SeanOMik fd9f4bee2a
Implement loading material textures from gltf and rendering them 2023-10-17 22:04:25 -04:00
SeanOMik 02a0eea7b3
Don't force loaded model indicies to U32 2023-10-08 00:03:53 -04:00
SeanOMik 8f7288339d
Add default texture which fixes render error, fix cube rendering 2023-10-05 11:42:24 -04:00
SeanOMik a5b145c9b3
Create a testbed example to make developing the engine easier 2023-09-29 14:57:22 -04:00
SeanOMik fdf1c4d338
Start implementing the new Model and Mesh types with the renderer 2023-09-29 14:46:08 -04:00
SeanOMik 9d6d51af83
Load materials from gltf 2023-09-29 14:20:28 -04:00
SeanOMik 792596078d
Create a material type for loading materials 2023-09-29 13:00:33 -04:00
SeanOMik dabc051b58
Add ModelComponent, add model loader to resource manager's default loaders 2023-09-26 17:14:38 -04:00
SeanOMik 64817b6142
Add MeshVertexAttribute instead of directly storing positions 2023-09-22 12:42:36 -04:00
SeanOMik e76ca1ec50
Write a very experimental gltf loader 2023-09-21 23:11:09 -04:00
SeanOMik dddf6123c4
Continue working on gltf loader 2023-09-21 17:27:21 -04:00
SeanOMik 38e7b543c0
Start working on gltf 2023-09-21 14:22:46 -04:00
47 changed files with 5590 additions and 318 deletions

2
.gitignore vendored
View File

@ -1 +1 @@
/target target

10
.vscode/launch.json vendored
View File

@ -7,20 +7,20 @@
{ {
"type": "lldb", "type": "lldb",
"request": "launch", "request": "launch",
"name": "Debug executable 'lyra-engine'", "name": "Debug lyra testbed",
"cargo": { "cargo": {
"args": [ "args": [
"build", "build",
"--bin=lyra-engine", "--manifest-path", "${workspaceFolder}/examples/testbed/Cargo.toml"
"--package=lyra-engine" //"--bin=testbed",
], ],
"filter": { "filter": {
"name": "lyra-engine", "name": "testbed",
"kind": "bin" "kind": "bin"
} }
}, },
"args": [], "args": [],
"cwd": "${workspaceFolder}" "cwd": "${workspaceFolder}/examples/testbed"
}, },
{ {
"type": "lldb", "type": "lldb",

6
.woodpecker.yml Normal file
View File

@ -0,0 +1,6 @@
steps:
build:
image: rust:1.73
commands:
- cargo build --release
- cargo test

156
Cargo.lock generated
View File

@ -297,6 +297,18 @@ dependencies = [
"rustc-demangle", "rustc-demangle",
] ]
[[package]]
name = "base64"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
[[package]]
name = "base64"
version = "0.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2"
[[package]] [[package]]
name = "bit-set" name = "bit-set"
version = "0.5.3" version = "0.5.3"
@ -890,6 +902,44 @@ dependencies = [
"web-sys", "web-sys",
] ]
[[package]]
name = "gltf"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad2dcfb6dd7a66f9eb3d181a29dcfb22d146b0bcdc2e1ed1713cbf03939a88ea"
dependencies = [
"base64 0.13.1",
"byteorder",
"gltf-json",
"image",
"lazy_static",
"urlencoding",
]
[[package]]
name = "gltf-derive"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2cbcea5dd47e7ad4e9ee6f040384fcd7204bbf671aa4f9e7ca7dfc9bfa1de20"
dependencies = [
"inflections",
"proc-macro2",
"quote",
"syn 2.0.26",
]
[[package]]
name = "gltf-json"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d5b810806b78dde4b71a95cc0e6fdcab34c4c617da3574df166f9987be97d03"
dependencies = [
"gltf-derive",
"serde",
"serde_derive",
"serde_json",
]
[[package]] [[package]]
name = "gpu-alloc" name = "gpu-alloc"
version = "0.5.4" version = "0.5.4"
@ -1041,6 +1091,18 @@ dependencies = [
"hashbrown 0.14.0", "hashbrown 0.14.0",
] ]
[[package]]
name = "infer"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb33622da908807a06f9513c19b3c1ad50fab3e4137d82a78107d502075aa199"
[[package]]
name = "inflections"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a257582fdcde896fd96463bf2d40eefea0580021c0712a0e2b028b60b47a837a"
[[package]] [[package]]
name = "instant" name = "instant"
version = "0.1.12" version = "0.1.12"
@ -1232,6 +1294,7 @@ dependencies = [
"tracing-appender", "tracing-appender",
"tracing-log", "tracing-log",
"tracing-subscriber", "tracing-subscriber",
"uuid",
"wgpu", "wgpu",
"winit", "winit",
] ]
@ -1241,8 +1304,16 @@ name = "lyra-resource"
version = "0.0.1" version = "0.0.1"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"base64 0.21.4",
"edict",
"glam",
"gltf",
"image", "image",
"infer",
"mime",
"percent-encoding",
"thiserror", "thiserror",
"tracing",
"uuid", "uuid",
] ]
@ -1311,6 +1382,12 @@ dependencies = [
"objc", "objc",
] ]
[[package]]
name = "mime"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]] [[package]]
name = "minimal-lexical" name = "minimal-lexical"
version = "0.2.1" version = "0.2.1"
@ -1476,7 +1553,7 @@ checksum = "e63899ad0da84ce718c14936262a41cee2c79c981fc0a0e7c7beb47d5a07e8c1"
dependencies = [ dependencies = [
"num-integer", "num-integer",
"num-traits", "num-traits",
"rand", "rand 0.4.6",
"rustc-serialize", "rustc-serialize",
] ]
@ -1791,6 +1868,12 @@ dependencies = [
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
[[package]]
name = "ppv-lite86"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]] [[package]]
name = "proc-easy" name = "proc-easy"
version = "0.3.0" version = "0.3.0"
@ -1858,6 +1941,27 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core 0.6.4",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core 0.6.4",
]
[[package]] [[package]]
name = "rand_core" name = "rand_core"
version = "0.3.1" version = "0.3.1"
@ -1873,6 +1977,15 @@ version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]] [[package]]
name = "range-alloc" name = "range-alloc"
version = "0.1.3" version = "0.1.3"
@ -1963,6 +2076,12 @@ dependencies = [
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
[[package]]
name = "ryu"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
[[package]] [[package]]
name = "scoped-tls" name = "scoped-tls"
version = "1.0.1" version = "1.0.1"
@ -1994,6 +2113,28 @@ version = "1.0.185"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be9b6f69f1dfd54c3b568ffa45c310d6973a5e5148fd40cf515acaf38cf5bc31" checksum = "be9b6f69f1dfd54c3b568ffa45c310d6973a5e5148fd40cf515acaf38cf5bc31"
[[package]]
name = "serde_derive"
version = "1.0.179"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "741e124f5485c7e60c03b043f79f320bff3527f4bbf12cf3831750dc46a0ec2c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.26",
]
[[package]]
name = "serde_json"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]] [[package]]
name = "sharded-slab" name = "sharded-slab"
version = "0.1.4" version = "0.1.4"
@ -2372,12 +2513,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
[[package]] [[package]]
name = "uuid" name = "urlencoding"
version = "1.4.1" version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d" checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da"
[[package]]
name = "uuid"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "88ad59a7560b41a70d191093a945f0b87bc1deeda46fb237479708a1d6b6cdfc"
dependencies = [ dependencies = [
"getrandom", "getrandom",
"rand 0.8.5",
] ]
[[package]] [[package]]

View File

@ -36,3 +36,4 @@ aligned-vec = "0.5.0"
tracing-appender = "0.2.2" tracing-appender = "0.2.2"
stopwatch = "0.0.7" stopwatch = "0.0.7"
petgraph = "0.6.4" petgraph = "0.6.4"
uuid = { version = "1.5.0", features = ["v4", "fast-rng"] }

3121
examples/testbed/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,15 @@
[package]
name = "testbed"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
lyra-engine = { path = "../../", version = "0.0.1" }
anyhow = "1.0.75"
async-std = "1.12.0"
tracing = "0.1.37"
fps_counter = "2.0.0"
[workspace]

Binary file not shown.

View File

@ -0,0 +1,121 @@
{
"asset":{
"generator":"Khronos glTF Blender I/O v3.6.5",
"version":"2.0"
},
"scene":0,
"scenes":[
{
"name":"Scene",
"nodes":[
0
]
}
],
"nodes":[
{
"mesh":0,
"name":"Cube"
}
],
"materials":[
{
"doubleSided":true,
"name":"Material",
"pbrMetallicRoughness":{
"baseColorFactor":[
0.800000011920929,
0.800000011920929,
0.800000011920929,
1
],
"metallicFactor":0,
"roughnessFactor":0.5
}
}
],
"meshes":[
{
"name":"Cube",
"primitives":[
{
"attributes":{
"POSITION":0,
"TEXCOORD_0":1,
"NORMAL":2
},
"indices":3,
"material":0
}
]
}
],
"accessors":[
{
"bufferView":0,
"componentType":5126,
"count":24,
"max":[
1,
1,
1
],
"min":[
-1,
-1,
-1
],
"type":"VEC3"
},
{
"bufferView":1,
"componentType":5126,
"count":24,
"type":"VEC2"
},
{
"bufferView":2,
"componentType":5126,
"count":24,
"type":"VEC3"
},
{
"bufferView":3,
"componentType":5123,
"count":36,
"type":"SCALAR"
}
],
"bufferViews":[
{
"buffer":0,
"byteLength":288,
"byteOffset":0,
"target":34962
},
{
"buffer":0,
"byteLength":192,
"byteOffset":288,
"target":34962
},
{
"buffer":0,
"byteLength":288,
"byteOffset":480,
"target":34962
},
{
"buffer":0,
"byteLength":72,
"byteOffset":768,
"target":34963
}
],
"buffers":[
{
"byteLength":840,
"uri":"data:application/octet-stream;base64,AACAPwAAgD8AAIC/AACAPwAAgD8AAIC/AACAPwAAgD8AAIC/AACAPwAAgL8AAIC/AACAPwAAgL8AAIC/AACAPwAAgL8AAIC/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgL8AAIA/AACAPwAAgL8AAIA/AACAPwAAgL8AAIA/AACAvwAAgD8AAIC/AACAvwAAgD8AAIC/AACAvwAAgD8AAIC/AACAvwAAgL8AAIC/AACAvwAAgL8AAIC/AACAvwAAgL8AAIC/AACAvwAAgD8AAIA/AACAvwAAgD8AAIA/AACAvwAAgD8AAIA/AACAvwAAgL8AAIA/AACAvwAAgL8AAIA/AACAvwAAgL8AAIA/AAAgPwAAAD8AACA/AAAAPwAAID8AAAA/AADAPgAAAD8AAMA+AAAAPwAAwD4AAAA/AAAgPwAAgD4AACA/AACAPgAAID8AAIA+AADAPgAAgD4AAMA+AACAPgAAwD4AAIA+AAAgPwAAQD8AACA/AABAPwAAYD8AAAA/AAAAPgAAAD8AAMA+AABAPwAAwD4AAEA/AAAgPwAAAAAAACA/AACAPwAAYD8AAIA+AAAAPgAAgD4AAMA+AAAAAAAAwD4AAIA/AAAAAAAAAAAAAIC/AAAAAAAAgD8AAACAAACAPwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIC/AACAPwAAAAAAAACAAAAAAAAAAAAAAIA/AAAAAAAAgD8AAACAAACAPwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIA/AACAPwAAAAAAAACAAACAvwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAAAAAgD8AAACAAAAAAAAAgL8AAACAAACAvwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAAAAAAAAAAIA/AACAvwAAAAAAAACAAAAAAAAAgD8AAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIA/AACAvwAAAAAAAACAAQAOABQAAQAUAAcACgAGABIACgASABYAFwATAAwAFwAMABAADwADAAkADwAJABUABQACAAgABQAIAAsAEQANAAAAEQAAAAQA"
}
]
}

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

View File

@ -0,0 +1,137 @@
{
"asset":{
"generator":"Khronos glTF Blender I/O v3.6.6",
"version":"2.0"
},
"scene":0,
"scenes":[
{
"name":"Scene",
"nodes":[
0
]
}
],
"nodes":[
{
"mesh":0,
"name":"Cube"
}
],
"materials":[
{
"doubleSided":true,
"name":"Material",
"pbrMetallicRoughness":{
"baseColorTexture":{
"index":0
},
"metallicFactor":0,
"roughnessFactor":0.5
}
}
],
"meshes":[
{
"name":"Cube",
"primitives":[
{
"attributes":{
"POSITION":0,
"TEXCOORD_0":1,
"NORMAL":2
},
"indices":3,
"material":0
}
]
}
],
"textures":[
{
"sampler":0,
"source":0
}
],
"images":[
{
"mimeType":"image/png",
"name":"uvgrid",
"uri":"uvgrid.png"
}
],
"accessors":[
{
"bufferView":0,
"componentType":5126,
"count":24,
"max":[
1,
1,
1
],
"min":[
-1,
-1,
-1
],
"type":"VEC3"
},
{
"bufferView":1,
"componentType":5126,
"count":24,
"type":"VEC2"
},
{
"bufferView":2,
"componentType":5126,
"count":24,
"type":"VEC3"
},
{
"bufferView":3,
"componentType":5123,
"count":36,
"type":"SCALAR"
}
],
"bufferViews":[
{
"buffer":0,
"byteLength":288,
"byteOffset":0,
"target":34962
},
{
"buffer":0,
"byteLength":192,
"byteOffset":288,
"target":34962
},
{
"buffer":0,
"byteLength":288,
"byteOffset":480,
"target":34962
},
{
"buffer":0,
"byteLength":72,
"byteOffset":768,
"target":34963
}
],
"samplers":[
{
"magFilter":9729,
"minFilter":9987
}
],
"buffers":[
{
"byteLength":840,
"uri":"texture-sep.bin"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View File

@ -0,0 +1,214 @@
use std::ops::DerefMut;
use lyra_engine::{math, ecs::{World, components::{mesh::MeshComponent, transform::TransformComponent, camera::CameraComponent, model::ModelComponent}, atomicell::{Ref, RefMut}, EventQueue}, render::{mesh::Mesh, material::Material, vertex::Vertex, window::{CursorGrabMode, WindowOptions}}, math::Transform, input::{KeyCode, InputButtons, MouseMotion}, game::Game, change_tracker::Ct};
use lyra_engine::assets::{ResourceManager, Texture, Model};
use tracing::debug;
/* pub const VERTICES: &[Vertex] = &[
Vertex { position: [-0.0868241, 0.49240386, 0.0], tex_coords: [0.4131759, 0.00759614], }, // A
Vertex { position: [-0.49513406, 0.06958647, 0.0], tex_coords: [0.0048659444, 0.43041354], }, // B
Vertex { position: [-0.21918549, -0.44939706, 0.0], tex_coords: [0.28081453, 0.949397], }, // C
Vertex { position: [0.35966998, -0.3473291, 0.0], tex_coords: [0.85967, 0.84732914], }, // D
Vertex { position: [0.44147372, 0.2347359, 0.0], tex_coords: [0.9414737, 0.2652641], }, // E
]; */
pub const INDICES: &[u16] = &[
0, 1, 4,
1, 2, 4,
2, 3, 4,
];
#[derive(Debug, Default)]
struct Point2d {
x: i32,
y: i32,
}
impl std::fmt::Display for Point2d {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "(x={}, y={})", self.x, self.y)
}
}
impl Point2d {
pub fn new(x: i32, y: i32) -> Self {
Self {
x,
y,
}
}
}
#[derive(Debug, Default)]
struct Point3d {
x: i32,
y: i32,
z: i32,
}
impl std::fmt::Display for Point3d {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "(x={}, y={}, z={})", self.x, self.y, self.z)
}
}
impl Point3d {
pub fn new(x: i32, y: i32, z: i32) -> Self {
Self {
x,
y,
z,
}
}
}
#[async_std::main]
async fn main() {
let setup_sys = |world: &mut World| -> anyhow::Result<()> {
/* {
let mut window_options = world.get_resource_mut::<Ct<WindowOptions>>().unwrap();
window_options.cursor_grab = CursorGrabMode::Confined;
window_options.cursor_visible = false;
} */
let mut resman = world.get_resource_mut::<ResourceManager>().unwrap();
let diffuse_texture = resman.request::<Texture>("assets/happy-tree.png").unwrap();
let antique_camera_model = resman.request::<Model>("assets/AntiqueCamera.glb").unwrap();
let cube_model = resman.request::<Model>("assets/texture-sep/texture-sep.gltf").unwrap();
drop(resman);
/* world.spawn((
ModelComponent(cube_model.clone()),
TransformComponent::from(Transform::from_xyz(3.0, 0.5, -2.2)),
)); */
world.spawn((
ModelComponent(antique_camera_model),
TransformComponent::from(Transform::from_xyz(0.0, -5.0, -10.0)),
));
let mut camera = CameraComponent::new_3d();
camera.transform.translation += math::Vec3::new(0.0, 0.0, 7.5);
//camera.transform.rotate_y(Angle::Degrees(-25.0));
camera.transform.rotate_z(math::Angle::Degrees(-90.0));
world.spawn((camera,));
Ok(())
};
let fps_system = |world: &mut World| -> anyhow::Result<()> {
let mut counter: RefMut<fps_counter::FPSCounter> = world.get_resource_mut().unwrap();
let fps = counter.tick();
debug!("FPS: {fps}");
Ok(())
};
let fps_plugin = move |game: &mut Game| {
let world = game.world();
world.insert_resource(fps_counter::FPSCounter::new());
game.with_system("fps", fps_system, &["input"]);
};
let jiggle_system = |world: &mut World| -> anyhow::Result<()> {
let keys = world.get_resource::<InputButtons<KeyCode>>();
if keys.is_none() {
return Ok(());
}
let keys = keys.unwrap();
let speed = 0.01;
let rot_speed = 1.0;
let mut dir_x = 0.0;
let mut dir_y = 0.0;
let mut dir_z = 0.0;
let mut rot_x = 0.0;
let mut rot_y = 0.0;
if keys.is_pressed(KeyCode::A) {
dir_x += speed;
}
if keys.is_pressed(KeyCode::D) {
dir_x -= speed;
}
if keys.is_pressed(KeyCode::S) {
dir_y += speed;
}
if keys.is_pressed(KeyCode::W) {
dir_y -= speed;
}
if keys.is_pressed(KeyCode::E) {
dir_z += speed;
}
if keys.is_pressed(KeyCode::Q) {
dir_z -= speed;
}
if keys.is_pressed(KeyCode::Left) {
rot_y -= rot_speed;
}
if keys.is_pressed(KeyCode::Right) {
rot_y += rot_speed;
}
if keys.is_pressed(KeyCode::Up) {
rot_x -= rot_speed;
}
if keys.is_pressed(KeyCode::Down) {
rot_x += rot_speed;
}
drop(keys);
if dir_x == 0.0 && dir_y == 0.0 && dir_z == 0.0 && rot_x == 0.0 && rot_y == 0.0 {
return Ok(());
}
//debug!("moving by ({}, {})", dir_x, dir_y);
for transform in world.query_mut::<(&mut TransformComponent,)>().iter_mut() {
let t = &mut transform.transform;
//debug!("Translation: {}", t.translation);
//debug!("Rotation: {}", t.rotation);
/* t.translation += glam::Vec3::new(0.0, 0.001, 0.0);
t.translation.x *= -1.0; */
t.translation.x += dir_x;
t.translation.y += dir_y;
t.translation.z += dir_z;
t.rotate_x(math::Angle::Degrees(rot_x));
t.rotate_y(math::Angle::Degrees(rot_y));
}
let events = world.get_resource_mut::<EventQueue>().unwrap();
if let Some(mm) = events.read_events::<MouseMotion>() {
debug!("Mouse motion: {:?}", mm);
}
Ok(())
};
let jiggle_plugin = move |game: &mut Game| {
game.with_system("jiggle", jiggle_system, &["input"]);
};
Game::initialize().await
.with_plugin(lyra_engine::DefaultPlugins)
.with_startup_system(setup_sys)
//.with_plugin(fps_plugin)
.with_plugin(jiggle_plugin)
.run().await;
}

View File

@ -7,6 +7,15 @@ edition = "2021"
[dependencies] [dependencies]
anyhow = "1.0.75" anyhow = "1.0.75"
base64 = "0.21.4"
edict = "0.5.0"
glam = "0.24.1"
gltf = { version = "1.3.0", features = ["KHR_materials_pbrSpecularGlossiness"] }
image = "0.24.7" image = "0.24.7"
# not using custom matcher, or file type from file path
infer = { version = "0.15.0", default-features = false }
mime = "0.3.17"
percent-encoding = "2.3.0"
thiserror = "1.0.48" thiserror = "1.0.48"
tracing = "0.1.37"
uuid = { version = "1.4.1", features = ["v4"] } uuid = { version = "1.4.1", features = ["v4"] }

View File

@ -9,3 +9,11 @@ pub use texture::*;
pub mod loader; pub mod loader;
pub use loader::*; pub use loader::*;
pub mod model;
pub use model::*;
pub mod material;
pub use material::*;
pub(crate) mod util;

View File

@ -0,0 +1,115 @@
use std::{fs::File, sync::Arc, io::Read};
use image::ImageError;
use crate::{resource_manager::ResourceStorage, texture::Texture, resource::Resource, ResourceManager};
use super::{LoaderError, ResourceLoader};
impl From<ImageError> for LoaderError {
fn from(value: ImageError) -> Self {
LoaderError::DecodingError(value.into())
}
}
/// A struct that implements the `ResourceLoader` trait used for loading textures.
#[derive(Default)]
pub struct ImageLoader;
impl ResourceLoader for ImageLoader {
fn extensions(&self) -> &[&str] {
&[
// the extensions of these are the names of the formats
"bmp", "dds", "gif", "ico", "jpeg", "jpg", "png", "qoi", "tga", "tiff", "webp",
// farbfeld
"ff",
// pnm
"pnm", "pbm", "pgm", "ppm",
]
}
fn mime_types(&self) -> &[&str] {
&[
"image/bmp", "image/vnd.ms-dds", "image/gif", "image/x-icon", "image/jpeg",
"image/png", "image/qoi", "image/tga", "image/tiff", "image/webp",
// no known mime for farbfeld
// pnm, pbm, pgm, ppm
"image/x-portable-anymap", "image/x-portable-bitmap", "image/x-portable-graymap", "image/x-portable-pixmap",
]
}
fn load(&self, _resource_manager: &mut ResourceManager, path: &str) -> Result<Arc<dyn ResourceStorage>, LoaderError> {
// check if the file is supported by this loader
if !self.does_support_file(path) {
return Err(LoaderError::UnsupportedExtension(path.to_string()));
}
// read file bytes
let mut file = File::open(path)?;
let mut buf = vec![];
file.read_to_end(&mut buf)?;
// load the image and construct Resource<Texture>
let image = image::load_from_memory(&buf)
.map_err(|e| match e {
ImageError::IoError(e) => LoaderError::IoError(e),
_ => LoaderError::DecodingError(e.into()),
})?;
let texture = Texture {
image,
};
let res = Resource::with_data(path, texture);
Ok(Arc::new(res))
}
fn load_bytes(&self, _resource_manager: &mut ResourceManager, bytes: Vec<u8>, offset: usize, length: usize) -> Result<Arc<dyn ResourceStorage>, LoaderError> {
let image = image::load_from_memory(&bytes[offset..(length-offset)])
.map_err(|e| match e {
ImageError::IoError(e) => LoaderError::IoError(e),
_ => LoaderError::DecodingError(e.into()),
})?;
let texture = Texture {
image,
};
let res = Resource::with_data(&uuid::Uuid::new_v4().to_string(), texture);
Ok(Arc::new(res))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn get_image(path: &str) -> String {
let manifest = std::env::var("CARGO_MANIFEST_DIR").unwrap();
format!("{manifest}/test_files/img/{path}")
}
#[test]
fn check_unsupport() {
let loader = ImageLoader::default();
assert_eq!(loader.does_support_file("test.gltf"), false);
}
/// Tests loading an image
#[test]
fn image_load() {
let mut manager = ResourceManager::new();
let loader = ImageLoader::default();
loader.load(&mut manager, &get_image("squiggles.png")).unwrap();
}
#[test]
fn image_load_unsupported() {
let mut manager = ResourceManager::new();
let loader = ImageLoader::default();
assert!(loader.load(&mut manager, &get_image("squiggles.gltf")).is_err());
}
}

View File

@ -1,10 +1,11 @@
pub mod texture; pub mod image;
pub mod model;
use std::{io, sync::Arc, fs::File}; use std::{io, sync::Arc, path::Path, ffi::OsStr};
use thiserror::Error; use thiserror::Error;
use crate::resource_manager::ResourceStorage; use crate::{resource_manager::ResourceStorage, ResourceManager};
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum LoaderError { pub enum LoaderError {
@ -15,7 +16,7 @@ pub enum LoaderError {
UnsupportedExtension(String), UnsupportedExtension(String),
#[error("IOError: '{0}'")] #[error("IOError: '{0}'")]
IOError(io::Error), IoError(io::Error),
// From is implemented for this field in each loader module // From is implemented for this field in each loader module
#[error("Decoding error: '{0}'")] #[error("Decoding error: '{0}'")]
@ -24,12 +25,50 @@ pub enum LoaderError {
impl From<io::Error> for LoaderError { impl From<io::Error> for LoaderError {
fn from(value: io::Error) -> Self { fn from(value: io::Error) -> Self {
LoaderError::IOError(value) LoaderError::IoError(value)
} }
} }
pub trait ResourceLoader: Send + Sync { pub trait ResourceLoader: Send + Sync {
/// Returns the extensions that this loader supports.
fn extensions(&self) -> &[&str]; fn extensions(&self) -> &[&str];
fn does_support_file(&self, path: &str) -> bool; /// Returns the mime types that this loader supports.
fn load(&self, path: &str) -> Result<Arc<dyn ResourceStorage>, LoaderError>; fn mime_types(&self) -> &[&str];
/// Returns true if this loader supports the file.
fn does_support_file(&self, path: &str) -> bool {
match Path::new(path).extension().and_then(OsStr::to_str) {
Some(ext) => {
self.extensions().contains(&ext)
},
_ => false,
}
}
/// Returns true if this loader supports the mime type.
fn does_support_mime(&self, mime: &str) -> bool {
self.mime_types().contains(&mime)
}
/// Load a resource from a path.
fn load(&self, resource_manager: &mut ResourceManager, path: &str) -> Result<Arc<dyn ResourceStorage>, LoaderError>;
/// Load a resource from bytes.
fn load_bytes(&self, resource_manager: &mut ResourceManager, bytes: Vec<u8>, offset: usize, length: usize) -> Result<Arc<dyn ResourceStorage>, LoaderError>;
}
#[cfg(test)]
mod tests {
use super::{*, image::ImageLoader};
/// Ensure that `does_support_file` works
#[test]
fn check_support() {
let loader = ImageLoader::default();
let extensions = loader.extensions();
let fake_paths: Vec<String> = extensions.iter().map(|e| format!("a.{}", e)).collect();
for path in fake_paths.iter() {
assert!(loader.does_support_file(&path));
}
}
} }

View File

@ -0,0 +1,230 @@
use std::{sync::Arc, path::{Path, PathBuf}};
use base64::Engine;
use thiserror::Error;
use crate::{ResourceLoader, LoaderError, Mesh, Model, MeshVertexAttribute, VertexAttributeData, Resource, Material, MeshIndices, ResourceManager, util};
use tracing::debug;
impl From<gltf::Error> for LoaderError {
fn from(value: gltf::Error) -> Self {
LoaderError::DecodingError(value.into())
}
}
#[derive(Error, Debug)]
enum ModelLoaderError {
#[error("The model ({0}) is missing the BIN section in the gltf file")]
MissingBin(String),
#[error("There was an error with decoding a uri defined in the model: '{0}'")]
UriDecodingError(util::UriReadError),
}
impl From<ModelLoaderError> for LoaderError {
fn from(value: ModelLoaderError) -> Self {
LoaderError::DecodingError(value.into())
}
}
pub(crate) struct GltfLoadContext<'a> {
pub resource_manager: &'a mut ResourceManager,
pub gltf: &'a gltf::Gltf,
/// Path to the gltf
pub gltf_path: &'a str,
/// The path to the directory that the gltf is contained in.
pub gltf_parent_path: &'a str,
/// List of buffers in the gltf
pub buffers: &'a Vec<Vec<u8>>,
}
#[derive(Default)]
pub struct ModelLoader;
impl ModelLoader {
/* fn parse_uri(containing_path: &str, uri: &str) -> Option<Vec<u8>> {
let uri = uri.strip_prefix("data")?;
let (mime, data) = uri.split_once(",")?;
let (_mime, is_base64) = match mime.strip_suffix(";base64") {
Some(mime) => (mime, true),
None => (mime, false),
};
if is_base64 {
Some(base64::engine::general_purpose::STANDARD.decode(data).unwrap())
} else {
let full_path = format!("{containing_path}/{data}");
let buf = std::fs::read(&full_path).unwrap();
Some(buf)
}
} */
fn process_node(&self, buffers: &Vec<Vec<u8>>, materials: &Vec<Material>, node: gltf::Node<'_>) -> Vec<Mesh> {
let mut meshes = vec![];
if let Some(mesh) = node.mesh() {
for prim in mesh.primitives() {
let reader = prim.reader(|buf| Some(buffers[buf.index()].as_slice()));
let mut new_mesh = Mesh::default();
// read the positions
if let Some(pos) = reader.read_positions() {
if prim.mode() != gltf::mesh::Mode::Triangles {
todo!("Load position primitives that aren't triangles"); // TODO
}
let pos: Vec<glam::Vec3> = pos.map(|t| t.into()).collect();
new_mesh.add_attribute(MeshVertexAttribute::Position, VertexAttributeData::Vec3(pos));
}
// read the normals
if let Some(norms) = reader.read_normals() {
let norms: Vec<glam::Vec3> = norms.map(|t| t.into()).collect();
new_mesh.add_attribute(MeshVertexAttribute::Normals, VertexAttributeData::Vec3(norms));
}
// read the tangents
if let Some(tangents) = reader.read_tangents() {
let tangents: Vec<glam::Vec4> = tangents.map(|t| t.into()).collect();
new_mesh.add_attribute(MeshVertexAttribute::Tangents, VertexAttributeData::Vec4(tangents));
}
// read tex coords
if let Some(tex_coords) = reader.read_tex_coords(0) {
let tex_coords: Vec<glam::Vec2> = tex_coords.into_f32().map(|t| t.into()).collect();
new_mesh.add_attribute(MeshVertexAttribute::TexCoords, VertexAttributeData::Vec2(tex_coords));
}
// read the indices
if let Some(indices) = reader.read_indices() {
let indices: MeshIndices = match indices {
// wpgu doesn't support u8 indices, so those must be converted to u16
gltf::mesh::util::ReadIndices::U8(i) => MeshIndices::U16(i.map(|i| i as u16).collect()),
gltf::mesh::util::ReadIndices::U16(i) => MeshIndices::U16(i.collect()),
gltf::mesh::util::ReadIndices::U32(i) => MeshIndices::U32(i.collect()),
};
new_mesh.indices = Some(indices);
}
let mat = materials.get(prim.material().index().unwrap()).unwrap();
new_mesh.set_material(mat.clone());
//prim.material().
meshes.push(new_mesh);
}
}
for child in node.children() {
let mut child_meshes = self.process_node(buffers, materials, child);
meshes.append(&mut child_meshes);
}
meshes
}
}
impl ResourceLoader for ModelLoader {
fn extensions(&self) -> &[&str] {
&[
"gltf", "glb"
]
}
fn mime_types(&self) -> &[&str] {
&[]
}
fn load(&self, resource_manager: &mut ResourceManager, path: &str) -> Result<std::sync::Arc<dyn crate::ResourceStorage>, crate::LoaderError> {
// check if the file is supported by this loader
if !self.does_support_file(path) {
return Err(LoaderError::UnsupportedExtension(path.to_string()));
}
let mut parent_path = PathBuf::from(path);
parent_path.pop();
let parent_path = parent_path.display().to_string();
/* let (document, buffers, images) = gltf::import(&path)?;
let buffers: Vec<Vec<u8>> = buffers.into_iter().map(|b| b.0).collect();
let scene = document.scenes().next().unwrap();
let materials: Vec<Material> = document.materials()
.map(|mat| Material::from_gltf(resource_manager, &parent_path, mat)).collect();
let meshes: Vec<Mesh> = scene.nodes()
.map(|node| self.process_node(&buffers, &materials, node))
.flatten().collect(); */
let gltf = gltf::Gltf::open(path)?;
let mut use_bin = false;
let buffers: Vec<Vec<u8>> = gltf.buffers().map(|b| match b.source() {
gltf::buffer::Source::Bin => {
use_bin = true;
gltf.blob.as_deref().map(|v| v.to_vec())
.ok_or(ModelLoaderError::MissingBin(path.to_string()))
},
gltf::buffer::Source::Uri(uri) => util::gltf_read_buffer_uri(&parent_path, uri)
.map_err(|e| ModelLoaderError::UriDecodingError(e)),
}).flatten().collect();
// TODO: Read in multiple scenes
let scene = gltf.scenes().next().unwrap();
let mut context = GltfLoadContext {
resource_manager,
gltf: &gltf,
gltf_path: path,
gltf_parent_path: &parent_path,
buffers: &buffers,
};
let materials: Vec<Material> = gltf.materials()
.map(|mat| Material::from_gltf(&mut context, mat)).collect();
let meshes: Vec<Mesh> = scene.nodes()
.map(|node| self.process_node(&buffers, &materials, node))
.flatten().collect();
debug!("Loaded {} meshes, and {} materials from '{}'", meshes.len(), materials.len(), path);
Ok(Arc::new(Resource::with_data(path, Model::new(meshes))))
}
fn load_bytes(&self, resource_manager: &mut ResourceManager, bytes: Vec<u8>, offset: usize, length: usize) -> Result<Arc<dyn crate::ResourceStorage>, LoaderError> {
todo!()
}
}
#[cfg(test)]
mod tests {
use crate::ResourceLoader;
use super::*;
fn test_file_path(path: &str) -> String {
let manifest = std::env::var("CARGO_MANIFEST_DIR").unwrap();
format!("{manifest}/test_files/gltf/{path}")
}
#[test]
fn test_loading() {
let path = test_file_path("texture-embedded.gltf");
let mut manager = ResourceManager::new();
let loader = ModelLoader::default();
let model = loader.load(&mut manager, &path).unwrap();
let model = Arc::downcast::<Resource<Model>>(model.as_arc_any()).unwrap();
let model = model.data.as_ref().unwrap();
assert_eq!(model.meshes.len(), 1); // There should only be 1 mesh
let mesh = &model.meshes[0];
assert!(mesh.position().unwrap().len() > 0);
assert!(mesh.normals().unwrap().len() > 0);
assert!(mesh.tex_coords().unwrap().len() > 0);
assert!(mesh.indices.clone().unwrap().len() > 0);
assert!(mesh.material().base_color_texture.is_some());
let _mesh_mat = mesh.material(); // inner panic if material was not loaded
}
}

View File

@ -1,107 +0,0 @@
use std::{fs::File, sync::Arc, path::Path, ffi::OsStr, io::Read};
use image::ImageError;
use crate::{resource_manager::ResourceStorage, texture::Texture, resource::Resource};
use super::{LoaderError, ResourceLoader};
impl From<ImageError> for LoaderError {
fn from(value: ImageError) -> Self {
LoaderError::DecodingError(anyhow::Error::from(value))
}
}
/// A struct that implements the `ResourceLoader` trait used for loading textures.
#[derive(Default)]
pub struct TextureLoader;
impl ResourceLoader for TextureLoader {
fn extensions(&self) -> &[&str] {
&[
// the extensions of these are the names of the formats
"bmp", "dds", "gif", "ico", "jpeg", "jpg", "png", "qoi", "tga", "tiff", "webp",
// farbfeld
"ff",
// pnm
"pnm", "pbm", "pgm", "ppm", "pam",
]
}
fn does_support_file(&self, path: &str) -> bool {
match Path::new(path).extension().and_then(OsStr::to_str) {
Some(ext) => {
self.extensions().contains(&ext)
},
_ => false,
}
}
fn load(&self, path: &str) -> Result<Arc<dyn ResourceStorage>, LoaderError> {
// check if the file is supported by this loader
if !self.does_support_file(path) {
return Err(LoaderError::UnsupportedExtension(path.to_string()));
}
// read file bytes
let mut file = File::open(path)?;
let mut buf = vec![];
file.read_to_end(&mut buf)?;
// load the image and construct Resource<Texture>
let image = image::load_from_memory(&buf)
.map_err(|e| match e {
ImageError::IoError(e) => LoaderError::IOError(e),
_ => LoaderError::DecodingError(e.into()),
})?;
let texture = Texture {
image,
};
let res = Resource::with_data(path, texture);
Ok(Arc::new(res))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn get_image(path: &str) -> String {
let manifest = std::env::var("CARGO_MANIFEST_DIR").unwrap();
format!("{manifest}/test_files/img/{path}")
}
/// Ensure that `does_support_file` works
#[test]
fn check_support() {
let loader = TextureLoader::default();
let extensions = loader.extensions();
let fake_paths: Vec<String> = extensions.iter().map(|e| format!("a.{}", e)).collect();
for path in fake_paths.iter() {
assert!(loader.does_support_file(&path));
}
}
#[test]
fn check_unsupport() {
let loader = TextureLoader::default();
assert_eq!(loader.does_support_file("test.gltf"), false);
}
/// Tests loading an image
#[test]
fn image_load() {
let loader = TextureLoader::default();
loader.load(&get_image("squiggles.png")).unwrap();
}
#[test]
fn image_load_unsupported() {
let loader = TextureLoader::default();
assert!(loader.load(&get_image("squiggles.gltf")).is_err());
}
}

View File

@ -0,0 +1,244 @@
use std::{fs::File, io::{BufReader, Read}, collections::hash_map::DefaultHasher, hash::{Hash, Hasher}};
use crate::{Texture, ResHandle, ResourceManager, util, loader::model::GltfLoadContext};
/// PBR metallic roughness
#[derive(Clone, Debug, Default)]
pub struct PbrRoughness {
/// The rgba base color of the PBR material
pub base_color: [f32; 4],
/// The metalness of the material
/// From 0.0 (non-metal) to 1.0 (metal)
pub metallic: f32,
/// The roughness of the material
/// From 0.0 (smooth) to 1.0 (rough)
pub roughness: f32,
// TODO: base_color_texture and metallic_roughness_texture
}
impl From<gltf::material::PbrMetallicRoughness<'_>> for PbrRoughness {
fn from(value: gltf::material::PbrMetallicRoughness) -> Self {
PbrRoughness {
base_color: value.base_color_factor(),
metallic: value.metallic_factor(),
roughness: value.roughness_factor(),
}
}
}
#[derive(Clone, Debug, Default)]
pub struct PbrGlossiness {
/// The rgba diffuse color of the material
pub diffuse_color: glam::Vec4,
// The base color texture
// pub diffuse_texture // TODO
pub specular: glam::Vec3,
/// The glossiness factor of the material.
/// From 0.0 (no glossiness) to 1.0 (full glossiness)
pub glossiness: f32,
// pub glossiness_texture // TODO
}
impl From<gltf::material::PbrSpecularGlossiness<'_>> for PbrGlossiness {
fn from(value: gltf::material::PbrSpecularGlossiness) -> Self {
PbrGlossiness {
diffuse_color: value.diffuse_factor().into(),
specular: value.specular_factor().into(),
glossiness: value.glossiness_factor()
}
}
}
/// The alpha rendering mode of a material.
/// This is essentially a re-export of gltf::material::AlphaMode
#[derive(Clone, Copy, Eq, PartialEq, Debug, Default)]
pub enum AlphaMode {
/// The alpha value is ignored and the rendered output is fully opaque.
#[default]
Opaque = 1,
/// The rendered output is either fully opaque or fully transparent depending on
/// the alpha value and the specified alpha cutoff value.
Mask,
/// The alpha value is used, to determine the transparency of the rendered output.
/// The alpha cutoff value is ignored.
Blend,
}
impl From<gltf::material::AlphaMode> for AlphaMode {
fn from(value: gltf::material::AlphaMode) -> Self {
match value {
gltf::material::AlphaMode::Opaque => AlphaMode::Opaque,
gltf::material::AlphaMode::Mask => AlphaMode::Mask,
gltf::material::AlphaMode::Blend => AlphaMode::Blend,
}
}
}
#[derive(Clone, Default)]
pub struct Material {
pub shader_uuid: Option<u64>,
pub name: Option<String>,
pub double_sided: bool,
//pub pbr_roughness: PbrRoughness,
/// The RGBA base color of the model. If a texture is supplied with `base_color_texture`, this value
/// will tint the texture. If a texture is not provided, this value would be the color of the Material.
pub base_color: glam::Vec4,
/// The metalness of the material
/// From 0.0 (non-metal) to 1.0 (metal)
pub metallic: f32,
/// The roughness of the material
/// From 0.0 (smooth) to 1.0 (rough)
pub roughness: f32,
/// The base color texture of the model.
pub base_color_texture: Option<ResHandle<Texture>>,
/// The metallic-roughness texture.
///
/// The metalness values are sampled from the B channel. The roughness values are sampled from
/// the G channel. These values are linear. If other channels are present (R or A), they are
/// ignored for metallic-roughness calculations.
pub metallic_roughness_texture: Option<ResHandle<Texture>>,
/// A set of parameter values that are used to define the specular-glossiness material model
/// from Physically-Based Rendering (PBR) methodology.
/// GLTF extension: [KHR_materials_pbrSpecularGlossiness](https://kcoley.github.io/glTF/extensions/2.0/Khronos/KHR_materials_pbrSpecularGlossiness)
pub pbr_glossiness: Option<PbrGlossiness>,
/// The optional alpha cutoff value of the material.
pub alpha_cutoff: Option<f32>,
/// The alpha rendering mode of the material. The material's alpha rendering
/// mode enumeration specifying the interpretation of the alpha value of the main
/// factor and texture.
///
/// * In `Opaque` mode (default) the alpha value is ignored
/// and the rendered output is fully opaque.
/// * In `Mask` mode, the rendered
/// output is either fully opaque or fully transparent depending on the alpha
/// value and the specified alpha cutoff value.
/// * In `Blend` mode, the alpha value is used to composite the source and
/// destination areas and the rendered output is combined with the background
/// using the normal painting operation (i.e. the Porter and Duff over
/// operator).
pub alpha_mode: AlphaMode,
//pub texture: Option<ResHandle<Texture>>,
}
impl Material {
/// Get a uri's identifier
///
/// I'm not actually sure how identifiable this would be
fn uri_ident(gltf_rel_path: &str, uri: &str) -> String {
let mut hasher = DefaultHasher::new();
uri.hash(&mut hasher);
let hash = hasher.finish();
format!("{gltf_rel_path};{hash}")
}
fn source_ident(gltf_rel_path: &str, src: &gltf::image::Source) -> Option<String> {
match src {
gltf::image::Source::View { view, mime_type } => {
let buf = view.buffer();
let src = buf.source();
match src {
gltf::buffer::Source::Bin => None,
gltf::buffer::Source::Uri(uri) => {
Some(Material::uri_ident(gltf_rel_path, uri))
}
}
},
gltf::image::Source::Uri { uri, mime_type } => {
Some(Material::uri_ident(gltf_rel_path, uri))
},
}
}
fn read_source(context: &mut GltfLoadContext, src: gltf::image::Source) -> Result<Vec<u8>, util::UriReadError> {
let gltf_rel_path = context.gltf_parent_path;
// TODO: Don't copy sources
match src {
gltf::image::Source::View { view, mime_type: _ } => {
let buf = view.buffer();
let src = buf.source();
let offset = view.offset();
let len = view.length();
match src {
gltf::buffer::Source::Bin => {
let mut b = context.gltf.blob.clone().unwrap();
b.drain(0..offset);
b.truncate(len);
Ok(b)
},
gltf::buffer::Source::Uri(uri) => {
util::gltf_read_buffer_uri(gltf_rel_path, uri)
.map(|mut buf| {
buf.drain(0..offset);
buf.truncate(len);
buf
})
}
}
},
gltf::image::Source::Uri { uri, mime_type: _ } => {
util::gltf_read_buffer_uri(gltf_rel_path, uri)
},
}
}
fn load_texture(context: &mut GltfLoadContext, texture_info: gltf::texture::Info<'_>) -> ResHandle<Texture> {
// TODO: texture_info.tex_coord()
let tex = texture_info.texture();
let img = tex.source();
let src = img.source();
let buf = Material::read_source(context, src).unwrap();
let buflen = buf.len();
let mime_type = infer::get(&buf).expect("Failed to get file type").mime_type();
context.resource_manager.load_bytes::<Texture>(&uuid::Uuid::new_v4().to_string(), mime_type,
buf, 0, buflen).unwrap()
}
/// Load the Material from a gltf::Material.
///
/// `gltf_rel_path`: The relative path of the gltf file,
/// e.g. gltf model path is "resource/models/player.gltf", the relative path would be "resource/models/"
pub(crate) fn from_gltf(context: &mut GltfLoadContext, gltf_mat: gltf::Material) -> Self {
let pbr_rough = gltf_mat.pbr_metallic_roughness();
let base_color = pbr_rough.base_color_factor().into();
let metallic = pbr_rough.metallic_factor();
let roughness = pbr_rough.roughness_factor();
let base_color_texture = pbr_rough.base_color_texture()
.map(|info| Material::load_texture(context, info));
let metallic_roughness_texture = pbr_rough.metallic_roughness_texture()
.map(|info| Material::load_texture(context, info));
Material {
name: gltf_mat.name()
.map(|s| s.to_string()),
double_sided: gltf_mat.double_sided(),
base_color,
metallic,
roughness,
pbr_glossiness: gltf_mat.pbr_specular_glossiness()
.map(|o| o.into()),
alpha_cutoff: gltf_mat.alpha_cutoff(),
alpha_mode: gltf_mat.alpha_mode().into(),
shader_uuid: None,
// TODO
base_color_texture,
metallic_roughness_texture,
}
}
}

147
lyra-resource/src/model.rs Normal file
View File

@ -0,0 +1,147 @@
use std::collections::HashMap;
use crate::Material;
#[repr(C)]
#[derive(Clone, Debug, PartialEq)]
pub enum MeshIndices {
//U8(Vec<u8>),
U16(Vec<u16>),
U32(Vec<u32>),
}
impl MeshIndices {
pub fn len(&self) -> usize {
match self {
MeshIndices::U16(v) => v.len(),
MeshIndices::U32(v) => v.len(),
}
}
}
/* impl From<Vec<u8>> for MeshIndices {
fn from(value: Vec<u8>) -> Self {
MeshIndices::U8(value)
}
} */
impl From<Vec<u16>> for MeshIndices {
fn from(value: Vec<u16>) -> Self {
MeshIndices::U16(value)
}
}
impl From<Vec<u32>> for MeshIndices {
fn from(value: Vec<u32>) -> Self {
MeshIndices::U32(value)
}
}
#[repr(C)]
#[derive(Clone, Debug, PartialEq)]
pub enum VertexAttributeData {
Vec2(Vec<glam::Vec2>),
Vec3(Vec<glam::Vec3>),
Vec4(Vec<glam::Vec4>),
}
impl VertexAttributeData {
/// Take self as a list of Vec2's
pub fn as_vec2(&self) -> &Vec<glam::Vec2> {
match self {
VertexAttributeData::Vec2(v) => v,
_ => panic!("Attempt to get {self:?} as `Vec2`"),
}
}
pub fn as_vec3(&self) -> &Vec<glam::Vec3> {
match self {
VertexAttributeData::Vec3(v) => v,
_ => panic!("Attempt to get {self:?} as `Vec3`"),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum MeshVertexAttribute {
Position,
Normals,
Tangents,
Colors, // TODO: Figure out best way to store color data
Joints, // TODO: Animation
TexCoords,
Weights, // TODO: Animation
MorphTargets, // TODO: Animation
/// Used during loading of the Mesh to process the materials taht it
MaterialRef,
Other(String),
}
#[derive(Clone, edict::Component)]
pub struct Mesh {
pub uuid: uuid::Uuid,
pub attributes: HashMap<MeshVertexAttribute, VertexAttributeData>,
pub indices: Option<MeshIndices>,
material: Option<Material>,
}
impl Default for Mesh {
fn default() -> Self {
Self {
uuid: uuid::Uuid::new_v4(),
attributes: Default::default(),
indices: Default::default(),
material: Default::default()
}
}
}
impl Mesh {
pub fn add_attribute(&mut self, attribute: MeshVertexAttribute, data: VertexAttributeData) {
self.attributes.insert(attribute, data);
}
/// Try to get the position attribute of the Mesh
pub fn position(&self) -> Option<&Vec<glam::Vec3>> {
self.attributes.get(&MeshVertexAttribute::Position)
.map(|p| p.as_vec3())
}
pub fn add_position(&mut self, pos: Vec<glam::Vec3>) {
self.attributes.insert(MeshVertexAttribute::Position, VertexAttributeData::Vec3(pos));
}
/// Try to get the normals attribute of the Mesh
pub fn normals(&self) -> Option<&Vec<glam::Vec3>> {
self.attributes.get(&MeshVertexAttribute::Normals)
.map(|p| p.as_vec3())
}
/// Try to get the texture coordinates attribute of the Mesh
pub fn tex_coords(&self) -> Option<&Vec<glam::Vec2>> {
self.attributes.get(&MeshVertexAttribute::TexCoords)
.map(|p| p.as_vec2())
}
pub fn material(&self) -> Material {
self.material.clone().expect("This mesh is missing a material!")
}
pub fn set_material(&mut self, val: Material) {
self.material = Some(val);
}
}
#[derive(Clone, Default)]
pub struct Model {
pub meshes: Vec<Mesh>,
//pub material
}
impl Model {
pub fn new(meshes: Vec<Mesh>) -> Self {
Self {
meshes,
}
}
}

View File

@ -16,6 +16,9 @@ pub struct Resource<T: Send + Sync + 'static> {
pub state: ResourceState, pub state: ResourceState,
} }
/// A helper type to make it easier to use resources
pub type ResHandle<T> = Arc<Resource<T>>;
impl<T: Send + Sync + 'static> Resource<T> { impl<T: Send + Sync + 'static> Resource<T> {
/// Create the resource with data, its assumed the state is `Ready` /// Create the resource with data, its assumed the state is `Ready`
pub fn with_data(path: &str, data: T) -> Self { pub fn with_data(path: &str, data: T) -> Self {

View File

@ -2,7 +2,7 @@ use std::{sync::Arc, collections::{HashMap, hash_map::DefaultHasher}, hash::{Has
use thiserror::Error; use thiserror::Error;
use crate::{resource::Resource, loader::{ResourceLoader, LoaderError, texture::TextureLoader}}; use crate::{resource::Resource, loader::{ResourceLoader, LoaderError, image::ImageLoader, model::ModelLoader}};
pub trait ResourceStorage: Send + Sync + Any + 'static { pub trait ResourceStorage: Send + Sync + Any + 'static {
fn as_any(&self) -> &dyn Any; fn as_any(&self) -> &dyn Any;
@ -31,6 +31,10 @@ pub enum RequestError {
Loader(LoaderError), Loader(LoaderError),
#[error("The file extension is unsupported: '{0}'")] #[error("The file extension is unsupported: '{0}'")]
UnsupportedFileExtension(String), UnsupportedFileExtension(String),
#[error("The mimetype is unsupported: '{0}'")]
UnsupportedMime(String),
#[error("The identifier is not found: '{0}'")]
IdentNotFound(String),
} }
impl From<LoaderError> for RequestError { impl From<LoaderError> for RequestError {
@ -39,16 +43,19 @@ impl From<LoaderError> for RequestError {
} }
} }
/// A struct that stores all Manager data. This is requried for sending
//struct ManagerStorage
pub struct ResourceManager { pub struct ResourceManager {
resources: HashMap<String, Arc<dyn ResourceStorage>>, resources: HashMap<String, Arc<dyn ResourceStorage>>,
loaders: Vec<Box<dyn ResourceLoader>>, loaders: Vec<Arc<dyn ResourceLoader>>,
} }
impl ResourceManager { impl ResourceManager {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
resources: HashMap::new(), resources: HashMap::new(),
loaders: vec![ Box::new(TextureLoader::default()) ], loaders: vec![ Arc::new(ImageLoader::default()), Arc::new(ModelLoader::default()) ],
} }
} }
@ -65,12 +72,14 @@ impl ResourceManager {
.find(|l| l.does_support_file(path)) { .find(|l| l.does_support_file(path)) {
// Load the resource and store it // Load the resource and store it
let res = loader.load(path)?; let loader = Arc::clone(&loader); // stop borrowing from self
let res = loader.load(self, path)?;
self.resources.insert(path.to_string(), res.clone()); self.resources.insert(path.to_string(), res.clone());
// convert Arc<dyn ResourceStorage> to Arc<Resource<T> // cast Arc<dyn ResourceStorage> to Arc<Resource<T>
let res = res.as_arc_any(); let res = res.as_arc_any();
let res = res.downcast::<Resource<T>>().expect("Failure to downcast resource"); let res = res.downcast::<Resource<T>>()
.expect("Failure to downcast resource! Does the loader return an `Arc<Resource<T>>`?");
Ok(res) Ok(res)
} else { } else {
@ -79,6 +88,49 @@ impl ResourceManager {
} }
} }
} }
/// Store bytes in the manager. If there is already an entry with the same identifier it will be updated.
///
/// Panics: If there is already an entry with the same `ident`, and the entry is not bytes, this function will panic.
///
/// Parameters:
/// * `ident` - The identifier to store along with these bytes. Make sure its unique to avoid overriding something.
/// * `bytes` - The bytes to store.
///
/// Returns: The `Arc` to the now stored resource
pub fn load_bytes<T: Send + Sync + Any + 'static>(&mut self, ident: &str, mime_type: &str, bytes: Vec<u8>, offset: usize, length: usize) -> Result<Arc<Resource<T>>, RequestError> {
if let Some(loader) = self.loaders.iter()
.find(|l| l.does_support_mime(mime_type)) {
let loader = loader.clone();
let res = loader.load_bytes(self, bytes, offset, length)?;
self.resources.insert(ident.to_string(), res.clone());
// code here...
// cast Arc<dyn ResourceStorage> to Arc<Resource<T>
let res = res.as_arc_any();
let res = res.downcast::<Resource<T>>()
.expect("Failure to downcast resource! Does the loader return an `Arc<Resource<T>>`?");
Ok(res)
} else {
Err(RequestError::UnsupportedMime(mime_type.to_string()))
}
}
/// Requests bytes from the manager.
pub fn request_loaded_bytes<T: Send + Sync + Any + 'static>(&mut self, ident: &str) -> Result<Arc<Resource<T>>, RequestError> {
match self.resources.get(&ident.to_string()) {
Some(res) => {
let res = res.clone().as_arc_any();
let res = res.downcast::<Resource<T>>().expect("Failure to downcast resource");
Ok(res)
},
None => {
Err(RequestError::IdentNotFound(ident.to_string()))
}
}
}
} }
#[cfg(test)] #[cfg(test)]
@ -125,7 +177,7 @@ mod tests {
assert!( assert!(
match err { match err {
// make sure the error is NotFound // make sure the error is NotFound
RequestError::Loader(LoaderError::IOError(e)) if e.kind() == io::ErrorKind::NotFound => true, RequestError::Loader(LoaderError::IoError(e)) if e.kind() == io::ErrorKind::NotFound => true,
_ => false _ => false
} }
); );

41
lyra-resource/src/util.rs Normal file
View File

@ -0,0 +1,41 @@
use base64::Engine;
use thiserror::Error;
use std::io;
#[derive(Error, Debug)]
pub enum UriReadError {
#[error("IOError: '{0}'")]
IoError(io::Error),
// From is implemented for this field in each loader module
#[error("Base64 decoding error: '{0}'")]
Base64Decode(base64::DecodeError),
#[error("Some data was missing from the uri")]
None
}
/// Read a buffer's uri string into a byte buffer.
///
/// * `containing_path`: The path of the containing folder of the buffers "parent",
/// the parent being where this buffer is defined in,
/// i.e. parent="resources/models/player.gltf", containing="resource/models"
pub(crate) fn gltf_read_buffer_uri(containing_path: &str, uri: &str) -> Result<Vec<u8>, UriReadError> {
if let Some((mime, data)) = uri.strip_prefix("data")
.and_then(|uri| uri.split_once(",")) {
let (_mime, is_base64) = match mime.strip_suffix(";base64") {
Some(mime) => (mime, true),
None => (mime, false),
};
if is_base64 {
base64::engine::general_purpose::STANDARD.decode(data)
.map_err(|e| UriReadError::Base64Decode(e))
} else {
Ok(data.as_bytes().to_vec())
}
} else {
let full_path = format!("{containing_path}/{uri}");
std::fs::read(&full_path).map_err(|e| UriReadError::IoError(e))
}
}

Binary file not shown.

View File

@ -0,0 +1,121 @@
{
"asset":{
"generator":"Khronos glTF Blender I/O v3.6.5",
"version":"2.0"
},
"scene":0,
"scenes":[
{
"name":"Scene",
"nodes":[
0
]
}
],
"nodes":[
{
"mesh":0,
"name":"Cube"
}
],
"materials":[
{
"doubleSided":true,
"name":"Material",
"pbrMetallicRoughness":{
"baseColorFactor":[
0.800000011920929,
0.800000011920929,
0.800000011920929,
1
],
"metallicFactor":0,
"roughnessFactor":0.5
}
}
],
"meshes":[
{
"name":"Cube",
"primitives":[
{
"attributes":{
"POSITION":0,
"TEXCOORD_0":1,
"NORMAL":2
},
"indices":3,
"material":0
}
]
}
],
"accessors":[
{
"bufferView":0,
"componentType":5126,
"count":24,
"max":[
1,
1,
1
],
"min":[
-1,
-1,
-1
],
"type":"VEC3"
},
{
"bufferView":1,
"componentType":5126,
"count":24,
"type":"VEC2"
},
{
"bufferView":2,
"componentType":5126,
"count":24,
"type":"VEC3"
},
{
"bufferView":3,
"componentType":5123,
"count":36,
"type":"SCALAR"
}
],
"bufferViews":[
{
"buffer":0,
"byteLength":288,
"byteOffset":0,
"target":34962
},
{
"buffer":0,
"byteLength":192,
"byteOffset":288,
"target":34962
},
{
"buffer":0,
"byteLength":288,
"byteOffset":480,
"target":34962
},
{
"buffer":0,
"byteLength":72,
"byteOffset":768,
"target":34963
}
],
"buffers":[
{
"byteLength":840,
"uri":"data:application/octet-stream;base64,AACAPwAAgD8AAIC/AACAPwAAgD8AAIC/AACAPwAAgD8AAIC/AACAPwAAgL8AAIC/AACAPwAAgL8AAIC/AACAPwAAgL8AAIC/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgD8AAIA/AACAPwAAgL8AAIA/AACAPwAAgL8AAIA/AACAPwAAgL8AAIA/AACAvwAAgD8AAIC/AACAvwAAgD8AAIC/AACAvwAAgD8AAIC/AACAvwAAgL8AAIC/AACAvwAAgL8AAIC/AACAvwAAgL8AAIC/AACAvwAAgD8AAIA/AACAvwAAgD8AAIA/AACAvwAAgD8AAIA/AACAvwAAgL8AAIA/AACAvwAAgL8AAIA/AACAvwAAgL8AAIA/AAAgPwAAAD8AACA/AAAAPwAAID8AAAA/AADAPgAAAD8AAMA+AAAAPwAAwD4AAAA/AAAgPwAAgD4AACA/AACAPgAAID8AAIA+AADAPgAAgD4AAMA+AACAPgAAwD4AAIA+AAAgPwAAQD8AACA/AABAPwAAYD8AAAA/AAAAPgAAAD8AAMA+AABAPwAAwD4AAEA/AAAgPwAAAAAAACA/AACAPwAAYD8AAIA+AAAAPgAAgD4AAMA+AAAAAAAAwD4AAIA/AAAAAAAAAAAAAIC/AAAAAAAAgD8AAACAAACAPwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIC/AACAPwAAAAAAAACAAAAAAAAAAAAAAIA/AAAAAAAAgD8AAACAAACAPwAAAAAAAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIA/AACAPwAAAAAAAACAAACAvwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAAAAAgD8AAACAAAAAAAAAgL8AAACAAACAvwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAAAAAAAAAAIA/AACAvwAAAAAAAACAAAAAAAAAgD8AAACAAAAAAAAAgL8AAACAAAAAAAAAAAAAAIA/AACAvwAAAAAAAACAAQAOABQAAQAUAAcACgAGABIACgASABYAFwATAAwAFwAMABAADwADAAkADwAJABUABQACAAgABQAIAAsAEQANAAAAEQAAAAQA"
}
]
}

Binary file not shown.

View File

@ -0,0 +1,121 @@
{
"asset":{
"generator":"Khronos glTF Blender I/O v3.6.5",
"version":"2.0"
},
"scene":0,
"scenes":[
{
"name":"Scene",
"nodes":[
0
]
}
],
"nodes":[
{
"mesh":0,
"name":"Cube"
}
],
"materials":[
{
"doubleSided":true,
"name":"Material",
"pbrMetallicRoughness":{
"baseColorFactor":[
0.800000011920929,
0.800000011920929,
0.800000011920929,
1
],
"metallicFactor":0,
"roughnessFactor":0.5
}
}
],
"meshes":[
{
"name":"Cube",
"primitives":[
{
"attributes":{
"POSITION":0,
"TEXCOORD_0":1,
"NORMAL":2
},
"indices":3,
"material":0
}
]
}
],
"accessors":[
{
"bufferView":0,
"componentType":5126,
"count":24,
"max":[
1,
1,
1
],
"min":[
-1,
-1,
-1
],
"type":"VEC3"
},
{
"bufferView":1,
"componentType":5126,
"count":24,
"type":"VEC2"
},
{
"bufferView":2,
"componentType":5126,
"count":24,
"type":"VEC3"
},
{
"bufferView":3,
"componentType":5123,
"count":36,
"type":"SCALAR"
}
],
"bufferViews":[
{
"buffer":0,
"byteLength":288,
"byteOffset":0,
"target":34962
},
{
"buffer":0,
"byteLength":192,
"byteOffset":288,
"target":34962
},
{
"buffer":0,
"byteLength":288,
"byteOffset":480,
"target":34962
},
{
"buffer":0,
"byteLength":72,
"byteOffset":768,
"target":34963
}
],
"buffers":[
{
"byteLength":840,
"uri":"test-sep.bin"
}
]
}

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,137 @@
{
"asset":{
"generator":"Khronos glTF Blender I/O v3.6.6",
"version":"2.0"
},
"scene":0,
"scenes":[
{
"name":"Scene",
"nodes":[
0
]
}
],
"nodes":[
{
"mesh":0,
"name":"Cube"
}
],
"materials":[
{
"doubleSided":true,
"name":"Material",
"pbrMetallicRoughness":{
"baseColorTexture":{
"index":0
},
"metallicFactor":0,
"roughnessFactor":0.5
}
}
],
"meshes":[
{
"name":"Cube",
"primitives":[
{
"attributes":{
"POSITION":0,
"TEXCOORD_0":1,
"NORMAL":2
},
"indices":3,
"material":0
}
]
}
],
"textures":[
{
"sampler":0,
"source":0
}
],
"images":[
{
"mimeType":"image/png",
"name":"uvgrid",
"uri":"uvgrid.png"
}
],
"accessors":[
{
"bufferView":0,
"componentType":5126,
"count":24,
"max":[
1,
1,
1
],
"min":[
-1,
-1,
-1
],
"type":"VEC3"
},
{
"bufferView":1,
"componentType":5126,
"count":24,
"type":"VEC2"
},
{
"bufferView":2,
"componentType":5126,
"count":24,
"type":"VEC3"
},
{
"bufferView":3,
"componentType":5123,
"count":36,
"type":"SCALAR"
}
],
"bufferViews":[
{
"buffer":0,
"byteLength":288,
"byteOffset":0,
"target":34962
},
{
"buffer":0,
"byteLength":192,
"byteOffset":288,
"target":34962
},
{
"buffer":0,
"byteLength":288,
"byteOffset":480,
"target":34962
},
{
"buffer":0,
"byteLength":72,
"byteOffset":768,
"target":34963
}
],
"samplers":[
{
"magFilter":9729,
"minFilter":9987
}
],
"buffers":[
{
"byteLength":840,
"uri":"texture-sep.bin"
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

1
rust-toolchain Normal file
View File

@ -0,0 +1 @@
nightly

View File

@ -1,18 +1,16 @@
use edict::Component; use edict::Component;
use crate::render::{vertex::Vertex, mesh::Mesh, material::Material}; use lyra_resource::Mesh;
#[derive(Clone, Component)] #[derive(Clone, Component)]
pub struct MeshComponent { pub struct MeshComponent {
pub mesh: Mesh, pub mesh: Mesh,
pub material: Material,
} }
impl MeshComponent { impl MeshComponent {
pub fn new(mesh: Mesh, material: Material) -> Self { pub fn new(mesh: Mesh) -> Self {
Self { Self {
mesh, mesh,
material
} }
} }
} }

View File

@ -1,3 +1,4 @@
pub mod mesh; pub mod mesh;
pub mod model;
pub mod transform; pub mod transform;
pub mod camera; pub mod camera;

View File

@ -0,0 +1,39 @@
use lyra_resource::ResHandle;
use crate::assets::Model;
#[derive(Clone, edict::Component)]
pub struct ModelComponent(pub ResHandle<Model>);
impl From<ResHandle<Model>> for ModelComponent {
fn from(value: ResHandle<Model>) -> Self {
ModelComponent(value)
}
}
/* impl From<ResHandle<Model> for ModelComponent {
} */
impl std::ops::Deref for ModelComponent {
type Target = ResHandle<Model>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl std::ops::DerefMut for ModelComponent {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
/* impl ModelComponent {
pub fn new(model, material: Material) -> Self {
Self {
mesh,
material
}
}
} */

View File

@ -297,7 +297,7 @@ impl Game {
.with(fmt::layer().with_writer(stdout_layer)) .with(fmt::layer().with_writer(stdout_layer))
.with(filter::Targets::new() .with(filter::Targets::new()
.with_target("lyra_engine", Level::TRACE) .with_target("lyra_engine", Level::TRACE)
.with_target("wgpu_core", Level::INFO) .with_target("wgpu_core", Level::WARN)
.with_default(Level::DEBUG)) .with_default(Level::DEBUG))
.init(); .init();

View File

@ -1,3 +1,5 @@
#![feature(hash_extract_if)]
pub mod game; pub mod game;
pub mod render; pub mod render;
pub mod input_event; pub mod input_event;

Binary file not shown.

After

Width:  |  Height:  |  Size: 545 B

View File

@ -2,53 +2,23 @@ use edict::EntityId;
use crate::math::Transform; use crate::math::Transform;
use super::{mesh::Mesh, material::Material};
pub struct RenderJob { pub struct RenderJob {
mesh: Mesh, pub entity: EntityId,
material: Material, pub shader_id: u64,
entity: EntityId, pub mesh_buffer_id: uuid::Uuid,
transform: Transform, pub transform: Transform,
last_transform: Option<Transform>, // TODO: render interpolation pub last_transform: Option<Transform>, // TODO: render interpolation
} }
impl RenderJob { impl RenderJob {
pub fn new(mesh: Mesh, material: Material, entity: EntityId, transform: Transform, last_transform: Option<Transform>) -> Self { pub fn new(entity: EntityId, shader_id: u64, mesh_buffer_id: uuid::Uuid, transform: Transform, last_transform: Option<Transform>) -> Self {
Self { Self {
mesh,
material,
entity, entity,
shader_id,
mesh_buffer_id,
transform, transform,
last_transform, last_transform,
} }
} }
pub fn mesh(&self)-> &Mesh {
&self.mesh
}
pub fn material(&self)-> &Material {
&self.material
}
pub fn entity(&self)-> EntityId {
self.entity
}
pub fn transform(&self)-> &Transform {
&self.transform
}
pub fn set_transform(&mut self, transform: Transform){
self.transform = transform;
}
pub fn last_transform(&self)-> Option<&Transform> {
self.last_transform.as_ref()
}
pub fn set_last_transform(&mut self, last_transform: Transform){
self.last_transform = Some(last_transform);
}
} }

View File

@ -1,8 +1,8 @@
use std::{ops::Range, cell::Ref}; use std::ops::Range;
use wgpu::{PipelineLayout, RenderPipeline, RenderPass, VertexBufferLayout, BindGroupLayout}; use wgpu::{PipelineLayout, RenderPipeline, RenderPass, VertexBufferLayout, BindGroupLayout};
use super::{render_job::RenderJob, vertex::Vertex, desc_buf_lay::DescVertexBufferLayout, texture::RenderTexture}; use super::{render_job::RenderJob, texture::RenderTexture};
pub struct FullRenderPipeline { pub struct FullRenderPipeline {
layout: PipelineLayout, layout: PipelineLayout,

View File

@ -5,30 +5,27 @@ use std::num::NonZeroU64;
use std::sync::Arc; use std::sync::Arc;
use std::borrow::Cow; use std::borrow::Cow;
use aligned_vec::AVec;
use async_std::sync::Mutex;
use async_trait::async_trait;
use atomicell::{AtomicCell, RefMut};
use edict::query::EpochOf; use edict::query::EpochOf;
use edict::{EntityId, Entities}; use edict::{EntityId, Entities};
use glam::Mat4; use glam::Vec3;
use tracing::{debug, warn}; use tracing::{debug, warn};
use wgpu::{BindGroup, BindGroupLayout, Limits, BufferBinding}; use wgpu::{BindGroup, BindGroupLayout, Limits};
use wgpu::util::DeviceExt; use wgpu::util::DeviceExt;
use winit::window::Window; use winit::window::Window;
use crate::ecs::components::camera::CameraComponent; use crate::ecs::components::camera::CameraComponent;
use crate::ecs::components::mesh::MeshComponent; use crate::ecs::components::mesh::MeshComponent;
use crate::ecs::components::model::ModelComponent;
use crate::ecs::components::transform::TransformComponent; use crate::ecs::components::transform::TransformComponent;
use crate::math::{Transform, Angle}; use crate::math::Transform;
use crate::resources;
use super::camera::RenderCamera; use super::camera::RenderCamera;
use super::desc_buf_lay::DescVertexBufferLayout; use super::desc_buf_lay::DescVertexBufferLayout;
use super::texture::RenderTexture; use super::texture::RenderTexture;
use super::vertex::Vertex; use super::vertex::Vertex;
use super::{render_pipeline::FullRenderPipeline, render_buffer::BufferStorage, render_job::RenderJob, mesh::Mesh}; use super::{render_pipeline::FullRenderPipeline, render_buffer::BufferStorage, render_job::RenderJob};
use lyra_resource::Mesh;
pub trait Renderer { pub trait Renderer {
fn prepare(&mut self, main_world: &mut edict::World); fn prepare(&mut self, main_world: &mut edict::World);
@ -36,16 +33,15 @@ pub trait Renderer {
fn on_resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>); fn on_resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>);
fn surface_size(&self) -> winit::dpi::PhysicalSize<u32>; fn surface_size(&self) -> winit::dpi::PhysicalSize<u32>;
fn add_render_pipeline(&mut self, shader_id: u32, pipeline: Arc<FullRenderPipeline>); fn add_render_pipeline(&mut self, shader_id: u64, pipeline: Arc<FullRenderPipeline>);
} }
struct RenderBufferStorage { struct RenderBufferStorage {
buffer_vertex: BufferStorage, buffer_vertex: BufferStorage,
buffer_indices: Option<BufferStorage>, buffer_indices: Option<(wgpu::IndexFormat, BufferStorage)>,
render_texture: Option<RenderTexture>, render_texture: Option<RenderTexture>,
texture_bindgroup: Option<BindGroup>, texture_bindgroup: Option<BindGroup>,
texture_layout: Option<BindGroupLayout>,
/// The index of the transform for this entity. /// The index of the transform for this entity.
/// The tuple is structured like this: (transform index, index of transform inside the buffer) /// The tuple is structured like this: (transform index, index of transform inside the buffer)
@ -73,13 +69,15 @@ struct TransformBuffers {
impl TransformBuffers { impl TransformBuffers {
/// Update an entity's buffer with the new transform. Will panic if the entity isn't stored /// Update an entity's buffer with the new transform. Will panic if the entity isn't stored
fn update_entity(&mut self, queue: &wgpu::Queue, limits: &Limits, entity: EntityId, transform: glam::Mat4) { fn update_entity(&mut self, queue: &wgpu::Queue, limits: &Limits, entity: EntityId, transform: glam::Mat4) -> TransformBufferIndices {
let indices = self.not_updated.remove(&entity) let indices = self.not_updated.remove(&entity)
.or_else(|| self.just_updated.remove(&entity))
.expect("Use 'insert_entity' for new entities"); .expect("Use 'insert_entity' for new entities");
self.just_updated.insert(entity, indices); self.just_updated.insert(entity, indices);
let (_, buffer, _) = self.buffer_bindgroups.get(indices.buffer_index).unwrap(); let (_, buffer, _) = self.buffer_bindgroups.get(indices.buffer_index).unwrap();
queue.write_buffer(buffer, indices.transform_index as u64 * limits.min_uniform_buffer_offset_alignment as u64, bytemuck::bytes_of(&transform)); queue.write_buffer(buffer, indices.transform_index as u64 * limits.min_uniform_buffer_offset_alignment as u64, bytemuck::bytes_of(&transform));
indices
} }
/// Insert a new entity into the buffer, returns where it was stored. /// Insert a new entity into the buffer, returns where it was stored.
@ -109,6 +107,22 @@ impl TransformBuffers {
indices indices
} }
/// Update or insert an entities transform
fn update_or_insert<TFn>(&mut self, queue: &wgpu::Queue, limits: &Limits, entity: EntityId, transform_fn: TFn) -> TransformBufferIndices
where TFn: Fn() -> glam::Mat4
{
if self.contains(entity) {
self.update_entity(queue, limits, entity, transform_fn())
} else {
self.insert_entity(queue, limits, entity, transform_fn())
}
}
/// Returns true if the entity's transform is stored (does not mean its up-to-date).
fn contains(&self, entity: EntityId) -> bool {
self.not_updated.contains_key(&entity) || self.just_updated.contains_key(&entity)
}
/// Collect the dead entities, mark entities and not updated for next updates. /// Collect the dead entities, mark entities and not updated for next updates.
fn tick(&mut self) { fn tick(&mut self) {
// take the dead entities, these were ones that were not updated this tick // take the dead entities, these were ones that were not updated this tick
@ -116,7 +130,6 @@ impl TransformBuffers {
.map(|t| t.clone()).collect(); .map(|t| t.clone()).collect();
self.dead_indices = dead; self.dead_indices = dead;
// swap just_updated into not_updated
self.not_updated = self.just_updated.clone(); self.not_updated = self.just_updated.clone();
self.just_updated.clear(); self.just_updated.clear();
} }
@ -145,10 +158,11 @@ pub struct BasicRenderer {
pub clear_color: wgpu::Color, pub clear_color: wgpu::Color,
pub render_pipelines: HashMap<u32, Arc<FullRenderPipeline>>, pub render_pipelines: HashMap<u64, Arc<FullRenderPipeline>>,
pub render_jobs: VecDeque<RenderJob>, pub render_jobs: VecDeque<RenderJob>,
buffer_storage: HashMap<EntityId, RenderBufferStorage>, // TODO: clean up left over buffers from deleted entities/components mesh_buffers: HashMap<uuid::Uuid, RenderBufferStorage>, // TODO: clean up left over buffers from deleted entities/components
entity_meshes: HashMap<EntityId, uuid::Uuid>,
transform_buffers: TransformBuffers, transform_buffers: TransformBuffers,
transform_bind_group_layout: BindGroupLayout, transform_bind_group_layout: BindGroupLayout,
@ -160,6 +174,8 @@ pub struct BasicRenderer {
camera_buffer: wgpu::Buffer, camera_buffer: wgpu::Buffer,
camera_bind_group: wgpu::BindGroup, camera_bind_group: wgpu::BindGroup,
texture_bind_group_layout: BindGroupLayout,
default_texture_bind_group: BindGroup,
depth_buffer_texture: RenderTexture, depth_buffer_texture: RenderTexture,
} }
@ -208,7 +224,7 @@ impl BasicRenderer {
false => surface_caps.present_modes[0] false => surface_caps.present_modes[0]
}; */ }; */
println!("present mode: {:?}", present_mode); debug!("present mode: {:?}", present_mode);
let surface_format = surface_caps.formats.iter() let surface_format = surface_caps.formats.iter()
.copied() .copied()
@ -348,12 +364,27 @@ impl BasicRenderer {
let depth_texture = RenderTexture::create_depth_texture(&device, &config, "Depth Buffer"); let depth_texture = RenderTexture::create_depth_texture(&device, &config, "Depth Buffer");
let mut pipelines = HashMap::new(); // load the default texture
pipelines.insert(0, Arc::new(FullRenderPipeline::new(&device, &config, &shader, let bytes = include_bytes!("default_texture.png");
vec![super::vertex::Vertex::desc(),], let tex = RenderTexture::from_bytes(&device, &queue, bytes, "default_texture").unwrap();
vec![&texture_bind_group_layout, &transform_bind_group_layout, &camera_bind_group_layout]))); let default_tex_bindgroup = device.create_bind_group(
&wgpu::BindGroupDescriptor {
layout: &texture_bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(tex.view()),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(tex.sampler()),
}
],
label: Some("default_texture"),
}
);
Self { let mut s = Self {
window, window,
surface, surface,
device, device,
@ -366,9 +397,10 @@ impl BasicRenderer {
b: 0.3, b: 0.3,
a: 1.0, a: 1.0,
}, },
render_pipelines: pipelines, render_pipelines: HashMap::new(),
render_jobs: VecDeque::new(), render_jobs: VecDeque::new(),
buffer_storage: HashMap::new(), mesh_buffers: HashMap::new(),
entity_meshes: HashMap::new(),
render_limits, render_limits,
transform_buffers, transform_buffers,
@ -378,29 +410,31 @@ impl BasicRenderer {
camera_buffer, camera_buffer,
camera_bind_group, camera_bind_group,
texture_bind_group_layout,
default_texture_bind_group: default_tex_bindgroup,
depth_buffer_texture: depth_texture, depth_buffer_texture: depth_texture,
} };
// create the default pipelines
let mut pipelines = HashMap::new();
pipelines.insert(0, Arc::new(FullRenderPipeline::new(&s.device, &s.config, &shader,
vec![super::vertex::Vertex::desc(),],
vec![&s.texture_bind_group_layout, &s.transform_bind_group_layout, &camera_bind_group_layout])));
s.render_pipelines = pipelines;
s
} }
fn find_next_multiple(n: u32, mul: u32) -> u32 { fn update_mesh_buffers(&mut self, _entity: EntityId, mesh: &Mesh) {
if n % mul == 0 { if let Some(buffers) = self.mesh_buffers.get_mut(&mesh.uuid) {
n
} else {
n + (mul - n % mul)
}
}
// TODO: minimize how often model buffers are updated by checking if they changed
fn update_model_buffers(&mut self, entity: EntityId, model: &MeshComponent) {
if let Some(buffers) = self.buffer_storage.get_mut(&entity) {
// check if the buffer sizes dont match. If they dont, completely remake the buffers // check if the buffer sizes dont match. If they dont, completely remake the buffers
let vertices = &model.mesh.vertices; let vertices = mesh.position().unwrap();
if buffers.buffer_vertex.count() != vertices.len() { if buffers.buffer_vertex.count() != vertices.len() {
drop(buffers); debug!("Recreating buffers for mesh {}", mesh.uuid.to_string());
let (vert, idx) = self.create_vertex_index_buffers(&model.mesh); let (vert, idx) = self.create_vertex_index_buffers(mesh);
// have to re-get buffers because of borrow checker // have to re-get buffers because of borrow checker
let buffers = self.buffer_storage.get_mut(&entity).unwrap(); let buffers = self.mesh_buffers.get_mut(&mesh.uuid).unwrap();
buffers.buffer_indices = idx; buffers.buffer_indices = idx;
buffers.buffer_vertex = vert; buffers.buffer_vertex = vert;
@ -411,109 +445,104 @@ impl BasicRenderer {
let vertex_buffer = buffers.buffer_vertex.buffer(); let vertex_buffer = buffers.buffer_vertex.buffer();
let vertices = vertices.as_slice(); let vertices = vertices.as_slice();
// align the vertices to 4 bytes (u32 is 4 bytes, which is wgpu::COPY_BUFFER_ALIGNMENT) // align the vertices to 4 bytes (u32 is 4 bytes, which is wgpu::COPY_BUFFER_ALIGNMENT)
let (_, vertices, _) = bytemuck::pod_align_to::<Vertex, u32>(vertices); let (_, vertices, _) = bytemuck::pod_align_to::<Vec3, u32>(vertices);
self.queue.write_buffer(&vertex_buffer, 0, bytemuck::cast_slice(&vertices)); self.queue.write_buffer(&vertex_buffer, 0, bytemuck::cast_slice(&vertices));
// update the indices if they're given // update the indices if they're given
if let Some(index_buffer) = buffers.buffer_indices.as_ref() { if let Some(index_buffer) = buffers.buffer_indices.as_ref() {
let index_buffer = index_buffer.buffer(); let aligned_indices = match mesh.indices.as_ref().unwrap() {
let indices = model.mesh.indices.as_ref().unwrap().as_slice(); // U16 indices need to be aligned to u32, for wpgu, which are 4-bytes in size.
let (_, indices, _) = bytemuck::pod_align_to::<u16, u32>(indices); lyra_resource::MeshIndices::U16(v) => bytemuck::pod_align_to::<u16, u32>(v).1,
lyra_resource::MeshIndices::U32(v) => bytemuck::pod_align_to::<u32, u32>(v).1,
};
self.queue.write_buffer(index_buffer, 0, bytemuck::cast_slice(&indices)); let index_buffer = index_buffer.1.buffer();
self.queue.write_buffer(index_buffer, 0, bytemuck::cast_slice(&aligned_indices));
} }
} }
} }
fn create_vertex_index_buffers(&mut self, mesh: &Mesh) -> (BufferStorage, Option<BufferStorage>) { fn create_vertex_index_buffers(&mut self, mesh: &Mesh) -> (BufferStorage, Option<(wgpu::IndexFormat, BufferStorage)>) {
let positions = mesh.position().unwrap();
let tex_coords: Vec<glam::Vec2> = mesh.tex_coords().cloned()
.unwrap_or_else(|| vec![glam::Vec2::new(0.0, 0.0); positions.len()]);
assert!(positions.len() == tex_coords.len());
let vertex_inputs: Vec<Vertex> = std::iter::zip(positions, tex_coords.into_iter())
.map(|(v, t)| Vertex::new(v.clone(), t))
.collect();
let vertex_buffer = self.device.create_buffer_init( let vertex_buffer = self.device.create_buffer_init(
&wgpu::util::BufferInitDescriptor { &wgpu::util::BufferInitDescriptor {
label: Some("Vertex Buffer"), label: Some("Vertex Buffer"),
contents: bytemuck::cast_slice(mesh.vertices.as_slice()), contents: bytemuck::cast_slice(vertex_inputs.as_slice()),//vertex_combined.as_slice(),
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages:: COPY_DST, usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages:: COPY_DST,
} }
); );
let vertex_buffer = BufferStorage::new(vertex_buffer, 0, mesh.vertices.len()); let vertex_buffer = BufferStorage::new(vertex_buffer, 0, vertex_inputs.len());
let buffer_indices = match mesh.indices.as_ref() { let indices = match mesh.indices.as_ref() {
Some(indices) => { Some(indices) => {
let (idx_type, len, contents) = match indices {
lyra_resource::MeshIndices::U16(v) => (wgpu::IndexFormat::Uint16, v.len(), bytemuck::cast_slice(&v)),
lyra_resource::MeshIndices::U32(v) => (wgpu::IndexFormat::Uint32, v.len(), bytemuck::cast_slice(&v)),
};
let index_buffer = self.device.create_buffer_init( let index_buffer = self.device.create_buffer_init(
&wgpu::util::BufferInitDescriptor { &wgpu::util::BufferInitDescriptor {
label: Some("Index Buffer"), label: Some("Index Buffer"),
contents: bytemuck::cast_slice(&indices), contents,
usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages:: COPY_DST, usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages:: COPY_DST,
} }
); );
let buffer_indices = BufferStorage::new(index_buffer, 0, indices.len()); let buffer_indices = BufferStorage::new(index_buffer, 0, len);
Some(buffer_indices) Some((idx_type, buffer_indices))
}, },
None => { None => {
None None
} }
}; };
( vertex_buffer, buffer_indices ) ( vertex_buffer, indices )
} }
fn create_model_buffers(&mut self, model: &MeshComponent, transform_indices: TransformBufferIndices) -> RenderBufferStorage { fn create_mesh_buffers(&mut self, mesh: &Mesh, transform_indices: TransformBufferIndices) -> RenderBufferStorage {
let mesh = &model.mesh;
let (vertex_buffer, buffer_indices) = self.create_vertex_index_buffers(mesh); let (vertex_buffer, buffer_indices) = self.create_vertex_index_buffers(mesh);
let model_texture = &model.material.texture; let diffuse_bindgroup = if let Some(model_texture) = &mesh.material().base_color_texture {
let image = &model_texture.data.as_ref().unwrap().image; let image = &model_texture.data.as_ref().unwrap().image;
let diffuse_texture = RenderTexture::from_image(&self.device, &self.queue, image, None).unwrap(); let diffuse_texture = RenderTexture::from_image(&self.device, &self.queue, image, None).unwrap();
let texture_bind_group_layout = let diffuse_bind_group = self.device.create_bind_group(
self.device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { &wgpu::BindGroupDescriptor {
entries: &[ layout: &self.texture_bind_group_layout,
wgpu::BindGroupLayoutEntry { entries: &[
binding: 0, wgpu::BindGroupEntry {
visibility: wgpu::ShaderStages::FRAGMENT, binding: 0,
ty: wgpu::BindingType::Texture { resource: wgpu::BindingResource::TextureView(diffuse_texture.view()),
multisampled: false,
view_dimension: wgpu::TextureViewDimension::D2,
sample_type: wgpu::TextureSampleType::Float { filterable: true },
}, },
count: None, wgpu::BindGroupEntry {
}, binding: 1,
wgpu::BindGroupLayoutEntry { resource: wgpu::BindingResource::Sampler(diffuse_texture.sampler()),
binding: 1, }
visibility: wgpu::ShaderStages::FRAGMENT, ],
// This should match the filterable field of the label: Some("diffuse_bind_group"),
// corresponding Texture entry above. }
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), );
count: None,
},
],
label: Some("texture_bind_group_layout"),
});
let diffuse_bind_group = self.device.create_bind_group( Some(diffuse_bind_group)
&wgpu::BindGroupDescriptor { } else {
layout: &texture_bind_group_layout, None
entries: &[ };
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(diffuse_texture.view()),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::Sampler(diffuse_texture.sampler()),
}
],
label: Some("diffuse_bind_group"),
}
);
RenderBufferStorage { RenderBufferStorage {
buffer_vertex: vertex_buffer, buffer_vertex: vertex_buffer,
buffer_indices, buffer_indices,
render_texture: None, render_texture: None,
texture_layout: None, texture_bindgroup: diffuse_bindgroup,
texture_bindgroup: Some(diffuse_bind_group),
transform_index: transform_indices transform_index: transform_indices
} }
} }
@ -553,6 +582,28 @@ impl BasicRenderer {
buffers.next_indices = indices; buffers.next_indices = indices;
indices indices
} }
/// Processes the mesh for the renderer, storing and creating buffers as needed. Returns true if a new mesh was processed.
fn process_mesh(&mut self, entity: EntityId, transform: Transform, mesh: &Mesh) -> bool {
let indices = self.transform_buffers.update_or_insert(&self.queue, &self.render_limits,
entity, || transform.calculate_mat4());
if self.mesh_buffers.contains_key(&mesh.uuid) {
false
} else {
// check if the transform buffers need to be expanded
if self.transform_buffers.should_expand() {
self.expand_transform_buffers();
}
// create the mesh's buffers
let buffers = self.create_mesh_buffers(mesh, indices);
self.mesh_buffers.insert(mesh.uuid, buffers);
self.entity_meshes.insert(entity, mesh.uuid);
true
}
}
} }
impl Renderer for BasicRenderer { impl Renderer for BasicRenderer {
@ -561,45 +612,39 @@ impl Renderer for BasicRenderer {
let mut alive_entities = HashSet::new(); let mut alive_entities = HashSet::new();
for (entity, model, model_epoch, transform) in main_world.query::<(Entities, &MeshComponent, EpochOf<MeshComponent>, &TransformComponent)>().iter() { for (entity, model, model_epoch, transform) in main_world.query::<(Entities, &ModelComponent, EpochOf<ModelComponent>, &TransformComponent)>().iter() {
// Create the render job and push it to the queue
let job = RenderJob::new(model.mesh.clone(), model.material.clone(), entity, transform.transform, None);
self.render_jobs.push_back(job);
alive_entities.insert(entity); alive_entities.insert(entity);
if self.buffer_storage.get(&entity).is_none() { let model = model.data.as_ref().unwrap().as_ref();
// check if the transform buffers need to be expanded
if self.transform_buffers.should_expand() { for mesh in model.meshes.iter() {
self.expand_transform_buffers(); if !self.process_mesh(entity, transform.transform, mesh) {
if model_epoch == last_epoch {
self.update_mesh_buffers(entity, mesh);
}
} }
// insert transform into buffers let shader = mesh.material().shader_uuid.unwrap_or(0);
let indices = self.transform_buffers.insert_entity(&self.queue, &self.render_limits, let job = RenderJob::new(entity, shader, mesh.uuid, transform.transform, None);
entity, transform.transform.calculate_mat4()); self.render_jobs.push_back(job);
// create the mesh's buffers
let buffers = self.create_model_buffers(model, indices);
self.buffer_storage.insert(entity, buffers);
} else {
// update entity transforms
self.transform_buffers.update_entity(&self.queue, &self.render_limits,
entity, transform.transform.calculate_mat4());
// if the model was updated, update its buffers
if model_epoch == last_epoch {
self.update_model_buffers(entity, model);
}
} }
} }
for (entity, mesh, mesh_epoch, transform) in main_world.query::<(Entities, &MeshComponent, EpochOf<MeshComponent>, &TransformComponent)>().iter() {
debug!("TODO: Process MeshComponents"); // TODO: Process MeshComponents
}
// collect dead entities // collect dead entities
self.transform_buffers.tick(); self.transform_buffers.tick();
// when buffer storage length does not match the amount of iterated entities, // when buffer storage length does not match the amount of iterated entities,
// remove all dead entities, and their buffers, if they weren't iterated over // remove all dead entities, and their buffers, if they weren't iterated over
if self.buffer_storage.len() != alive_entities.len() { if self.mesh_buffers.len() != alive_entities.len() {
self.buffer_storage.retain(|e, _| alive_entities.contains(e)); let removed_entities: Vec<uuid::Uuid> = self.entity_meshes
.extract_if(|e, _| !alive_entities.contains(e))
.map(|(_, v)| v)
.collect();
self.mesh_buffers.retain(|u, _| !removed_entities.contains(u));
} }
if let Some(camera) = main_world.query_mut::<(&mut CameraComponent,)>().into_iter().next() { if let Some(camera) = main_world.query_mut::<(&mut CameraComponent,)>().into_iter().next() {
@ -644,17 +689,18 @@ impl Renderer for BasicRenderer {
// Pop off jobs from the queue as they're being processed // Pop off jobs from the queue as they're being processed
while let Some(job) = self.render_jobs.pop_front() { while let Some(job) = self.render_jobs.pop_front() {
if let Some(pipeline) = self.render_pipelines.get(&job.material().shader_id) { if let Some(pipeline) = self.render_pipelines.get(&job.shader_id) {
// specify to use this pipeline // specify to use this pipeline
render_pass.set_pipeline(pipeline.get_wgpu_pipeline()); render_pass.set_pipeline(pipeline.get_wgpu_pipeline());
// get the mesh (containing vertices) and the buffers from storage // get the mesh (containing vertices) and the buffers from storage
let mesh = job.mesh(); let buffers = self.mesh_buffers.get(&job.mesh_buffer_id).unwrap();
let buffers = self.buffer_storage.get(&job.entity()).unwrap();
// Bind the optional texture // Bind the optional texture
if let Some(tex) = buffers.texture_bindgroup.as_ref() { if let Some(tex) = buffers.texture_bindgroup.as_ref() {
render_pass.set_bind_group(0, &tex, &[]); render_pass.set_bind_group(0, &tex, &[]);
} else {
render_pass.set_bind_group(0, &self.default_texture_bind_group, &[]);
} }
// Get the bindgroup for job's transform and bind to it using an offset. // Get the bindgroup for job's transform and bind to it using an offset.
@ -667,18 +713,18 @@ impl Renderer for BasicRenderer {
render_pass.set_bind_group(2, &self.camera_bind_group, &[]); render_pass.set_bind_group(2, &self.camera_bind_group, &[]);
// if this mesh uses indices, use them to draw the mesh // if this mesh uses indices, use them to draw the mesh
if let Some(indices) = buffers.buffer_indices.as_ref() { if let Some((idx_type, indices)) = buffers.buffer_indices.as_ref() {
let indices_len = indices.count() as u32; let indices_len = indices.count() as u32;
render_pass.set_vertex_buffer(buffers.buffer_vertex.slot(), buffers.buffer_vertex.buffer().slice(..)); render_pass.set_vertex_buffer(buffers.buffer_vertex.slot(), buffers.buffer_vertex.buffer().slice(..));
render_pass.set_index_buffer(indices.buffer().slice(..), wgpu::IndexFormat::Uint16); render_pass.set_index_buffer(indices.buffer().slice(..), idx_type.clone());
render_pass.draw_indexed(0..indices_len, 0, 0..1); render_pass.draw_indexed(0..indices_len, 0, 0..1);
} else { } else {
let vertex_count = buffers.buffer_vertex.count();
render_pass.set_vertex_buffer(buffers.buffer_vertex.slot(), buffers.buffer_vertex.buffer().slice(..)); render_pass.set_vertex_buffer(buffers.buffer_vertex.slot(), buffers.buffer_vertex.buffer().slice(..));
render_pass.draw(0..mesh.vertices.len() as u32, 0..1); render_pass.draw(0..vertex_count as u32, 0..1);
} }
} else {
warn!("Failure to find RenderPipeline with shader id of '{}'!", job.material().shader_id);
} }
} }
} }
@ -706,7 +752,7 @@ impl Renderer for BasicRenderer {
self.size self.size
} }
fn add_render_pipeline(&mut self, shader_id: u32, pipeline: Arc<FullRenderPipeline>) { fn add_render_pipeline(&mut self, shader_id: u64, pipeline: Arc<FullRenderPipeline>) {
self.render_pipelines.insert(shader_id, pipeline); self.render_pipelines.insert(shader_id, pipeline);
} }
} }

View File

@ -3,9 +3,17 @@ use super::desc_buf_lay::DescVertexBufferLayout;
#[repr(C)] #[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] #[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct Vertex { pub struct Vertex {
pub position: [f32; 3], pub position: glam::Vec3,
pub tex_coords: glam::Vec2
//pub color: [f32; 3], // TODO: add color again //pub color: [f32; 3], // TODO: add color again
pub tex_coords: [f32; 2] }
impl Vertex {
pub fn new(position: glam::Vec3, tex_coords: glam::Vec2) -> Self {
Self {
position, tex_coords
}
}
} }
impl DescVertexBufferLayout for Vertex { impl DescVertexBufferLayout for Vertex {
@ -17,12 +25,12 @@ impl DescVertexBufferLayout for Vertex {
wgpu::VertexAttribute { wgpu::VertexAttribute {
offset: 0, offset: 0,
shader_location: 0, shader_location: 0,
format: wgpu::VertexFormat::Float32x3, format: wgpu::VertexFormat::Float32x3, // Vec3
}, },
wgpu::VertexAttribute { wgpu::VertexAttribute {
offset: std::mem::size_of::<[f32; 3]>() as wgpu::BufferAddress, offset: std::mem::size_of::<[f32; 3]>() as wgpu::BufferAddress,
shader_location: 1, shader_location: 1,
format: wgpu::VertexFormat::Float32x2, format: wgpu::VertexFormat::Float32x2, // Vec2
} }
] ]
} }