This document consolidates the current and intended rendering architecture, clarifying responsibilities, data flow, and lifecycle across Scene Nodes, CPU render data, GPU render objects, and the render cycle.
It reflects:
- Multi-material meshes via submeshes
- WebGPU-specific constraints
- RenderCollector–based rendering
- Dirty / update logic
- Shader & material handling
Application Assets (basePath): Example-specific assets
| Build | Location | Set By |
| Debug | Example source directory | ASSETS_ROOT_DIR CMake define | <example_dir>/assets/ (source) |
| Release | Executable dir | getExecutablePath() at runtime | <exe>/assets/ (deployed) |
2. Engine Library Resource Paths (resourceRoot)
| Build | resourceRoot Value |
Determined By | Engine Resources Location |
|---|---|---|---|
| Debug | Project root + /resources/ |
DEBUG_ROOT_DIR + /resources/ |
<project>/resources/ (source) |
| Release | Library dir + /resources/ |
getEnginePath() + /resources/ |
<lib>/resources/ (deployed) |
Usage:
// Application assets
auto appTexture = PathProvider::getTextures("brick.png");
// Engine resources
auto engineShader = PathProvider::getResource("PBR_Lit_Shader.wgsl");Key Rules:
- Use
PathProviderfor all paths (never hardcode) assets/= example-specific,resources/= engine-wide- Debug: CMake defines paths, Release: runtime detection
Project Root/
├── build/
│ └── Windows/
│ ├── Debug/ # Engine library + dependencies
│ └── Release/
├── examples/
│ └── build/
│ └── <example_name>/
│ └── Windows/
│ ├── Debug/ # Example executable + copied assets
│ └── Release/
└── resources/ # Source assets (Debug builds access directly)
Key Points:
- Debug executables run from project root, accessing
resources/directly - Release executables expect assets in their output directory
- CMake automatically copies required assets to Release builds
- Always use
PathProviderto ensure path portability
Each layer has a strict responsibility:
| Layer | Responsibility |
|---|---|
| Scene Nodes | Visibility, transforms, scene logic |
| CPU Render Data | Geometry, materials, submesh layout |
| GPU Render Objects | GPU buffers, bind groups, pipelines |
| RenderItem | One draw call |
| Renderer | Sorting, batching, issuing draws |
No layer reaches “up” or sideways.
- Owns vertex and index buffers
- No materials
- No submeshes
- CPU-only geometry
- Versioned
Indented example:
Mesh
├─ vertices[]
└─ indices[]
- Defined as ranges into a mesh
- Exists only at the Model level
Indented example:
struct Submesh
{
uint32_t indexOffset;
uint32_t indexCount;
MaterialHandle material;
};
- Owns:
- one Mesh
- multiple Submeshes
- No GPU logic
- No rendering logic
Indented example:
Model
├─ MeshHandle
└─ Submesh[]
ObjLoader does not create materials or GPU objects.
It outputs:
- Unified indexed geometry
- Material ranges
- Raw
tinyobj::material_t
Indented example:
ObjGeometryData
{
vertices
indices
materials // tinyobj materials
materialRanges[] // offset + count + materialId
}
This avoids:
- Duplicated meshes
- Material managers in loaders
- GPU knowledge in asset loading
- Wraps one vertex buffer and one index buffer
- Created once per Mesh
- Shared across all models and submeshes
Indented example:
WebGPUMesh
├─ vertexBuffer
├─ indexBuffer
└─ updateGPUResources()
Submeshes do not become separate GPU meshes.
- Wraps:
- Bind groups
- Shader selection
- Knows shader type or custom shader name
- Does not render directly
Indented example:
WebGPUMaterial
├─ shaderType OR customShaderName
├─ textures[]
├─ bindGroup
└─ updateGPUResources()
Shader lookup happens via:
context.shaderRegistry()
- Owns
WebGPUShaderInfo - Separates:
- Default shaders (
ShaderType) - Custom shaders (string key)
- Default shaders (
Materials store identifiers, not shader objects.
- Represents one draw call
- Contains references to:
- Mesh
- Submesh range
- Material / Shader info
- Can check and update GPU resources on demand
- Returned by
RenderObject::getRenderItems()or similar
- Virtual Node with hooks:
preRender()onRenderCollect(RenderCollector&)postRender()
- Does not know about GPU objects directly
- Responsible for submitting CPU objects to RenderCollector
- Holds CPU Model handle
- On
onRenderCollect():- Gives CPU Model to RenderCollector
- RenderCollector retrieves / caches GPU objects
- RenderCollector creates
RenderItems for each submesh
- PreRender
- Setup state, transform matrices
- RenderCollect
- Nodes push CPU objects
- GPU objects created / cached lazily
RenderItems collected for batching / sorting
- Sort & Batch
- Sort by:
- Shader / pipeline
- Material
- Depth (optional)
- Minimize pipeline / bind group switches
- Sort by:
- Draw
- Iterate
RenderItems - Bind shader / material
- Draw mesh / submesh range
- Iterate
- PostRender
- Cleanup (optional)
- Each GPU object tracks
dirtyflag and CPU version - On
update():- Compare CPU version or dirty flag
- Update GPU buffers / bind groups if needed
- Suggested:
- Model asks Mesh and Submeshes if they are dirty
- WebGPUMaterial updates only when shader / textures change
- Lazy updates: only update when item is actually drawn