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

> Discover how ArmorPaint manages and compiles shaders using the Iron engine. Learn about shader caching and compilation processes for efficient rendering.

- Repository: [Armory 3D/armorpaint](https://github.com/armory3d/armorpaint)
- Tags: deep-dive
- Published: 2026-09-13

---

**ArmorPaint caches and reuses shaders through the `sys_get_shader` function in [`base/sources/iron_system.c`](https://github.com/armory3d/armorpaint/blob/main/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`](https://github.com/armory3d/armorpaint/blob/main/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.

```c
// 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`](https://github.com/armory3d/armorpaint/blob/main/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.

```c
// 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`](https://github.com/armory3d/armorpaint/blob/main/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:

```c
// 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:

```c
// 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:

```c
// 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`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/util/util_uv.c):

```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

- **[`base/sources/iron_system.c`](https://github.com/armory3d/armorpaint/blob/main/base/sources/iron_system.c)** – Implements `sys_get_shader`, handling file I/O, compilation, and the global shader cache.
- **[`base/sources/iron_system.h`](https://github.com/armory3d/armorpaint/blob/main/base/sources/iron_system.h)** – Declares the shader API including `gpu_shader_t *sys_get_shader(char *name)`.
- **[`paint/sources/types.h`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/types.h)** – Defines `shader_data_t`, `shader_context_t`, and uniform constant structures.
- **[`paint/sources/pipes.c`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/pipes.c)** – Initializes rendering pipes and assigns shaders via `sys_get_shader`.
- **[`paint/sources/startup.c`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/startup.c)** – Pre-compiles default shaders during application initialization.
- **[`paint/sources/util/util_uv.c`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/util/util_uv.c)** – Example of utility-specific shader loading patterns.

## Summary

- ArmorPaint manages shaders through the Iron engine's `sys_get_shader` function located in [`base/sources/iron_system.c`](https://github.com/armory3d/armorpaint/blob/main/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`](https://github.com/armory3d/armorpaint/blob/main/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`](https://github.com/armory3d/armorpaint/blob/main/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`](https://github.com/armory3d/armorpaint/blob/main/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.