# How Custom Shaders Are Compiled Using ashader.c and Registered with Iron’s Runtime Shader System in ArmorPaint

> Learn how ArmorPaint compiles custom shaders using ashader.c and registers them with Iron's runtime system. Understand the process from node graphs to GPU layer integration.

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

---

**ArmorPaint compiles custom material shaders by first generating Kong source code from node graphs, converting that source to platform-specific binaries via [`ashader.c`](https://github.com/armory3d/armorpaint/blob/main/ashader.c), and finally registering the compiled modules with Iron’s GPU layer through [`iron_gpu.c`](https://github.com/armory3d/armorpaint/blob/main/iron_gpu.c).**

ArmorPaint enables artists to author material shaders using the high-level **Kong** language (`.kong` files). When a material is saved, the engine executes a four-stage pipeline that bridges the visual node editor to the native GPU runtime. This article explains how the [`ashader.c`](https://github.com/armory3d/armorpaint/blob/main/ashader.c) compiler transforms Kong scripts into executable shader binaries and how Iron’s runtime system loads and binds these assets.

## The Kong-to-GPU Pipeline Overview

The compilation workflow traverses four distinct layers of the ArmorPaint codebase:

1. **[`node_shader.c`](https://github.com/armory3d/armorpaint/blob/main/node_shader.c)** constructs a temporary Kong script containing vertex and fragment functions, constants, and texture declarations.
2. **[`ashader.c`](https://github.com/armory3d/armorpaint/blob/main/ashader.c)** parses the Kong AST and emits platform-specific code (WGSL, HLSL, Metal) or binary blobs (SPIR-V, D3D11 bytecode).
3. **[`iron_gpu.c`](https://github.com/armory3d/armorpaint/blob/main/iron_gpu.c)** loads the compiled files, creates native GPU objects (e.g., `VkShaderModule`, `ID3D11VertexShader`), and caches them.
4. **Shader Context Binding** attaches the loaded modules to a `shader_context_t` structure for pipeline creation.

## Step 1: Generating Kong Source with node_shader.c

Before compilation can begin, the material’s node graph must be serialized into the Kong shading language.

### Building the Material Definition

In [`paint/sources/node_shader.c`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/node_shader.c), the function `node_shader_get()` iterates over the material’s nodes to build a complete Kong script. It constructs input/output structs, samples textures, and emits the `kong_vert` and `kong_frag` functions that define the material’s behavior.

```c
// Build a Kong script containing the final vertex and fragment code.
// The script is later written to <material>_<name>.kong and handed to ashader().
char *node_shader_get(node_shader_t *raw) {
    // … build input/output structs, constants, samplers …
    string_buffer_append(sb, "fun kong_vert(...){ … }\n");
    string_buffer_append(sb, "fun kong_frag(...){ … }\n");
    // … create the pipe structure that ties the two functions together …
    return string_buffer_get(&out);   // returns the full Kong source string
}

```

The resulting `.kong` file is saved to disk by the editor and serves as the input for the `amake` build tool.

## Step 2: Compiling Kong to Platform-Specific Shaders with ashader.c

The [`ashader.c`](https://github.com/armory3d/armorpaint/blob/main/ashader.c) compiler in [`base/tools/amake/ashader.c`](https://github.com/armory3d/armorpaint/blob/main/base/tools/amake/ashader.c) is responsible for transforming the intermediate Kong representation into executable GPU code.

### The ashader() Entry Point

The `ashader()` function acts as the primary interface for the build pipeline. It accepts the target shader language, the source Kong file path, and the output base name.

```c
int ashader(char *shader_lang, char *from, char *to) {
    // `from`  = path to the generated *.kong file
    // `to`    = base name for the output files (e.g. "…/myMat_myPass")
    // `shader_lang` = "wgsl", "spirv", "hlsl", "metal"
    kong_compile(shader_lang, from, to);
    return 0;
}

```

### Backend-Specific Code Generation

Inside `kong_compile()`, the compiler performs lexical analysis, parses the Kong source into an AST, and resolves types. It then dispatches to backend-specific exporters:

- **WGSL** – `wgsl_export2()` writes `*.vert.wgsl` and `*.frag.wgsl` for WebGPU targets.
- **Direct3D 11** – `hlsl_export2()` generates HLSL source strings, which are then compiled with `D3DCompile` to produce binary files `*.vert.d3d11` and `*.frag.d3d11`.
- **Metal** – `metal_export()` creates Metal Shading Language source files (`*.vert.metal` and `*.frag.metal`).
- **SPIR-V** – `spirv_export2()` emits binary SPIR-V files (`*.vert.spirv` and `*.frag.spirv`) for Vulkan, Linux, and Android builds.

The compiled artifacts are written to disk using the output buffer `to_` (see lines 190-210 of [`ashader.c`](https://github.com/armory3d/armorpaint/blob/main/ashader.c)), producing pairs of vertex and fragment shader files for the target platform.

## Step 3: Loading and Registering Shaders in Iron’s Runtime (iron_gpu.c)

Once the binaries exist on disk, Iron’s GPU abstraction layer takes over to make them available to the rendering pipeline.

### Creating Native GPU Objects

In [`base/sources/iron_gpu.c`](https://github.com/armory3d/armorpaint/blob/main/base/sources/iron_gpu.c), the `shader_load()` function reads the compiled shader files and instantiates the appropriate native objects based on the current graphics API.

```c
// Load a compiled shader file and create a GPU-native module.
static shader_t *shader_load(const char *path, api_kind api) {
    if (api == API_VULKAN) {
        // read SPIR-V binary …
        vkCreateShaderModule(device, &createInfo, NULL, &module);
    } else if (api == API_DIRECT3D11) {
        // read D3D bytecode …
        device->CreateVertexShader(...);
    } else if (api == API_METAL) {
        // compile Metal source into a MTLFunction …
    }
    // Store the object in the engine’s shader cache.
    shader_cache_add(path, module);
    return module;
}

```

### The Shader Context and Pipeline Binding

When a material is instantiated, Iron populates a `shader_context_t` structure (defined in [`iron_gpu.c`](https://github.com/armory3d/armorpaint/blob/main/iron_gpu.c)) with the paths to the compiled vertex and fragment shaders. Before the first draw call, `material_prepare()` invokes `shader_load()` for both stages, caches the returned handles in the context’s `vertex_shader` and `fragment_shader` fields, and creates the graphics pipeline.

```c
// Example for a Vulkan pipeline:
shader_t *vs = shader_load("myMat_myPass.vert.spirv", API_VULKAN);
shader_t *fs = shader_load("myMat_myPass.frag.spirv", API_VULKAN);
pipeline_create(vs, fs, ...);

```

During the render loop, Iron retrieves these cached objects from the `shader_context_t` and binds them to the command buffer.

## Practical Implementation Example

Implementing a custom shader workflow requires interaction with the editor, the build system, and the runtime API.

### Creating a Material in the Editor

First, define the material structure and node graph programmatically:

```c
// 1. Create a new material and obtain its context.
material_t *mat = material_create("MyMaterial");

// 2. Add a custom Kong shader (the editor writes this to a .kong file).
node_shader_context_add_elem(mat->shader_ctx, "myCustomVert", "float4");
node_shader_add_constant(mat->shader_ctx, "myColor: float4", NULL);
node_shader_add_texture(mat->shader_ctx, "albedoTex", NULL);

```

### Triggering Compilation via amake

Use the `amake` build tool to invoke [`ashader.c`](https://github.com/armory3d/armorpaint/blob/main/ashader.c) and generate the platform binaries:

```bash

# Inside the repository root:

./amake -c myMat_myPass.kong -t wasm   # or -t windows, -t macos, -t linux

# The tool calls ashader.c → produces:

#   myMat_myPass.vert.spirv

#   myMat_myPass.frag.spirv

```

### Runtime Loading and Rendering

At runtime, Iron automatically loads the compiled shaders when the material is first used:

```c
// Inside the rendering loop (simplified):
if (!material_is_ready(mat)) {
    // Iron loads the compiled shaders and creates the pipeline.
    material_prepare(mat);    // calls shader_load() for the .vert/.frag files
}
renderer_draw(mesh, mat);

```

## Summary

- **[`node_shader.c`](https://github.com/armory3d/armorpaint/blob/main/node_shader.c)** generates Kong source files from material node graphs, defining vertex and fragment entry points.
- **[`ashader.c`](https://github.com/armory3d/armorpaint/blob/main/ashader.c)** compiles Kong scripts to platform-specific formats (WGSL, SPIR-V, HLSL, Metal) via the `ashader()` and `kong_compile()` functions.
- **[`iron_gpu.c`](https://github.com/armory3d/armorpaint/blob/main/iron_gpu.c)** loads the compiled binaries into native GPU objects (e.g., `VkShaderModule`) and manages them through `shader_load()`.
- **`shader_context_t`** stores references to the compiled shaders, allowing Iron to bind them to the rendering pipeline when the material is drawn.

## Frequently Asked Questions

### What is the Kong shading language in ArmorPaint?

**Kong is a high-level shading language used by ArmorPaint to author material shaders.** It provides a syntax for defining vertex and fragment functions, uniforms, and texture samplers. The [`node_shader.c`](https://github.com/armory3d/armorpaint/blob/main/node_shader.c) module generates Kong source code from the visual node graph editor, which is then compiled to platform-specific GPU languages by [`ashader.c`](https://github.com/armory3d/armorpaint/blob/main/ashader.c).

### How does ashader.c support multiple graphics APIs?

**The `kong_compile()` function in [`ashader.c`](https://github.com/armory3d/armorpaint/blob/main/ashader.c) dispatches to backend-specific exporters based on the target parameter.** For WebGPU it calls `wgsl_export2()`, for Vulkan it calls `spirv_export2()`, for Direct3D it calls `hlsl_export2()` followed by `D3DCompile`, and for Metal it invokes `metal_export()`. This architecture allows a single Kong source file to target Windows, macOS, Linux, and web platforms.

### Where are compiled shaders cached in Iron?

**Compiled shader objects are cached in Iron’s internal shader cache within [`iron_gpu.c`](https://github.com/armory3d/armorpaint/blob/main/iron_gpu.c).** When `shader_load()` creates a native GPU module (such as a `VkShaderModule` or `ID3D11VertexShader`), it immediately calls `shader_cache_add()` to store the handle. Subsequent requests for the same file path retrieve the cached object instead of reloading it from disk, minimizing GPU upload overhead.

### What triggers shader compilation in the ArmorPaint workflow?

**Shader compilation is triggered when a material is saved in the editor, invoking the `amake` build tool.** The editor writes the generated Kong file to disk, then `amake` calls the `ashader()` function in [`ashader.c`](https://github.com/armory3d/armorpaint/blob/main/ashader.c) to produce the binary artifacts. At runtime, [`iron_gpu.c`](https://github.com/armory3d/armorpaint/blob/main/iron_gpu.c) loads these binaries automatically when `material_prepare()` is called prior to the first draw command.