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

> Discover how ArmorPaint's timeline tab integrates with the PBR render pipeline to capture and play back GPU animation keyframes. Experience seamless texture animation and scrubbing.

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

---

**ArmorPaint's [`tab_timeline.c`](https://github.com/armory3d/armorpaint/blob/main/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`](https://github.com/armory3d/armorpaint/blob/main/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`:

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

```c
// 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`](https://github.com/armory3d/armorpaint/blob/main/tab_timeline.c). This pipeline uses the vertex and fragment shaders `layer_tween.vert` and `layer_tween.frag` to perform the interpolation:

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

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

## Summary

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