# Rendering Pipelines in ArmorPaint: Architecture and Implementation Guide

> Explore ArmorPaint's rendering pipelines architecture. Learn how gpu_pipeline_t bundles shaders, vertex layouts, and render states for efficient GPU rendering.

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

---

**ArmorPaint utilizes a low-level GPU abstraction called `gpu_pipeline_t` to bundle shaders, vertex layouts, and render states, with all rendering pipelines centralized in [`paint/sources/pipes.c`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/pipes.c) and compiled during startup via `pipes_init()`.**

ArmorPaint is an open-source 3D texture painting application built on the Krom engine. The **rendering pipelines in ArmorPaint** form the core GPU abstraction layer that handles layer composition, mask operations, brush rendering, and UI display. Every pipeline defines a complete GPU state including shader pairs, vertex attribute layouts, and blend modes.

## Core Pipeline Architecture

The foundation of ArmorPaint's rendering system is the **`gpu_pipeline_t`** structure defined in the engine layer. A pipeline bundles:

- **Vertex and fragment shaders** loaded via `sys_get_shader`
- A **vertex layout** (`gpu_vertex_structure_t`) defining input attributes such as position, texture coordinates, and color
- **Render state flags** including blend modes, depth write settings, and color-write masks
- **Constant locations** for per-draw data like transformation matrices and opacity values

All pipelines are instantiated once during application startup inside **[`paint/sources/pipes.c`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/pipes.c)**. The initialization function `pipes_init()` (lines 19‑38) creates each pipeline object, configures its vertex layout, and compiles the GPU program via `gpu_pipeline_compile`.

## Layer Composition Pipelines

The primary **rendering pipelines in ArmorPaint** handle texture layer merging and pixel manipulation. These operate within the paint render path defined in [`paint/sources/render/render_path_paint.c`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/render/render_path_paint.c).

### pipes_merge and Channel Variants

The **`pipes_merge`** pipeline performs general RGBA layer merging using the `layer_merge.vert` and `layer_merge.frag` shader pair. This pipeline writes to all four color channels simultaneously.

For channel-specific operations, ArmorPaint provides **`pipes_merge_r`**, **`pipes_merge_g`**, and **`pipes_merge_b`**. These utilize the same shaders as `pipes_merge` but configure the color-write mask to target only the red, green, or blue channel respectively.

### Texture Copy Pipelines

The copy family of pipelines handles texture blitting between render targets:

- **`pipes_copy`**: Standard RGBA copy using `layer_copy.vert` and `layer_copy.frag` with a vertex layout containing **pos**, **tex**, and **col** attributes
- **`pipes_copy_bgra`**: Variant for BGRA texture formats using dedicated `layer_copy_bgra` shaders
- **`pipes_copy8`**, **`pipes_copy64`**, **`pipes_copy128`**: Target-specific bit-depth variants that set the `color_attachment` format to `GPU_TEXTURE_FORMAT_*` appropriate for 8-bit, 64-bit, or 128-bit render targets
- **`pipes_copy_rgb`**: Copies RGB data while disabling alpha channel writes, useful for preview passes

## Mask Operation Pipelines

ArmorPaint implements several specialized **rendering pipelines** for mask generation and manipulation:

- **`pipes_invert_mask`**: Inverts mask textures using `layer_invert.vert` and `layer_invert.frag`
- **`pipes_apply_mask`**: Applies a mask to a layer via `mask_apply.vert` and `mask_apply.frag`, binding two texture slots (`pipes_tex0_mask` and `pipes_texa_mask`)
- **`pipes_merge_mask`**: Merges two masks together using `mask_merge.vert` and `mask_merge.frag`, providing constant locations for opacity and blending parameters
- **`pipes_colorid_to_mask`**: Generates selection masks from color-ID textures using `mask_colorid.vert` and `mask_colorid.frag`, enabling "paint by ID" workflows

## Cursor and Interaction Pipelines

Interactive painting requires real-time cursor rendering:

- **`pipes_cursor`**: Renders the brush tip using `cursor.vert` and `cursor.frag`. This pipeline configures alpha blending with depth testing disabled
- **`pipes_cursor_decal`**: Handles texture stamp decals under the cursor using `cursor_decal.vert` and `cursor_decal.frag`, providing additional constant slots for decal parameters such as rotation and scale

## Utility and UI Pipelines

Beyond painting operations, specialized **rendering pipelines** handle interface elements:

- **`ui_view2d_pipe`**: Defined in [`paint/sources/ui/ui_view2d.c`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/ui/ui_view2d.c), this pipeline renders 2D UI overlay elements using `ui_view2d.vert` and `ui_view2d.frag`
- **`tab_timeline_tween_pipe`**: Created in [`paint/sources/ui/tab_timeline.c`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/ui/tab_timeline.c), this handles animation scrubber rendering via `timeline_tween.vert` and `timeline_tween.frag`
- **`util_uv_pipe_dilate`**: Located in [`paint/sources/util/util_uv.c`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/util/util_uv.c), performs UV dilation operations essential for texture painting algorithms

## Pipeline Initialization and Usage

All pipelines are created inside **`pipes_init()`** in [`paint/sources/pipes.c`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/pipes.c). After compilation, the engine binds pipelines using `gpu_set_pipeline()` or the helper `draw_set_pipeline()` before issuing draw calls.

Typical usage follows this pattern:

```c
/* Merging a painted layer */
gpu_set_pipeline(pipes_merge);
gpu_set_uniform(pipes_opac, opacity);
draw_call(...);

```

For UI elements:

```c
draw_set_pipeline(ui_view2d_pipe);
draw_quad(...);
draw_set_pipeline(NULL);  // Reset to default

```

Copy operations with specific formats:

```c
gpu_set_pipeline(pipes_copy8);
gpu_set_uniform(pipes_opac, 1.0f);
draw_quad(...);

```

Cursor rendering with custom constants:

```c
gpu_set_pipeline(pipes_cursor);
gpu_set_uniform(pipes_cursor_radius, brush_radius);
gpu_set_uniform(pipes_cursor_tint, brush_color);
draw_mesh(cursor_mesh);
gpu_set_pipeline(NULL);

```

## Integration with the Render Path

The **rendering pipelines** are orchestrated within [`paint/sources/render/render_path_paint.c`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/render/render_path_paint.c). This file manages the high-level composition flow: binding the appropriate pipeline for layer merging, switching to mask pipelines for selection operations, and finally rendering the cursor and decals on top.

The low-level GPU abstraction functions—including `gpu_create_pipeline()` and `gpu_set_pipeline()`—are implemented in [`base/sources/engine.c`](https://github.com/armory3d/armorpaint/blob/main/base/sources/engine.c), providing the hardware abstraction that makes the pipeline system portable across graphics APIs.

## Summary

- **Rendering pipelines in ArmorPaint** are encapsulated by the `gpu_pipeline_t` structure, bundling shaders, vertex layouts, and render states
- All pipeline definitions reside in **[`paint/sources/pipes.c`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/pipes.c)**, initialized via `pipes_init()` during startup
- The system provides specialized pipelines for RGBA merging, single-channel operations, mask manipulation, and UI rendering
- Copy pipelines support multiple bit-depths (8, 64, 128-bit) and color formats (RGBA, BGRA)
- Real-time brush interaction uses dedicated cursor pipelines with alpha blending and depth testing disabled
- The paint render path in [`render_path_paint.c`](https://github.com/armory3d/armorpaint/blob/main/render_path_paint.c) coordinates pipeline binding to composite the final texture output

## Frequently Asked Questions

### What is the purpose of the `gpu_pipeline_t` structure in ArmorPaint?

The `gpu_pipeline_t` structure serves as ArmorPaint's low-level GPU abstraction, bundling compiled shader programs, vertex attribute layouts, and fixed-function render states like blend modes and depth writes. This design allows the application to pre-compile all GPU states at startup and switch between them efficiently during rendering by calling `gpu_set_pipeline()`.

### Where are the rendering pipelines initialized in the ArmorPaint codebase?

All **rendering pipelines** are initialized in **[`paint/sources/pipes.c`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/pipes.c)** within the `pipes_init()` function (lines 19‑38). This function creates each pipeline object, configures the vertex structure using `gpu_vertex_structure_t`, loads shaders via `sys_get_shader()`, and compiles the final GPU program using `gpu_pipeline_compile()` before the main render loop begins.

### How does ArmorPaint handle different color channel operations in pipelines?

ArmorPaint uses separate pipeline instances for channel-specific operations. While `pipes_merge` writes to all RGBA channels, `pipes_merge_r`, `pipes_merge_g`, and `pipes_merge_b` target individual channels by configuring the color-write mask during pipeline setup. Similarly, `pipes_copy_rgb` disables alpha writes for operations that should only affect color data.

### What is the difference between `pipes_copy` and `pipes_copy8`/`pipes_copy64`/`pipes_copy128`?

`pipes_copy` is the standard 32-bit RGBA copy pipeline, while `pipes_copy8`, `pipes_copy64`, and `pipes_copy128` are specialized variants that configure the render target format via the `color_attachment` parameter to match 8-bit, 64-bit, or 128-bit GPU texture formats. These variants ensure correct pixel data handling when copying to high-dynamic-range or reduced-precision render targets.