VEX Engine
Retro looking game engine made in vulkan mostly because i wanted to understand it better.
Loading...
Searching...
No Matches
MeshManager.cpp
1#include "MeshManager.hpp"
4#include "components/Mesh.hpp"
5#include "components/ErrorUtils.hpp"
6#include "components/ThreadPool.hpp"
7
8#include <fstream>
9#include <iterator>
10#include <unordered_set>
11#include <SDL3/SDL.h>
12#include "limits.hpp"
13
14#include <filesystem>
15#include <algorithm>
16
17namespace vex {
18 MeshManager::MeshManager(VulkanContext& context, std::unique_ptr<VulkanResources>& resources, VirtualFileSystem* vfs)
19 : m_r_context(context), m_p_resources(resources), m_vfs(vfs) {
20 log("MeshManager initialized");
21 }
22
23 MeshManager::~MeshManager() {
24 m_vulkanMeshes.clear();
25 log("MeshManager destroyed");
26 }
27
28 ModelObject* MeshManager::createModel(const std::string& name, MeshComponent meshComponent, TransformComponent transformComponent, vex::Entity parent = vex::NULL_ENTITY){
29 log("Constructing model: %s...", name.c_str());
30
31 std::string tempName = name;
32
33 uint32_t newId;
34 if (!m_freeModelIds.empty()) {
35 newId = m_freeModelIds.back();
36 m_freeModelIds.pop_back();
37 } else {
38 newId = m_nextModelId++;
39 if (newId >= MAX_MODELS) {
40 throw_error("Maximum model count exceeded");
41 }
42 }
43 meshComponent.id = newId;
44 meshComponent.textureNames.clear();
45
46 for (const auto& submesh : meshComponent.meshData.submeshes) {
47 if (!submesh.texturePath.empty()) {
48 meshComponent.textureNames.push_back(submesh.texturePath);
49 }
50 }
51 try {
52 log("Creating Vulkan mesh for %s", tempName.c_str());
53 if(m_vulkanMeshes.find(meshComponent.meshData.meshPath) == m_vulkanMeshes.end()){
54 m_vulkanMeshes.emplace(meshComponent.meshData.meshPath, std::make_unique<VulkanMesh>(m_r_context));
55 m_vulkanMeshes.at(meshComponent.meshData.meshPath)->upload(meshComponent.meshData);
56 m_vulkanMeshes.at(meshComponent.meshData.meshPath)->addInstance();
57 }else{
58 m_vulkanMeshes.at(meshComponent.meshData.meshPath)->addInstance();
59 log("Reusing same mesh model");
60 }
61 //m_vulkanMeshes.push_back(std::make_unique<VulkanMesh>(m_r_context));
62 //log("vulkanMesh id: %i", m_vulkanMeshes.size());
63 log("Mesh upload successful");
64 } catch (const std::exception& e) {
65 log(LogLevel::ERROR, "Mesh upload failed");
66 m_vulkanMeshes.erase(meshComponent.meshData.meshPath);
68 }
69
70 //vex::Entity modelEntity = m_p_engine->getRegistry()->.create();
71 ModelObject* modelObject = new ModelObject(*m_p_engine, tempName, meshComponent, transformComponent);
72 //modelObject->cleanup = [this](std::string& tempName, const MeshComponent& meshComponent) { destroyModel(tempName, meshComponent); };
73 return modelObject;
74 }
75
76 MeshComponent MeshManager::loadMesh(const std::string& path) {
77 MeshData meshData;
78 MeshComponent meshComponent;
79 std::string realPath = GetAssetPath(path);
80
81 try {
82 log("Loading mesh data from: %s", realPath.c_str());
83
84 if (!m_vfs->file_exists(realPath)) {
85 throw_error("File not found: " + realPath);
86 }
87
88 meshData.loadFromFile(realPath, m_vfs);
89 meshData.meshPath = path;
90 } catch (const std::exception& e) {
91 log(LogLevel::ERROR, "Mesh load failed: %s", path.c_str());
93 }
94
95 meshComponent.meshData = std::move(meshData);
96
97 glm::vec3 min = glm::vec3(FLT_MAX);
98 glm::vec3 max = glm::vec3(-FLT_MAX);
99
100 for(auto& submesh : meshComponent.meshData.submeshes) {
101 for(auto& vertex : submesh.vertices) {
102 min = glm::min(min, vertex.position);
103 max = glm::max(max, vertex.position);
104 }
105 }
106
107 glm::vec3 center = (min + max) * 0.5f;
108 float radius = glm::length(max - center);
109
110 meshComponent.localCenter = center;
111 meshComponent.localRadius = radius;
112
113 return meshComponent;
114 }
115
116 std::unique_ptr<VulkanMesh>& MeshManager::getVulkanMeshByMesh(MeshComponent& meshComponent) {
117 if (meshComponent.id == UINT32_MAX) {
118 if (!m_freeModelIds.empty()) {
119 meshComponent.id = m_freeModelIds.back();
120 m_freeModelIds.pop_back();
121 } else {
122 meshComponent.id = m_nextModelId++;
123 }
124 }
125
126 std::string& installedPath = m_installedPaths[meshComponent.id];
127 std::string requestedPath = meshComponent.meshData.meshPath;
128
129 if (installedPath != requestedPath) [[unlikely]] {
130 log("Swapping mesh %s -> %s for model %d", installedPath.c_str(), requestedPath.c_str(), meshComponent.id);
131
132 releaseMeshReference(installedPath, meshComponent);
133
134 installedPath = requestedPath;
135
136 bool alreadyExisted = m_vulkanMeshes.count(requestedPath) > 0;
137
138 registerVulkanMesh(meshComponent);
139
140 if (alreadyExisted && m_vulkanMeshes.count(requestedPath)) {
141 m_vulkanMeshes.at(requestedPath)->addInstance();
142 }
143 } else [[likely]] {
144 registerVulkanMesh(meshComponent);
145 }
146
147 if (m_vulkanMeshes.count(requestedPath)) {
148 return m_vulkanMeshes.at(requestedPath);
149 }
150
151 static std::unique_ptr<VulkanMesh> nullMesh = nullptr;
152 return nullMesh;
153 }
154
156 const std::string& path = meshComponent.meshData.meshPath;
157 if (m_vulkanMeshes.count(path)) {
158 if (m_meshBoundsCache.count(path)) {
159 auto& bounds = m_meshBoundsCache[path];
160 meshComponent.localCenter = bounds.first;
161 meshComponent.localRadius = bounds.second;
162 meshComponent.forceRefresh();
163 }
164 return;
165 }
166 if (!(meshComponent.meshData.meshPath == "" || meshComponent.meshData.meshPath.empty() || path == GetAssetDir())) [[unlikely]] {
167 #if DEBUG
168 if (meshComponent.meshData.meshPath.empty() || path == GetAssetDir() || !m_vfs->file_exists(GetAssetPath(path))) {
169 log(LogLevel::WARNING, "Skipping registration for invalid path: %s", path.c_str());
170 return;
171 }
172 #endif
173 MeshComponent loadedAsset = loadMesh(path);
174
175 meshComponent.meshData = std::move(loadedAsset.meshData);
176 meshComponent.localCenter = loadedAsset.localCenter;
177 meshComponent.localRadius = loadedAsset.localRadius;
178 meshComponent.forceRefresh();
179
180 m_meshBoundsCache[path] = { loadedAsset.localCenter, loadedAsset.localRadius };
181
182 meshComponent.textureNames = std::move(loadedAsset.textureNames);
183 }else [[likely]] {
184 return;
185 }
186
187 meshComponent.textureNames.clear();
188 std::unordered_set<std::string> uniqueTextures;
189
190 for (const auto& submesh : meshComponent.meshData.submeshes) {
191 if (!submesh.texturePath.empty()) {
192 uniqueTextures.insert(submesh.texturePath);
193 meshComponent.textureNames.push_back(submesh.texturePath);
194 }
195 }
196
197 log("Lazy-loading %zu submesh textures for %s", uniqueTextures.size(), path.c_str());
198
199 if (!uniqueTextures.empty()) {
200 std::vector<std::string> texturesToLoad(uniqueTextures.begin(), uniqueTextures.end());
201 m_p_resources->loadTexturesBatched(texturesToLoad);
202 log("Batch Loaded textures");
203 }
204
205 try {
206 log("Initializing Vulkan mesh for: %s", path.c_str());
207
208 auto newVulkanMesh = std::make_unique<VulkanMesh>(m_r_context);
209 newVulkanMesh->upload(meshComponent.meshData);
210 newVulkanMesh->addInstance();
211
212 m_vulkanMeshes.emplace(path, std::move(newVulkanMesh));
213
214 log("Successfully registered mesh: %s", path.c_str());
215
216 } catch (const std::exception& e) {
217 log(LogLevel::ERROR, "Failed to register VulkanMesh: %s", path.c_str());
218 }
219 }
220
221 void MeshManager::loadMeshesAsync(const std::vector<MeshComponent*>& pendingComponents) {
222 if (pendingComponents.empty()) return;
223
224 std::vector<std::future<std::pair<MeshComponent*, MeshComponent>>> futures;
225
226 for (MeshComponent* comp : pendingComponents) {
227 const std::string path = comp->meshData.meshPath;
228 futures.push_back(GetThreadPool().enqueue([this, comp, path]() {
229 return std::make_pair(comp, this->loadMesh(path));
230 }));
231 }
232
233 std::unordered_set<std::string> uniqueTextures;
234
235 for (auto& future : futures) {
236 auto result = future.get();
237 MeshComponent* originalComp = result.first;
238 MeshComponent loadedData = std::move(result.second);
239
240 uint32_t originalId = originalComp->id;
241 RenderType originalRenderType = originalComp->renderType;
242 vex::rgba originalColor = originalComp->color;
243 auto originalOverrides = originalComp->textureOverrides;
244
245 *originalComp = std::move(loadedData);
246
247 originalComp->id = originalId;
248 originalComp->renderType = originalRenderType;
249 originalComp->color = originalColor;
250 originalComp->textureOverrides = originalOverrides;
251
252 for (const auto& texPath : originalComp->textureNames) {
253 if (!texPath.empty()) {
254 uniqueTextures.insert(texPath);
255 }
256 }
257 }
258
259 if (!uniqueTextures.empty()) {
260 std::vector<std::string> texturesToLoad(uniqueTextures.begin(), uniqueTextures.end());
261 m_p_resources->loadTexturesBatched(texturesToLoad);
262 }
263
264 for (MeshComponent* comp : pendingComponents) {
265 const std::string& path = comp->meshData.meshPath;
266
267 if (m_vulkanMeshes.find(path) == m_vulkanMeshes.end()) {
268 auto newVulkanMesh = std::make_unique<VulkanMesh>(m_r_context);
269 newVulkanMesh->upload(comp->meshData);
270 newVulkanMesh->addInstance();
271
272 m_meshBoundsCache[path] = { comp->localCenter, comp->localRadius };
273 m_vulkanMeshes.emplace(path, std::move(newVulkanMesh));
274
275 log("Async bulk loaded & registered mesh: %s", path.c_str());
276 } else {
277 m_vulkanMeshes[path]->addInstance();
278 }
279
280 comp->forceRefresh();
281 }
282 }
283
285 m_freeModelIds.clear();
286 m_nextModelId = 0;
287 m_vulkanMeshes.clear();
288 m_installedPaths.clear();
289 m_meshBoundsCache.clear();
290 }
291
293 auto& meshComponent = registry.get<MeshComponent>(entity);
294
295 if (!m_freeModelIds.empty()) {
296 meshComponent.id = m_freeModelIds.back();
297 m_freeModelIds.pop_back();
298 } else {
299 meshComponent.id = m_nextModelId++;
300 }
301
302 m_installedPaths[meshComponent.id] = meshComponent.meshData.meshPath;
303 const std::string& path = meshComponent.meshData.meshPath;
304
305 bool alreadyExisted = m_vulkanMeshes.count(path) > 0;
306
307 registerVulkanMesh(meshComponent);
308
309 if (alreadyExisted) {
310 m_vulkanMeshes.at(path)->addInstance();
311 }
312
313 if(registry.has<PhysicsComponent>(entity)) {
314 auto& oldPC = registry.get<PhysicsComponent>(entity);
315 if(oldPC.shape == ShapeType::MESH){
316 PhysicsComponent newPC = PhysicsComponent::Mesh(meshComponent, oldPC.bodyType, oldPC.mass, oldPC.friction, oldPC.bounce);
317 registry.add_or_replace<PhysicsComponent>(entity, newPC);
318 }
319
320 }
321 }
322
324 auto& meshComponent = registry.get<MeshComponent>(entity);
325
326 m_freeModelIds.push_back(meshComponent.id);
327
328 if (m_vulkanMeshes.count(meshComponent.meshData.meshPath)) {
329 auto& vulkanMesh = m_vulkanMeshes.at(meshComponent.meshData.meshPath);
330 vulkanMesh->removeInstance();
331
332 log("Reference count decreased for: %s (Total: %d)", meshComponent.meshData.meshPath.c_str(), vulkanMesh->getNumOfInstances());
333
334 if (vulkanMesh->getNumOfInstances() <= 0) {
335 log("Cleaning up unused mesh: %s", meshComponent.meshData.meshPath.c_str());
336
337 for (const auto& tex : meshComponent.textureNames) {
338 m_p_resources->unloadTexture(tex);
339 }
340
341 m_vulkanMeshes.erase(meshComponent.meshData.meshPath);
342 }
343 }
344 }
345
346 void MeshManager::releaseMeshReference(const std::string& path, MeshComponent& ownerComp) {
347 if (path.empty()) return;
348
349 if (m_vulkanMeshes.count(path)) {
350 auto& vulkanMesh = m_vulkanMeshes.at(path);
351 vulkanMesh->removeInstance();
352
353 log("Ref count decreased for: %s (Remaining: %d)", path.c_str(), vulkanMesh->getNumOfInstances());
354
355 if (vulkanMesh->getNumOfInstances() <= 0) {
356 log("Cleaning up unused mesh: %s", path.c_str());
357
358 for (const auto& tex : ownerComp.textureNames) {
359 m_p_resources->unloadTexture(tex);
360 }
361 ownerComp.textureNames.clear();
362 m_vulkanMeshes.erase(path);
363 }
364 }
365 }
366
367 void MeshManager::destroyModel(std::string& name, MeshComponent meshComponent) {
368 log("Freed model id");
369 m_freeModelIds.push_back(meshComponent.id);
370
371 if(getVulkanMeshByMesh(meshComponent)->getNumOfInstances() <= 1){
372 log("Erased VulkanMesh data");
373 m_vulkanMeshes.erase(meshComponent.meshData.meshPath);
374 for(size_t i = 0; i < meshComponent.textureNames.size(); i++){
375 m_p_resources->unloadTexture(meshComponent.textureNames[i]);
376 }
377 log("Unloaded textures");
378 }else{
379 getVulkanMeshByMesh(meshComponent)->removeInstance();
380 }
381 }
382}
Contains basic components like transform, camera, name components..
This file defines MeshData struct and all other structs needed for it.
This file defines PhysicsSystem class.
MeshComponent loadMesh(const std::string &path)
Loads mesh from a file, creates vulkan mesh and returns a MeshComponent.
void clearState()
Clears the state of the mesh manager, resetting model IDs and clearing the mesh map.
void registerVulkanMesh(MeshComponent &meshComponent)
Registers a Vulkan mesh component.
void onMeshComponentDestroy(vex::Registry &registry, vex::Entity entity)
Internally handles the destruction of a mesh component called by entt callbacks.
std::unique_ptr< VulkanMesh > & getVulkanMeshByMesh(MeshComponent &meshComponent)
Retrieves a Vulkan mesh by mesh component.
ModelObject * createModel(const std::string &name, MeshComponent meshComponent, TransformComponent transformComponent, vex::Entity parent)
Creates a model object from a mesh component, transform component, and parent entity.
void destroyModel(std::string &name, MeshComponent meshComponent)
Destroys a model object by name and mesh component.
void onMeshComponentConstruct(vex::Registry &registry, vex::Entity entity)
Internally handles the construction of a mesh component called by entt callbacks.
void loadMeshesAsync(const std::vector< MeshComponent * > &pendingComponents)
Loads meshes asynchronously from the given paths.
MeshManager(VulkanContext &context, std::unique_ptr< VulkanResources > &resources, VirtualFileSystem *vfs)
Constructor for MeshManager.
void releaseMeshReference(const std::string &path, MeshComponent &ownerComp)
Internally handles the release of a mesh reference called by entt callbacks.
ModelObject class represents a model object in the engine. Thanks to engine separation from the backe...
Central registry for managing entities and their components in the ECS system.
Definition Registry.hpp:29
bool has(Entity entity)
Checks if an entity has a component of type T.
Definition Registry.hpp:137
T & add_or_replace(Entity entity, Args &&... args)
Adds or replaces a component for an entity.
Definition Registry.hpp:100
T & get(Entity entity)
Retrieves a component from an entity.
Definition Registry.hpp:118
This class provides abstraction of file system needed for loading packed and unpacked assets.
This files defines global variables defining vulkan limits.
std::string VEX_EXPORT GetAssetDir()
Gets the current asset directory override.
Definition PathUtils.cpp:29
RenderType
Enum class for different types of rendering. It internally switches used pipeline.
uint32_t Entity
Type alias representing a unique entity identifier in the ECS system.
Definition Types.hpp:11
void VEX_EXPORT throw_error(const std::string &msg)
Throws an error or terminates the application.
void VEX_EXPORT log(const char *fmt,...)
Logs a formatted message.
std::string VEX_EXPORT GetAssetPath(const std::string &relativePath)
Resolves the absolute path of an asset based on the current build configuration.
Definition PathUtils.cpp:73
void VEX_EXPORT handle_exception(const std::exception &e)
Handles an exception based on build configuration.
constexpr Entity NULL_ENTITY
Special entity value indicating an invalid or null entity.
Definition Types.hpp:15
Struct containing raw meshData, mesh id, texture paths and material properties. It just template and ...
void forceRefresh()
(used internally by the engine, DO NOT CALL) forces the component to be refreshed.
Mesh data structure for loading and managing mesh data.
Definition Mesh.hpp:41
void loadFromFile(const std::string &path, VirtualFileSystem *vfs)
Loads mesh data from a file using the Virtual File System.
Definition Mesh.cpp:271
Structure representing a physics component.
static PhysicsComponent Mesh(MeshComponent &mesh, BodyType bodyType=BodyType::STATIC, float mass=1.0f, float friction=0.5f, float bounce=0.1f)
Creates a mesh-shaped physics component.
Struct containing transform data and methods.
Struct holding all vulkan data, like device, surface, swapchain, images, views, and more.
Definition context.hpp:26
Represents an RGBA color value. Used mainly for fancy rendering in editor.