Utilities
This module provides async task execution, model and texture loading and management, data serialization and deserialization using yaml and json and more
Asset manager
Not yet exposed to lua
The asset manager currently is non-copyable and non-movable
AssetManager();
~AssetManager();
AssetManager( const AssetManager& ) = delete;
AssetManager& operator=( const AssetManager& ) = delete;
AssetManager( AssetManager&& ) = delete;
AssetManager& operator=( AssetManager&& ) = delete;
The purpose of this class is to provide means to load, store and manage different kinds of assets, it is tied deeply with asset lifetime also.
Since geometry needs to be uploaded to the GPU and model files take time to load and those could also have textures, I had to come up with a way of loading models in an asynchronous way and not crash due to the OpenGL thread safety.
All I had to do was add a bool flag and right before rendering the frame, doo all the necessary OpenGL API calls on the main thread.
Asset types
Textures
There are two functions used for the texture loading
auto addTexture( const std::string& textureName,
const std::string& texturePath ) -> Texture*;
// this one is just prepending the path as pathToProject/resources/textures/...
auto addTexture( const std::string& textureName ) -> Texture*;
The default folder for textures is resources/textures/... so you should save the model as gltf and separate textures into its own folder
We also have a load from memory function but that is not yet implemented.
Meshes
auto addMesh( const std::string& meshName, const std::string& meshPath ) -> Mesh*;
auto addMesh( const std::string& meshName ) -> Mesh*;
Each Mesh instance has its own VAO, EBO and VBO.
Because we have a per mesh buffer, we have a single vector of submeshes for an easier GPU "upload", a submesh looks like this
struct Submesh
{
uint32_t vertexOffest{ 0 };
uint32_t indexOffset{ 0 };
uint32_t indexCount{ 0 };
};
When we load a mesh, we parse each submesh and we insert vertices in the main Mesh.vertices vector, then we take the vertices.size value and assign it to the vertex offset variable in the submesh.
So if we had 0->10 vertices for the first mesh, and the second mesh is the same size in vertices, the offset will be as follows
submeshes.at(0).vertexOffset = 0
submeshes.at(1).vertexOffset = 10
I think you understand where this is going
Task manager
Not yet exposed to lua
This is the class responsible for executing tasks on a separate thread.
It has a predefined number of threads initialized, I used std::thread::hardware_concurrency() as a member variable m_threadCount initializer.
The concept is as follows, we enqueue a lambda or a function and the task manager pops from the front of the queue and executes it, and so on.
The function for enqueueing is the following
template <typename Func, typename... Args>
auto enqueue( Func&& func, Args&&... args )
-> std::future<std::invoke_result_t<Func, Args...>>
{
using return_type = std::invoke_result_t<Func, Args...>;
auto p_task = std::make_shared<std::packaged_task<return_type()>>(
std::bind( std::forward<Func>( func ), std::forward<Args>( args )... ) );
{
std::lock_guard lock( m_queue_mutex );
if ( m_stop )
{
throw std::runtime_error( "Thread pool is stopped" );
}
m_tasks.emplace( [p_task]() { ( *p_task )(); } );
}
m_cvar.notify_one();
return p_task->get_future();
}
Common use case is when we try to load a heavy model file and if we did that on the main thread, the UI would freeze for the remainder of the loading phase.