|
SleakEngine 1.0.0
C++23 multi-backend game engine
|
SleakEngine has no unified cross-backend hardware abstraction. Rendering is built around an abstract base class, Sleak::RenderEngine::Renderer (include/private/Graphics/Common/Renderer.hpp, a private header), with four independent backend implementations: VulkanRenderer, OpenGLRenderer, DirectX11Renderer, DirectX12Renderer. Each backend class implements the RenderContext command-recording interface directly on itself (multiple inheritance, e.g. class VulkanRenderer : public Renderer, public RenderContext), so a backend renderer is its own render context. The four backends share a lifecycle contract but diverge in real, load-bearing ways in what they actually implement.
| Type | Role |
|---|---|
| Sleak::Application | Drives the frame: window pump, scene ticks, queue flush, frame boundary. |
| RenderEngine::Renderer | Abstract backend lifecycle: Initialize, BeginRender, EndRender, Resize, Cleanup. |
| RenderEngine::RenderContext | Abstract command-recording surface; each backend implements it on itself. |
| RenderEngine::RendererFactory | Picks and constructs the backend from a RendererType or a CLI string. |
| RenderEngine::RenderCommandQueue | Singleton frame queue of draws and state changes, replayed into the context. |
| RenderEngine::Shader / BufferBase | Backend-agnostic compiled shader and GPU buffer handles. |
| Sleak::Texture / CubemapTexture | Sampled image resources, the cubemap implemented per backend. |
The frame boundary is BeginRender and EndRender with the queue replayed between them. There is no RenderFrame method and no separate Present call.
Renderer's real interface:
plus concrete getters/setters for post-process toggles (SSAO, SSR, TAA, bloom, tonemapping, IBL, shadow map resolution, MSAA, VSync). There is no RenderFrame() method; the frame boundary is BeginRender() / EndRender(), with RenderCommandQueue replaying queued commands in between (section 3).
Sleak::RenderEngine::RendererFactory (include/private/Graphics/Common/RendererFactory.hpp) instantiates the active backend:
RendererType is Vulkan, OpenGL, DirectX11, DirectX12. DirectX 11 and DirectX 12 are only compiled under PLATFORM_WIN; requesting DirectX 12 on a GPU that fails DirectX12Renderer::IsSupport() falls back to CreateRenderer(RendererType::DirectX11, window) with a warning.
Sleak::Application::Run(GameBase* game) drives one frame:
Shutdown calls renderer->WaitIdle() (also exposed as Application::WaitGPUIdle()) before any scene teardown, since descriptor sets can still reference sampler/image-view resources owned by scene objects; then deletes the GameBase (cascading into scene and object cleanup), Sleak::UI::ShutdownTextureCache(), MeshBatch::Shutdown(), and renderer->Cleanup().
Each backend reports what it actually implements through an overridable GetFeatureCaps() bitmask (GraphicsCaps: CapDeferred, CapSSAO, CapSSR, CapTAA, CapBloom, CapIBL, CapTonemapPass, CapShadows, CapHDRTarget, CapVelocity, CapLightShaft, CapProceduralSky). The base class defaults to CapShadows only.
| Backend | GetFeatureCaps() returns | Approx. source size |
|---|---|---|
| Vulkan | CapDeferred \| CapSSAO \| CapSSR \| CapTAA \| CapBloom \| CapIBL \| CapShadows \| CapHDRTarget | ~12,200 lines / 16 files (VulkanBloom, VulkanDeferred, VulkanIBL, VulkanShadow, VulkanSSAO, VulkanSSR, VulkanTAA, ...) |
| OpenGL | CapDeferred \| CapSSAO \| CapIBL \| CapShadows | ~2,400 lines / 6 files (no dedicated SSR/TAA/bloom files) |
| DirectX 11 | CapShadows \| CapTonemapPass | ~2,400 lines / 5 files |
| DirectX 12 | CapShadows (base default, not overridden further) | ~3,000 lines / 5 files |
Vulkan is the most feature-complete backend by a wide margin. Per-backend mechanics:
Sleak::RenderEngine::RenderCommandQueue (include/private/Graphics/Common/RenderCommandQueue.hpp) is a singleton, frame-scoped queue of recorded draw/state commands, replayed into a RenderContext at flush time:
Game code (through MeshComponent/MeshBatch) submits draws into the queue during Scene::Update; Application::Run flushes it once per frame after FlushPendingTransfers(). The queue also caches shadow-pass draws across frames (ShadowDrawEntry, a 3-frame retirement window) so static geometry does not need to resubmit into the shadow pass every frame.
On Vulkan, the deferred path is not a sequence of top-level calls from Application. Most of it hangs off three points: BeginRender, the queue flush, and EndRender.
BeginRender does two things before any game draw exists this frame. It replays the cached shadow draw list if there is one, then opens the GBuffer render pass and binds the GBuffer pipeline. Because BeginRender runs before the scene update, the shadow pass necessarily replays the previous frame's geometry, which is what the queue's cross-frame shadow cache is for.
ExecuteCommands then walks this frame's queue. Opaque, non-skinned draws execute immediately into the open GBuffer pass. Draws whose material reports IsForwardRendered(), skinned draws, and every custom command (skybox, debug lines) are held back in a forward list instead.
ExecuteDeferredLightingPass closes the GBuffer pass, runs SSAO and its bilateral blur, refreshes the GBuffer descriptors, then opens the lighting pass and shades the whole screen with a single fullscreen triangle. SSAO lives inside the lighting stage rather than in the post chain, since the lighting pass consumes its result directly.
The forward transparent pass follows, replaying the held-back list against the lit result so transparent surfaces, skinned meshes, and debug geometry composite over it.
EndRender runs the post chain in a fixed order: TAA, then SSR, then bloom, then the bloom composite, which also applies ACES tonemapping and gamma and draws the ImGui layer straight into the swapchain image. TAA running ahead of SSR is deliberate; TAA also issues the depth barrier SSR would otherwise need. The command buffer closes, submits, and presents.
Backends without CapDeferred skip the split entirely and execute the queue in submission order.
| Question | File |
|---|---|
| The frame loop and shutdown order | src/Core/Application.cpp |
| The lifecycle contract every backend implements | include/private/Graphics/Common/Renderer.hpp |
| Backend selection and the DirectX 12 fallback | include/private/Graphics/Common/RendererFactory.hpp |
| Queue commands, replay, and the shadow cache | src/Graphics/Common/RenderCommandQueue.cpp |
| Vulkan frame boundary and post chain | src/Graphics/Vulkan/VulkanRenderer.cpp |
| The GBuffer, SSAO, and lighting passes | src/Graphics/Vulkan/VulkanDeferred.cpp, VulkanSSAO.cpp |
| The OpenGL deferred path | src/Graphics/OpenGL/OpenGLRenderer.cpp |