DaVinci Resolve DCTL and OFX Plugins

Thanks @Paul_Dore. I think I’ll stick with the DCTLs then. Cheers!

For the 1D shaper LUT there is now the option of a custom expression. An expression library has been added to the plugin, thereby increasing its size somewhat (the library is over twice the size of even the complete ACES library), but it doesn’t affect the main image processing.

A precision parameter has also been added, which controls the number of decimal points per value in the LUT. Resolve seems to have issues with certain input parameters when values exceed 6 decimal points (in macOS, Win, and Linux), whereas in other applications this isn’t the case.

ACES 1.1 OFX Plugin beta

ACES 1.1 OFX Source beta

Hi to all,

Still having some problems with the most recent source files that @Paul_Dore published. All the old DCTL I created don’t link when I replace the ACES_LIB content with either _CUDA or _CPU.

I’m not super techy and maybe I’m just not going about this the right way… Am I even supposed to replace these or should I just keep the old older version of the ACES_LIB folder I have that still “connects” properly to the DCTLs?

Thanks!

The ACES_LIB_CUDA and ACES_LIB_CPU folders are only for compiling the OFX Plugin, and shouldn’t be used with the DCTLs. The ACES_LIB folder that is part of the ACES_DCTL set is the correct option.

Thanks @Paul_Dore. I learn as I go. :blush:

This is huge Paul! For some reason I’m having trouble getting the OFX plugins to show up on a non-Cuda machine (2018 iMac Pro). Any tips?

If you’re referring specifically to the ACES OFX Plugin, it should still show up but only run on the CPU. If it’s not showing up, then try installing the Natron version of the plugin to see if that works.

Sorry, yes, was referring specifically to the ACES OFX plugin. Natron version works! Though as you’ve said, CPU is very slow. Any plans for a Metal-compatible version? I’d ask about OpenCL, but sounds like that’s more to do with Apple’s outdated implementation that isn’t likely to change. Thanks again for making cool stuff!

That all sounds great, but the plugin doesn’t show up here in Resolve Version 15.2

Is there something I am missing? I’ve placed the .ofx file in the plugins-folder?
Thats obviously wrong :slight_smile:

Thanks for any help!

Peter

No one any idea?

Thanks!

Peter

What operating system are you on, and what’s your GPU?

We are on Windows 10 Professional 64bit and intel core i9-7940X,
with 11GB GTX1080Ti

Thanks!

Peter

I don’t know why there would be a problem if you placed the plugin in the usual plugin folder. It runs on CUDA or CPU so should work. It’s the full plugin (i.e. ACES.ofx.bundle) not just the ACES.ofx file, otherwise it wouldn’t show up.

I have a folder called: ACES.ofx.bundle
In that is another folder called “Contents”
That contains 4 further folders.

I put that whole ACES.ofx.bundle folder into: C:\Program Files\Blackmagic Design\DaVinci Resolve\Plugins

But still I cant see it listed with the Effects.

Thanks for your help!

Peter

I found it … it’s not the usual Plugins folder.
It has to be created manually for OFX.

Thanks for your help!

Peter

I ported Analytic LMT - Example 3 to a standalone DCTL. The ACES DCTL set is not required to run it, and it should work with CUDA and OpenCL (or Metal on macOS). It can be applied as a standard DCTL, or selected form the drop-down menu in Resolve’s DCTL OFX Plugin (which enables the adjustable parameters).

LMT Print Film Emulation OFX DCTL

DEFINE_UI_PARAMS(SCALEC, Scale Color, DCTLUI_SLIDER_FLOAT, 0.7, 0, 2, 0.001)
DEFINE_UI_PARAMS(SLOPEGLOBAL, Slope, DCTLUI_SLIDER_FLOAT, 1, 0, 2, 0.001)
DEFINE_UI_PARAMS(SLOPER, Slope R, DCTLUI_SLIDER_FLOAT, 1, 0, 2, 0.001)
DEFINE_UI_PARAMS(SLOPEG, Slope G, DCTLUI_SLIDER_FLOAT, 1, 0, 2, 0.001)
DEFINE_UI_PARAMS(SLOPEB, Slope B, DCTLUI_SLIDER_FLOAT, 0.4, 0, 2, 0.001)
DEFINE_UI_PARAMS(OFFSETGLOBAL, Offset, DCTLUI_SLIDER_FLOAT, 0, -1, 1, 0.001)
DEFINE_UI_PARAMS(OFFSETR, Offset R, DCTLUI_SLIDER_FLOAT, 0, -1, 1, 0.001)
DEFINE_UI_PARAMS(OFFSETG, Offset G, DCTLUI_SLIDER_FLOAT, 0, -1, 1, 0.001)
DEFINE_UI_PARAMS(OFFSETB, Offset B, DCTLUI_SLIDER_FLOAT, 0.2, -1, 1, 0.001)
DEFINE_UI_PARAMS(POWERGLOBAL, Power, DCTLUI_SLIDER_FLOAT, 1, 0, 2, 0.001)
DEFINE_UI_PARAMS(POWERR, Power R, DCTLUI_SLIDER_FLOAT, 1, 0, 2, 0.001)
DEFINE_UI_PARAMS(POWERG, Power G, DCTLUI_SLIDER_FLOAT, 1, 0, 2, 0.001)
DEFINE_UI_PARAMS(POWERB, Power B, DCTLUI_SLIDER_FLOAT, 1, 0, 2, 0.001)
DEFINE_UI_PARAMS(SATT, Sat, DCTLUI_SLIDER_FLOAT, 1, 0, 2, 0.001)
DEFINE_UI_PARAMS(GAMMA1, Contrast, DCTLUI_SLIDER_FLOAT, 1.5, 0, 3, 0.001)
DEFINE_UI_PARAMS(GAMMA2, Pivot, DCTLUI_SLIDER_FLOAT, 0.18, 0, 1, 0.001)
DEFINE_UI_PARAMS(ROTATEH11, Hue1, DCTLUI_SLIDER_FLOAT, 0, 0, 360, 0.001)
DEFINE_UI_PARAMS(ROTATEH12, Range1, DCTLUI_SLIDER_FLOAT, 30, 0, 360, 0.001)
DEFINE_UI_PARAMS(ROTATEH13, Rotate1, DCTLUI_SLIDER_FLOAT, 5, -360, 360, 0.001)
DEFINE_UI_PARAMS(ROTATEH21, Hue2, DCTLUI_SLIDER_FLOAT, 80, 0, 360, 0.001)
DEFINE_UI_PARAMS(ROTATEH22, Range2, DCTLUI_SLIDER_FLOAT, 60, 0, 360, 0.001)
DEFINE_UI_PARAMS(ROTATEH23, Rotate2, DCTLUI_SLIDER_FLOAT, -15, -360, 360, 0.001)
DEFINE_UI_PARAMS(ROTATEH31, Hue3, DCTLUI_SLIDER_FLOAT, 52, 0, 360, 0.001)
DEFINE_UI_PARAMS(ROTATEH32, Range3, DCTLUI_SLIDER_FLOAT, 50, 0, 360, 0.001)
DEFINE_UI_PARAMS(ROTATEH33, Rotate3, DCTLUI_SLIDER_FLOAT, -14, -360, 360, 0.001)
DEFINE_UI_PARAMS(SCALECH11, Scale Color Hue1, DCTLUI_SLIDER_FLOAT, 45, 0, 360, 0.001)
DEFINE_UI_PARAMS(SCALECH12, Scale Color Range1, DCTLUI_SLIDER_FLOAT, 40, 0, 360, 0.001)
DEFINE_UI_PARAMS(SCALECH13, Scale Color1, DCTLUI_SLIDER_FLOAT, 1.4, 0, 3, 0.001)
DEFINE_UI_PARAMS(ROTATEH41, Hue4, DCTLUI_SLIDER_FLOAT, 190, 0, 360, 0.001)
DEFINE_UI_PARAMS(ROTATEH42, Range4, DCTLUI_SLIDER_FLOAT, 40, 0, 360, 0.001)
DEFINE_UI_PARAMS(ROTATEH43, Rotate4, DCTLUI_SLIDER_FLOAT, 30, -360, 360, 0.001)
DEFINE_UI_PARAMS(SCALECH21, Scale Color Hue2, DCTLUI_SLIDER_FLOAT, 240, 0, 360, 0.001)
DEFINE_UI_PARAMS(SCALECH22, Scale Color Range2, DCTLUI_SLIDER_FLOAT, 120, 0, 360, 0.001)
DEFINE_UI_PARAMS(SCALECH23, Scale Color2, DCTLUI_SLIDER_FLOAT, 1.4, 0, 3, 0.001)
DEFINE_UI_PARAMS(ACESCCT, ACEScct, DCTLUI_CHECK_BOX, 1)

typedef struct
{
float2 red;
float2 green;
float2 blue;
float2 white;
} Chromaticities;

typedef struct
{
float3 c0, c1, c2;
} mat3;

typedef struct
{
float4 c0, c1, c2, c3;
} mat4;

CONSTANT float X_BRK = 0.0078125f;
CONSTANT float Y_BRK = 0.155251141552511f;
CONSTANT float A = 10.5402377416545f;
CONSTANT float B = 0.0729055341958355f;
CONSTANT float sqrt3over4 = 0.433012701892219f;
CONSTANT Chromaticities AP0 = { {0.7347f, 0.2653f}, {0.0f, 1.0f}, {0.0001f, -0.077f}, {0.32168f, 0.33767f} };
CONSTANT Chromaticities AP1 = { {0.713f, 0.293f}, {0.165f, 0.83f}, {0.128f, 0.044f}, {0.32168f, 0.33767f} };
CONSTANT Chromaticities REC709_PRI = { {0.64f, 0.33f}, {0.3f, 0.6f}, {0.15f, 0.06f}, {0.3127f, 0.329f} };

#define pi 3.14159265358979323846264338327950288f
#define HALF_MAX 65504.0f
#define AP0_2_XYZ_MAT RGBtoXYZ_m4( AP0, 1.0f)
#define XYZ_2_AP0_MAT XYZtoRGB_m4( AP0, 1.0f)
#define AP1_2_XYZ_MAT RGBtoXYZ_m4( AP1, 1.0f)
#define XYZ_2_AP1_MAT XYZtoRGB_m4( AP1, 1.0f)
#define AP0_2_AP1_MAT mult_f44_f44( AP0_2_XYZ_MAT, XYZ_2_AP1_MAT)
#define AP1_2_AP0_MAT mult_f44_f44( AP1_2_XYZ_MAT, XYZ_2_AP0_MAT)
#define AP1_RGB2Y make_float3(AP1_2_XYZ_MAT.c0.y, AP1_2_XYZ_MAT.c1.y, AP1_2_XYZ_MAT.c2.y)
#define RGB_2_YAB_MAT make_mat3(make_float3(1.0f/3.0f, 1.0f/2.0f, 0.0f), make_float3(1.0f/3.0f, -1.0f/4.0f, sqrt3over4), make_float3(1.0f/3.0f, -1.0f/4.0f, -sqrt3over4))
#define REC709_2_XYZ_MAT RGBtoXYZ( REC709_PRI, 1.0f)
#define REC709_RGB2Y float3(REC709_2_XYZ_MAT.c0.y, REC709_2_XYZ_MAT.c1.y, REC709_2_XYZ_MAT.c2.y)

DEVICE Chromaticities make_chromaticities(float2 A, float2 B, float2 C, float2 D);
DEVICE mat3 make_mat3(float3 A, float3 B, float3 C);
DEVICE float3 overlay_f3(float3 a, float3 b);
DEVICE float3 scale_C(float3 rgb, float percentC);
DEVICE float3 rotate_H_in_H(float3 rgb, float centerH, float widthH, float degreesShift);
DEVICE float3 ych_2_rgb(float3 ych);
DEVICE float3 rgb_2_ych(float3 rgb);
DEVICE float3 ych_2_yab(float3 ych );
DEVICE float3 yab_2_ych(float3 yab);
DEVICE float3 yab_2_rgb(float3 yab);
DEVICE float3 rgb_2_yab(float3 rgb);
DEVICE float3 sat_adjust(float3 rgbIn, float SAT_FACTOR, float3 RGB2Y);
DEVICE float3 gamma_adjust_linear(float3 rgbIn, float GAMMA, float PIVOT);
DEVICE float3 ASCCDL_inACEScct(float3 acesIn, float3 SLOPE, float3 OFFSET, float3 POWER, float SAT);
DEVICE float3 ACEScct_to_ACES(float3 in);
DEVICE float3 ACES_to_ACEScct(float3 in);
DEVICE float ACEScct_to_lin(float in);
DEVICE float lin_to_ACEScct(float in);
DEVICE mat4 XYZtoRGB_m4(Chromaticities N, float W);
DEVICE mat3 XYZtoRGB( Chromaticities N);
DEVICE mat4 RGBtoXYZ_m4(Chromaticities N, float W);
DEVICE mat3 RGBtoXYZ(Chromaticities N);
DEVICE float interpolate1D (float2 table[], int Size, float p);
DEVICE float uncenter_hue(float hueCentered, float centerH);
DEVICE mat4 make_mat4(float4 A, float4 B, float4 C, float4 D);
DEVICE float4 make_float4_f3_f(float3 A, float B);
DEVICE mat4 make_mat4_f33_f(mat3 A, float B);
DEVICE float3 mult_f3_f33 (float3 X, mat3 A);
DEVICE mat3 mult_f33_f33 (mat3 A, mat3 B);
DEVICE float3 mult_f3_f44 (float3 X, mat4 A);
DEVICE mat3 mult_f_f33 (float f, mat3 A);
DEVICE mat4 mult_f44_f44 (mat4 A, mat4 B);
DEVICE mat3 invert_f33 (mat3 A);
DEVICE mat3 transpose_f33(mat3 A);
DEVICE mat3 calc_sat_adjust_matrix(float sat, float3 rgb2Y);
DEVICE float center_hue(float hue, float centerH);
DEVICE float cubic_basis_shaper(float x, float w);
DEVICE float3 scale_C_at_H(float3 rgb, float centerH, float widthH, float percentC);
DEVICE float3 clamp_f3(float3 in, float clampMin, float clampMax);
DEVICE float3 ACES_to_ACEScc(float3 ACES);
DEVICE float lin_to_ACEScc(float in);
DEVICE float ACEScc_to_lin(float in);
DEVICE float3 ACEScc_to_ACES(float3 ACEScc);

DEVICE Chromaticities make_chromaticities(float2 A, float2 B, float2 C, float2 D)
{
Chromaticities E;
E.red = A;
E.green = B;
E.blue = C;
E.white = D;
return E;
}

DEVICE float3 clamp_f3( float3 in, float clampMin, float clampMax)
{
float3 out;
out.x = _clampf( in.x, clampMin, clampMax);
out.y = _clampf( in.y, clampMin, clampMax);
out.z = _clampf( in.z, clampMin, clampMax);
return out;
}

DEVICE mat3 make_mat3(float3 A, float3 B, float3 C)
{
mat3 D;
D.c0 = A;
D.c1 = B;
D.c2 = C;
return D;
}

DEVICE mat4 make_mat4(float4 A, float4 B, float4 C, float4 D)
{
mat4 E;
E.c0 = A;
E.c1 = B;
E.c2 = C;
E.c3 = D;
return E;
}

DEVICE float4 make_float4_f3_f( float3 A, float B)
{
return make_float4(A.x, A.y, A.z, B);
}

DEVICE mat4 make_mat4_f33_f(mat3 A, float B)
{
mat4 C;
C.c0 = make_float4_f3_f(A.c0, 0.0f);
C.c1 = make_float4_f3_f(A.c1, 0.0f);
C.c2 = make_float4_f3_f(A.c2, 0.0f);
C.c3 = make_float4(0.0f, 0.0f, 0.0f, B);
return C;
}

DEVICE float3 mult_f3_f33 ( float3 X, mat3 A)
{
float r[3];
float x[3] = {X.x, X.y, X.z};
float a[3][3] = {{A.c0.x, A.c0.y, A.c0.z}, {A.c1.x, A.c1.y, A.c1.z}, {A.c2.x, A.c2.y, A.c2.z}};
for( int i = 0; i < 3; ++i)
{
r[i] = 0.0f;
for( int j = 0; j < 3; ++j)
{
r[i] = r[i] + x[j] * a[j][i];
}
}
return make_float3(r[0], r[1], r[2]);
}

DEVICE mat3 mult_f33_f33( mat3 A, mat3 B)
{
float r[3][3];
float a[3][3] = {{A.c0.x, A.c0.y, A.c0.z}, {A.c1.x, A.c1.y, A.c1.z}, {A.c2.x, A.c2.y, A.c2.z}};
float b[3][3] = {{B.c0.x, B.c0.y, B.c0.z}, {B.c1.x, B.c1.y, B.c1.z}, {B.c2.x, B.c2.y, B.c2.z}};
for( int i = 0; i < 3; ++i)
{
for( int j = 0; j < 3; ++j)
{
r[i][j] = 0.0f;
for( int k = 0; k < 3; ++k)
{
r[i][j] = r[i][j] + a[i][k] * b[k][j];
}
}
}
mat3 R = make_mat3(make_float3(r[0][0], r[0][1], r[0][2]),
make_float3(r[1][0], r[1][1], r[1][2]), make_float3(r[2][0], r[2][1], r[2][2]));
return R;
}

DEVICE float3 mult_f3_f44( float3 X, mat4 A)
{
float r[3];
float x[3] = {X.x, X.y, X.z};
float a[4][4] = {{A.c0.x, A.c0.y, A.c0.z, A.c0.w},
{A.c1.x, A.c1.y, A.c1.z, A.c1.w},
{A.c2.x, A.c2.y, A.c2.z, A.c2.w},
{A.c3.x, A.c3.y, A.c3.z, A.c3.w}};
for( int i = 0; i < 3; ++i)
{
r[i] = 0.0f;
for( int j = 0; j < 3; ++j)
{
r[i] = r[i] + x[j] * a[j][i];
}
r[i] = r[i] + a[3][i];
}
float s = 1.0f / (x[0] * a[0][3] + x[1] * a[1][3] + x[2] * a[2][3] + a[3][3]);
for( int k = 0; k < 3; ++k)
{
r[k] = r[k] * s;
}
return make_float3(r[0], r[1], r[2]);
}

DEVICE mat3 mult_f_f33( float f, mat3 A)
{
float r[3][3];
float a[3][3] = {{A.c0.x, A.c0.y, A.c0.z}, {A.c1.x, A.c1.y, A.c1.z}, {A.c2.x, A.c2.y, A.c2.z}};
for( int i = 0; i < 3; ++i )
{
for( int j = 0; j < 3; ++j )
{
r[i][j] = f * a[i][j];
}
}
mat3 R = make_mat3(make_float3(r[0][0], r[0][1], r[0][2]),
make_float3(r[1][0], r[1][1], r[1][2]), make_float3(r[2][0], r[2][1], r[2][2]));
return R;
}

DEVICE mat4 mult_f44_f44( mat4 A, mat4 B)
{
float r[4][4];
float a[4][4] = {{A.c0.x, A.c0.y, A.c0.z, A.c0.w},
{A.c1.x, A.c1.y, A.c1.z, A.c1.w},
{A.c2.x, A.c2.y, A.c2.z, A.c2.w},
{A.c3.x, A.c3.y, A.c3.z, A.c3.w}};
float b[4][4] = {{B.c0.x, B.c0.y, B.c0.z, B.c0.w},
{B.c1.x, B.c1.y, B.c1.z, B.c1.w},
{B.c2.x, B.c2.y, B.c2.z, B.c2.w},
{B.c3.x, B.c3.y, B.c3.z, B.c3.w}};
for( int i = 0; i < 4; ++i)
{
for( int j = 0; j < 4; ++j)
{
r[i][j] = 0.0f;
for( int k = 0; k < 4; ++k)
{
r[i][j] = r[i][j] + a[i][k] * b[k][j];
}
}
}
mat4 R = make_mat4(make_float4(r[0][0], r[0][1], r[0][2], r[0][3]),
make_float4(r[1][0], r[1][1], r[1][2], r[1][3]), make_float4(r[2][0], r[2][1], r[2][2], r[2][3]),
make_float4(r[3][0], r[3][1], r[3][2], r[3][3]));
return R;
}

DEVICE mat3 invert_f33( mat3 A)
{
mat3 R;
float result[3][3];
float a[3][3] = {{A.c0.x, A.c0.y, A.c0.z}, {A.c1.x, A.c1.y, A.c1.z}, {A.c2.x, A.c2.y, A.c2.z}};
float det = a[0][0] * a[1][1] * a[2][2]

  • a[0][1] * a[1][2] * a[2][0]
  • a[0][2] * a[1][0] * a[2][1]
  • a[2][0] * a[1][1] * a[0][2]
  • a[2][1] * a[1][2] * a[0][0]
  • a[2][2] * a[1][0] * a[0][1];
    if( det != 0.0f )
    {
    result[0][0] = a[1][1] * a[2][2] - a[1][2] * a[2][1];
    result[0][1] = a[2][1] * a[0][2] - a[2][2] * a[0][1];
    result[0][2] = a[0][1] * a[1][2] - a[0][2] * a[1][1];
    result[1][0] = a[2][0] * a[1][2] - a[1][0] * a[2][2];
    result[1][1] = a[0][0] * a[2][2] - a[2][0] * a[0][2];
    result[1][2] = a[1][0] * a[0][2] - a[0][0] * a[1][2];
    result[2][0] = a[1][0] * a[2][1] - a[2][0] * a[1][1];
    result[2][1] = a[2][0] * a[0][1] - a[0][0] * a[2][1];
    result[2][2] = a[0][0] * a[1][1] - a[1][0] * a[0][1];
    R = make_mat3(make_float3(result[0][0], result[0][1], result[0][2]),
    make_float3(result[1][0], result[1][1], result[1][2]), make_float3(result[2][0], result[2][1], result[2][2]));
    return mult_f_f33( 1.0f / det, R);
    }
    R = make_mat3(make_float3(1.0f, 0.0f, 0.0f),
    make_float3(0.0f, 1.0f, 0.0f), make_float3(0.0f, 0.0f, 1.0f));
    return R;
    }

DEVICE mat3 transpose_f33( mat3 A)
{
mat3 B;
B.c0 = make_float3(A.c0.x, A.c1.x, A.c2.x);
B.c1 = make_float3(A.c0.y, A.c1.y, A.c2.y);
B.c2 = make_float3(A.c0.z, A.c1.z, A.c2.z);
return B;
}

DEVICE mat3 calc_sat_adjust_matrix( float sat, float3 rgb2Y)
{
float M[3][3];
M[0][0] = (1.0f - sat) * rgb2Y.x + sat;
M[1][0] = (1.0f - sat) * rgb2Y.x;
M[2][0] = (1.0f - sat) * rgb2Y.x;
M[0][1] = (1.0f - sat) * rgb2Y.y;
M[1][1] = (1.0f - sat) * rgb2Y.y + sat;
M[2][1] = (1.0f - sat) * rgb2Y.y;
M[0][2] = (1.0f - sat) * rgb2Y.z;
M[1][2] = (1.0f - sat) * rgb2Y.z;
M[2][2] = (1.0f - sat) * rgb2Y.z + sat;
mat3 R = make_mat3(make_float3(M[0][0], M[0][1], M[0][2]),
make_float3(M[1][0], M[1][1], M[1][2]), make_float3(M[2][0], M[2][1], M[2][2]));
R = transpose_f33®;
return R;
}

DEVICE float cubic_basis_shaper( float x, float w)
{
float M[4][4] = { {-1.0f/6.0f, 3.0f/6.0f, -3.0f/6.0f, 1.0f/6.0f}, {3.0f/6.0f, -1.0f, 3.0f/6.0f, 0.0f},
{-3.0f/6.0f, 0.0f, 3.0f/6.0f, 0.0f}, {1.0f/6.0f, 4.0f/6.0f, 1.0f/6.0f, 0.0f} };
float knots[5] = {-w/2.0f, -w/4.0f, 0.0f, w/4.0f, w/2.0f};
float y = 0.0f;
if ((x > knots[0]) && (x < knots[4])) {
float knot_coord = (x - knots[0]) * 4.0f/w;
int j = knot_coord;
float t = knot_coord - j;
float monomials[4] = { t * t * t, t * t, t, 1.0f };
if ( j == 3) {
y = monomials[0] * M[0][0] + monomials[1] * M[1][0] + monomials[2] * M[2][0] + monomials[3] * M[3][0];
} else if ( j == 2) {
y = monomials[0] * M[0][1] + monomials[1] * M[1][1] + monomials[2] * M[2][1] + monomials[3] * M[3][1];
} else if ( j == 1) {
y = monomials[0] * M[0][2] + monomials[1] * M[1][2] + monomials[2] * M[2][2] + monomials[3] * M[3][2];
} else if ( j == 0) {
y = monomials[0] * M[0][3] + monomials[1] * M[1][3] + monomials[2] * M[2][3] + monomials[3] * M[3][3];
} else {
y = 0.0f;
}
}
return y * 3.0f/2.0f;
}

DEVICE float center_hue( float hue, float centerH)
{
float hueCentered = hue - centerH;
if (hueCentered < -180.0f) hueCentered = hueCentered + 360.0f;
else if (hueCentered > 180.0f) hueCentered = hueCentered - 360.0f;
return hueCentered;
}

DEVICE float uncenter_hue( float hueCentered, float centerH)
{
float hue = hueCentered + centerH;
if (hue < 0.0f) hue = hue + 360.0f;
else if (hue > 360.0f) hue = hue - 360.0f;
return hue;
}

DEVICE float interpolate1D (float2 table[], int Size, float p)
{
if( p <= table[0].x ) return table[0].y;
if( p >= table[Size-1].x ) return table[Size-1].y;
for( int i = 0; i < Size - 1; ++i )
{
if( table[i].x <= p && p < table[i+1].x )
{
float s = (p - table[i].x) / (table[i+1].x - table[i].x);
return table[i].y * ( 1 - s ) + table[i+1].y * s;
}
}
return 0.0f;
}

DEVICE mat3 RGBtoXYZ( Chromaticities N)
{
mat3 M = make_mat3(make_float3(N.red.x, N.red.y, 1.0f - (N.red.x + N.red.y)),
make_float3(N.green.x, N.green.y, 1.0f - (N.green.x + N.green.y)),
make_float3(N.blue.x, N.blue.y, 1.0f - (N.blue.x + N.blue.y)));
float3 wh = make_float3(N.white.x / N.white.y, 1.0f, (1.0f - (N.white.x + N.white.y)) / N.white.y);
wh = mult_f3_f33(wh, invert_f33(M));
mat3 WH = make_mat3(make_float3(wh.x, 0.0f, 0.0f),
make_float3(0.0f, wh.y, 0.0f), make_float3(0.0f, 0.0f, wh.z));
M = mult_f33_f33(WH, M);
return M;
}

DEVICE mat4 RGBtoXYZ_m4( Chromaticities N, float W)
{
mat3 A = RGBtoXYZ(N);
mat4 M = make_mat4_f33_f(A, W);
return M;
}

DEVICE mat3 XYZtoRGB( Chromaticities N)
{
mat3 M = invert_f33(RGBtoXYZ(N));
return M;
}

DEVICE mat4 XYZtoRGB_m4( Chromaticities N, float W)
{
mat3 A = XYZtoRGB(N);
mat4 M = make_mat4_f33_f(A, W);
return M;
}

DEVICE float lin_to_ACEScc( float in)
{
if (in <= 0.0f)
return -0.3584474886f;
else if (in < _powf(2.0f, -15.0f))
return (_log2f( _powf(2.0f, -16.0f) + in * 0.5f) + 9.72f) / 17.52f;
else
return (_log2f(in) + 9.72f) / 17.52f;
}

DEVICE float3 ACES_to_ACEScc( float3 ACES)
{
ACES = clamp_f3( ACES, 0.0f, HALF_MAX);
float3 lin_AP1 = mult_f3_f44( ACES, AP0_2_AP1_MAT);
float3 out;
out.x = lin_to_ACEScc(lin_AP1.x);
out.y = lin_to_ACEScc(lin_AP1.y);
out.z = lin_to_ACEScc(lin_AP1.z);
return out;
}

DEVICE float ACEScc_to_lin( float in)
{
if (in < -0.3013698630f)
return (_powf( 2.0f, in * 17.52f - 9.72f) - _powf( 2.0f, -16.0f)) * 2.0f;
else if ( in < (_log2f(HALF_MAX) + 9.72f) / 17.52f )
return _powf( 2.0f, in * 17.52f - 9.72f);
else
return HALF_MAX;
}

DEVICE float3 ACEScc_to_ACES( float3 ACEScc)
{
float3 lin_AP1;
lin_AP1.x = ACEScc_to_lin(ACEScc.x);
lin_AP1.y = ACEScc_to_lin(ACEScc.y);
lin_AP1.z = ACEScc_to_lin(ACEScc.z);
float3 ACES = mult_f3_f44(lin_AP1, AP1_2_AP0_MAT);
return ACES;
}

DEVICE float lin_to_ACEScct( float in)
{
if (in <= X_BRK)
return A * in + B;
else
return (_log2f(in) + 9.72f) / 17.52f;
}

DEVICE float ACEScct_to_lin( float in)
{
if (in > Y_BRK)
return _powf( 2.0f, in * 17.52f - 9.72f);
else
return (in - B) / A;
}

DEVICE float3 ACES_to_ACEScct( float3 in)
{
float3 ap1_lin = mult_f3_f44(in, AP0_2_AP1_MAT);
float3 acescct;
acescct.x = lin_to_ACEScct(ap1_lin.x);
acescct.y = lin_to_ACEScct(ap1_lin.y);
acescct.z = lin_to_ACEScct(ap1_lin.z);
return acescct;
}

DEVICE float3 ACEScct_to_ACES( float3 in)
{
float3 ap1_lin;
ap1_lin.x = ACEScct_to_lin( in.x);
ap1_lin.y = ACEScct_to_lin( in.y);
ap1_lin.z = ACEScct_to_lin( in.z);
return mult_f3_f44( ap1_lin, AP1_2_AP0_MAT);
}

DEVICE float3 ASCCDL_inACEScct( float3 acesIn, float3 SLOPE, float3 OFFSET, float3 POWER, float SAT)
{
float3 acescct = ACES_to_ACEScct(acesIn);
acescct.x = _powf( _clampf( (acescct.x * SLOPE.x) + OFFSET.x, 0.0f, 1.0f), 1.0f / POWER.x);
acescct.y = _powf( _clampf( (acescct.y * SLOPE.y) + OFFSET.y, 0.0f, 1.0f), 1.0f / POWER.y);
acescct.z = _powf( _clampf( (acescct.z * SLOPE.z) + OFFSET.z, 0.0f, 1.0f), 1.0f / POWER.z);
float luma = 0.2126f * acescct.x + 0.7152f * acescct.y + 0.0722f * acescct.z;
float satClamp = _saturatef(SAT);
acescct.x = luma + satClamp * (acescct.x - luma);
acescct.y = luma + satClamp * (acescct.y - luma);
acescct.z = luma + satClamp * (acescct.z - luma);
return ACEScct_to_ACES( acescct);
}

DEVICE float3 gamma_adjust_linear( float3 rgbIn, float GAMMA, float PIVOT)
{
const float SCALAR = PIVOT / _powf( PIVOT, GAMMA);
float3 rgbOut = rgbIn;
if (rgbIn.x > 0.0f) rgbOut.x = _powf( rgbIn.x, GAMMA) * SCALAR;
if (rgbIn.y > 0.0f) rgbOut.y = _powf( rgbIn.y, GAMMA) * SCALAR;
if (rgbIn.z > 0.0f) rgbOut.z = _powf( rgbIn.z, GAMMA) * SCALAR;
return rgbOut;
}

DEVICE float3 sat_adjust( float3 rgbIn, float SAT_FACTOR, float3 RGB2Y)
{
const mat3 SAT_MAT = calc_sat_adjust_matrix( SAT_FACTOR, RGB2Y);
return mult_f3_f33( rgbIn, SAT_MAT);
}

DEVICE float3 rgb_2_yab( float3 rgb)
{
float3 yab = mult_f3_f33( rgb, RGB_2_YAB_MAT);
return yab;
}

DEVICE float3 yab_2_rgb( float3 yab)
{
float3 rgb = mult_f3_f33( yab, invert_f33(RGB_2_YAB_MAT));
return rgb;
}

DEVICE float3 yab_2_ych(float3 yab)
{
float3 ych = yab;
float yo = yab.y * yab.y + yab.z * yab.z;
ych.y = _sqrtf(yo);
ych.z = _atan2f(yab.z, yab.y) * (180.0f / pi);
if (ych.z < 0.0f) ych.z += 360.0f;
return ych;
}

DEVICE float3 ych_2_yab( float3 ych )
{
float3 yab;
yab.x = ych.x;
float h = ych.z * (pi / 180.0f);
yab.y = ych.y * _cosf(h);
yab.z = ych.y * _sinf(h);
return yab;
}

DEVICE float3 rgb_2_ych( float3 rgb)
{
return yab_2_ych(rgb_2_yab(rgb));
}

DEVICE float3 ych_2_rgb( float3 ych)
{
return yab_2_rgb(ych_2_yab(ych));
}

DEVICE float3 scale_C_at_H( float3 rgb, float centerH, float widthH, float percentC)
{
float3 new_rgb = rgb;
float3 ych = rgb_2_ych(rgb);
if (ych.y > 0.0f) {
float centeredHue = center_hue(ych.z, centerH);
float f_H = cubic_basis_shaper(centeredHue, widthH);
if (f_H > 0.0f) {
float3 new_ych = ych;
new_ych.y = ych.y * (f_H * (percentC - 1.0f) + 1.0f);
new_rgb = ych_2_rgb( new_ych);
} else {
new_rgb = rgb;
}
}
return new_rgb;
}

DEVICE float3 rotate_H_in_H( float3 rgb,float centerH,float widthH, float degreesShift)
{
float3 ych = rgb_2_ych( rgb);
float3 new_ych = ych;
float centeredHue = center_hue( ych.z, centerH);
float f_H = cubic_basis_shaper( centeredHue, widthH);
float old_hue = centeredHue;
float new_hue = centeredHue + degreesShift;
float2 table[2] = { {0.0f, old_hue}, {1.0f, new_hue} };
float blended_hue = interpolate1D( table, 2, f_H);
if (f_H > 0.0f) new_ych.z = uncenter_hue(blended_hue, centerH);
return ych_2_rgb( new_ych);
}

DEVICE float3 scale_C( float3 rgb, float percentC)
{
float3 ych = rgb_2_ych(rgb);
ych.y = ych.y * percentC;
return ych_2_rgb(ych);
}

DEVICE float3 overlay_f3( float3 a, float3 b)
{
const float LUMA_CUT = lin_to_ACEScct( 0.5f);
float luma = 0.2126f * a.x + 0.7152f * a.y + 0.0722f * a.z;
float3 out;
if (luma < LUMA_CUT) {
out.x = 2.0f * a.x * b.x;
out.y = 2.0f * a.y * b.y;
out.z = 2.0f * a.z * b.z;
} else {
out.x = 1.0f - (2.0f * (1.0f - a.x) * (1.0f - b.x));
out.y = 1.0f - (2.0f * (1.0f - a.y) * (1.0f - b.y));
out.z = 1.0f - (2.0f * (1.0f - a.z) * (1.0f - b.z));
}
return out;
}

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 aces = make_float3(p_R, p_G, p_B);
aces = ACESCCT ? ACEScct_to_ACES(aces) : ACEScc_to_ACES(aces);
aces = scale_C( aces, SCALEC);
float SlopeR = 1.0f + (SLOPER + SLOPEGLOBAL - 2.0f) / 10.0f;
float SlopeG = 1.0f + (SLOPEG + SLOPEGLOBAL - 2.0f) / 10.0f;
float SlopeB = 1.0f + (SLOPEB + SLOPEGLOBAL - 2.0f) / 10.0f;
float OffsetR = (OFFSETR + OFFSETGLOBAL) / 10.0f;
float OffsetG = (OFFSETG + OFFSETGLOBAL) / 10.0f;
float OffsetB = (OFFSETB + OFFSETGLOBAL) / 10.0f;
float PowerR = 1.0f + (POWERR + POWERGLOBAL - 2.0f) / 10.0f;
float PowerG = 1.0f + (POWERG + POWERGLOBAL - 2.0f) / 10.0f;
float PowerB = 1.0f + (POWERB + POWERGLOBAL - 2.0f) / 10.0f;
float3 SLOPE = make_float3(SlopeR, SlopeG, SlopeB);
float3 OFFSET = make_float3(OffsetR, OffsetG, OffsetB);
float3 POWER = make_float3(PowerR, PowerG, PowerB);
aces = ASCCDL_inACEScct(aces, SLOPE, OFFSET, POWER, SATT);
aces = gamma_adjust_linear(aces, GAMMA1, GAMMA2);
aces = rotate_H_in_H(aces, ROTATEH11, ROTATEH12, ROTATEH13);
aces = rotate_H_in_H(aces, ROTATEH21, ROTATEH22, ROTATEH23);
aces = rotate_H_in_H(aces, ROTATEH31, ROTATEH32, ROTATEH33);
aces = scale_C_at_H(aces, SCALECH11, SCALECH12, SCALECH13);
aces = rotate_H_in_H(aces, ROTATEH41, ROTATEH42, ROTATEH43);
aces = scale_C_at_H(aces, SCALECH21, SCALECH22, SCALECH23);
aces = ACESCCT ? ACES_to_ACEScct(aces) : ACES_to_ACEScc(aces);
return aces;
}

It expects ACEScct input by default, but unticking this parameter enables ACEScc instead.

1 Like

I made a quick video about how to use the LMT PFE OFX DCTL (which now includes exposure and grain controls).

The DCTL is self contained and doesn’t require the ACES DCTL set. It should be used in ACEScct in Resolve, and works with CUDA, Metal (macOS), and OpenCL (Win10).

LMT_PFE_OFX.dctl (24.5 KB)
LMT_PFE_OFX_neutral.dctl (24.5 KB)

1 Like

Nice work Paul!

Can I ask, what is the reason for line 560?

float satClamp = _saturatef(SAT);

It means you have a CDL Saturation slider which goes up to 2.0, but has no effect above 1.0.

Thanks Nick, that’s an error on my part. I think I carried it over from the earlier conversions.

The original definition in CTL is

float satClamp = clamp( SAT, 0., HALF_POS_INF);

Therefore I could define HALF_POS_INF at the top of the DCTL

#define HALF_POS_INF 31744.0f

Then update the relevant line

float satClamp = _clampf(SAT, 0.0f, HALF_POS_INF);

I’m not sure that line is even altogether necessary, so perhaps just commenting it out would be a better option

// float satClamp = _clampf(SAT, 0.0f, HALF_POS_INF);

Either way, at least now the slider will be able to work properly. Thanks again for the keen eye.

I don’t think it is necessary, as the value is that of saturation, coming from a 0-2 slider. It wouldn’t ever get near infinity. Even if you type a value in, it clamps to 2.0.

Also 31744.0f != +INF
31744 is the 16-bit integer which is used to code +inf in half-float. 31744.0f is just a float, with no special significance.

Obviously if you comment it out, you need to change satClamp in the subsequent lines to simply SAT.