October 24, 2025
Sidreal Engine 2025
The Goal
Sidreal Engine 2025 is a ground-up C++20 rendering/game engine designed as a hobby and experiment project to learn more about graphics programming. It emphasizes modern OpenGL practices: indirect drawing, bindless textures, SSBO-driven data, paired with a lightweight custom ECS for game logic. The engine is split into two compilation targets: a static Engine library and an App executable. This layer-based architecture cleanly separates engine code from game code, allowing multiple applications to share the same engine binary.
This project is now on hold as now I’m building my ongoing Sidreal 2.0 - Vulkan Edition project (I will link to it here soon), where I switch my entire rendering backend to Vulkan using it’s most recent render APIs for even more performance, features and granular control over my rendering.
What I Built
- GPU-driven rendering pipeline with indirect draws scaling to 100,000+ instances
- Custom binary glTF model loading with cached asset registry
- Custom ECS with type-erased component storage and free-list entity recycling
- Bindless-texture material lookup via
GL_ARB_bindless_texturewith zero texture binding state changes - Mesh deduplication via hash-based flyweight pattern
- Directional-light shadow mapping with a dedicated 2048×2048 depth pass
- Fly and orbital camera systems with quaternion-based rotation
- Hot shader reloading: press
Rand iterate without restarting - Debug renderer for wireframe boxes and line segments
- Premake5 build system with Debug, Release, and Dist configurations
Rendering Architecture
GPU Memory Layout
The entire renderer is SSBO-centric. All vertex and index data lives in Shader Storage Buffer Objects, and draw calls are batched via glMultiDrawElementsIndirect.
| Binding | Buffer | Capacity | Purpose |
|---|---|---|---|
| 0 | Vertex SSBO | 1M vertices × 48 bytes | All vertex data |
| 1 | Index SSBO | 3M indices × 4 bytes | All index data |
| 2 | Model Matrix SSBO | 100K instances × 64 bytes | Per-instance transforms |
| 3 | Texture Handle SSBO | 100K instances × 8 bytes | ARB bindless texture handles |
| 4 | Texture Index SSBO | 100K instances × 4 bytes | Texture sampler indices |
| 5 | Debug Line SSBO | Variable | Debug wireframe lines |
Vertex Pulling & GPU-Driven Draws
The default vertex shader pulls its own input with gl_VertexID, then uses gl_BaseInstance and gl_InstanceID to address instance data directly from SSBOs. Mesh work is submitted with glMultiDrawElementsIndirect, batching indexed draw commands from GPU-resident data rather than making a separate CPU-side draw call per mesh.
Bindless Textures
Using GL_ARB_bindless_texture eliminates texture binding state changes between draw calls. Each texture gets a 64-bit handle stored in an SSBO, and the fragment shader indexes into it, removing texture unit management and MAX_TEXTURE_IMAGE_UNITS constraints entirely.
Mesh Registration & Deduplication
Mesh registration hashes vertex data for deduplication: if a hash matches an existing mesh, the mesh ID is reused (flyweight pattern). New meshes upload vertices and indices to SSBOs and create DrawElementsIndirectCommand structures. Built-in primitives (CreateCube, CreateQuad) are available for debug rendering and prototyping.
Shadow Mapping
A separate framebuffer with a 2048×2048 depth texture attachment. RenderShadowPass() renders all meshes from the light’s perspective using a dedicated shadow.vert / shadow.frag shader pair. The shadow map is passed as sampler2DShadow to the main fragment shader, which combines it with diffuse lighting using smoothstep.
Shader System
Namespace-based API with uniform location caching. CreateShaderProgram() compiles and links, while ReloadShaders() hot-reloads all cached shader programs from disk.
| Program | Shaders | Purpose |
|---|---|---|
| Default | default.vert + default.frag | Main scene: diffuse lighting, texturing, shadow mapping |
| Shadow | shadow.vert + shadow.frag | Depth-only pass for shadow map generation |
| Debug | debug.vert + debug.frag | Debug line rendering |
Debug Renderer
Frame-buffered debug line rendering with a separate SSBO and shader program. Supports drawing wireframe bounding boxes (DrawBox) and individual line segments (DrawLine), rendered through RenderDebug() with optional depth testing.
Entity Component System
A custom ECS with type-erased component storage and free-list entity ID recycling (up to 65,536 entities). Built from scratch rather than using a library, keeping full control over the architecture and the engine self-contained.
Components
| Component | Purpose |
|---|---|
| Transform | Position, rotation (quaternion), scale |
| Camera | FOV, near/far planes, view and projection matrices |
| MeshRenderer | Mesh ID, instance index, AABB |
| HierarchyNode | Parent reference, children array (32 max) |
| StageTag | Stage membership |
Systems
- RenderSystem: Iterates
MeshRendererentities, computes model matrices fromTransform+ parent hierarchy, updates GPU buffers, and triggers render - FlyCameraSystem: FPS-style camera with WASD movement, mouse look via quaternion composition, Alt toggles mouse lock
- OrbitalCameraSystem: Orbit camera with scroll-to-zoom, right-click rotate, middle-click pan, and LERP smoothing
Architecture
Layer-Based Application
The engine owns the window, input, renderer, and ECS. Application layers inherit from ApplicationLayer and implement lifecycle hooks:
OnStart(): Initialize assets, create entities, attach components, set up cameraOnUpdate(): Run ECS systems, handle input-driven logicOnRender(): Trigger renderer, draw debug overlays, render ImGui
Core Systems
| System | Purpose |
|---|---|
| Logger | Header-only variadic logging with Windows console color support |
| Window | GLFW abstraction with multi-window support and lifecycle hooks |
| Input | Per-window input state with edge-triggered (fire-once) and continuous detection |
| Stage | Logical scene grouping with object pool and free-list ID recycling |
| Engine | Main loop orchestrator: poll events, update windows, render, present |
Asset Pipeline
Asset Registry
Flyweight pattern for asset caching with static modelCache and textureCache. Cache-first lookup before attempting disk load, with support for loading from file path or raw memory (for embedded GLB textures).
Binary glTF Loader
Custom .glb parser that validates the GLB header, parses the JSON chunk (accessors, buffer views, buffers, meshes, materials, textures), extracts binary chunk data, and reads POSITION, NORMAL, TEXCOORD_0 attributes alongside index buffers and embedded texture data. Returns vector<MeshData> for each mesh primitive.
Image Loader
stb_image wrapper supporting both file-based and memory-based image loading (PNG, JPEG, etc.).
Dependencies
| Library | Purpose | Integration |
|---|---|---|
| GLFW | Windowing, input, V-Sync | Static library, built from source |
| GLAD | OpenGL function loader | Header + source, included in Engine |
| GLM | Mathematics (vectors, matrices, quaternions) | Header-only |
| ImGui | Debug UI / IMGUI overlay | Source files, OpenGL3 + GLFW backend |
| MathFu | Extended math utilities (ray casting, geometry) | Header-only |
| stb_image | Image loading | Header-only |
| nlohmann/json | JSON parsing (GLTF metadata) | Header-only |