# Loading and Applying LUTs in a Video Compositor: Palmier Pro's 3D Color Grading Pipeline

> Learn to load and apply LUTs in a video compositor with Palmier Pro. Explore its 3D color grading pipeline, GPU acceleration, and real-time features.

- Repository: [Palmier/palmier-pro](https://github.com/palmier-io/palmier-pro)
- Tags: how-to-guide
- Published: 2026-06-24

---

**Palmier Pro implements a three-stage pipeline for 3D ".cube" Look-Up Tables that combines Swift-based file parsing with Metal GPU acceleration to perform tetrahedral interpolation and real-time color grading in the video compositor.**

The Palmier-Pro repository demonstrates a production-grade approach to loading and applying LUTs in a video compositor. By leveraging the Metal framework for tetrahedral interpolation and caching parsed `.cube` files as packed RGBA data, the system enables real-time color grading with adjustable intensity blending directly within the compositor pipeline.

## How Palmier Pro Loads and Validates LUT Files

The **LUT loading system** centers on the `LUTLoader` class, which handles the ingestion of industry-standard `.cube` files and prepares them for GPU processing.

### Parsing and Validating .cube Files

In [`LUTLoader.swift`](https://github.com/palmier-io/palmier-pro/blob/main/LUTLoader.swift), the parser validates the `LUT_3D_SIZE` header to determine the cube dimension (typically 33 or 64). It reads the floating-point RGB triplets and converts them into a packed RGBA `Data` buffer optimized for Metal texture binding. This conversion step ensures that the 3D LUT can be efficiently encoded as a 2D texture strip where width equals `n` and height equals `n²`, as required by the Metal kernel.

### Project Storage and Caching Strategy

The loader copies the original LUT file into the project's private storage directory, ensuring that color grading settings survive project moves or saves. The parsed `CubeLUT` object is cached in memory keyed by the file path, preventing redundant disk reads during iterative color adjustments. According to the Palmier-Pro source code, this caching mechanism is critical for maintaining 60fps playback while applying multiple effects.

## Metal-Based LUT Interpolation Pipeline

Once loaded, the **LUT application** moves to the GPU via `LUTTetraKernel`, which wraps a high-performance Metal compute kernel.

### Tetrahedral Interpolation in LUTTetraKernel

The [`LUTTetraKernel.swift`](https://github.com/palmier-io/palmier-pro/blob/main/LUTTetraKernel.swift) file exposes the `apply(_:cube:key:intensity:)` method, which receives the source `CIImage`, the cached `CubeLUT` data, and a blend intensity factor. This kernel wrapper handles texture binding and image caching, ensuring that repeated applications of the same LUT reuse allocated GPU resources.

### The LUTTetra.metal Kernel Implementation

The actual color transformation occurs in `LUTTetra.metal`, where the `lutTetra` function performs tetrahedral interpolation on the 3D color cube. The kernel receives four parameters: the source image sampler, the LUT strip sampler, the cube dimension `n`, and the intensity blend factor. For each pixel, it calculates the position within the color cube, identifies the appropriate tetrahedron, and interpolates the output color. The final pixel value is computed as `mix(original.rgb, lutColor.rgb, intensity)`, allowing seamless blending between the original footage and the graded look.

## Integrating LUTs into the Video Compositor

The **effect registration system** bridges the UI and the Metal backend through `EffectRegistry`.

### Effect Registration in EffectRegistry

In [`EffectRegistry.swift`](https://github.com/palmier-io/palmier-pro/blob/main/EffectRegistry.swift), the compositor registers the LUT effect under the identifier `color.lut`. When the effect is applied, the registry loads the cached `CubeLUT` from `LUTLoader` using the stored file path, then forwards the image to `LUTTetraKernel.apply`. This architecture keeps the video compositor agnostic to the underlying LUT implementation while providing a consistent interface for all color effects.

### UI Controls and Intensity Blending

The Inspector interface in [`AdjustTab.swift`](https://github.com/palmier-io/palmier-pro/blob/main/AdjustTab.swift) presents a "Choose .cube file" button that triggers `LUTLoader.store(path:projectId:)` to secure the file within the project bundle. The intensity slider drives the `setLUTIntensity(value:)` method, which updates the effect parameters and commits changes through the `commitEffects` helper. This generic helper ensures that all LUT modifications are undoable and sync with the project's revision history.

## Code Examples

### Loading a LUT from the Inspector

When a user selects a `.cube` file through the UI, [`AdjustTab.swift`](https://github.com/palmier-io/palmier-pro/blob/main/AdjustTab.swift) stores the file and applies the effect:

```swift
// Called from the inspector when the user picks a .cube file.
func setLUTPath(_ path: String, clips: [Clip]) {
    // Store the file inside the project package and get a stable location.
    guard let stored = try? LUTLoader.store(path: path, projectId: editor.projectId) else { return }
    commitEffects(clips, actionName: "Apply LUT") { effects in
        // Replace any existing LUT effect on the clip.
        effects["color.lut"] = ["path": stored.path, "intensity": 1.0]
    }
}

```

*Source:* [`AdjustTab.swift`](https://github.com/palmier-io/palmier-pro/blob/main/AdjustTab.swift), lines approximately 447-460.

### Adjusting Blend Intensity Programmatically

The intensity parameter controls the mix between original and processed colors:

```swift
func setLUTIntensity(_ value: Double, clips: [Clip], commit: Bool) {
    commitEffects(clips, actionName: "Change LUT Intensity") { effects in
        var params = effects["color.lut"] as? [String: Any] ?? [:]
        params["intensity"] = value          // 0 = no LUT, 1 = full LUT
        effects["color.lut"] = params
    }
}

```

*Source:* [`AdjustTab.swift`](https://github.com/palmier-io/palmier-pro/blob/main/AdjustTab.swift), lines approximately 470-478.

### Understanding the Metal Kernel Logic

The `LUTTetra.metal` kernel implements the tetrahedral interpolation algorithm:

```metal
extern "C" float4 lutTetra(coreimage::sampler img,
                          coreimage::sampler lut,
                          float n,
                          float intensity) {
    float4 s = img.sample(img.coord());          // original pixel
    float3 rgb = saturate(s.rgb);
    float3 p = rgb * (n - 1.0);
    float3 b0 = clamp(floor(p), 0.0, n - 2.0);
    float3 f = p - b0;

    // fetch the eight cube corners, blend inside the appropriate tetrahedron …
    float3 c000 = fetch(lut, n, b0);
    float3 c111 = fetch(lut, n, b0 + 1.0);
    float3 o = /* tetrahedral blending logic */;

    // final colour = (1‑intensity)·src + intensity·lut‑col
    return float4(mix(s.rgb, o, intensity), s.a);
}

```

*Source:* `LUTTetra.metal`, full kernel implementation.

## Summary

- **Three-stage architecture**: The pipeline separates LUT loading ([`LUTLoader.swift`](https://github.com/palmier-io/palmier-pro/blob/main/LUTLoader.swift)), GPU interpolation ([`LUTTetraKernel.swift`](https://github.com/palmier-io/palmier-pro/blob/main/LUTTetraKernel.swift)), and effect registration ([`EffectRegistry.swift`](https://github.com/palmier-io/palmier-pro/blob/main/EffectRegistry.swift)) to maintain clean separation of concerns.
- **Tetrahedral interpolation**: The Metal kernel in `LUTTetra.metal` performs high-quality 3D color interpolation using the tetrahedral method, which preserves color accuracy better than trilinear interpolation for most LUT sizes.
- **Project-safe storage**: The `LUTLoader.store` method automatically copies external LUT files into the project bundle, preventing broken references when sharing projects.
- **Real-time intensity control**: The intensity parameter passed to the Metal kernel enables non-destructive blending from 0.0 (original) to 1.0 (full LUT application).

## Frequently Asked Questions

### How does Palmier Pro handle file portability when loading LUTs?

When a user selects a `.cube` file through the Inspector UI, `LUTLoader.store(path:projectId:)` copies the file into the project's private storage directory and returns a stable path relative to the project bundle. This ensures that LUT references remain valid when the project is moved or archived, as the color grading data travels with the project rather than relying on external file system paths.

### What interpolation method does the video compositor use for LUT application?

The compositor uses **tetrahedral interpolation** implemented in the `LUTTetra.metal` kernel. This method divides the 3D color cube into tetrahedrons and interpolates within the specific tetrahedron containing the source color, providing more accurate color reproduction than simpler trilinear interpolation, particularly for smaller LUT sizes like 33³ cubes.

### Can the LUT intensity be adjusted in real-time during playback?

Yes. The `setLUTIntensity` method in [`AdjustTab.swift`](https://github.com/palmier-io/palmier-pro/blob/main/AdjustTab.swift) updates the intensity parameter in the effect dictionary, which is passed directly to the Metal kernel's `intensity` argument. Because the kernel performs the blend calculation on the GPU using `mix(s.rgb, o, intensity)`, changes to the intensity slider update in real-time without requiring the LUT to be reloaded or the video to be re-rendered from source.

### What file formats are supported for loading and applying LUTs?

The system specifically supports 3D LUTs in the `.cube` format as defined by the `LUT_3D_SIZE` header specification. The parser in [`LUTLoader.swift`](https://github.com/palmier-io/palmier-pro/blob/main/LUTLoader.swift) validates this header and expects floating-point RGB triplets, rejecting malformed files before they reach the GPU pipeline. This focused support ensures compatibility with standard color grading tools used in professional video workflows.