How Shaders Are Managed and Compiled in ArmorPaint: A Deep Dive into the Iron Engine

ArmorPaint caches and reuses shaders through the sys_get_shader function in base/sources/iron_system.c, which checks a global hash-map before loading GLSL source files from assets/shaders/ and compiling them via the Iron engine's GPU API.

ArmorPaint's rendering pipeline is built on the Iron game engine, which provides a robust system for shader management and compilation. The codebase centralizes shader handling through the gpu_shader_t type and the sys_get_shader lookup function, ensuring that GLSL programs are compiled once and reused across material, UI, and post-processing passes.

The Shader Architecture and Data Structures

Before compilation occurs, ArmorPaint represents shaders using two primary structures defined in paint/sources/types.h. The shader_context_t struct stores metadata including vertex and fragment file names, a boolean flag indicating whether the shader originates from source code, and an array of uniform constants. These contexts are wrapped by shader_data_t, which maintains references to the compiled GPU resources and their associated contexts.

// Conceptual representation based on types.h
shader_data_t *sd = scene->shader_datas->buffer[0];
shader_context_t *sc = sd->context->data;
// sc contains: vertex filename, fragment filename, constants array

This separation between the high-level shader data and the low-level GPU implementation allows the engine to manage shader lifecycles independently from their compilation artifacts.

How sys_get_shader Loads and Compiles Shaders

The core of ArmorPaint's shader compilation system resides in base/sources/iron_system.c. The sys_get_shader function implements a four-stage caching and compilation pipeline:

  • Cache Lookup – The function first queries a global hash-map to determine if a shader with the requested name has already been compiled. If found, it returns the cached gpu_shader_t pointer immediately.
  • Path Resolution – For new shaders, the function constructs file paths by appending the name to assets/shaders/<name>.vert and assets/shaders/<name>.frag.
  • Compilation – It reads the GLSL source code and invokes the Iron GPU API to compile the vertex and fragment stages into a complete shader program.
  • Caching – The resulting gpu_shader_t object is stored in the global hash-map with the shader name as the key, ensuring subsequent requests return the compiled instance without disk I/O or GPU recompilation.
// Example from paint/sources/pipes.c showing typical usage
pipe->vertex_shader   = sys_get_shader("layer_merge.vert");
pipe->fragment_shader = sys_get_shader("layer_merge.frag");

Integrating Shaders into Rendering Pipelines

Once compiled, shaders are attached to rendering pipes through simple pointer assignment. The pipe_t structure maintains vertex_shader and fragment_shader fields of type gpu_shader_t*, which are populated by sys_get_shader calls during pipe initialization.

At application startup, ArmorPaint pre-loads frequently used shaders to eliminate compilation stutter during the first frame. The initialization code in paint/sources/startup.c creates default shaders such as mesh_posnor.vert, mesh_posnor.frag, compositor_pass.vert, and compositor_pass.frag, storing them in scene->shader_datas for immediate availability.

When executing a draw call, the rendering code retrieves the cached shader from the pipe structure and binds it to the GPU before issuing commands. This design pattern eliminates redundant compilation and enables dynamic shader swapping by changing the string argument passed to sys_get_shader.

Working with Shader Uniforms

ArmorPaint allows runtime modification of shader behavior through uniform constants. After retrieving a shader_data_t instance from the scene or a pipe, you can access its shader_context_t and append custom constants:

// Retrieve shader context from scene data
shader_data_t *sd = scene->shader_datas->buffer[0];
shader_context_t *sc = sd->context->data;

// Configure a float uniform
shader_const_t *strength = (shader_const_t *)calloc(1, sizeof(shader_const_t));
strength->name = "strength";
strength->type = SHADER_CONST_F32;
strength->value_f32 = 0.8f;
any_array_push(sc->constants, strength);

This pattern is used throughout the UI toolkit and material system to pass dynamic parameters like brush strength or layer opacity to the GPU.

Practical Implementation Examples

Loading a Custom Shader

To create a new rendering effect, instantiate a pipe and assign compiled shaders by name:

// Create pipe and assign shaders
pipe_t *effect_pipe = pipe_create();
effect_pipe->vertex_shader   = sys_get_shader("my_effect.vert");
effect_pipe->fragment_shader = sys_get_shader("my_effect.frag");

// Use in render path
render_path_draw_pipe(effect_pipe);

UI Toolkit Shader Assignment

The 2D view system demonstrates how UI components load specialized shaders:

// From UI view initialization
ui_view2d_pipe->vertex_shader   = sys_get_shader("layer_view.vert");
ui_view2d_pipe->fragment_shader = sys_get_shader("layer_view.frag");

// Execute draw
ui_view2d_draw();

UV Utility Shaders

Specialized utilities like the UV dilate tool load specific shader pairs as seen in paint/sources/util/util_uv.c:

// Loading dilate shader for UV operations
pipe->vertex_shader   = sys_get_shader("dilate_map.vert");
pipe->fragment_shader = sys_get_shader("dilate_map.frag");

Key Source Files for Shader Management

Summary

  • ArmorPaint manages shaders through the Iron engine's sys_get_shader function located in base/sources/iron_system.c.
  • Shaders are cached in a global hash-map after their first compilation to prevent redundant GPU operations.
  • Source files follow the naming convention assets/shaders/<name>.vert and assets/shaders/<name>.frag.
  • The pipe_t structure binds compiled gpu_shader_t objects to rendering commands.
  • Default shaders are pre-loaded at startup via paint/sources/startup.c to ensure immediate availability.
  • Uniform constants are managed through shader_context_t and appended dynamically at runtime.

Frequently Asked Questions

Where does ArmorPaint store its GLSL shader source files?

ArmorPaint stores shader source code in the assets/shaders/ directory, with vertex shaders using the .vert extension and fragment shaders using .frag. The sys_get_shader function in base/sources/iron_system.c constructs these paths dynamically when loading new shaders.

What function handles shader compilation in ArmorPaint?

The sys_get_shader function in base/sources/iron_system.c handles all shader compilation. It checks a global hash-map for existing compiled shaders, reads GLSL source files from disk if necessary, and invokes the Iron GPU API to create the shader program.

How does ArmorPaint prevent shaders from recompiling every frame?

The engine implements a caching mechanism where sys_get_shader stores compiled gpu_shader_t objects in a global hash-map using the shader name as the key. Subsequent calls with the same name return the cached pointer immediately, avoiding file system access and GPU compilation overhead during rendering loops.

How can I add custom uniform variables to a shader at runtime?

Retrieve the shader_data_t from your scene or pipe, access its shader_context_t, and create a shader_const_t structure with your variable name, type (such as SHADER_CONST_F32), and value. Append this constant to the context's constants array using any_array_push, and the uniform will be available in your shader during the next draw call.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →