# How to Implement Forward Rendering in Equilibrium Engine: A Complete Guide

> Learn how to implement forward rendering in Equilibrium Engine with our comprehensive guide. Explore ECS systems, PBR materials, and draw call submission for efficient graphics.

- Repository: [Alexander/equilibriumengine](https://github.com/clibequilibrium/equilibriumengine)
- Tags: how-to-guide
- Published: 2026-02-27

---

**Forward rendering in Equilibrium Engine is implemented through a series of ECS systems that validate hardware capabilities, initialize the `ForwardRenderer` component, prepare the view each frame, and iterate over visible meshes to submit draw calls with PBR materials.**

The Equilibrium Engine (`clibequilibrium/equilibriumengine`) provides a data-driven forward rendering pipeline built on top of bgfx. This guide walks through the exact implementation steps found in the source code, from hardware validation to the final mesh draw loop.

## Prerequisites and Hardware Validation

Before initializing the forward renderer, the engine confirms that the target device supports the required color attachments for standard or HDR rendering.

### Checking Renderer Support

The `renderer_supported` function in [`equilibrium/utils/bgfx_utils.h`](https://github.com/clibequilibrium/equilibriumengine/blob/main/equilibrium/utils/bgfx_utils.h) performs this check. It validates that the current bgfx renderer can handle the forward rendering path before any resources are allocated.

```c
// From bgfx_utils.h - returns false if forward rendering is unsupported
if (!renderer_supported(false)) {
    ecs_err("Forward rendering not supported");
    return;
}

```

If this check fails, initialization aborts immediately to prevent runtime errors.

## Initializing the Forward Renderer

Once hardware support is confirmed, the engine creates the necessary ECS components and registers the rendering systems.

### Creating the ForwardRenderer Component

The `InitializeForwardRenderer` function in [`equilibrium/systems/rendering/forward_renderer_system.c`](https://github.com/clibequilibrium/equilibriumengine/blob/main/equilibrium/systems/rendering/forward_renderer_system.c) handles component creation. It compiles the forward shader program (`vs_forward.bin` and `fs_forward.bin`) and stores the handle in the `ForwardRenderer` component.

```c
// From forward_renderer_system.c
static void InitializeForwardRenderer(ecs_iter_t *it) {
    if (!renderer_supported(false)) {
        ecs_err("Forward rendering not supported");
        return;
    }
    entity_t e = { it->entities[0], it->world };
    ForwardRenderer *fr = entity_get_or_add_component(e, ForwardRenderer);
    fr->program = create_program(e, program, ForwardRenderer,
                                 "vs_forward.bin", "fs_forward.bin");
}

```

The `ForwardRenderer` component itself is defined in [`equilibrium/components/renderer/renderer_components.h`](https://github.com/clibequilibrium/equilibriumengine/blob/main/equilibrium/components/renderer/renderer_components.h) and contains only the compiled program handle.

### Registering ECS Systems

The `ForwardRendererSystemImport` function registers the renderer within the ECS world. It sets up the initialization observer and two critical systems: `ForwardRendererBeginFrame` (triggered on `OnBeginRender`) and `DrawMeshes` (triggered on `OnRender`).

```c
// From forward_renderer_system.c
void ForwardRendererSystemImport(ecs_world_t *world) {
    ECS_MODULE(world, ForwardRendererSystem);
    
    // Import dependencies
    ECS_IMPORT(world, BaseRenderingSystem);
    ECS_IMPORT(world, CameraSystem);
    ECS_IMPORT(world, PBRSystem);
    ECS_IMPORT(world, LightSystem);
    ECS_IMPORT(world, BgfxSystem);
    
    // Register systems
    ECS_OBSERVER(world, InitializeForwardRenderer, EcsOnSet, [in] Bgfx);
    ECS_SYSTEM(world, ForwardRendererBeginFrame, OnBeginRender, 
               [in] ForwardRenderer, [in] AppWindow, [in] FrameData, [in] Camera);
    ECS_SYSTEM(world, DrawMeshes, OnRender, 
               [in] FrameData, [in] PBRShader, [in] ForwardRenderer);
}

```

This registration ensures the forward renderer integrates with the engine's camera, PBR, and lighting systems.

## Per-Frame Rendering Pipeline

With systems registered, the engine executes the forward rendering pipeline every frame through two main phases.

### Preparing the View

The `ForwardRendererBeginFrame` function in [`forward_renderer_system.c`](https://github.com/clibequilibrium/equilibriumengine/blob/main/forward_renderer_system.c) prepares the rendering surface. It clears the color and depth buffers, sets the viewport dimensions, binds the frame buffer, and updates the view-projection matrix using `set_view_projection` from [`bgfx_utils.h`](https://github.com/clibequilibrium/equilibriumengine/blob/main/bgfx_utils.h).

```c
// From forward_renderer_system.c
static void ForwardRendererBeginFrame(ecs_iter_t *it) {
    ForwardRenderer *fr = ecs_field(it, ForwardRenderer, 1);
    AppWindow *win      = ecs_field(it, AppWindow, 2);
    FrameData *fd      = ecs_field(it, FrameData, 3);
    Camera *cam        = ecs_field(it, Camera, 4);

    for (int i = 0; i < it->count; ++i) {
        bgfx_set_view_name(0, "Forward render pass");
        bgfx_set_view_clear(0, BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH,
                            0x303030FF, 1.0f, 0);
        bgfx_set_view_rect(0, 0, 0, win[i].width, win[i].height);
        bgfx_set_view_frame_buffer(0, fd[i].frame_buffer);
        bgfx_touch(0);
        set_view_projection(0, &cam[i], win[i].width, win[i].height);
    }
}

```

### Drawing Meshes

The `DrawMeshes` function executes the core rendering logic. It queries all entities with `Mesh`, `Material`, and `Transform` components, then iterates through mesh groups to submit draw calls.

For each mesh group, the system:
1. Applies the world transform via `bgfx_set_transform`
2. Sets the normal matrix via `set_normal_matrix`
3. Binds vertex and index buffers
4. Applies PBR material parameters via `bind_material` from the PBR system
5. Submits the draw call with the forward shader program

```c
// From forward_renderer_system.c
static void DrawMeshes(ecs_iter_t *it) {
    FrameData *fd   = ecs_field(it, FrameData, 1);
    PBRShader *pbr = ecs_field(it, PBRShader, 2);
    ForwardRenderer *fr = ecs_field(it, ForwardRenderer, 3);

    ecs_iter_t q = ecs_query_iter(it->world, it->ctx);
    while (ecs_query_next(&q)) {
        Mesh *m = ecs_field(&q, Mesh, 1);
        Material *mat = ecs_field(&q, Material, 2);
        Transform *tr = ecs_field(&q, Transform, 3);

        for (int i = 0; i < q.count; ++i) {
            for (size_t g = 0; g < ecs_vector_count(m[i].groups); ++g) {
                Group *grp = ecs_vector_get(m[i].groups, Group, g);
                bgfx_set_transform(&tr[i].value, 1);
                set_normal_matrix(fd, tr[i].value);
                bgfx_set_vertex_buffer(0, grp->vertex_buffer, 0, UINT32_MAX);
                bgfx_set_index_buffer(grp->index_buffer, 0, UINT32_MAX);
                uint64_t matState = bind_material(pbr, &mat[i]);
                bgfx_set_state(BGFX_STATE_DEFAULT & ~BGFX_STATE_CULL_MASK | matState, 0);
                bgfx_submit(0, fr->program, 0,
                            ~BGFX_DISCARD_BINDINGS | BGFX_DISCARD_INDEX_BUFFER |
                            BGFX_DISCARD_VERTEX_STREAMS);
            }
        }
    }
    bgfx_discard(BGFX_DISCARD_ALL);
}

```

## Key Source Files and Architecture

The forward rendering implementation spans several modules within the `clibequilibrium/equilibriumengine` repository:

- **[`equilibrium/systems/rendering/forward_renderer_system.c`](https://github.com/clibequilibrium/equilibriumengine/blob/main/equilibrium/systems/rendering/forward_renderer_system.c)** – Core implementation containing `InitializeForwardRenderer`, `ForwardRendererBeginFrame`, and `DrawMeshes`.
- **[`equilibrium/systems/rendering/forward_renderer_system.h`](https://github.com/clibequilibrium/equilibriumengine/blob/main/equilibrium/systems/rendering/forward_renderer_system.h)** – Public API exposing `ForwardRendererSystemImport` for module registration.
- **[`equilibrium/components/renderer/renderer_components.h`](https://github.com/clibequilibrium/equilibriumengine/blob/main/equilibrium/components/renderer/renderer_components.h)** – Definition of the `ForwardRenderer` component storing the shader program handle.
- **[`equilibrium/utils/bgfx_utils.h`](https://github.com/clibequilibrium/equilibriumengine/blob/main/equilibrium/utils/bgfx_utils.h)** – Utility functions including `renderer_supported` for hardware validation and `set_view_projection` for matrix updates.
- **[`equilibrium/systems/rendering/base_rendering_system.c`](https://github.com/clibequilibrium/equilibriumengine/blob/main/equilibrium/systems/rendering/base_rendering_system.c)** – Provides the underlying bgfx context and frame buffer management that forward rendering depends upon.
- **[`equilibrium/systems/rendering/pbr_system.c`](https://github.com/clibequilibrium/equilibriumengine/blob/main/equilibrium/systems/rendering/pbr_system.c)** – Supplies the `bind_material` function used to apply PBR parameters during the mesh draw loop.

## Summary

- **Validate hardware** using `renderer_supported` in [`bgfx_utils.h`](https://github.com/clibequilibrium/equilibriumengine/blob/main/bgfx_utils.h) before allocating resources.
- **Initialize the component** with `InitializeForwardRenderer` to compile forward shaders and create the `ForwardRenderer` component.
- **Register systems** via `ForwardRendererSystemImport` to hook into `OnBeginRender` and `OnRender` phases.
- **Prepare the frame** in `ForwardRendererBeginFrame` by clearing buffers, setting viewports, and updating view-projection matrices.
- **Render meshes** in `DrawMeshes` by iterating ECS queries, binding PBR materials, and submitting draw calls with the forward shader program.

## Frequently Asked Questions

### What is the difference between forward rendering and deferred rendering in Equilibrium Engine?

Forward rendering processes lighting calculations directly during the mesh draw call using the `DrawMeshes` system, while deferred rendering separates geometry and lighting into distinct passes. The engine implements forward rendering in [`forward_renderer_system.c`](https://github.com/clibequilibrium/equilibriumengine/blob/main/forward_renderer_system.c) and deferred rendering in [`deferred_renderer_system.c`](https://github.com/clibequilibrium/equilibriumengine/blob/main/deferred_renderer_system.c), with both systems sharing the same base rendering infrastructure but differing in how they handle lighting calculations and frame buffer attachments.

### How does the forward renderer handle PBR materials?

The forward renderer delegates material binding to the PBR system through the `bind_material` function defined in [`pbr_system.c`](https://github.com/clibequilibrium/equilibriumengine/blob/main/pbr_system.c). During the `DrawMeshes` execution, the system calls `bind_material` with the PBR shader context and material component, which returns a render state mask. This mask is then combined with default bgfx states before submitting the draw call, ensuring proper metallic-roughness workflow support in the forward pass.

### Can I use custom shaders with the forward rendering system?

Yes, you can extend the forward renderer by modifying the shader compilation step in `InitializeForwardRenderer`. The system loads compiled shader binaries (`vs_forward.bin` and `fs_forward.bin`) using the `create_program` utility. To use custom shaders, replace these binary names with your own compiled vertex and fragment shaders, or create additional `ForwardRenderer` variants with different program handles stored in the component defined in [`renderer_components.h`](https://github.com/clibequilibrium/equilibriumengine/blob/main/renderer_components.h).

### What ECS components are required for an entity to render with the forward renderer?

An entity must possess three core components to be processed by the `DrawMeshes` system: `Mesh` (containing vertex and index buffer groups), `Material` (PBR material parameters), and `Transform` (world transformation matrix). Additionally, the world must have entities with `ForwardRenderer`, `AppWindow`, `FrameData`, `Camera`, and `PBRShader` components to satisfy the system queries defined in [`forward_renderer_system.c`](https://github.com/clibequilibrium/equilibriumengine/blob/main/forward_renderer_system.c).