Skip to content

DCTL

ExpertDuration ~25 min video + codingTools DaVinci Resolve Studio, A code editor (VS Code)

A LUT is a sealed box; a DCTL is a box you can open and write. DCTL — DaVinci Color Transform Language — lets you build custom color operations as small programs, which means when a look or a transform doesn’t exist yet, you can make it exist. You don’t need to become a programmer. You need to read a DCTL without fear, change the parts that matter, and understand the handful of math operations underneath the common ones. This lesson gets you there with one complete example built line by line, so the syntax stops being intimidating and starts being obvious.

DCTL is a Studio-only feature. Everything here reads and reasons the same on free Resolve; you just can’t run it until you have Studio.

Watch for: The gentlest possible on-ramp. Watch the boilerplate: the transform() function Resolve calls for every pixel, the float3 RGB triplet in and out, and make_float3 to build one. Then watch him break things on purpose — a missing semicolon, the ^ power operator that doesn't exist (you need _powf), a float3 assigned to a float — and fix each by reading the log file. That debug loop is 80% of DCTL work.

The shape of every DCTL. Resolve runs your DCTL’s transform() function once for every pixel of every frame. It hands you the pixel’s position and its red, green and blue values (floats, 0–1 for a normalized image), and you must return a float3 — the new RGB. That’s the whole contract. A do-nothing DCTL:

__DEVICE__ float3 transform(int p_Width, int p_Height, int p_X, int p_Y,
float p_R, float p_G, float p_B)
{
float3 out = make_float3(p_R, p_G, p_B); // build an RGB triplet from the inputs
return out; // hand it back unchanged
}

Because it runs per pixel, a DCTL can’t blur or read neighbours cheaply, and it can’t write files or metadata — it’s pure “pixel in, pixel out.” That constraint is also why it’s fast enough to run live.

A complete worked example: an exposure / printer-lights DCTL. Here is a real, useful transform — a master exposure control plus three per-channel “printer-light” offsets — built line by line.

// Exposure in stops, plus per-channel printer-light offsets (also in stops).
DEFINE_UI_PARAMS(exposure, Exposure, DCTLUI_SLIDER_FLOAT, 0.0f, -5.0f, 5.0f, 0.01f)
DEFINE_UI_PARAMS(red_pl, Red print, DCTLUI_SLIDER_FLOAT, 0.0f, -1.0f, 1.0f, 0.01f)
DEFINE_UI_PARAMS(grn_pl, Green print,DCTLUI_SLIDER_FLOAT, 0.0f, -1.0f, 1.0f, 0.01f)
DEFINE_UI_PARAMS(blu_pl, Blue print, DCTLUI_SLIDER_FLOAT, 0.0f, -1.0f, 1.0f, 0.01f)
__DEVICE__ float3 transform(int p_Width, int p_Height, int p_X, int p_Y,
float p_R, float p_G, float p_B)
{
float master = _powf(2.0f, exposure); // one stop = a doubling, so 2^stops
float r_gain = master * _powf(2.0f, red_pl);
float g_gain = master * _powf(2.0f, grn_pl);
float b_gain = master * _powf(2.0f, blu_pl);
float3 out = make_float3(p_R * r_gain, // scale each channel by its gain
p_G * g_gain,
p_B * b_gain);
return out;
}

Reading it top to bottom:

  • The four DEFINE_UI_PARAMS lines create sliders in Resolve’s DCTL panel: name, label, type, default, min, max, step. This is how a DCTL exposes controls to a colorist. Every number literal ends in f — DCTL wants floats made explicit.
  • _powf(2.0f, exposure) is the key idea: one stop is a doubling of light, so the linear multiplier for n stops is 2ⁿ. (You can’t write 2.0 ^ exposure — as the video shows, ^ isn’t a power operator here; you must call _powf.)
  • The three _gain lines fold the master exposure together with each channel’s own offset. Nudging Red print up warms the image by exposing the red channel harder — which is exactly the intuition behind printer lights: on a film printer you dial the red, green and blue lamp intensities in discrete “points” to time each print. This DCTL is a linear-light approximation of that; true printer lights work in log density (Lesson 3.5), but the mental model — per-channel exposure of the image — is identical.
  • make_float3(...) builds the output triplet, scaling each input channel by its gain, and return out hands it back. Done.

Save it into your Resolve LUT/DCTL folder, restart Resolve (new DCTLs are only compiled at startup; edits to an existing file reload live), drop a DCTL OFX onto a node, choose the file, and you have four working sliders.

The math you’ll keep meeting. Three operations cover most simple DCTLs. Gain (multiply — what we just did) scales light; it’s exposure and white balance. Offset / power shape tone — a _powf(x, gamma) is a gamma curve, and log-to-linear conversions are power and log functions of exactly this kind (the “log curve math” behind a camera’s LogC → linear). And a matrix — a 3×3 grid of numbers — mixes channels: each output channel is a weighted sum of the three inputs. That’s what the RGB mixer does, what a gamut conversion is under the hood, and what the AMA discussion means by “1D vs 3D operations”: a curve touches each channel alone (1D), a matrix or 3D LUT lets channels influence each other (3D). Hold gain, curve and matrix and you can read the guts of most transforms.

  1. Type the do-nothing boilerplate, save it to your DCTL folder, restart Resolve, and confirm it changes nothing. That’s your baseline.
  2. Add the exposure slider and _powf(2.0f, exposure) gain. Prove it against Resolve’s own controls: set it to +1 stop, wipe it against a Gain node, and check they match on the waveform.
  3. Add the three printer-light sliders. Warm the image with Red print up / Blue print down and watch the parade — you’ve built a printer-light balance from scratch.
  4. Break it on purpose: delete a semicolon, then read Resolve’s r_debug.log to find the error. Learning to read the log is the real skill.
Level 3 workbook — every Do it exercise, 3.1–3.10, plus the capstone (printable)level-3-workbook.pdf764 KBOriginal course material — free to use

Check yourself

  1. What does a DCTL operate on, and how often does its transform() run?

  2. In the worked exposure DCTL, why is the gain computed as _powf(2.0f, stops)?

  3. What is a 3×3 color matrix used for in a DCTL?

  4. Why does a DCTL need the paid Studio version, and why must you restart Resolve after adding a new one?

You can move on when you can… read a simple DCTL and say what each line does, write an exposure or per-channel-gain transform yourself, explain why one stop is _powf(2, n), and say what a 3×3 matrix does that a curve can’t.

Cullen Kelly — AMA, DCTL & color-math segments: a gamut-compression DCTL compared against a sat-vs-sat curve, then 1D-vs-3D operations demonstrated live through the RGB mixer / matrix — the exact “curve touches one channel, matrix mixes all three” distinction, shown on real footage.

Segment: 20:50–39:20 — gamut-compression DCTL vs sat curve, then 1D vs 3D ops via the matrixwatch full video

Watch for: Why a gamut-compression operation wants a real transform, not a curve, and the live demo of a channel matrix mixing R, G and B — the 3×3 math from the explainer, in action.

Segment: 58:30–1:13:23 — gamut excursions in wide working spaces + node blend-mode trickswatch full video

Watch for: Why even a wide working space still produces out-of-gamut colors — the case for a compression DCTL — plus composite/blend-mode tricks for color-only and luminosity-only operations.
  • DaVinci Resolve DCTL developer docs (bundled with Resolve Studio, in the Developer/DCTL folder, and mirrored across the community): the authoritative reference for DEFINE_UI_PARAMS, the built-in _powf / _logf / matrix helpers, and the transform() signature.
  • Juan Melara (juanmelara.com.au) and the LiftGammaGain / community DCTL threads: worked DCTLs (log conversions, print emulations) you can read and modify — the best way to learn is to open someone else’s.

The paid path: structured DCTL-and-color-math teaching lives in Mixing Light’s developer tutorials and colour.training’s advanced modules. This page gets you reading and editing; those take you to writing transforms from a spec.

Next up: 3.5 · Print film emulation — construction — building a film look as a chain, with printer lights done properly in log density.