How ArmorPaint's Timeline Integrates with the Render Pipeline for PBR Texture Animation

ArmorPaint's tab_timeline.c module captures live GPU render targets as animation keyframes and feeds them back into the standard PBR render pipeline during playback, enabling GPU-accelerated scrubbing through texture sequences without duplicating the renderer.

The tab_timeline.c file in the armory3d/armorpaint repository serves as the core animation system for PBR texture authoring. It stores temporal snapshots of paint layers and mesh transforms, then orchestrates the render pipeline so that the correct resources are bound when the timeline advances. This architecture allows the existing PBR renderer to draw animated sequences by simply swapping the underlying GPU textures and matrices rather than implementing a separate animation path.

Storing PBR Textures as Render Target Snapshots

When an artist creates a keyframe, the system captures the current state of the painting buffers and scene objects. These snapshots become the source data that the render pipeline consumes during playback.

Capturing Layer Textures

The function tab_timeline_add_keyframe_on_next_frame allocates a new keyframe object and copies the current paint layer textures into it. This operation duplicates the GPU render targets texpaint, texpaint_nor, and texpaint_pack using tab_timeline_copy_tex:

void tab_timeline_add_keyframe_on_next_frame(void *_)
{
    i32 fr = tab_timeline_pending_kf_frame;
    i32 li = tab_timeline_pending_kf_layer;
    // … allocate keyframe …
    tab_timeline_copy_tex(kf->texpaint,      l->texpaint);
    tab_timeline_copy_tex(kf->texpaint_nor,  l->texpaint_nor);
    tab_timeline_copy_tex(kf->texpaint_pack, l->texpaint_pack);
}

These textures are ordinary GPU render targets created with gpu_create_render_target. By copying them into the keyframe structure, the system preserves a snapshot of the PBR material state that can later be restored to the active layer.

Recording Mesh and Camera Transforms

For mesh animation, tab_timeline_capture_mesh stores the object's world transform matrix. Camera keyframes are handled similarly, allowing the timeline to animate the viewpoint independently of the paint layers. This data is stored alongside the texture keyframes in the timeline's internal arrays maintained in tab_timeline.c.

Playback and Frame Loading

During animation playback, tab_timeline_update calculates the current frame and prepares the render pipeline state before the next draw call.

Computing the Current Frame

The update logic converts the playback time into a floating-point frame index (playback_frame). Based on this value, tab_timeline_load_from_keyframes retrieves the appropriate keyframe for each layer. The calculation happens in the update loop:

// Convert time to frame index
playback_frame = (f32)(current_time - start_time) * fps;

// Load corresponding textures
tab_timeline_load_from_keyframes(layer, frame_index);

Restoring GPU State

When a specific frame is selected, tab_timeline_copy_tex transfers the stored keyframe textures back onto the live material's render targets. This operation simply overwrites the active GPU textures with the snapshot data, ensuring that the standard PBR shader sees the correct pixel data without requiring any pipeline state changes. The engine then renders the scene exactly as it would for a static brush stroke, but using the historical texture data prepared by the timeline.

GPU-Accelerated Interpolation Between Keyframes

For smooth transitions, the timeline implements tweening using a dedicated GPU shader that blends between two keyframe textures.

The Tween Shader Pipeline

The system initializes a specialized graphics pipeline via tab_timeline_init_tween_pipe in tab_timeline.c. This pipeline uses the vertex and fragment shaders layer_tween.vert and layer_tween.frag to perform the interpolation:

static void tab_timeline_init_tween_pipe(void) {
    // Creates gpu_pipeline_t for layer tweening
    tab_timeline_tween_pipe = gpu_create_pipeline(
        "layer_tween.vert",
        "layer_tween.frag"
    );
}

Executing Texture Blends

When a keyframe has tween = true, tab_timeline_tween_tex renders the interpolation between two source textures into a temporary render target. The function binds the two source keyframe textures, sets the interpolation factor t (0.0 to 1.0), and executes the draw call:

static void tab_timeline_tween_tex(gpu_texture_t *dst,
                                   gpu_texture_t *from,
                                   gpu_texture_t *to,
                                   f32 t)
{
    tab_timeline_init_tween_pipe();
    _gpu_begin(dst, NULL, NULL, GPU_CLEAR_NONE, 0, 0.0);
    gpu_set_pipeline(tab_timeline_tween_pipe);
    gpu_set_texture(tab_timeline_tween_tex0, from);
    gpu_set_texture(tab_timeline_tween_tex1, to);
    gpu_set_float(tab_timeline_tween_factor, t);
    gpu_draw();  // Runs layer_tween shader
    gpu_end();
}

The resulting blended texture is written back into the active layer's render target, allowing the animation playback to remain fully GPU-accelerated while using the same PBR pipeline that handles static painting.

Animating Meshes and Cameras

Beyond textures, the timeline manipulates the scene graph to animate object poses and camera movements.

Transform Interpolation

Mesh keyframes store 4×4 transformation matrices. During playback, tab_timeline_load_mesh_keyframes interpolates between matrices using mat4_tween and applies the result via tab_timeline_set_mesh_transform. After updating the transform, tab_timeline_sync_mesh_body synchronizes physics bodies to match the new pose.

These matrix updates modify the scene graph before the render pass begins. Consequently, the standard PBR renderer draws the mesh at the interpolated pose without requiring any animation-specific logic in the drawing code.

Integration with the Main Render Loop

The timeline does not own its own draw routine. Instead, it functions as a preparatory step within the main engine loop.

In paint/sources/base.c, the main update loop calls tab_timeline_update() each frame to process playback state, load keyframes, and execute tween shaders. After this function returns, the regular rendering logic draws the scene using the now-populated GPU textures and updated transforms.

This design means the rendered frame you see while scrubbing or playing is produced by the existing render pipeline, now fed with the appropriate animated resources prepared by tab_timeline.c.

Summary

  • Keyframe Storage: tab_timeline.c copies GPU render targets (texpaint, texpaint_nor, texpaint_pack) into keyframe objects when artists record frames.
  • Pipeline Preparation: During playback, tab_timeline_update loads the correct textures and transforms back into the active GPU state before the render pass executes.
  • GPU Tweening: Interpolation between frames uses a dedicated shader pipeline (layer_tween.vert/frag) to blend textures on the GPU without CPU overhead.
  • Scene Graph Updates: Mesh and camera transforms are interpolated and applied to the scene graph, allowing the standard renderer to draw animated poses.
  • Non-Invasive Integration: The timeline modifies GPU resources (textures, matrices) but does not alter the core PBR renderer, leveraging the existing draw loop in base.c.

Frequently Asked Questions

How does ArmorPaint store animation keyframes for PBR textures?

According to the armory3d/armorpaint source code, keyframes are stored as copies of GPU render targets. When tab_timeline_add_keyframe_on_next_frame is triggered, it calls tab_timeline_copy_tex to duplicate the current texpaint, texpaint_nor, and texpaint_pack textures into a new keyframe structure. These are standard GPU textures created with gpu_create_render_target, preserving the exact PBR material state at that moment.

What happens when the timeline plays in ArmorPaint?

When playback starts via tab_timeline_play_on_next_frame, the system sets tab_timeline_playing = true and records a start timestamp. On each engine tick, tab_timeline_update (called from paint/sources/base.c) calculates the current frame index, loads the corresponding keyframe textures into the active material slots, and updates mesh transforms. The regular render pass then draws the scene using these prepared resources.

How does tweening work between keyframes in tab_timeline.c?

If a keyframe has the tween flag enabled, the system blends the two surrounding frames using tab_timeline_tween_tex. This function creates a GPU pipeline with layer_tween.vert and layer_tween.frag shaders, binds the two source textures, sets an interpolation factor t, and draws the result into the active layer's render target. This GPU-accelerated blend ensures smooth transitions without CPU overhead.

Does the timeline modify the core PBR renderer?

No. The timeline integration is designed to be non-invasive. tab_timeline.c only manipulates the data fed into the renderer—specifically the GPU textures bound to material slots and the transformation matrices in the scene graph. The actual PBR rendering loop remains unchanged, drawing whatever textures and transforms are currently active, whether static or animated.

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 →