Rendering Pipelines in ArmorPaint: Architecture and Implementation Guide

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

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, 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, 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, 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. 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:

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

For UI elements:

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

Copy operations with specific formats:

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

Cursor rendering with custom constants:

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. 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, 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, 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 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 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.

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 →