feat: route mesh access through AssetManager instead of MeshRegistry

AssetManager now owns scene loading and single-model registration
(MeshRegistry::register_mesh appends a mesh into the shared buffers
and rebuilds them), and exposes vertex/index buffers and mesh lookups.
app.rs and RenderBatch now go through AssetManager rather than talking
to MeshRegistry directly, so material lookups can be added there later
without touching callers again.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01986QNHM1cUsnRskKvBbbuo
This commit is contained in:
alphafetusandClaude Sonnet 5 committed 2026-09-10 11:42:09 -04:00
1 parent 9a93c0d189
commit 4d9a0c8b43
4 files changed
+174 -110

No files matched your search

+15 -28
View File
@@ -1,6 +1,6 @@
use crate::assets::asset_manager::AssetManager;
use crate::common::camera::{Camera, CameraResources};
use crate::common::instance::InstanceRegistry;
use crate::common::mesh::MeshRegistry;
use crate::common::render_batch::RenderBatch;
use crate::common::scene::Scene;
use crate::gpu::instance::GpuInstance;
@@ -62,7 +62,7 @@ struct VulkanContext {
render_pass: Arc<RenderPass>,
framebuffers: Vec<Arc<Framebuffer>>,
pipeline: Arc<GraphicsPipeline>,
mesh_registry: Arc<MeshRegistry>,
asset_manager: Arc<AssetManager>,
instance_registry: Arc<InstanceRegistry>,
camera: Camera,
camera_resources: Arc<CameraResources>,
@@ -189,9 +189,11 @@ impl VulkanContext {
);
let scene = Scene::from_file_json("./test_scene.scene");
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 mut asset_manager = AssetManager::new(mem_allocator.clone());
asset_manager
.load_scene(&scene)
.expect("Couldn't load scene's meshes");
let asset_manager = Arc::new(asset_manager);
let instance_registry = Arc::new(InstanceRegistry::from_scene(&scene));
Ok(Self {
@@ -208,7 +210,7 @@ impl VulkanContext {
frag_shader,
framebuffers,
pipeline,
mesh_registry,
asset_manager,
instance_registry,
camera,
camera_resources,
@@ -247,10 +249,10 @@ impl VulkanContext {
self.framebuffers[image_idx as usize].clone(),
&self.camera,
&self.camera_resources,
self.mesh_registry.clone(),
self.asset_manager.clone(),
RenderBatch::build_batches(
self.mem_allocator.clone(),
self.mesh_registry.clone(),
self.asset_manager.clone(),
self.instance_registry.clone(),
),
)?;
@@ -431,7 +433,7 @@ impl VulkanContext {
framebuffer: Arc<Framebuffer>,
camera: &Camera,
camera_resources: &CameraResources,
mesh_registry: Arc<MeshRegistry>,
asset_manager: Arc<AssetManager>,
render_batches: Vec<RenderBatch>,
) -> Result<Arc<PrimaryAutoCommandBuffer>, Box<dyn Error>> {
let mut builder = AutoCommandBufferBuilder::primary(
@@ -460,29 +462,14 @@ impl VulkanContext {
camera_resources.descriptor_set().clone(),
)?
.bind_pipeline_graphics(pipeline.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(),
)?;
.bind_vertex_buffers(0, asset_manager.vertex_buffer())?
.bind_index_buffer(asset_manager.index_buffer())?;
unsafe {
render_batches.iter().for_each(|batch| {
// Grab mesh information
let mesh = mesh_registry
.get(&batch.mesh_id)
let mesh = asset_manager
.get_mesh(&batch.mesh_id)
.expect("Mesh does not exist in registry");
builder
+55 -16
View File
@@ -1,15 +1,25 @@
use std::collections::HashMap;
use std::path::Path;
use std::sync::Arc;
use anyhow::Result;
use vulkano::buffer::Subbuffer;
use vulkano::memory::allocator::MemoryAllocator;
use crate::assets::loaders::material::mat_loader::MaterialLoader;
use crate::assets::loaders::mesh_loader::MeshLoader;
use crate::common::material::MaterialRegistry;
use crate::common::mesh::{MeshHandle, MeshRegistry};
use crate::common::scene::Scene;
use crate::gpu::vertex3::Vertex3;
/// A manager that controls all assets used for display. It handles loading all meshes, materials,
/// and textures for now.
///
/// Callers outside this module should go through `AssetManager` rather than reaching into a
/// [`MeshRegistry`]/[`MaterialRegistry`] directly - it's the thing that will keep meshes and their
/// materials consistent with each other once material loading is implemented.
pub struct AssetManager {
/// The memory allocator used to (re)allocate the mesh registry's buffers.
allocator: Arc<dyn MemoryAllocator>,
/// The asset manager's [`MeshRegistry`].
mesh_registry: MeshRegistry,
/// The asset manager's [`MaterialRegistry`].
@@ -26,6 +36,7 @@ impl AssetManager {
let mesh_registry = MeshRegistry::new(allocator.clone());
let material_registry = MaterialRegistry::new();
Self {
allocator,
mesh_registry,
material_registry,
mesh_loader: MeshLoader::new(),
@@ -39,23 +50,51 @@ 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<P: AsRef<Path>>(&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)?;
pub fn load_model<P: AsRef<Path>>(&mut self, mesh_id: String, path: P) -> Result<()> {
// TODO: load this model's materials via `self.material_loader` and register them into
// `self.material_registry` under "{mesh_id}_mat_{index}" ids. `MeshRegistry` already
// derives each submesh's `material_id` using that exact format, so nothing else needs to
// change here once a real material loader exists.
let mesh = self.mesh_loader.load_mesh(&path)?;
self.mesh_registry.register_mesh(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)?;
// `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.
pub fn load_scene<P: AsRef<Path>>(&self, path: P) -> Result<()> {
Ok(())
}
/// Loads every mesh referenced by a [`Scene`] and allocates the buffers needed to render them.
pub fn load_scene(&mut self, scene: &Scene) -> Result<()> {
self.mesh_registry.load_scene(scene, self.allocator.clone());
Ok(())
}
/// Returns the shared vertex buffer backing every mesh currently registered.
pub fn vertex_buffer(&self) -> Subbuffer<[Vertex3]> {
self.mesh_registry
.vertex_buffer
.as_ref()
.expect("Vertex buffer not allocated")
.as_ref()
.clone()
}
/// Returns the shared index buffer backing every mesh currently registered.
pub fn index_buffer(&self) -> Subbuffer<[u32]> {
self.mesh_registry
.index_buffer
.as_ref()
.expect("Index buffer not allocated")
.as_ref()
.clone()
}
/// Looks up a registered mesh by its ID.
pub fn get_mesh(&self, mesh_id: &String) -> Option<&MeshHandle> {
self.mesh_registry.get(mesh_id)
}
/// Returns every currently registered mesh, keyed by ID.
pub fn meshes(&self) -> &HashMap<String, MeshHandle> {
&self.mesh_registry.meshes
}
}
+99 -61
View File
@@ -34,10 +34,11 @@ impl MeshHandle {}
/// 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<usize>,
/// The id this submesh's material should be looked up under in a
/// [`crate::common::material::MaterialRegistry`], if the source file assigned one. Follows
/// the `"{mesh_id}_mat_{index}"` convention - nothing requires a material actually be
/// registered under this id yet, until material loading is wired up.
pub material_id: Option<String>,
pub index_loc: usize,
pub index_count: usize,
}
@@ -58,6 +59,11 @@ pub struct MeshRegistry {
pub vertex_buffer: Option<VertexBuffer>,
/// The index buffer allocated for all registered meshes.
pub index_buffer: Option<IndexBuffer>,
/// The raw vertex data backing `vertex_buffer`, kept around so a single mesh can be appended
/// via [`Self::register_mesh`] without losing every previously registered mesh's data.
raw_vertices: Vec<Vertex3>,
/// The raw index data backing `index_buffer`. See `raw_vertices`.
raw_indices: Vec<u32>,
}
impl MeshRegistry {
@@ -67,13 +73,79 @@ impl MeshRegistry {
meshes: HashMap::new(),
vertex_buffer: None,
index_buffer: None,
raw_vertices: Vec::new(),
raw_indices: Vec::new(),
allocator
}
}
/// Registers a [`MeshHandle`] to the registry, if it does not already exist.
pub fn register(&mut self, mesh_id: String, mesh: MeshHandle) {
self.meshes.insert(mesh_id, mesh);
/// Appends a single already-loaded mesh to `verts`/`indices`, returning a [`MeshHandle`]
/// describing where its data landed. A submesh's `material_id` is derived purely from
/// `mesh_id` and the submesh's position in the source file, so this works whether or not a
/// material has actually been registered under that id yet.
fn append_mesh(
mesh_id: &str,
info: MeshInfo,
verts: &mut Vec<Vertex3>,
indices: &mut Vec<u32>,
) -> MeshHandle {
let vertex_loc = verts.len();
let vertex_count = info.vertices.len();
info.vertices
.iter()
.zip(info.normals.iter())
.zip(info.uvs.iter())
.for_each(|((pos, norm), uv)| {
verts.push(Vertex3::new(*pos, *norm, *uv));
});
let index_loc = indices.len();
let index_count = info.indices.len();
indices.extend(info.indices);
let submeshes = info
.submeshes
.into_iter()
.map(|submesh| SubmeshHandle {
material_id: submesh
.material_index
.map(|i| format!("{mesh_id}_mat_{i}")),
index_loc: index_loc + submesh.index_start,
index_count: submesh.index_count,
})
.collect::<Vec<_>>();
println!(
"Loaded mesh \"{}\" with {} vertices, {} indices, and {} submesh(es)",
mesh_id, vertex_count, index_count, submeshes.len()
);
MeshHandle {
id: mesh_id.to_string(),
vertex_loc,
vertex_count,
index_loc,
index_count,
submeshes,
}
}
/// Registers a single mesh, independently of a full [`Scene`] - e.g. from
/// [`crate::assets::asset_manager::AssetManager`] loading one model at a time. Appends the
/// mesh's data to the registry's shared buffers and reallocates them.
///
/// This is far less efficient than batching a whole scene through [`Self::load_scene`], since
/// every call reallocates and re-uploads the full vertex/index buffers - fine for loading a
/// handful of models up front, not for anything performance-sensitive.
pub fn register_mesh(&mut self, mesh_id: String, info: MeshInfo) {
let handle = Self::append_mesh(
&mesh_id,
info,
&mut self.raw_vertices,
&mut self.raw_indices,
);
self.meshes.insert(mesh_id, handle);
self.rebuild_buffers();
}
/// Creates a `MeshRegistry` from the given [`Scene`].
@@ -84,6 +156,7 @@ impl MeshRegistry {
pub fn load_scene(&mut self, scene: &Scene, allocator: Arc<dyn MemoryAllocator>) {
// Load all the meshes into a single map.
println!("Loading meshes from scene");
self.allocator = allocator;
let mesh_loader = MeshLoader::new();
let meshes = scene
@@ -99,64 +172,29 @@ impl MeshRegistry {
})
.collect::<HashMap<String, MeshInfo>>();
let mut buf_verts = Vec::new();
let mut buf_indices = Vec::new();
self.raw_vertices.clear();
self.raw_indices.clear();
let mut mesh_list = HashMap::new();
meshes.into_iter().for_each(|(id, obj)| {
// Information will be added directly to the verts and indices vectors, and a new Mesh
// struct will be created and inserted into the mesh_list.
let vert_start = buf_verts.len();
let all_verts = obj.vertices;
let all_norms = obj.normals;
let obj_num_verts = all_verts.len();
all_verts
.iter()
.zip(all_norms.iter())
.zip(obj.uvs.iter())
.for_each(|((pos, norm), uv)| {
buf_verts.push(Vertex3::new(*pos, *norm, *uv));
});
let index_start = buf_indices.len();
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::<Vec<_>>();
println!(
"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.
mesh_list.insert(
id.clone(),
MeshHandle {
id,
vertex_loc: vert_start,
vertex_count: obj_num_verts,
index_loc: index_start,
index_count: obj_num_idx,
submeshes,
},
);
meshes.into_iter().for_each(|(id, info)| {
let handle = Self::append_mesh(&id, info, &mut self.raw_vertices, &mut self.raw_indices);
mesh_list.insert(id, handle);
});
// Allocate buffers for the information.
let vertex_buffer = Self::alloc_vert_buffer(allocator.clone(), buf_verts);
let index_buffer = Self::alloc_index_buffer(allocator.clone(), buf_indices);
self.meshes = mesh_list;
self.vertex_buffer = Some(vertex_buffer);
self.index_buffer = Some(index_buffer);
self.rebuild_buffers();
}
/// (Re)allocates `vertex_buffer`/`index_buffer` from the currently accumulated
/// `raw_vertices`/`raw_indices`.
fn rebuild_buffers(&mut self) {
self.vertex_buffer = Some(Self::alloc_vert_buffer(
self.allocator.clone(),
self.raw_vertices.clone(),
));
self.index_buffer = Some(Self::alloc_index_buffer(
self.allocator.clone(),
self.raw_indices.clone(),
));
}
/// Allocates and writes to a vertex buffer with the given `Vec<Vertex3>`.
+5 -5
View File
@@ -1,5 +1,5 @@
use crate::assets::asset_manager::AssetManager;
use crate::common::instance::InstanceRegistry;
use crate::common::mesh::MeshRegistry;
use crate::gpu::instance::GpuInstance;
use std::sync::Arc;
use vulkano::buffer::{Buffer, BufferCreateInfo, BufferUsage, Subbuffer};
@@ -17,13 +17,13 @@ impl RenderBatch {
/// once per frame.
pub fn build_batches(
allocator: Arc<dyn MemoryAllocator>,
mesh_registry: Arc<MeshRegistry>,
asset_manager: Arc<AssetManager>,
instance_registry: Arc<InstanceRegistry>,
) -> Vec<Self> {
mesh_registry
.meshes
asset_manager
.meshes()
.iter()
.filter_map(|(id, mesh)| {
.filter_map(|(id, _mesh)| {
let instances = instance_registry.get_instances_for(id);
if instances.is_empty() {