Skip to content

Commit

Permalink
more progress on FIXED_FUNCTION_PQ_TO_LINEAR
Browse files Browse the repository at this point in the history
- Python: Added docs
- Python: added the builtin function style
- Simplified the CPU code, it's still a prototype anyhow. Will optimize later.
- Style->OpData now takes the direction into account.
- Preliminary GPU implementation. This still clamps negative values.
- Added GPU tests.

Signed-off-by: cuneyt.ozdas <[email protected]>
  • Loading branch information
cozdas committed Jul 26, 2024
1 parent 1754818 commit 33cdc83
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 88 deletions.
7 changes: 7 additions & 0 deletions docs/api/python/frozen/pyopencolorio_fixedfunctionstyle.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@

FIXED_FUNCTION_ACES_GAMUT_COMP_13 : ACES 1.3 Parametric Gamut Compression (expects ACEScg values)

FIXED_FUNCTION_PQ_TO_LINEAR : SMPTE ST 2084:2014 EOTF Linearization Equation

.. py:method:: name() -> str
:property:

Expand Down Expand Up @@ -104,6 +106,11 @@
:value: <FixedFunctionStyle.FIXED_FUNCTION_XYZ_TO_xyY: 7>


.. py:attribute:: FixedFunctionStyle.FIXED_FUNCTION_PQ_TO_LINEAR
:module: PyOpenColorIO
:value: <FixedFunctionStyle.FIXED_FUNCTION_PQ_TO_LINEAR: 13>


.. py:property:: FixedFunctionStyle.value
:module: PyOpenColorIO

111 changes: 26 additions & 85 deletions src/OpenColorIO/ops/fixedfunction/FixedFunctionOpCPU.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1210,60 +1210,32 @@ Renderer_PQ_TO_LINEAR::Renderer_PQ_TO_LINEAR(ConstFixedFunctionOpDataRcPtr & /*d

void Renderer_PQ_TO_LINEAR::apply(const void *inImg, void *outImg, long numPixels) const
{
// TODO optimize
/// TODO: This is a short, proof-of-concept implementation, needs optimization.

using namespace ST_2084;
const float *in = (const float *)inImg;
float *out = (float *)outImg;

for (long idx = 0; idx < numPixels; ++idx, out += 4, in += 4)
for (long idx = 0; idx < numPixels; ++idx)
{
// (0..1) values will be pass through
// output values are scaled by 100 to convert nits/10,000 into nits/100

// R
{
float r = in[0];
if ((r <= 0.0f) || (r >= 1.0f))
{
out[0] = r * 100.0f;
}
else
{
const float x = std::pow(r, 1.f / m2);
out[0] = 100.0f * std::pow(std::max(0.f, x - c1) / (c2 - c3 * x), 1.f / m1);
};
}

// G
{
float g = in[1];
if ((g <= 0.0f) || (g >= 1.0f))
{
out[1] = g * 100.0f;
}
else
{
const float x = std::pow(g, 1.f / m2);
out[1] = 100.0f * std::pow(std::max(0.f, x - c1) / (c2 - c3 * x), 1.f / m1);
};
}

// B
// RGB
for (int ch = 0; ch < 3; ++ch)
{
float b = in[2];
if ((b <= 0.0f) || (b >= 1.0f))
float v = *(in++);
if ((v <= 0.0f) /*|| (v >= 1.0f)*/)
{
out[2] = b * 100.0f;
//*(out++) = v * 100.0f;
*(out++) = 0.0f;
}
else
{
const float x = std::pow(b, 1.f / m2);
out[2] = 100.0f * std::pow(std::max(0.f, x - c1) / (c2 - c3 * x), 1.f / m1);
const float x = std::pow(v, 1.f / m2);
*(out++) = 100.0f * std::pow(std::max(0.f, x - c1) / (c2 - c3 * x), 1.f / m1);
};
}

// A
out[3] = in[3];
// Alpha
*(out++) = *(in++);
}
}

Expand All @@ -1279,63 +1251,32 @@ void Renderer_LINEAR_TO_PQ::apply(const void *inImg, void *outImg, long numPixel
const float* in = (const float*)inImg;
float* out = (float*)outImg;

// Input is in nits/100, convert to [0,1], where 1 is 10000 nits.
// TODO: This is a short, proof of concept implementation, needs optimization.

for (long idx = 0; idx < numPixels; ++idx, out += 4, in += 4)
// Input is in nits/100, convert to [0,1], where 1 is 10000 nits.
for (long idx = 0; idx < numPixels; ++idx)
{
// R
{
float r = 0.01f * in[0];
if (r < 0.0f || r > 1.0f)
{
out[0] = r;
}
else
{
const float L = std::max(0.0f, r);
const float y = std::pow(L, m1);
const float ratpoly = (c1 + c2 * y) / (1.f + c3 * y);
const float N = std::pow(std::max(0.f, ratpoly), m2);
out[0] = N;
}
}

// G
{
float g = 0.01f * in[1];
if (g < 0.0f || g > 1.0f)
{
out[1] = g;
}
else
{
const float L = std::max(0.0f, g);
const float y = std::pow(L, m1);
const float ratpoly = (c1 + c2 * y) / (1.f + c3 * y);
const float N = std::pow(std::max(0.f, ratpoly), m2);
out[1] = N;
}
}

// B
// RGB
for(int ch = 0; ch < 3; ++ch)
{
float b = 0.01f * in[2];
if (b < 0.0f || b > 1.0f)
float v = *(in++) * 0.01f;
if (v < 0.0f /*|| v > 1.0f*/)
{
out[2] = b;
//*(out++) = v;
*(out++) = 0.0f;
}
else
{
const float L = std::max(0.0f, b);
const float L = std::max(0.0f, v);
const float y = std::pow(L, m1);
const float ratpoly = (c1 + c2 * y) / (1.f + c3 * y);
const float N = std::pow(std::max(0.f, ratpoly), m2);
out[2] = N;
*(out++) = N;
}
}

//A
out[3] = in[3];
// Alpha
*(out++) = *(in++);
};
}

Expand Down
3 changes: 2 additions & 1 deletion src/OpenColorIO/ops/fixedfunction/FixedFunctionOpData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,8 @@ FixedFunctionOpData::Style FixedFunctionOpData::ConvertStyle(FixedFunctionStyle
}
case FIXED_FUNCTION_PQ_TO_LINEAR:
{
return FixedFunctionOpData::PQ_TO_LINEAR;
return isForward ? FixedFunctionOpData::PQ_TO_LINEAR :
FixedFunctionOpData::LINEAR_TO_PQ;
}
}

Expand Down
57 changes: 55 additions & 2 deletions src/OpenColorIO/ops/fixedfunction/FixedFunctionOpGPU.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,56 @@ void Add_LUV_TO_XYZ(GpuShaderCreatorRcPtr & shaderCreator, GpuShaderText & ss)
ss.newLine() << pxl << ".rgb.g = Y;";
}


namespace
{
namespace ST_2084
{
static constexpr double m1 = 0.25 * 2610. / 4096.;
static constexpr double m2 = 128. * 2523. / 4096.;
static constexpr double c2 = 32. * 2413. / 4096.;
static constexpr double c3 = 32. * 2392. / 4096.;
static constexpr double c1 = c3 - c2 + 1.;
}
} // anonymous

void Add_PQ_TO_LINEAR(GpuShaderCreatorRcPtr& shaderCreator, GpuShaderText& ss)
{
using namespace ST_2084;
const std::string pxl(shaderCreator->getPixelName());

// TODO: this still clamps negative inputs

// x = max(min(x, vec3(1.)), vec3(0.));
// x = pow(x, vec3(1. / m2));
// vec3 v = 1. * pow(max(vec3(0.), x - vec3(c1)) / (vec3(c2) - c3 * x), vec3(1. / m1));

ss.newLine() << ss.float3Decl("x") << " = " << pxl << ".rgb;";
ss.newLine() << "x = max(x, " << ss.float3Const(0.0) << ");";
ss.newLine() << "x = pow(x, "<< ss.float3Const(1.0 / m2) << ");";
ss.newLine() << pxl << ".rgb = 100. * pow(max(" << ss.float3Const(0.0) << ", x - " << ss.float3Const(c1) << ") / ("
<< ss.float3Const(c2) << " - " << c3 << " * x), " << ss.float3Const(1.0 / m1) << ");";
}

void Add_LINEAR_TO_PQ(GpuShaderCreatorRcPtr& shaderCreator, GpuShaderText& ss)
{
using namespace ST_2084;
const std::string pxl(shaderCreator->getPixelName());

// TODO: this still clamps negative inputs

// double L = std::max(0., input * 0.01);
// double y = std::pow(L, m1);
// double ratpoly = (c1 + c2 * y) / (1. + c3 * y);
// double N = std::pow(std::max(0., ratpoly), m2);

ss.newLine() << ss.float3Decl("L") << " = max(vec3(0.), 0.01 * " << pxl << ".rgb);";
ss.newLine() << ss.float3Decl("y") << " = pow(L, " << ss.float3Const(m1) << ");";
ss.newLine() << ss.float3Decl("ratpoly") << " = (" << ss.float3Const(c1) << " + " << c2 << " * y) / ("
<< ss.float3Const(1.0) << " + " << c3 << " * y);";
ss.newLine() << pxl << ".rgb = pow(max(" << ss.float3Const(0.0) << ", ratpoly), " << ss.float3Const(m2) << ");";
}

void GetFixedFunctionGPUShaderProgram(GpuShaderCreatorRcPtr & shaderCreator,
ConstFixedFunctionOpDataRcPtr & func)
{
Expand Down Expand Up @@ -670,14 +720,17 @@ void GetFixedFunctionGPUShaderProgram(GpuShaderCreatorRcPtr & shaderCreator,
case FixedFunctionOpData::LUV_TO_XYZ:
{
Add_LUV_TO_XYZ(shaderCreator, ss);
break;
}
case FixedFunctionOpData::PQ_TO_LINEAR:
{
// TODO: Add function
Add_PQ_TO_LINEAR(shaderCreator, ss);
break;
}
case FixedFunctionOpData::LINEAR_TO_PQ:
{
// TODO: Add function
Add_LINEAR_TO_PQ(shaderCreator, ss);
break;
}
}

Expand Down
3 changes: 3 additions & 0 deletions src/bindings/python/PyTypes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,9 @@ void bindPyTypes(py::module & m)
DOC(PyOpenColorIO, FixedFunctionStyle, FIXED_FUNCTION_ACES_GAMUTMAP_07))
.value("FIXED_FUNCTION_ACES_GAMUT_COMP_13", FIXED_FUNCTION_ACES_GAMUT_COMP_13,
DOC(PyOpenColorIO, FixedFunctionStyle, FIXED_FUNCTION_ACES_GAMUT_COMP_13))
.value("FIXED_FUNCTION_PQ_TO_LINEAR", FIXED_FUNCTION_PQ_TO_LINEAR,
DOC(PyOpenColorIO, FixedFunctionStyle, FIXED_FUNCTION_PQ_TO_LINEAR))

.export_values();

py::enum_<ExposureContrastStyle>(
Expand Down
23 changes: 23 additions & 0 deletions tests/gpu/FixedFunctionOp_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -505,3 +505,26 @@ OCIO_ADD_GPU_TEST(FixedFunction, style_XYZ_TO_LUV_inv)

test.setErrorThreshold(1e-5f);
}

OCIO_ADD_GPU_TEST(FixedFunction, style_PQ_TO_LINEAR_fwd)
{
OCIO::FixedFunctionTransformRcPtr func =
OCIO::FixedFunctionTransform::Create(OCIO::FIXED_FUNCTION_PQ_TO_LINEAR);
func->setDirection(OCIO::TRANSFORM_DIR_FORWARD);

test.setTestWideRange(false);
test.setProcessor(func);
test.setErrorThreshold(1e-4f);
test.setRelativeComparison(true); // since output will be 0..100, let's set the relative epsilon.
}

OCIO_ADD_GPU_TEST(FixedFunction, style_PQ_TO_LINEAR_inv)
{
OCIO::FixedFunctionTransformRcPtr func =
OCIO::FixedFunctionTransform::Create(OCIO::FIXED_FUNCTION_PQ_TO_LINEAR);
func->setDirection(OCIO::TRANSFORM_DIR_INVERSE);

test.setTestWideRange(false);
test.setProcessor(func);
test.setErrorThreshold(1e-5f);
}

0 comments on commit 33cdc83

Please sign in to comment.