How the Equilibrium Engine bgfx_system Bridges ECS and the bgfx Rendering Pipeline
The bgfx_system acts as the sole bridge between the Flecs ECS world and the low-level bgfx graphics API, handling initialization, platform-specific window binding, resize events, and frame submission while exposing a Bgfx component that other rendering systems query to access the GPU context.
The bgfx_system in the Equilibrium Engine cleanly decouples platform-specific graphics initialization from rendering logic. By encapsulating bgfx lifecycle management within a dedicated ECS system, the engine enables data-driven rendering pipelines where subsystems like the forward renderer or PBR system simply query the active context without managing low-level API state.
Architecture Overview
The bgfx_system serves four critical responsibilities in the rendering pipeline:
- Initialize bgfx with platform data, renderer type selection, and debug configuration
- Expose a
Bgfxcomponent that storesbgfx_init_tinitialization data and reset flags for every window-owning entity - React to window events (creation and resize) to synchronize swap-chain dimensions and view rectangles
- Advance the frame at the end of each render pass via
bgfx_frame()
Other rendering subsystems—including forward_renderer_system, pbr_system, and light_system—query this Bgfx component to obtain the active context and utilize helper functions in bgfx_utils.h for creating textures, shaders, and buffers.
Initialization and Platform Setup
Component Definition
The Bgfx component defined in equilibrium/components/bgfx_components.h stores the initialization state required by all rendering systems:
// equilibrium/components/bgfx_components.h#L7-L11
typedef struct Bgfx {
bgfx_init_t data;
uint32_t reset; // Flags used when calling bgfx_reset()
} Bgfx;
This component is attached to any entity possessing a Renderer, AppWindow, and AppWindowHandle, ensuring that each window context maintains its own bgfx configuration.
Platform Data Configuration
Before bgfx can initialize, the system must extract native window handles using SDL-SysWM. The SetPlatformData function in equilibrium/systems/bgfx_system.c (lines 16-46) populates bgfx_platform_data_t with platform-specific handles (ndt for display connection, nwh for native window handle) across Linux, macOS, and Windows.
// Simplified excerpt from SetPlatformData
bgfx_platform_data_t pd;
pd.nwh = GetNativeWindowHandle(window); // Platform-specific extraction
pd.ndt = GetNativeDisplayType(); // Required for X11/Wayland
bgfx_set_platform_data(&pd);
Initialization Observer
The BgfxInitialize observer triggers on EcsOnSet when the prerequisite components (Renderer, AppWindow, AppWindowHandle) are present and the entity lacks a Bgfx component. Implemented in equilibrium/systems/bgfx_system.c (lines 48-82), this function:
- Calls
SetPlatformDatato bind the native window - Selects the renderer type (e.g., Vulkan, Metal, DirectX 11/12)
- Invokes
bgfx_init()to create the context - Configures the default view with profiling and quality settings
// equilibrium/systems/bgfx_system.c#L70-L78
bgfx_set_debug(BGFX_DEBUG_PROFILER);
uint32_t reset = BGFX_RESET_MAXANISOTROPY | BGFX_RESET_MSAA_X16;
bgfx_reset(app_window->width, app_window->height, reset, format);
bgfx_set_view_clear(0, BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH, 0x000000ff, 1.0f, 0);
bgfx_set_view_rect(0, 0, 0, app_window->width, app_window->height);
The system then stores the initialized Bgfx component on the entity, making the context available to downstream systems.
Runtime Pipeline Integration
Frame Advancement
At the end of every render frame, the BgfxEndRender system—registered in BgfxSystemImport—executes on the OnEndRender phase. This system simply calls bgfx_frame(false) to submit all queued draw calls to the GPU:
// equilibrium/systems/bgfx_system.c#L85-L89
static void BgfxEndRender(ecs_iter_t *it) {
(void)it;
bgfx_frame(false);
}
Window Resize Handling
When window dimensions change, the OnAppWindowResized observer (lines 91-99) detects the EcsOnSet event for AppWindow and Bgfx components, invoking bgfx_reset() with updated width, height, and the stored reset flags from the Bgfx component:
// equilibrium/systems/bgfx_system.c#L91-L99
static void OnAppWindowResized(ecs_iter_t *it) {
// ... field extraction ...
bgfx_reset(width, height, bgfx->reset, BGFX_TEXTURE_FORMAT_COUNT);
bgfx_set_view_rect(0, 0, 0, width, height);
}
Interfacing with Rendering Subsystems
Forward Renderer Integration
The forward_renderer_system relies entirely on the view rectangle and context established by bgfx_system. In equilibrium/systems/rendering/forward_renderer_system.c (lines 34-53), the ForwardRendererBeginFrame function queries window dimensions from the AppWindow component and configures its view using the pre-initialized bgfx context:
// forward_renderer_system.c example usage
bgfx_set_view_rect(default_view, 0, 0,
app_window[i].width,
app_window[i].height);
This works because bgfx_system has already validated the context and set default view parameters before any rendering systems execute.
PBR and Resource Management
The PBR system (pbr_system.c) creates shader programs using the create_program macro defined in equilibrium/utils/bgfx_utils.h (lines 47-66). While this macro handles shader compilation and hot-reload registration via the HotReloadableShader component, the actual GPU submission (bgfx_submit) assumes the view and context initialized by bgfx_system.
The GfxResource system (gfx_resource_system.c) complements this by destroying bgfx handles when components are removed, but it never reinitializes the API—that remains the exclusive domain of bgfx_system.
Practical Implementation Example
To add a custom rendering pass that interfaces with the bgfx_system pipeline, import the module and rely on the pre-initialized context:
#include "bgfx_system.h"
#include "utils/bgfx_utils.h"
#include "components/renderer/renderer_components.h"
#include "flecs.h"
static bgfx_view_id_t custom_view = 2;
static void CustomPassInit(ecs_iter_t *it) {
FrameData *fd = ecs_field(it, FrameData, 1);
for (int i = 0; i < it->count; ++i) {
if (!BGFX_HANDLE_IS_VALID(fd[i].frame_buffer))
continue;
bgfx_set_view_name(custom_view, "Custom pass");
bgfx_set_view_clear(custom_view, BGFX_CLEAR_COLOR, 0x0000FF00, 1.0f, 0);
bgfx_set_view_rect(custom_view, 0, 0,
fd[i].frame_buffer_width,
fd[i].frame_buffer_height);
bgfx_set_view_frame_buffer(custom_view, fd[i].frame_buffer);
bgfx_touch(custom_view);
}
}
static void CustomPassDraw(ecs_iter_t *it) {
FrameData *fd = ecs_field(it, FrameData, 1);
for (int i = 0; i < it->count; ++i) {
bgfx_set_texture(0, fd[i].debug_texture_uniform,
fd[i].debug_texture, UINT32_MAX);
bgfx_submit(custom_view, fd[i].debug_program, 0, BGFX_DISCARD_ALL);
}
}
void CustomPassSystemImport(world_t *world) {
ECS_MODULE(world, CustomPassSystem);
ECS_IMPORT(world, RendererComponents);
ECS_IMPORT(world, BgfxSystem); // Guarantees bgfx initialization
ECS_SYSTEM(world, CustomPassInit, OnBeginRender,
renderer.components.FrameData);
ECS_SYSTEM(world, CustomPassDraw, OnRender,
renderer.components.FrameData);
}
Key integration points:
- Import
BgfxSystemto ensurebgfx_init()completes before your system runs - Reuse view IDs or claim unused ones (view 0 is reserved by the system)
- Use
bgfx_utils.hhelpers for resource creation to maintain hot-reload compatibility
Summary
- The
bgfx_systemencapsulates all platform-specific bgfx initialization inequilibrium/systems/bgfx_system.c, creating a clean separation between ECS logic and graphics API setup. - The
Bgfxcomponent storesbgfx_init_tand reset flags, allowing multiple window contexts to coexist while providing a queryable interface for rendering subsystems. - Lifecycle observers (
BgfxInitialize,OnAppWindowResized) handle context creation and dynamic resize events automatically when window components change. - Frame advancement occurs via
BgfxEndRenderon theOnEndRenderphase, ensuring all rendering systems complete before GPU submission. - Downstream systems (forward renderer, PBR, lights) depend on the
BgfxSystemmodule but never directly manage the API context, enabling fully data-driven rendering pipelines.
Frequently Asked Questions
Where is the bgfx initialization struct stored in the ECS?
The initialization struct is stored in the Bgfx component defined in equilibrium/components/bgfx_components.h. This component contains a bgfx_init_t data field and a uint32_t reset field for resize flags, and it is attached to any entity that has a Renderer, AppWindow, and AppWindowHandle present.
How does the engine handle window resizing with bgfx?
The OnAppWindowResized observer in equilibrium/systems/bgfx_system.c detects changes to the AppWindow component and automatically calls bgfx_reset() with the new dimensions and the stored reset flags (such as BGFX_RESET_MSAA_X16), then updates the view rectangle to match the new window size.
Can multiple rendering systems use the same bgfx context?
Yes. All rendering subsystems—including the forward renderer, deferred renderer, and PBR system—query the same Bgfx component created by bgfx_system. They use helper functions from equilibrium/utils/bgfx_utils.h to create resources, but share the single initialized context per window entity.
What happens if I forget to import BgfxSystem in a custom renderer?
If you omit ECS_IMPORT(world, BgfxSystem), your system may attempt to call bgfx functions before bgfx_init() has executed, resulting in undefined behavior or crashes. Always import the module to ensure the BgfxInitialize observer runs first and validates the platform data.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →