Vulkan Schnee 0.0.1
High-performance rendering engine
Loading...
Searching...
No Matches
Asset Loading System

The asset loading system handles asynchronous loading of glTF models, meshes, materials, and textures from disk to GPU memory.

Architecture

Components

  • AssetManager: Central coordinator, owns all sub-managers and pipelines
  • GltfLoader: Parses glTF/GLB files using tinygltf
  • Asset Pipelines: Multi-stage async loading (TextureAssetPipeline, ModelAssetPipeline)
  • Asset Managers: Store and track loading state (MeshAssetManager, MaterialAssetManager, TextureAssetManager, ModelAssetManager)
  • RenderingDataManager: Receives load notifications, creates Vulkan resources, uploads to GPU

Asset Types

Type Class Storage
Mesh geometry MeshAsset ECS component Ecs::MeshPrimitiveData
Material data MaterialAsset ECS component (type-specific, e.g., DiffuseShaderMaterialData)
Texture image TextureAsset ECS component Ecs::ImageData
glTF file ModelAsset Bundles meshes/materials/textures from one file

All asset classes inherit from Asset::AssetBase, which provides loading state tracking (UNLOADED, REQUESTED_LOAD, LOADING, LOADED).

Loading Flow

1. Request Asset

assetManager->loadEcsModel("assets/models/scene.gltf");

Creates a ModelAsset entry and submits to ModelAssetPipeline.

2. Pipeline Processing

The ModelAssetPipeline runs 4 stages per frame during Engine::processResourceLoadingPipelines():

assetManager->getMeshAssetPipeline().tick(true); // async=true

Stage 1: submitLoadModel

  • Submits async tinygltf parse task to thread pool
  • Returns std::future<tinygltf::Model>

Stage 2: processModel

  • Checks if future is ready (non-blocking)
  • Extracts meshes, materials, textures from glTF
  • Creates MeshAsset, MaterialAsset, TextureAsset entries
  • Submits texture paths to TextureAssetPipeline
  • Pre-registers texture handles in TextureHandleRegistry

Stage 3: processMaterial

  • Parses material extensions (VulkanSchnee custom data)
  • Creates ECS material components with shader parameters
  • Calls RenderingDataManager::onMaterialLoaded()

Stage 4: processMeshData

  • Extracts vertex/index data from glTF buffers
  • Generates meshlets using meshoptimizer
  • Creates ECS mesh components
  • Calls RenderingDataManager::onMeshLoaded()

3. Texture Loading

Textures follow a separate pipeline (TextureAssetPipeline):

For EXR files (lightmaps):

  1. requestExrHeaders() - Load header metadata
  2. loadImageData() - Load pixel data
  3. retrieveImageData() - Store in ECS, call onTextureLoaded()

For glTF images (PNG/JPG):

  • Already loaded during processModel() (tinygltf loads eagerly)
  • Extracted and stored in TextureAsset with image data

4. GPU Upload

RenderingDataManager hooks receive loaded assets:

void RenderingDataManager::onTextureLoaded(TextureAsset* asset, const std::filesystem::path& path)
{
// 1. Extract image data from ECS component
tinygltf::Image& image = registry.get<Ecs::ImageData>(asset->getEntity()).image;
// 2. Create Vulkan texture
Texture texture(context, renderer);
texture.createFromImage(image);
// 3. Register with AssetManager (creates descriptor index)
assetManager->registerTexture(path, texture);
// 4. Queue for GPU upload via staging buffer
renderer->getRenderingDataManager()->queueTextureForUpload(texturePtr);
// 5. Update texture handle registry with descriptor index
textureHandleRegistry->updateHandle(path, descriptorIndex);
}
tinygltf::Image image
Definition EcsData.h:90

Similar flow for meshes:

void RenderingDataManager::onMeshLoaded(MeshAsset* asset)
{
// Extract vertex/index/meshlet data from ECS
// Upload to device-local buffers
// Mark dirty for descriptor set rebuild
}

Asset Managers

All managers use Asset::AssetManager<Key, AssetClass> template:

class MeshAssetManager : public Asset::AssetManager<Asset::Path, MeshAsset> {};
class MaterialAssetManager : public Asset::AssetManager<Asset::Path, MaterialAsset> {};
class TextureAssetManager : public Asset::AssetManager<std::filesystem::path, TextureAsset> {};
class ModelAssetManager : public Asset::AssetManager<std::filesystem::path, ModelAsset> {};
A manager which is used to look up existing assets and their loading state.
Definition Asset.h:141

Common Operations

Check if asset exists:

if (meshAssetManager->exists(assetPath)) { /* ... */ }

Get asset (returns std::optional):

auto asset = materialAssetManager->getAsset(path);
if (asset.has_value()) {
MaterialAsset* mat = asset.value();
}

Check loading state:

Asset::CpuLoadingState state = textureAssetManager->getAssetLoadingState(path);
// UNLOADED, REQUESTED_LOAD, LOADING, LOADED
CpuLoadingState
State for assets in the asset loading process.
Definition Asset.h:21

GltfLoader

Parses glTF files and extracts structured data.

Loading Models

GltfLoader loader(&threadPool);
std::optional<tinygltf::Model> model = loader.loadModel("scene.gltf");

Metadata-Only Load

Fast parse for node transforms without loading buffers:

tinygltf::Model metadata = loader.loadMetadataOnly("scene.gltf");
// Use for spawning actors before full mesh data loads

Extracting Data

GltfLoader::GltfMeshData meshData(model, mesh, node, transform);
std::vector<GltfLoader::GltfMeshPrimitiveData> primitives = meshData.getPrimitives();
for (auto& prim : primitives) {
std::vector<Vertex> vertices = prim.getVertices();
std::vector<uint32_t> indices = prim.getIndices();
GltfLoader::GltfMaterialData material = prim.getMaterialData();
}

Material Extensions

The engine uses custom glTF extensions:

VulkanSchnee Extension (extensions.VulkanSchnee):

GltfLoader::VulkanSchneeExtension ext(mesh.extensions);
if (ext.hasLightmapProperties()) {
std::filesystem::path lightmapPath = ext.lightmapProperties->lightmapPath;
uint32_t uvIndex = ext.lightmapProperties->lightmapUvIndex;
}

Material Extension (material.extensions.VulkanSchneeMaterial):

GltfLoader::VulkanSchneeMaterialExtension matExt(material.extensions);
PipelineNames shaderType = matExt.materialName;
std::map<std::string, ShaderParameter> params = matExt.shaderParameters;

Material Assets

Materials store shader type and texture references:

MaterialAsset* mat = materialAssetManager->getAsset(materialPath).value();
PipelineNames shaderType = mat->getType(); // NORMALS_SHADER, DIFFUSE_FLAT_COLOR, etc.
// Access material data (type-specific)
DiffuseShaderMaterialData& data = mat->getData<DiffuseShaderMaterialData>();
// Texture paths
std::filesystem::path baseColor = mat->getBaseColorTexturePath();
std::filesystem::path normal = mat->getNormalTexturePath();
std::filesystem::path metallicRoughness = mat->getMetallicRoughnessTexturePath();
// Type-safe handles (O(1) descriptor lookup)
AlbedoTextureHandle albedo = mat->getBaseColorTextureHandle();

Texture Assets

TextureAsset* tex = textureAssetManager->getAsset(path).value();
// Check state
Asset::CpuLoadingState state = tex->getLoadingState();
// Access image data (after LOADED state)
entt::entity entity = tex->getEntity();
Ecs::ImageData& imageData = registry.get<Ecs::ImageData>(entity);
tinygltf::Image& image = imageData.image;

Descriptor Index Lookup

Fast path for shader binding:

uint32_t index = assetManager->getTextureDescriptorIndex("textures/albedo.png");
if (index != 0xFFFFFFFF) {
// Valid descriptor, texture is loaded
}

Mesh Assets

MeshAsset* mesh = assetManager->getMeshAsset(assetPath);
if (mesh == nullptr) {
// Not loaded yet, will trigger loadEcsModel()
return;
}
// Access geometry data
Ecs::MeshPrimitiveData* primData = mesh->getMeshPrimitiveData();

Thread Safety

  • Asset registration uses mutex-protected maps (AssetManager::registerTexture, registerMesh)
  • Pipeline ticking runs on main thread, async tasks on worker threads
  • ECS writes happen only on main thread after futures complete

Performance Notes

  • Thread pools: 4 threads for asset loading, 10 for bounding sphere calculations
  • Futures checked every frame with wait_for(0) (non-blocking)
  • Meshlet generation is async and CPU-bound (uses meshoptimizer)
  • Texture uploads use staging buffers for optimal transfer

Example: Full Load Cycle

// Frame 0: User requests model
assetManager->loadEcsModel("models/house.gltf");
// Frame 1-2: Background thread parses glTF
// ModelAssetPipeline::submitLoadModel() -> future pending
// Frame 3: Parse complete
// ModelAssetPipeline::processModel()
// - Creates MeshAsset, MaterialAsset, TextureAsset entries
// - Submits texture load tasks
// Frame 4-5: Load vertex data, generate meshlets
// ModelAssetPipeline::processMaterial/processMeshData()
// - Calls RenderingDataManager::onMaterialLoaded()
// - Calls RenderingDataManager::onMeshLoaded()
// Frame 6: Textures finish loading
// TextureAssetPipeline::retrieveImageData()
// - Calls RenderingDataManager::onTextureLoaded()
// - Creates Vulkan images, queues for upload
// Frame 7: GPU upload via staging buffer
// RenderingDataManager::updateIfDirty()
// - Uploads meshes, materials, textures
// - Rebuilds descriptor sets
// Frame 8+: Asset ready for rendering

Key Files

Path Purpose
Engine/include/Engine/Mesh/AssetManager.h Central manager
Engine/include/Engine/Mesh/GltfLoader.h glTF parsing
Engine/include/Engine/Ecs/FrameProcessing.h Pipeline definitions
Engine/src/Engine/Ecs/FrameProcessing.cpp Pipeline implementation
Engine/include/Engine/Renderer/RenderingDataManager.h GPU upload hooks
Engine/include/Engine/Core/Asset.h Base asset classes