Rendering Pipeline¶
Helios features a modern, high-performance rendering stack built on Vulkan 1.3. The architecture is designed for scalability and efficiency, utilizing a Forward+ rendering path for dynamic lighting and a robust abstraction layer to manage GPU resources safely.
Architecture Overview¶
The rendering stack is split into three distinct layers:
- RHI (Render Hardware Interface) (
helios/rhi/): A stateless, abstract GPU interface that provides a unified way to manage resources (textures, buffers, pipelines) without Vulkan boilerplate. - Vulkan Backend (
helios/vulkan/): A fully-featured implementation of the RHI using modern Vulkan best practices, including dynamic rendering, synchronization 2, and VMA for memory management. - Forward+ Pipeline (
helios/forward_plus/): The default rendering pipeline. It utilizes compute shaders for light culling, allowing for hundreds of point lights per scene with high performance.
RHI Abstraction¶
The RHI lives in the helios::rhi namespace. It treats all GPU objects as opaque resources returned via std::unique_ptr. Raw Vulkan types are strictly encapsulated within the backend and never leak into the engine or application code.
Resource Lifecycle & Pacing¶
To maximize GPU throughput while ensuring safety, Helios employs a triple-buffering strategy for transient state.
MAX_FRAMES_IN_FLIGHT = 3: This constant, defined inrhi::Device, dictates how many frames the GPU can work on simultaneously while the CPU prepares the next.- Deferred Deletion: GPU resources cannot be destroyed while in-flight. Use
device->defer_destroy(std::move(resource))to safely queue a resource for deletion afterMAX_FRAMES_IN_FLIGHTframes.
Key Resource Types¶
| Type | Description |
|---|---|
Device |
The central factory for all GPU resources and command submission. |
Texture |
Manages 2D, Cube, and Array textures with automated layout transitions. |
Buffer |
Unified interface for vertex, index, uniform, and storage buffers. |
Pipeline |
Encapsulates graphics or compute state, avoiding redundant state changes. |
CommandBuffer |
Records GPU commands for submission to the queue. |
DescriptorSet |
Manages shader resource binding (textures, buffers). |
Render Context & Settings¶
The renderer is integrated into the ECS via two primary resources: RenderContext and RenderSettings.
RenderContext¶
The RenderContext resource holds the lifetime of the GPU device, the swapchain, and the per-frame command buffers. It also manages the scene framebuffers and per-camera render targets.
Systems interacting with the renderer should use the ResMut<RenderContext> pattern:
void my_render_system(ResMut<RenderContext> ctx) {
auto* device = ctx->device.get(); // Access low-level RHI
auto* cmd = ctx->cmd; // Current frame command buffer
// ...
}
RenderMode¶
The global rendering mode is configured via RenderPlugin and reflected in RenderContext::mode:
RenderMode::Direct: The default mode. The internal scene framebuffer is automatically blitted to the swapchain at the end of the frame for display.RenderMode::Offscreen: The scene is rendered to the internal framebuffer but not blitted to the swapchain. This is used by the Editor to display the scene within an ImGui window.
RenderSettings¶
Centralized, runtime-changeable settings that govern the global render state. These settings are typically accessed via ResMut<RenderSettings> within a system.
- VSync: Controlled via
present_mode. Useset_vsync(bool)for a simplified interface (Fifofor VSync,Immediatefor off). - Resolution Scale: Dynamically scales the internal render resolution (0.25x to 2.0x) without resizing the window.
- Exposure & Ambient Lighting: Global
exposurecontrol for tonemapping, andambient_color/ambient_intensityfor basic environmental fill lighting. - Shadow Settings: Configure
shadow_resolutionandshadow_cascadesfor directional lights. Updating these may trigger a lazy reallocation of shadow map resources. - Tonemapping: Selection of operators:
ACES(default),Reinhard, orFilmic. Set toNonefor raw HDR output. - Debug Visualization: The
debug_drawfield supports bitwise flags for various visualizations. Usetoggle_debug(flag)orhas_debug(flag)to manage:Wireframe: Renders geometry in wireframe mode.Colliders: Displays physics collider geometry.Normals: Shows surface normals for debugging mesh data.LightBounds: Visualizes the screen-space tiles and point light influence.BoundingBoxes: Shows AABBs for all renderable entities.
Example Usage¶
void adjust_settings(ResMut<RenderSettings> settings) {
// Enable VSync
settings->set_vsync(true);
// Boost ambient lighting for a daylight look
settings->ambient_intensity = 0.5f;
settings->ambient_color = glm::vec3(1.0f, 0.9f, 0.8f);
// Enable debug visualizations
settings->toggle_debug(DebugDraw::Wireframe | DebugDraw::Colliders);
// Increase shadow quality (triggers a resource refresh)
settings->set_shadow_resolution(4096);
settings->shadow_cascades = 4;
// Switch tonemapper
settings->tonemap = TonemapMode::Filmic;
}
Camera System¶
Rendering is driven by entities with the Camera and ActiveCamera components.
ActiveCamera Tag¶
The ActiveCamera marker component is essential; only cameras with this tag are processed by the rendering pipeline. In the editor, this tag is dynamically managed to switch between the Editor Camera and game-view cameras.
Camera Component¶
Defines the projection (Perspective or Orthographic), FOV, clipping planes, and clear behavior. Each camera also specifies a render_schedule (e.g., "forward_plus"), allowing different cameras to use different rendering techniques.
Camera Driver¶
The camera_driver system executes during PreRender. It gathers all active cameras, sorts them by their order property, and executes their associated render schedules.
Forward+ Pipeline¶
The ForwardPlusPlugin provides the engine's primary rendering path. It is a high-performance solution that handles many light sources by using a compute-based light culling pass.
Anti-Aliasing
MSAA (Multi-Sample Anti-Aliasing) is not currently supported in the Forward+ pipeline. Improved anti-aliasing techniques are on the roadmap for future development.
Configuration (ForwardPlusConfig)¶
The pipeline is configured via the ForwardPlusConfig resource, typically set during application startup:
- skybox_hdr_path: The path to the HDR environment map used for the skybox and IBL (Image Based Lighting). This is the only way to configure the skybox; it is not currently an ECS component.
- tile_size: The size of the screen-space tiles used for light culling (default 16x16).
- shadow_resolution: The resolution of the cascaded shadow maps.
Pipeline Stages¶
The pipeline executes the following stages in sequence:
- Extract: Gathers all mesh renderers and lights into a
FramePacket. - Depth Prepass: Renders geometry to the depth buffer first. This reduces overdraw in the forward pass and provides the depth information needed for light culling.
- Shadow Pass: Generates cascaded shadow maps for directional lights.
- Light Culling: A compute shader tiles the screen and culls the list of point lights against the Z-bounds of each tile.
- Forward Pass: The main PBR shading pass. It uses the tiled light lists from the culling stage to shade geometry efficiently.
- Skybox Pass: Renders the environment cubemap behind the geometry.
- Tonemapping: Final pass that converts the HDR scene buffer to the display's LDR range.
Scene Setup¶
To render a mesh, you must spawn an entity with Transform and MeshRenderer components. The MeshRenderer requires a Handle to a MeshAsset and optionally a MaterialAsset.
void setup_scene(App& app, Res<AssetServer> assets) {
// Load assets
auto mesh = assets->load<MeshAsset>("Meshes/Sphere.hvemesh");
auto material = assets->load<MaterialAsset>("Materials/Gold.hvemat");
// Spawn entity
app.spawn()
.add<Transform>({ .position = glm::vec3(0, 0, 0) })
.add<MeshRenderer>({
.mesh = mesh,
.material = material // Overrides the mesh's default material
});
}
Lighting¶
Lighting is integrated into the ECS with support for various light types:
DirectionalLight: A global, distant light source with support for cascaded shadows.PointLight: An omnidirectional light with a specific radius.SpotLight: Note: Currently, SpotLights are internal-only and not yet exposed as ECS components.
Light Components¶
// Create a bright white directional light
app.spawn()
.add<Transform>({ .rotation = glm::quatLookAt(glm::vec3(-1.0f, -1.0f, -1.0f), glm::vec3(0, 1, 0)) })
.add<DirectionalLight>({
.color = glm::vec3(1.0f),
.intensity = 5.0f
});
// Create a small red point light
app.spawn()
.add<Transform>({ .position = glm::vec3(0, 5, 0) })
.add<PointLight>({
.color = glm::vec3(1.0f, 0.0f, 0.0f),
.intensity = 10.0f,
.radius = 25.0f
});
Render Layers¶
Both cameras and renderable entities (meshes, lights) support RenderLayers. A camera only renders an entity if their layer masks intersect, providing a flexible way to partition scenes or create specialized view effects.