diff --git a/src/app.rs b/src/app.rs index 511d30f..faa9c5f 100644 --- a/src/app.rs +++ b/src/app.rs @@ -189,7 +189,9 @@ impl VulkanContext { ); let scene = Scene::from_file_json("./test_scene.scene"); - let mesh_registry = Arc::new(MeshRegistry::from_scene(&scene, mem_allocator.clone())); + let mut mesh_registry = MeshRegistry::new(mem_allocator.clone()); + mesh_registry.load_scene(&scene, mem_allocator.clone()); + let mesh_registry = Arc::new(mesh_registry); let instance_registry = Arc::new(InstanceRegistry::from_scene(&scene)); Ok(Self { @@ -458,8 +460,23 @@ impl VulkanContext { camera_resources.descriptor_set().clone(), )? .bind_pipeline_graphics(pipeline.clone())? - .bind_vertex_buffers(0, mesh_registry.vertex_buffer.as_ref().clone())? - .bind_index_buffer(mesh_registry.index_buffer.as_ref().clone())?; + .bind_vertex_buffers( + 0, + mesh_registry + .vertex_buffer + .as_ref() + .expect("Vertex buffer not allocated") + .as_ref() + .clone(), + )? + .bind_index_buffer( + mesh_registry + .index_buffer + .as_ref() + .expect("Index buffer not allocated") + .as_ref() + .clone(), + )?; unsafe { render_batches.iter().for_each(|batch| { @@ -470,15 +487,21 @@ impl VulkanContext { builder .bind_vertex_buffers(1, batch.instance_buffer.clone()) - .unwrap() - .draw_indexed( - mesh.index_count as u32, - batch.instance_count, - mesh.index_loc as u32, - mesh.vertex_loc as i32, - 0, - ) .unwrap(); + + // Issue one draw call per submesh so each can eventually be bound to its own + // material. All submeshes share the mesh's instance buffer. + for submesh in &mesh.submeshes { + builder + .draw_indexed( + submesh.index_count as u32, + batch.instance_count, + submesh.index_loc as u32, + mesh.vertex_loc as i32, + 0, + ) + .unwrap(); + } }); } diff --git a/src/assets/asset_manager.rs b/src/assets/asset_manager.rs index 696560d..761e3de 100644 --- a/src/assets/asset_manager.rs +++ b/src/assets/asset_manager.rs @@ -39,15 +39,19 @@ impl AssetManager { /// /// This method will attempt to auto-generate IDs for materials and textures if they do not have /// any preconfigured, using the ID of the mesh and the type of resource being stored. - pub fn load_model>(&mut self, mesh_id: String, path: P) -> Result<()> { + pub fn load_model>(&mut self, _mesh_id: String, path: P) -> Result<()> { // Load and register all materials first - submeshes will reference these materials. - let materials = self.material_loader.load_materials(&path)?; + let _materials = self.material_loader.load_materials(&path)?; - // Load and register the mesh using the materials - let mesh = self.mesh_loader.load_mesh(&path)?; - self.mesh_registry.register(mesh_id, mesh); + // Load the mesh. Each of `mesh.submeshes` carries a `material_index` local to this file, + // which should be resolved against `materials` (e.g. via a generated + // "{mesh_id}_mat_{index}" id) once there's a way to register it. + let _mesh = self.mesh_loader.load_mesh(&path)?; - Ok(()) + // `MeshRegistry` only knows how to allocate its shared vertex/index buffers in one batch + // via `load_scene`; there's no way yet to register a single mesh's `MeshInfo` into + // already-allocated buffers (or grow them), so this can't build a `MeshHandle` yet. + todo!("MeshRegistry needs support for registering a single mesh after its buffers are already allocated") } /// Loads all information from a scene, returning an empty `Result` indicating success or failure. diff --git a/src/assets/loaders/mesh/gltf_loader.rs b/src/assets/loaders/mesh/gltf_loader.rs index a8cc03f..a61d4c2 100644 --- a/src/assets/loaders/mesh/gltf_loader.rs +++ b/src/assets/loaders/mesh/gltf_loader.rs @@ -1,4 +1,4 @@ -use crate::assets::loaders::mesh_loader::{MeshFileLoader, MeshInfo}; +use crate::assets::loaders::mesh_loader::{MeshFileLoader, MeshInfo, Submesh}; use anyhow::Result; use gltf::buffer::Data; use nalgebra::{Matrix4, Vector4}; @@ -15,7 +15,8 @@ impl GltfLoader { vertices: &mut Vec<[f32; 3]>, normals: &mut Vec<[f32; 3]>, uvs: &mut Vec<[f32; 3]>, - indices: &mut Vec + indices: &mut Vec, + submeshes: &mut Vec, ) { let transform = node.transform().matrix() .iter() @@ -29,6 +30,7 @@ impl GltfLoader { // Read the mesh and apply the current transformation to every vertex for primitive in mesh.primitives() { let vert_offset = vertices.len() as u32; + let index_start = indices.len(); let reader = primitive.reader(|buffer_index| Some(&buffers[buffer_index.index()])); @@ -59,8 +61,11 @@ impl GltfLoader { } } - primitive.material().pbr_metallic_roughness() - .metallic_factor(); + submeshes.push(Submesh { + material_index: primitive.material().index(), + index_start, + index_count: indices.len() - index_start, + }); } } @@ -73,7 +78,8 @@ impl GltfLoader { vertices, normals, uvs, - indices + indices, + submeshes, ); } } @@ -87,6 +93,7 @@ impl MeshFileLoader for GltfLoader { let mut normals = Vec::new(); let mut uvs = Vec::new(); let mut indices = Vec::::new(); + let mut submeshes = Vec::new(); let scenes = document.scenes(); @@ -104,7 +111,8 @@ impl MeshFileLoader for GltfLoader { &mut vertices, &mut normals, &mut uvs, - &mut indices + &mut indices, + &mut submeshes, ); } } @@ -114,6 +122,7 @@ impl MeshFileLoader for GltfLoader { indices, normals, uvs, + submeshes, }) } } diff --git a/src/assets/loaders/mesh/mesh_loader.rs b/src/assets/loaders/mesh/mesh_loader.rs index 33087e4..d87a40c 100644 --- a/src/assets/loaders/mesh/mesh_loader.rs +++ b/src/assets/loaders/mesh/mesh_loader.rs @@ -14,6 +14,23 @@ pub struct MeshInfo { pub indices: Vec, /// The texture coordinates. Note that these may also be used on 3D textures. pub uvs: Vec<[f32; 3]>, + /// The contiguous ranges of `indices` that should be drawn with a single material, in the + /// order they were encountered in the source file. + pub submeshes: Vec, +} + +/// A contiguous range within a [`MeshInfo`]'s `indices`, along with the material it should be +/// drawn with. Ranges are local to a single loaded file - a [`crate::common::mesh::MeshRegistry`] +/// rewrites these into offsets within its own shared index buffer. +#[derive(Debug, Clone)] +pub struct Submesh { + /// The index of the material this submesh uses, as defined by the source file (e.g. a glTF + /// material index). `None` if the file did not assign a material to this range. + pub material_index: Option, + /// The offset of this submesh's first index within `MeshInfo::indices`. + pub index_start: usize, + /// The number of indices that make up this submesh. + pub index_count: usize, } pub trait MeshFileLoader { diff --git a/src/assets/loaders/mesh/obj_loader.rs b/src/assets/loaders/mesh/obj_loader.rs index e1473e2..463b270 100644 --- a/src/assets/loaders/mesh/obj_loader.rs +++ b/src/assets/loaders/mesh/obj_loader.rs @@ -1,4 +1,4 @@ -use crate::assets::loaders::mesh_loader::{MeshFileLoader, MeshInfo}; +use crate::assets::loaders::mesh_loader::{MeshFileLoader, MeshInfo, Submesh}; use std::path::Path; use wavefront::Obj; @@ -17,11 +17,20 @@ impl MeshFileLoader for ObjLoader { }) .collect::>(); + // Wavefront `.mtl` material groups aren't loaded yet, so the whole mesh is treated as a + // single submesh with no assigned material. + let submeshes = vec![Submesh { + material_index: None, + index_start: 0, + index_count: index_list.len(), + }]; + Ok(MeshInfo { vertices: obj_info.positions().to_vec(), normals: obj_info.normals().to_vec(), indices: index_list, uvs: obj_info.uvs().to_vec(), + submeshes, }) } } diff --git a/src/common/mesh.rs b/src/common/mesh.rs index 99b3c6d..f0da5ac 100644 --- a/src/common/mesh.rs +++ b/src/common/mesh.rs @@ -19,10 +19,29 @@ pub struct MeshHandle { pub vertex_count: usize, pub index_loc: usize, pub index_count: usize, + /// The submeshes that make up this mesh, in the order they should be drawn. Each one covers + /// a contiguous range of the registry's shared index buffer and (once material loading is + /// wired up) should be drawn with its own material bound. + pub submeshes: Vec, } impl MeshHandle {} +/// A submesh's location within a [`MeshRegistry`]'s shared index buffer, along with a reference +/// to the material it was assigned in its source file. +/// +/// Unlike [`crate::assets::loaders::mesh_loader::Submesh`], `index_loc` here is an absolute +/// offset into the registry's shared index buffer rather than an offset local to one loaded file. +#[derive(Debug, Clone)] +pub struct SubmeshHandle { + /// The material index this submesh was assigned in its source file, if any. This is not yet + /// resolved to a [`crate::common::material::Material`] - that mapping is the caller's + /// responsibility until material loading is wired into mesh registration. + pub material_index: Option, + pub index_loc: usize, + pub index_count: usize, +} + type VertexBuffer = Arc>; type IndexBuffer = Arc>; @@ -102,9 +121,19 @@ impl MeshRegistry { let obj_num_idx = obj.indices.len(); buf_indices.extend(obj.indices); + let submeshes = obj + .submeshes + .into_iter() + .map(|submesh| SubmeshHandle { + material_index: submesh.material_index, + index_loc: index_start + submesh.index_start, + index_count: submesh.index_count, + }) + .collect::>(); + println!( - "Loaded mesh \"{}\" with {} vertices and {} indices", - id, obj_num_verts, obj_num_idx + "Loaded mesh \"{}\" with {} vertices, {} indices, and {} submesh(es)", + id, obj_num_verts, obj_num_idx, submeshes.len() ); // TODO Create bounding boxes for each mesh and associate them with the struct. @@ -116,6 +145,7 @@ impl MeshRegistry { vertex_count: obj_num_verts, index_loc: index_start, index_count: obj_num_idx, + submeshes, }, ); });