This document describes the fundamental architecture, design patterns, and coding conventions used in the Vienna-WebGPU-Engine.
Two path systems via PathProvider:
getTextures(), getModels() → <example>/assets/getResource() → <engine>/resources/Rules:
PathProvider (never hardcode paths)The engine follows a layered architecture with clear separation of concerns:
┌─────────────────────────────────────┐
│ Application Layer │
│ (Game Loop, Input, UI) │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ Scene Graph Layer │
│ (Nodes, Transforms, Camera) │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ Rendering Layer │
│ (Renderer, Passes, Pipelines) │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ Resource Layer │
│ (Managers, Loaders, Factories) │
└─────────────────────────────────────┘
↓
┌─────────────────────────────────────┐
│ Core Layer │
│ (Handle, Identifiable, Versioned) │
└─────────────────────────────────────┘
Location: include/engine/core/
Foundation classes used throughout the engine:
Identifiable<T>: Provides unique IDs for resourcesVersioned: Tracks resource versions for dirty checkingHandle<T>: Type-safe resource handles with validationLocation: include/engine/resources/
Manages CPU-side resources:
TextureManager, MeshManager, MaterialManager, ModelManagerTextureLoader, ObjLoader, GltfLoaderTexture, Mesh, Material, ModelKey Rules:
PathProvider for path resolutionresolve(key) for application assetsgetResource(file) for engine resourcesLocation: include/engine/rendering/
Handles GPU-side rendering:
MeshPass, ShadowPass, DebugPassPipelineManager with hot-reloadingBindGroupBinder for centralized bindingKey Rule: All GPU resource creation goes through factories accessed via WebGPUContext.
Location: include/engine/scene/
Hierarchical scene representation:
Scene: Manages the node hierarchy and frame lifecycleNode: Base class for scene graph nodesTransform: Position, rotation, scale with lazy matrix updatesCameraNode: Camera with view/projection matricesLocation: include/engine/
Top-level game loop integration:
GameEngine: Main loop, event processing, subsystem managementApplication: User-facing application base classEngineContext: Provides nodes with access to core systemsLoad via Manager → Create GPU version via Factory → Use in Renderer
Example:
// 1. Load CPU resource via manager
auto textureOpt = textureManager->createTextureFromFile("texture.png");
if (!textureOpt)
return;
// 2. Create GPU resource via factory
auto gpuTexture = context.textureFactory().createFromHandle(textureOpt.value()->getHandle());
// 3. Use in renderer
material->setTexture("albedo", gpuTexture);
Use Handle<T> for type-safe resource references:
auto textureOpt = textureManager->createTextureFromFile("texture.png");
if (textureOpt) {
auto texture = textureOpt.value();
// Use texture
}
Rule: Always use factories for GPU resource creation. Never create WebGPU resources manually.
All factories are accessed through WebGPUContext:
// Texture creation
auto texture = context.textureFactory().createFromHandle(handle);
// Material creation
auto material = context.materialFactory().createFromHandle(handle);
// Mesh creation
auto mesh = context.meshFactory().createFromHandle(handle);
// Pipeline creation
auto pipeline = context.pipelineFactory().createPipeline(descriptor, layouts, count);
| Factory | Purpose | Key Methods |
|---|---|---|
WebGPUTextureFactory |
GPU textures | createFromHandle(), createFromColor(), getWhiteTexture() |
WebGPUMaterialFactory |
GPU materials with bind groups | createFromHandle() |
WebGPUMeshFactory |
GPU mesh buffers | createFromHandle() |
WebGPUModelFactory |
GPU models | createFromHandle() |
WebGPUPipelineFactory |
Render pipelines | createPipeline(), getDefaultRenderPipeline() |
WebGPUBufferFactory |
GPU buffers | createBuffer(), createBufferWithData() |
WebGPUBindGroupFactory |
Bind groups and layouts | createBindGroup(), createBindGroupLayout() |
1. clearForNewFrame() // Clear frame cache
2. prepareGPUResources() // Upload uniforms, create bind groups
3. processBindGroupProviders() // Process custom bind groups
4. Render passes:
- Shadow pass (optional)
- Main mesh pass
- Debug pass (optional)
5. Present frame
binder.bind(renderPass, pipeline, cameraId, bindGroups, objectId, materialId);
Features:
| Pass | Purpose | Outputs |
|---|---|---|
ShadowPass |
Shadow map rendering | 2D and cube shadow maps |
MeshPass |
Main scene rendering | Color + depth |
DebugPass |
Debug visualization | Debug primitives overlay |
Every Scene contains:
Example:
// Scene automatically creates root and camera
auto scene = std::make_shared<Scene>();
// Add custom nodes
auto objectNode = std::make_shared<Node>();
objectNode->getTransform().setLocalPosition({0, 1, 0});
scene->getRoot()->addChild(objectNode);
Node: Base scene graph nodeUpdateNode: Node with custom update logicRenderNode: Node with custom render logicCameraNode: Camera with view/projectionUnity-like lazy matrix updates:
transform.setLocalPosition({0, 1, 0});
transform.setLocalEulerAngles({0, 45, 0}); // Euler angles in degrees
transform.setLocalScale({2, 2, 2});
// Matrices updated lazily on first access
glm::mat4 world = transform->getWorldMatrix();
Use tabs for indentation, not spaces.
Use blank lines between groups:
Example:
#include "MyClass.h"
#include <memory>
#include <vector>
#include <glm/glm.hpp>
#include <spdlog/spdlog.h>
#include "engine/rendering/Renderer.h"
#include "engine/scene/Scene.h"
Use #pragma once for all header files.
Organize by feature/module with clear naming:
engine/
├── core/ # Foundation classes
├── resources/ # Resource management
├── rendering/ # Rendering system
│ └── webgpu/ # WebGPU-specific code
├── scene/ # Scene graph
└── input/ # Input handling
Never create WebGPU resources manually. Always use factories:
// ❌ Bad
wgpu::Buffer buffer = device.createBuffer(desc);
// ✅ Good
auto buffer = context.bufferFactory().createBuffer(desc);
// 1. Manager (CPU)
auto textureOpt = textureManager->createTextureFromFile("texture.png");
if (!textureOpt)
return;
// 2. Factory (GPU)
auto gpuTexture = context.textureFactory().createFromHandle(textureOpt.value()->getHandle());
// 3. Renderer (Usage)
material->setTexture("albedo", gpuTexture);
Nodes access engine systems via EngineContext:
// In custom UpdateNode
void update(float deltaTime) override {
if (engine()->input()->isKey(SDL_SCANCODE_W)) {
// Move forward
}
auto textureOpt = engine()->resources()->m_textureManager->createTextureFromFile("tex.png");
}
WebGPU resources need explicit cleanup:
texture.release();
buffer.release();
bindGroup.release();
Standard bind group indices:
Use PipelineManager for shader hot-reloading:
pipelineManager->createPipeline("myShader", config);
// Later...
pipelineManager->reloadPipeline("myShader");
| Class | Purpose | Location |
|---|---|---|
WebGPUContext |
Central GPU resource hub | rendering/webgpu/ |
FrameCache |
Frame-wide rendering data | rendering/ |
BindGroupBinder |
Centralized bind group binding | rendering/ |
Scene |
Scene graph root | scene/ |
ResourceManager |
Aggregate resource manager | resources/ |