diff --git a/include/OpenColorIO/OpenColorTypes.h b/include/OpenColorIO/OpenColorTypes.h index 33654baf83..2302c1e9d2 100644 --- a/include/OpenColorIO/OpenColorTypes.h +++ b/include/OpenColorIO/OpenColorTypes.h @@ -474,19 +474,23 @@ enum RangeStyle /// Enumeration of the :cpp:class:`FixedFunctionTransform` transform algorithms. enum FixedFunctionStyle { - FIXED_FUNCTION_ACES_RED_MOD_03 = 0, ///< Red modifier (ACES 0.3/0.7) - FIXED_FUNCTION_ACES_RED_MOD_10, ///< Red modifier (ACES 1.0) - FIXED_FUNCTION_ACES_GLOW_03, ///< Glow function (ACES 0.3/0.7) - FIXED_FUNCTION_ACES_GLOW_10, ///< Glow function (ACES 1.0) - FIXED_FUNCTION_ACES_DARK_TO_DIM_10, ///< Dark to dim surround correction (ACES 1.0) - FIXED_FUNCTION_REC2100_SURROUND, ///< Rec.2100 surround correction (takes one double for the gamma param) - FIXED_FUNCTION_RGB_TO_HSV, ///< Classic RGB to HSV function - FIXED_FUNCTION_XYZ_TO_xyY, ///< CIE XYZ to 1931 xy chromaticity coordinates - FIXED_FUNCTION_XYZ_TO_uvY, ///< CIE XYZ to 1976 u'v' chromaticity coordinates - FIXED_FUNCTION_XYZ_TO_LUV, ///< CIE XYZ to 1976 CIELUV colour space (D65 white) - FIXED_FUNCTION_ACES_GAMUTMAP_02, ///< ACES 0.2 Gamut clamping algorithm -- NOT IMPLEMENTED YET - FIXED_FUNCTION_ACES_GAMUTMAP_07, ///< ACES 0.7 Gamut clamping algorithm -- NOT IMPLEMENTED YET - FIXED_FUNCTION_ACES_GAMUT_COMP_13 ///< ACES 1.3 Parametric Gamut Compression (expects ACEScg values) + FIXED_FUNCTION_ACES_RED_MOD_03 = 0, ///< Red modifier (ACES 0.3/0.7) + FIXED_FUNCTION_ACES_RED_MOD_10, ///< Red modifier (ACES 1.0) + FIXED_FUNCTION_ACES_GLOW_03, ///< Glow function (ACES 0.3/0.7) + FIXED_FUNCTION_ACES_GLOW_10, ///< Glow function (ACES 1.0) + FIXED_FUNCTION_ACES_DARK_TO_DIM_10, ///< Dark to dim surround correction (ACES 1.0) + FIXED_FUNCTION_REC2100_SURROUND, ///< Rec.2100 surround correction (takes one double for the gamma param) + FIXED_FUNCTION_RGB_TO_HSV, ///< Classic RGB to HSV function + FIXED_FUNCTION_XYZ_TO_xyY, ///< CIE XYZ to 1931 xy chromaticity coordinates + FIXED_FUNCTION_XYZ_TO_uvY, ///< CIE XYZ to 1976 u'v' chromaticity coordinates + FIXED_FUNCTION_XYZ_TO_LUV, ///< CIE XYZ to 1976 CIELUV colour space (D65 white) + FIXED_FUNCTION_ACES_GAMUTMAP_02, ///< ACES 0.2 Gamut clamping algorithm -- NOT IMPLEMENTED YET + FIXED_FUNCTION_ACES_GAMUTMAP_07, ///< ACES 0.7 Gamut clamping algorithm -- NOT IMPLEMENTED YET + FIXED_FUNCTION_ACES_GAMUT_COMP_13, ///< ACES 1.3 Parametric Gamut Compression (expects ACEScg values) + FIXED_FUNCTION_ACES_OUTPUT_TRANSFORM_20, ///< ACES 2.0 Display Rendering -- EXPERIMENTAL + FIXED_FUNCTION_ACES_RGB_TO_JMH_20, ///< ACES 2.0 RGB to JMh -- EXPERIMENTAL + FIXED_FUNCTION_ACES_TONESCALE_COMPRESS_20, ///< ACES 2.0 Tonescale and chroma compression -- EXPERIMENTAL + FIXED_FUNCTION_ACES_GAMUT_COMPRESS_20, ///< ACES 2.0 Gamut compression -- EXPERIMENTAL }; /// Enumeration of the :cpp:class:`ExposureContrastTransform` transform algorithms. diff --git a/src/OpenColorIO/CMakeLists.txt b/src/OpenColorIO/CMakeLists.txt index 4db41aa096..7d2894da6b 100755 --- a/src/OpenColorIO/CMakeLists.txt +++ b/src/OpenColorIO/CMakeLists.txt @@ -84,6 +84,7 @@ set(SOURCES ops/exposurecontrast/ExposureContrastOpData.cpp ops/exposurecontrast/ExposureContrastOpGPU.cpp ops/exposurecontrast/ExposureContrastOp.cpp + ops/fixedfunction/ACES2/Transform.cpp ops/fixedfunction/FixedFunctionOpCPU.cpp ops/fixedfunction/FixedFunctionOpData.cpp ops/fixedfunction/FixedFunctionOpGPU.cpp diff --git a/src/OpenColorIO/Config.cpp b/src/OpenColorIO/Config.cpp index bf2f2d29f8..c3d63698bc 100644 --- a/src/OpenColorIO/Config.cpp +++ b/src/OpenColorIO/Config.cpp @@ -5233,7 +5233,38 @@ void Config::Impl::checkVersionConsistency(ConstTransformRcPtr & transform) cons } if (m_majorVersion == 2 && m_minorVersion < 4 && ( 0 == Platform::Strcasecmp(blt->getStyle(), "APPLE_LOG_to_ACES2065-1") - || 0 == Platform::Strcasecmp(blt->getStyle(), "CURVE - APPLE_LOG_to_LINEAR") ) + || 0 == Platform::Strcasecmp(blt->getStyle(), "CURVE - APPLE_LOG_to_LINEAR") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - SDR-100nit-REC709_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - SDR-100nit-P3-D65_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-108nit-P3-D65_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-300nit-P3-D65_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-500nit-P3-D65_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-1000nit-P3-D65_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-2000nit-P3-D65_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-4000nit-P3-D65_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-500nit-REC2020_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-1000nit-REC2020_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-2000nit-REC2020_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-4000nit-REC2020_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - SDR-100nit-REC709-D60-in-REC709-D65_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - SDR-100nit-REC709-D60-in-P3-D65_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - SDR-100nit-REC709-D60-in-REC2020-D65_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - SDR-100nit-P3-D60-in-P3-D65_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - SDR-100nit-P3-D60-in-XYZ-E_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-108nit-P3-D60-in-P3-D65_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-300nit-P3-D60-in-XYZ-E_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-500nit-P3-D60-in-P3-D65_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-1000nit-P3-D60-in-P3-D65_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-2000nit-P3-D60-in-P3-D65_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-4000nit-P3-D60-in-P3-D65_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-500nit-P3-D60-in-REC2020-D65_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-1000nit-P3-D60-in-REC2020-D65_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-2000nit-P3-D60-in-REC2020-D65_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-4000nit-P3-D60-in-REC2020-D65_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-500nit-REC2020-D60-in-REC2020-D65_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-1000nit-REC2020-D60-in-REC2020-D65_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-2000nit-REC2020-D60-in-REC2020-D65_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-4000nit-REC2020-D60-in-REC2020-D65_2.0") ) ) { std::ostringstream os; @@ -5311,6 +5342,30 @@ void Config::Impl::checkVersionConsistency(ConstTransformRcPtr & transform) cons throw Exception("Only config version 2.1 (or higher) can have " "FixedFunctionTransform style 'ACES_GAMUT_COMP_13'."); } + + if (m_majorVersion == 2 && m_minorVersion < 4 && ff->getStyle() == FIXED_FUNCTION_ACES_OUTPUT_TRANSFORM_20) + { + throw Exception("Only config version 2.4 (or higher) can have " + "FixedFunctionTransform style 'ACES_OUTPUT_TRANSFORM_20'."); + } + + if (m_majorVersion == 2 && m_minorVersion < 4 && ff->getStyle() == FIXED_FUNCTION_ACES_RGB_TO_JMH_20) + { + throw Exception("Only config version 2.4 (or higher) can have " + "FixedFunctionTransform style 'ACES_RGB_TO_JMH_20'."); + } + + if (m_majorVersion == 2 && m_minorVersion < 4 && ff->getStyle() == FIXED_FUNCTION_ACES_TONESCALE_COMPRESS_20) + { + throw Exception("Only config version 2.4 (or higher) can have " + "FixedFunctionTransform style 'ACES_TONESCALE_COMPRESS_20'."); + } + + if (m_majorVersion == 2 && m_minorVersion < 4 && ff->getStyle() == FIXED_FUNCTION_ACES_GAMUT_COMPRESS_20) + { + throw Exception("Only config version 2.4 (or higher) can have " + "FixedFunctionTransform style 'ACES_GAMUT_COMPRESS_20'."); + } } else if (DynamicPtrCast(transform)) { diff --git a/src/OpenColorIO/GpuShaderUtils.cpp b/src/OpenColorIO/GpuShaderUtils.cpp index a3adefbdc0..faa6ba1714 100644 --- a/src/OpenColorIO/GpuShaderUtils.cpp +++ b/src/OpenColorIO/GpuShaderUtils.cpp @@ -399,6 +399,16 @@ std::string GpuShaderText::intKeywordConst() const return str; } +std::string GpuShaderText::intDecl(const std::string & name) const +{ + if (name.empty()) + { + throw Exception("GPU variable name is empty."); + } + + return intKeyword() + " " + name; +} + std::string GpuShaderText::colorDecl(const std::string & name) const { if (name.empty()) @@ -617,6 +627,13 @@ std::string GpuShaderText::float2Keyword() const return getVecKeyword<2>(m_lang); } +std::string GpuShaderText::float2Const(const std::string& x, const std::string& y) const +{ + std::ostringstream kw; + kw << float2Keyword() << "(" << x << ", " << y << ")"; + return kw.str(); +} + std::string GpuShaderText::float2Decl(const std::string & name) const { if (name.empty()) @@ -894,6 +911,72 @@ void GpuShaderText::declareUniformArrayInt(const std::string & uniformName, unsi newLine() << (m_lang == GPU_LANGUAGE_MSL_2_0 ? "" : "uniform ") << intKeyword() << " " << uniformName << "[" << size << "];"; } +// Keep the method private as only float & double types are expected +template +std::string matrix3Mul(const T * m3x3, const std::string & vecName, GpuLanguage lang) +{ + if (vecName.empty()) + { + throw Exception("GPU variable name is empty."); + } + + std::ostringstream kw; + switch (lang) + { + case GPU_LANGUAGE_GLSL_1_2: + case GPU_LANGUAGE_GLSL_1_3: + case GPU_LANGUAGE_GLSL_4_0: + case GPU_LANGUAGE_GLSL_ES_1_0: + case GPU_LANGUAGE_GLSL_ES_3_0: + { + // OpenGL shader program requests a transposed matrix + kw << "mat3(" + << getMatrixValues(m3x3, lang, true) << ") * " << vecName; + break; + } + case GPU_LANGUAGE_CG: + { + kw << "mul(half3x3(" + << getMatrixValues(m3x3, lang, false) << "), " << vecName << ")"; + break; + } + case GPU_LANGUAGE_HLSL_DX11: + { + kw << "mul(" << vecName + << ", float3x3(" << getMatrixValues(m3x3, lang, true) << "))"; + break; + } + case LANGUAGE_OSL_1: + { + kw << "matrix(" << getMatrixValues(m3x3, lang, false) << ") * " << vecName; + break; + } + case GPU_LANGUAGE_MSL_2_0: + { + kw << "float3x3(" << getMatrixValues(m3x3, lang, true) << ") * " << vecName; + break; + } + + default: + { + throw Exception("Unknown GPU shader language."); + } + } + return kw.str(); +} + +std::string GpuShaderText::mat3fMul(const float * m3x3, + const std::string & vecName) const +{ + return matrix3Mul(m3x3, vecName, m_lang); +} + +std::string GpuShaderText::mat3fMul(const double * m3x3, + const std::string & vecName) const +{ + return matrix3Mul(m3x3, vecName, m_lang); +} + // Keep the method private as only float & double types are expected template std::string matrix4Mul(const T * m4x4, const std::string & vecName, GpuLanguage lang) diff --git a/src/OpenColorIO/GpuShaderUtils.h b/src/OpenColorIO/GpuShaderUtils.h index b533110749..d9d666ffe9 100644 --- a/src/OpenColorIO/GpuShaderUtils.h +++ b/src/OpenColorIO/GpuShaderUtils.h @@ -75,6 +75,7 @@ class GpuShaderText std::string intKeyword() const; std::string intKeywordConst() const; + std::string intDecl(const std::string& name) const; std::string colorDecl(const std::string& name) const; @@ -105,6 +106,8 @@ class GpuShaderText // std::string float2Keyword() const; + // Get the string for creating constant vector with three elements + std::string float2Const(const std::string& x, const std::string& y) const; std::string float2Decl(const std::string& name) const; // @@ -195,7 +198,11 @@ class GpuShaderText // Matrix multiplication helpers // - // Get the string for multiplying a 4x4 matrix and a four-element vector + // Get the string for multiplying a 3x3 matrix and a three-elements vector + std::string mat3fMul(const float * m3x3, const std::string & vecName) const; + std::string mat3fMul(const double * m3x3, const std::string & vecName) const; + + // Get the string for multiplying a 4x4 matrix and a four-elements vector std::string mat4fMul(const float * m4x4, const std::string & vecName) const; std::string mat4fMul(const double * m4x4, const std::string & vecName) const; diff --git a/src/OpenColorIO/OCIOYaml.cpp b/src/OpenColorIO/OCIOYaml.cpp index fd5395dfc4..b1cee18982 100644 --- a/src/OpenColorIO/OCIOYaml.cpp +++ b/src/OpenColorIO/OCIOYaml.cpp @@ -1351,7 +1351,10 @@ inline void load(const YAML::Node& node, FixedFunctionTransformRcPtr& t) { std::vector params; load(iter->second, params); - t->setParams(¶ms[0], params.size()); + if (!params.empty()) + { + t->setParams(¶ms[0], params.size()); + } } else if(key == "style") { @@ -1359,6 +1362,17 @@ inline void load(const YAML::Node& node, FixedFunctionTransformRcPtr& t) load(iter->second, style); t->setStyle( FixedFunctionStyleFromString(style.c_str()) ); styleFound = true; + + const FixedFunctionStyle styleID = t->getStyle(); + if (styleID == FIXED_FUNCTION_ACES_OUTPUT_TRANSFORM_20 + || styleID == FIXED_FUNCTION_ACES_RGB_TO_JMH_20 + || styleID == FIXED_FUNCTION_ACES_TONESCALE_COMPRESS_20 + || styleID == FIXED_FUNCTION_ACES_GAMUT_COMPRESS_20) + { + std::ostringstream os; + os << "FixedFunction style is experimental and may be removed in a future release: '" << style << "'."; + LogWarning(os.str()); + } } else if(key == "direction") { @@ -1393,6 +1407,17 @@ inline void save(YAML::Emitter& out, ConstFixedFunctionTransformRcPtr t) out << YAML::Key << "style"; out << YAML::Value << YAML::Flow << FixedFunctionStyleToString(t->getStyle()); + const FixedFunctionStyle styleID = t->getStyle(); + if (styleID == FIXED_FUNCTION_ACES_OUTPUT_TRANSFORM_20 + || styleID == FIXED_FUNCTION_ACES_RGB_TO_JMH_20 + || styleID == FIXED_FUNCTION_ACES_TONESCALE_COMPRESS_20 + || styleID == FIXED_FUNCTION_ACES_GAMUT_COMPRESS_20) + { + std::ostringstream os; + os << "FixedFunction style is experimental and may be removed in a future release: '" << FixedFunctionStyleToString(t->getStyle()) << "'."; + LogWarning(os.str()); + } + const size_t numParams = t->getNumParams(); if(numParams>0) { diff --git a/src/OpenColorIO/ParseUtils.cpp b/src/OpenColorIO/ParseUtils.cpp index eaeefc00e4..5ecf07fe73 100644 --- a/src/OpenColorIO/ParseUtils.cpp +++ b/src/OpenColorIO/ParseUtils.cpp @@ -353,17 +353,21 @@ const char * FixedFunctionStyleToString(FixedFunctionStyle style) { switch(style) { - case FIXED_FUNCTION_ACES_RED_MOD_03: return "ACES_RedMod03"; - case FIXED_FUNCTION_ACES_RED_MOD_10: return "ACES_RedMod10"; - case FIXED_FUNCTION_ACES_GLOW_03: return "ACES_Glow03"; - case FIXED_FUNCTION_ACES_GLOW_10: return "ACES_Glow10"; - case FIXED_FUNCTION_ACES_DARK_TO_DIM_10: return "ACES_DarkToDim10"; - case FIXED_FUNCTION_ACES_GAMUT_COMP_13: return "ACES_GamutComp13"; - case FIXED_FUNCTION_REC2100_SURROUND: return "REC2100_Surround"; - case FIXED_FUNCTION_RGB_TO_HSV: return "RGB_TO_HSV"; - case FIXED_FUNCTION_XYZ_TO_xyY: return "XYZ_TO_xyY"; - case FIXED_FUNCTION_XYZ_TO_uvY: return "XYZ_TO_uvY"; - case FIXED_FUNCTION_XYZ_TO_LUV: return "XYZ_TO_LUV"; + case FIXED_FUNCTION_ACES_RED_MOD_03: return "ACES_RedMod03"; + case FIXED_FUNCTION_ACES_RED_MOD_10: return "ACES_RedMod10"; + case FIXED_FUNCTION_ACES_GLOW_03: return "ACES_Glow03"; + case FIXED_FUNCTION_ACES_GLOW_10: return "ACES_Glow10"; + case FIXED_FUNCTION_ACES_DARK_TO_DIM_10: return "ACES_DarkToDim10"; + case FIXED_FUNCTION_ACES_GAMUT_COMP_13: return "ACES_GamutComp13"; + case FIXED_FUNCTION_ACES_OUTPUT_TRANSFORM_20: return "ACES2_OutputTransform"; + case FIXED_FUNCTION_ACES_RGB_TO_JMH_20: return "ACES2_RGB_TO_JMh"; + case FIXED_FUNCTION_ACES_TONESCALE_COMPRESS_20: return "ACES2_TonescaleCompress"; + case FIXED_FUNCTION_ACES_GAMUT_COMPRESS_20: return "ACES2_GamutCompress"; + case FIXED_FUNCTION_REC2100_SURROUND: return "REC2100_Surround"; + case FIXED_FUNCTION_RGB_TO_HSV: return "RGB_TO_HSV"; + case FIXED_FUNCTION_XYZ_TO_xyY: return "XYZ_TO_xyY"; + case FIXED_FUNCTION_XYZ_TO_uvY: return "XYZ_TO_uvY"; + case FIXED_FUNCTION_XYZ_TO_LUV: return "XYZ_TO_LUV"; case FIXED_FUNCTION_ACES_GAMUTMAP_02: case FIXED_FUNCTION_ACES_GAMUTMAP_07: throw Exception("Unimplemented fixed function types: " @@ -380,17 +384,21 @@ FixedFunctionStyle FixedFunctionStyleFromString(const char * style) const char * p = (style ? style : ""); const std::string str = StringUtils::Lower(p); - if(str == "aces_redmod03") return FIXED_FUNCTION_ACES_RED_MOD_03; - else if(str == "aces_redmod10") return FIXED_FUNCTION_ACES_RED_MOD_10; - else if(str == "aces_glow03") return FIXED_FUNCTION_ACES_GLOW_03; - else if(str == "aces_glow10") return FIXED_FUNCTION_ACES_GLOW_10; - else if(str == "aces_darktodim10") return FIXED_FUNCTION_ACES_DARK_TO_DIM_10; - else if(str == "aces_gamutcomp13") return FIXED_FUNCTION_ACES_GAMUT_COMP_13; - else if(str == "rec2100_surround") return FIXED_FUNCTION_REC2100_SURROUND; - else if(str == "rgb_to_hsv") return FIXED_FUNCTION_RGB_TO_HSV; - else if(str == "xyz_to_xyy") return FIXED_FUNCTION_XYZ_TO_xyY; - else if(str == "xyz_to_uvy") return FIXED_FUNCTION_XYZ_TO_uvY; - else if(str == "xyz_to_luv") return FIXED_FUNCTION_XYZ_TO_LUV; + if(str == "aces_redmod03") return FIXED_FUNCTION_ACES_RED_MOD_03; + else if(str == "aces_redmod10") return FIXED_FUNCTION_ACES_RED_MOD_10; + else if(str == "aces_glow03") return FIXED_FUNCTION_ACES_GLOW_03; + else if(str == "aces_glow10") return FIXED_FUNCTION_ACES_GLOW_10; + else if(str == "aces_darktodim10") return FIXED_FUNCTION_ACES_DARK_TO_DIM_10; + else if(str == "aces_gamutcomp13") return FIXED_FUNCTION_ACES_GAMUT_COMP_13; + else if(str == "aces2_outputtransform") return FIXED_FUNCTION_ACES_OUTPUT_TRANSFORM_20; + else if(str == "aces2_rgb_to_jmh") return FIXED_FUNCTION_ACES_RGB_TO_JMH_20; + else if(str == "aces2_tonescalecompress") return FIXED_FUNCTION_ACES_TONESCALE_COMPRESS_20; + else if(str == "aces2_gamutcompress") return FIXED_FUNCTION_ACES_GAMUT_COMPRESS_20; + else if(str == "rec2100_surround") return FIXED_FUNCTION_REC2100_SURROUND; + else if(str == "rgb_to_hsv") return FIXED_FUNCTION_RGB_TO_HSV; + else if(str == "xyz_to_xyy") return FIXED_FUNCTION_XYZ_TO_xyY; + else if(str == "xyz_to_uvy") return FIXED_FUNCTION_XYZ_TO_uvY; + else if(str == "xyz_to_luv") return FIXED_FUNCTION_XYZ_TO_LUV; // Default style is meaningless. std::stringstream ss; diff --git a/src/OpenColorIO/fileformats/ctf/CTFTransform.cpp b/src/OpenColorIO/fileformats/ctf/CTFTransform.cpp index 80dd5812f7..700f8bf6f6 100644 --- a/src/OpenColorIO/fileformats/ctf/CTFTransform.cpp +++ b/src/OpenColorIO/fileformats/ctf/CTFTransform.cpp @@ -255,7 +255,17 @@ CTFVersion GetOpMinimumVersion(const ConstOpDataRcPtr & op) { minVersion = CTF_PROCESS_LIST_VERSION_2_1; } - + else if (ff->getStyle() == FixedFunctionOpData::ACES_OUTPUT_TRANSFORM_20_FWD + || ff->getStyle() == FixedFunctionOpData::ACES_OUTPUT_TRANSFORM_20_INV + || ff->getStyle() == FixedFunctionOpData::ACES_RGB_TO_JMh_20 + || ff->getStyle() == FixedFunctionOpData::ACES_JMh_TO_RGB_20 + || ff->getStyle() == FixedFunctionOpData::ACES_TONESCALE_COMPRESS_20_FWD + || ff->getStyle() == FixedFunctionOpData::ACES_TONESCALE_COMPRESS_20_INV + || ff->getStyle() == FixedFunctionOpData::ACES_GAMUT_COMPRESS_20_FWD + || ff->getStyle() == FixedFunctionOpData::ACES_GAMUT_COMPRESS_20_INV) + { + minVersion = CTF_PROCESS_LIST_VERSION_2_4; + } break; } case OpData::GradingPrimaryType: diff --git a/src/OpenColorIO/fileformats/ctf/CTFTransform.h b/src/OpenColorIO/fileformats/ctf/CTFTransform.h index bf655a7812..b15e98a63b 100644 --- a/src/OpenColorIO/fileformats/ctf/CTFTransform.h +++ b/src/OpenColorIO/fileformats/ctf/CTFTransform.h @@ -115,9 +115,12 @@ static const CTFVersion CTF_PROCESS_LIST_VERSION_2_0 = CTFVersion(2, 0); // Version 2.1 2021-08 adds the 'FIXED_FUNCTION_ACES_GAMUT_COMP_13' style to FixedFunctionOp. static const CTFVersion CTF_PROCESS_LIST_VERSION_2_1 = CTFVersion(2, 1); +// Version 2.4 2024-08 adds the ACES 2 related FixedFunctionOps. +static const CTFVersion CTF_PROCESS_LIST_VERSION_2_4 = CTFVersion(2, 4); + // Add new version before this line // and do not forget to update the following line. -static const CTFVersion CTF_PROCESS_LIST_VERSION = CTF_PROCESS_LIST_VERSION_2_1; +static const CTFVersion CTF_PROCESS_LIST_VERSION = CTF_PROCESS_LIST_VERSION_2_4; // Version 1.0 initial Autodesk version for InfoElt. diff --git a/src/OpenColorIO/ops/fixedfunction/ACES2/ColorLib.h b/src/OpenColorIO/ops/fixedfunction/ACES2/ColorLib.h new file mode 100644 index 0000000000..f10ddf3d56 --- /dev/null +++ b/src/OpenColorIO/ops/fixedfunction/ACES2/ColorLib.h @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright Contributors to the OpenColorIO Project. + +#ifndef INCLUDED_OCIO_ACES2COLORLIB_H +#define INCLUDED_OCIO_ACES2COLORLIB_H + +#include "transforms/builtins/ColorMatrixHelpers.h" +#include "MatrixLib.h" + + +namespace OCIO_NAMESPACE +{ + +namespace ACES2 +{ + +inline f3 HSV_to_RGB(const f3 &HSV) +{ + const float C = HSV[2] * HSV[1]; + const float X = C * (1.f - std::abs(std::fmod(HSV[0] * 6.f, 2.f) - 1.f)); + const float m = HSV[2] - C; + + f3 RGB{}; + if (HSV[0] < 1.f/6.f) { + RGB = {C, X, 0.f}; + } else if (HSV[0] < 2./6.) { + RGB = {X, C, 0.f}; + } else if (HSV[0] < 3./6.) { + RGB = {0.f, C, X}; + } else if (HSV[0] < 4./6.) { + RGB = {0.f, X, C}; + } else if (HSV[0] < 5./6.) { + RGB = {X, 0.f, C}; + } else { + RGB = {C, 0.f, X}; + } + RGB = add_f_f3(m, RGB); + return RGB; +} + +inline m33f RGBtoXYZ_f33(const Primaries &C) +{ + return m33_from_ocio_matrix_array( + *build_conversion_matrix(C, CIE_XYZ_ILLUM_E::primaries, ADAPTATION_NONE) + ); +} + +inline m33f XYZtoRGB_f33(const Primaries &C) +{ + return m33_from_ocio_matrix_array( + *build_conversion_matrix(C, CIE_XYZ_ILLUM_E::primaries, ADAPTATION_NONE)->inverse() + ); +} + +inline m33f RGBtoRGB_f33(const Primaries &Csrc, const Primaries &Cdst) +{ + return mult_f33_f33(XYZtoRGB_f33(Cdst), RGBtoXYZ_f33(Csrc)); +} + +constexpr m33f Identity_M33 = { + 1.f, 0.f, 0.f, + 0.f, 1.f, 0.f, + 0.f, 0.f, 1.f +}; + +} // ACES2 namespace + + +} // OCIO namespace + +#endif \ No newline at end of file diff --git a/src/OpenColorIO/ops/fixedfunction/ACES2/Common.h b/src/OpenColorIO/ops/fixedfunction/ACES2/Common.h new file mode 100644 index 0000000000..aeb1524071 --- /dev/null +++ b/src/OpenColorIO/ops/fixedfunction/ACES2/Common.h @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright Contributors to the OpenColorIO Project. + +#ifndef INCLUDED_OCIO_ACES2_COMMON_H +#define INCLUDED_OCIO_ACES2_COMMON_H + +#include "MatrixLib.h" +#include "ColorLib.h" + + +namespace OCIO_NAMESPACE +{ + +namespace ACES2 +{ + +constexpr int TABLE_SIZE = 360; +constexpr int TABLE_ADDITION_ENTRIES = 2; +constexpr int TABLE_TOTAL_SIZE = TABLE_SIZE + TABLE_ADDITION_ENTRIES; +constexpr int GAMUT_TABLE_BASE_INDEX = 1; + +struct Table3D +{ + static constexpr int base_index = GAMUT_TABLE_BASE_INDEX; + static constexpr int size = TABLE_SIZE; + static constexpr int total_size = TABLE_TOTAL_SIZE; + float table[TABLE_TOTAL_SIZE][3]; +}; + +struct Table1D +{ + static constexpr int base_index = GAMUT_TABLE_BASE_INDEX; + static constexpr int size = TABLE_SIZE; + static constexpr int total_size = TABLE_TOTAL_SIZE; + float table[TABLE_TOTAL_SIZE]; +}; + +struct JMhParams +{ + float F_L; + float z; + float A_w; + float A_w_J; + f3 XYZ_w; + f3 D_RGB; + m33f MATRIX_RGB_to_CAM16; + m33f MATRIX_CAM16_to_RGB; +}; + +struct ToneScaleParams +{ + float n; + float n_r; + float g; + float t_1; + float c_t; + float s_2; + float u_2; + float m_2; +}; + +struct ChromaCompressParams +{ + float limit_J_max; + float model_gamma; + float sat; + float sat_thr; + float compr; + Table1D reach_m_table; + float chroma_compress_scale; + static constexpr float cusp_mid_blend = 1.3f; +}; + +struct GamutCompressParams +{ + float limit_J_max; + float mid_J; + float model_gamma; + float focus_dist; + float lower_hull_gamma; + Table1D reach_m_table; + Table3D gamut_cusp_table; + Table1D upper_hull_gamma_table; +}; + +// CAM +constexpr float reference_luminance = 100.f; +constexpr float L_A = 100.f; +constexpr float Y_b = 20.f; +constexpr float ac_resp = 1.f; +constexpr float ra = 2.f * ac_resp; +constexpr float ba = 0.05f + (2.f - ra); +constexpr f3 surround = {0.9f, 0.59f, 0.9f}; // Dim surround + +// Chroma compression +constexpr float chroma_compress = 2.4f; +constexpr float chroma_compress_fact = 3.3f; +constexpr float chroma_expand = 1.3f; +constexpr float chroma_expand_fact = 0.69f; +constexpr float chroma_expand_thr = 0.5f; + +// Gamut compression +constexpr float smooth_cusps = 0.12f; +constexpr float smooth_m = 0.27f; +constexpr float cusp_mid_blend = 1.3f; +constexpr float focus_gain_blend = 0.3f; +constexpr float focus_adjust_gain = 0.55f; +constexpr float focus_distance = 1.35f; +constexpr float focus_distance_scaling = 1.75f; +constexpr float compression_threshold = 0.75f; + +namespace CAM16 +{ + static const Chromaticities red_xy(0.8336, 0.1735); + static const Chromaticities grn_xy(2.3854, -1.4659); + static const Chromaticities blu_xy(0.087 , -0.125 ); + static const Chromaticities wht_xy(0.333 , 0.333 ); + + const Primaries primaries(red_xy, grn_xy, blu_xy, wht_xy); +} + +// Table generation +constexpr float gammaMinimum = 0.0f; +constexpr float gammaMaximum = 5.0f; +constexpr float gammaSearchStep = 0.4f; +constexpr float gammaAccuracy = 1e-5f; + + +} // namespace ACES2 + +} // OCIO namespace + +#endif diff --git a/src/OpenColorIO/ops/fixedfunction/ACES2/MatrixLib.h b/src/OpenColorIO/ops/fixedfunction/ACES2/MatrixLib.h new file mode 100644 index 0000000000..27a24df0c9 --- /dev/null +++ b/src/OpenColorIO/ops/fixedfunction/ACES2/MatrixLib.h @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright Contributors to the OpenColorIO Project. + +#ifndef INCLUDED_OCIO_ACES2_MATRIXLIB_H +#define INCLUDED_OCIO_ACES2_MATRIXLIB_H + +#include "ops/matrix/MatrixOpData.h" + + +namespace OCIO_NAMESPACE +{ + +namespace ACES2 +{ + +using f2 = std::array; +using f3 = std::array; +using f4 = std::array; +using m33f = std::array; + + +inline f3 f3_from_f(float v) +{ + return f3 {v, v, v}; +} + +inline f3 add_f_f3(float v, const f3 &f3) +{ + return { + v + f3[0], + v + f3[1], + v + f3[2] + }; +} + +inline f3 mult_f_f3(float v, const f3 &f3) +{ + return { + v * f3[0], + v * f3[1], + v * f3[2] + }; +} + +inline f3 mult_f3_f33(const f3 &f3, const m33f &mat33) +{ + return { + f3[0] * mat33[0] + f3[1] * mat33[1] + f3[2] * mat33[2], + f3[0] * mat33[3] + f3[1] * mat33[4] + f3[2] * mat33[5], + f3[0] * mat33[6] + f3[1] * mat33[7] + f3[2] * mat33[8] + }; +} + +inline m33f mult_f33_f33(const m33f &a, const m33f &b) +{ + return { + a[0] * b[0] + a[1] * b[3] + a[2] * b[6], + a[0] * b[1] + a[1] * b[4] + a[2] * b[7], + a[0] * b[2] + a[1] * b[5] + a[2] * b[8], + + a[3] * b[0] + a[4] * b[3] + a[5] * b[6], + a[3] * b[1] + a[4] * b[4] + a[5] * b[7], + a[3] * b[2] + a[4] * b[5] + a[5] * b[8], + + a[6] * b[0] + a[7] * b[3] + a[8] * b[6], + a[6] * b[1] + a[7] * b[4] + a[8] * b[7], + a[6] * b[2] + a[7] * b[5] + a[8] * b[8] + }; +} + +inline m33f scale_f33(const m33f &mat33, const f3 &scale) +{ + return { + mat33[0] * scale[0], mat33[3], mat33[6], + mat33[1], mat33[4] * scale[1], mat33[7], + mat33[2], mat33[5], mat33[8] * scale[2] + }; +} + +inline m33f m33_from_ocio_matrix_array(const MatrixOpData::MatrixArray &array) +{ + const auto& v = array.getValues(); + return { + (float) v[0], (float) v[1], (float) v[2], + (float) v[4], (float) v[5], (float) v[6], + (float) v[8], (float) v[9], (float) v[10], + }; +} + +inline m33f invert_f33(const m33f &mat33) +{ + MatrixOpData::MatrixArray array; + MatrixOpData::MatrixArray::Values & v = array.getValues(); + + v[0] = mat33[0]; + v[1] = mat33[1]; + v[2] = mat33[2]; + + v[4] = mat33[3]; + v[5] = mat33[4]; + v[6] = mat33[5]; + + v[8] = mat33[6]; + v[9] = mat33[7]; + v[10] = mat33[8]; + + MatrixOpData::MatrixArray inverse = *(array.inverse()); + return m33_from_ocio_matrix_array(inverse); +} + +} // ACES2 namespace + +} // OCIO namespace + +#endif diff --git a/src/OpenColorIO/ops/fixedfunction/ACES2/Transform.cpp b/src/OpenColorIO/ops/fixedfunction/ACES2/Transform.cpp new file mode 100644 index 0000000000..80b8ba5c72 --- /dev/null +++ b/src/OpenColorIO/ops/fixedfunction/ACES2/Transform.cpp @@ -0,0 +1,930 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright Contributors to the OpenColorIO Project. + +#include "Transform.h" + + +namespace OCIO_NAMESPACE +{ + +namespace ACES2 +{ + +// +// Table lookups +// + +float wrap_to_360(float hue) +{ + float y = std::fmod(hue, 360.f); + if ( y < 0.f) + { + y = y + 360.f; + } + return y; +} + +float base_hue_for_position(int i_lo, int table_size) +{ + const float result = i_lo * 360.f / table_size; + return result; +} + +int hue_position_in_uniform_table(float hue, int table_size) +{ + const float wrapped_hue = wrap_to_360(hue); + return int(wrapped_hue / 360.f * (float) table_size); +} + +int next_position_in_table(int entry, int table_size) +{ + return (entry + 1) % table_size; +} + +int clamp_to_table_bounds(int entry, int table_size) +{ + return std::min(table_size - 1, std::max(0, entry)); +} + +f2 cusp_from_table(float h, const Table3D >) +{ + int i_lo = 0; + int i_hi = gt.base_index + gt.size; // allowed as we have an extra entry in the table + int i = clamp_to_table_bounds(hue_position_in_uniform_table(h, gt.size) + gt.base_index, gt.total_size); + + while (i_lo + 1 < i_hi) + { + if (h > gt.table[i][2]) + { + i_lo = i; + } + else + { + i_hi = i; + } + i = clamp_to_table_bounds((i_lo + i_hi) / 2, gt.total_size); + } + + i_hi = std::max(1, i_hi); + + const f3 lo { + gt.table[i_hi-1][0], + gt.table[i_hi-1][1], + gt.table[i_hi-1][2] + }; + + const f3 hi { + gt.table[i_hi][0], + gt.table[i_hi][1], + gt.table[i_hi][2] + }; + + const float t = (h - lo[2]) / (hi[2] - lo[2]); + const float cuspJ = lerpf(lo[0], hi[0], t); + const float cuspM = lerpf(lo[1], hi[1], t); + + return { cuspJ, cuspM }; +} + +float reach_m_from_table(float h, const ACES2::Table1D >) +{ + const int i_lo = clamp_to_table_bounds(hue_position_in_uniform_table(h, gt.size), gt.total_size); + const int i_hi = clamp_to_table_bounds(next_position_in_table(i_lo, gt.size), gt.total_size); + + const float t = (h - i_lo) / (i_hi - i_lo); + return lerpf(gt.table[i_lo], gt.table[i_hi], t); +} + +float hue_dependent_upper_hull_gamma(float h, const ACES2::Table1D >) +{ + const int i_lo = clamp_to_table_bounds(hue_position_in_uniform_table(h, gt.size) + gt.base_index, gt.total_size); + const int i_hi = clamp_to_table_bounds(next_position_in_table(i_lo, gt.size), gt.total_size); + + const float base_hue = (float) (i_lo - gt.base_index); + + const float t = wrap_to_360(h) - base_hue; + + return lerpf(gt.table[i_lo], gt.table[i_hi], t); +} + +// +// CAM +// + +// Post adaptation non linear response compression +float panlrc_forward(float v, float F_L) +{ + const float F_L_v = powf(F_L * std::abs(v) / reference_luminance, 0.42f); + // Note that std::copysign(1.f, 0.f) returns 1 but the CTL copysign(1.,0.) returns 0. + // TODO: Should we change the behaviour? + return (400.f * std::copysign(1.f, v) * F_L_v) / (27.13f + F_L_v); +} + +float panlrc_inverse(float v, float F_L) +{ + return std::copysign(1.f, v) * reference_luminance / F_L * powf((27.13f * std::abs(v) / (400.f - std::abs(v))), 1.f / 0.42f); +} + +// Optimization used during initialization +float Y_to_J(float Y, const JMhParams ¶ms) +{ + float F_L_Y = powf(params.F_L * std::abs(Y) / reference_luminance, 0.42f); + return std::copysign(1.f, Y) * reference_luminance * powf(((400.f * F_L_Y) / (27.13f + F_L_Y)) / params.A_w_J, surround[1] * params.z); +} + +f3 RGB_to_JMh(const f3 &RGB, const JMhParams &p) +{ + const float red = RGB[0]; + const float grn = RGB[1]; + const float blu = RGB[2]; + + const float red_m = red * p.MATRIX_RGB_to_CAM16[0] + grn * p.MATRIX_RGB_to_CAM16[1] + blu * p.MATRIX_RGB_to_CAM16[2]; + const float grn_m = red * p.MATRIX_RGB_to_CAM16[3] + grn * p.MATRIX_RGB_to_CAM16[4] + blu * p.MATRIX_RGB_to_CAM16[5]; + const float blu_m = red * p.MATRIX_RGB_to_CAM16[6] + grn * p.MATRIX_RGB_to_CAM16[7] + blu * p.MATRIX_RGB_to_CAM16[8]; + + const float red_a = panlrc_forward(red_m * p.D_RGB[0], p.F_L); + const float grn_a = panlrc_forward(grn_m * p.D_RGB[1], p.F_L); + const float blu_a = panlrc_forward(blu_m * p.D_RGB[2], p.F_L); + + const float A = 2.f * red_a + grn_a + 0.05f * blu_a; + const float a = red_a - 12.f * grn_a / 11.f + blu_a / 11.f; + const float b = (red_a + grn_a - 2.f * blu_a) / 9.f; + + const float J = 100.f * powf(A / p.A_w, surround[1] * p.z); + + const float M = J == 0.f ? 0.f : 43.f * surround[2] * sqrt(a * a + b * b); + + const float PI = 3.14159265358979f; + const float h_rad = std::atan2(b, a); + float h = std::fmod(h_rad * 180.f / PI, 360.f); + if (h < 0.f) + { + h += 360.f; + } + + return {J, M, h}; +} + +f3 JMh_to_RGB(const f3 &JMh, const JMhParams &p) +{ + const float J = JMh[0]; + const float M = JMh[1]; + const float h = JMh[2]; + + const float PI = 3.14159265358979f; + const float h_rad = h * PI / 180.f; + + const float scale = M / (43.f * surround[2]); + const float A = p.A_w * powf(J / 100.f, 1.f / (surround[1] * p.z)); + const float a = scale * cos(h_rad); + const float b = scale * sin(h_rad); + + const float red_a = (460.f * A + 451.f * a + 288.f * b) / 1403.f; + const float grn_a = (460.f * A - 891.f * a - 261.f * b) / 1403.f; + const float blu_a = (460.f * A - 220.f * a - 6300.f * b) / 1403.f; + + float red_m = panlrc_inverse(red_a, p.F_L) / p.D_RGB[0]; + float grn_m = panlrc_inverse(grn_a, p.F_L) / p.D_RGB[1]; + float blu_m = panlrc_inverse(blu_a, p.F_L) / p.D_RGB[2]; + + const float red = red_m * p.MATRIX_CAM16_to_RGB[0] + grn_m * p.MATRIX_CAM16_to_RGB[1] + blu_m * p.MATRIX_CAM16_to_RGB[2]; + const float grn = red_m * p.MATRIX_CAM16_to_RGB[3] + grn_m * p.MATRIX_CAM16_to_RGB[4] + blu_m * p.MATRIX_CAM16_to_RGB[5]; + const float blu = red_m * p.MATRIX_CAM16_to_RGB[6] + grn_m * p.MATRIX_CAM16_to_RGB[7] + blu_m * p.MATRIX_CAM16_to_RGB[8]; + + return {red, grn, blu}; +} + +// +// Tonescale / Chroma compress +// + +float chroma_compress_norm(float h, float chroma_compress_scale) +{ + const float PI = 3.14159265358979f; + const float h_rad = h / 180.f * PI; + const float a = cos(h_rad); + const float b = sin(h_rad); + const float cos_hr2 = a * a - b * b; + const float sin_hr2 = 2.0f * a * b; + const float cos_hr3 = 4.0f * a * a * a - 3.0f * a; + const float sin_hr3 = 3.0f * b - 4.0f * b * b * b; + + const float M = 11.34072f * a + + 16.46899f * cos_hr2 + + 7.88380f * cos_hr3 + + 14.66441f * b + + -6.37224f * sin_hr2 + + 9.19364f * sin_hr3 + + 77.12896f; + + return M * chroma_compress_scale; +} + +float toe_fwd( float x, float limit, float k1_in, float k2_in) +{ + if (x > limit) + { + return x; + } + + const float k2 = std::max(k2_in, 0.001f); + const float k1 = sqrt(k1_in * k1_in + k2 * k2); + const float k3 = (limit + k1) / (limit + k2); + return 0.5f * (k3 * x - k1 + sqrt((k3 * x - k1) * (k3 * x - k1) + 4.f * k2 * k3 * x)); +} + +float toe_inv( float x, float limit, float k1_in, float k2_in) +{ + if (x > limit) + { + return x; + } + + const float k2 = std::max(k2_in, 0.001f); + const float k1 = sqrt(k1_in * k1_in + k2 * k2); + const float k3 = (limit + k1) / (limit + k2); + return (x * x + k1 * x) / (k3 * (x + k2)); +} + +f3 tonescale_chroma_compress_fwd(const f3 &JMh, const JMhParams &p, const ToneScaleParams &pt, const ChromaCompressParams &pc) +{ + const float J = JMh[0]; + const float M = JMh[1]; + const float h = JMh[2]; + + // Tonescale applied in Y (convert to and from J) + const float A = p.A_w_J * powf(std::abs(J) / 100.f, 1.f / (surround[1] * p.z)); + const float Y = std::copysign(1.f, J) * 100.f / p.F_L * powf((27.13f * A) / (400.f - A), 1.f / 0.42f) / 100.f; + + const float f = pt.m_2 * powf(std::max(0.f, Y) / (Y + pt.s_2), pt.g); + const float Y_ts = std::max(0.f, f * f / (f + pt.t_1)) * pt.n_r; + + const float F_L_Y = powf(p.F_L * std::abs(Y_ts) / 100.f, 0.42f); + const float J_ts = std::copysign(1.f, Y_ts) * 100.f * powf(((400.f * F_L_Y) / (27.13f + F_L_Y)) / p.A_w_J, surround[1] * p.z); + + // ChromaCompress + float M_cp = M; + + if (M != 0.0) + { + const float nJ = J_ts / pc.limit_J_max; + const float snJ = std::max(0.f, 1.f - nJ); + const float Mnorm = chroma_compress_norm(h, pc.chroma_compress_scale); + const float limit = powf(nJ, pc.model_gamma) * reach_m_from_table(h, pc.reach_m_table) / Mnorm; + + M_cp = M * powf(J_ts / J, pc.model_gamma); + M_cp = M_cp / Mnorm; + M_cp = limit - toe_fwd(limit - M_cp, limit - 0.001f, snJ * pc.sat, sqrt(nJ * nJ + pc.sat_thr)); + M_cp = toe_fwd(M_cp, limit, nJ * pc.compr, snJ); + M_cp = M_cp * Mnorm; + } + + return {J_ts, M_cp, h}; +} + +f3 tonescale_chroma_compress_inv(const f3 &JMh, const JMhParams &p, const ToneScaleParams &pt, const ChromaCompressParams &pc) +{ + const float J_ts = JMh[0]; + const float M_cp = JMh[1]; + const float h = JMh[2]; + + // Inverse Tonescale applied in Y (convert to and from J) + const float A = p.A_w_J * powf(std::abs(J_ts) / 100.f, 1.f / (surround[1] * p.z)); + const float Y_ts = std::copysign(1.f, J_ts) * 100.f / p.F_L * powf((27.13f * A) / (400.f - A), 1.f / 0.42f) / 100.f; + + const float Z = std::max(0.f, std::min(pt.n / (pt.u_2 * pt.n_r), Y_ts)); + const float ht = (Z + sqrt(Z * (4.f * pt.t_1 + Z))) / 2.f; + const float Y = pt.s_2 / (powf((pt.m_2 / ht), (1.f / pt.g)) - 1.f) * pt.n_r; + + const float F_L_Y = powf(p.F_L * std::abs(Y) / 100.f, 0.42f); + const float J = std::copysign(1.f, Y) * 100.f * powf(((400.f * F_L_Y) / (27.13f + F_L_Y)) / p.A_w_J, surround[1] * p.z); + + // Inverse ChromaCompress + float M = M_cp; + + if (M_cp != 0.0) + { + const float nJ = J_ts / pc.limit_J_max; + const float snJ = std::max(0.f, 1.f - nJ); + const float Mnorm = chroma_compress_norm(h, pc.chroma_compress_scale); + const float limit = powf(nJ, pc.model_gamma) * reach_m_from_table(h, pc.reach_m_table) / Mnorm; + + M = M_cp / Mnorm; + M = toe_inv(M, limit, nJ * pc.compr, snJ); + M = limit - toe_inv(limit - M, limit - 0.001f, snJ * pc.sat, sqrt(nJ * nJ + pc.sat_thr)); + M = M * Mnorm; + M = M * powf(J_ts / J, -pc.model_gamma); + } + + return {J, M, h}; +} + +JMhParams init_JMhParams(const Primaries &P) +{ + JMhParams p; + + const m33f MATRIX_16 = XYZtoRGB_f33(CAM16::primaries); + const m33f RGB_to_XYZ = RGBtoXYZ_f33(P); + const f3 XYZ_w = mult_f3_f33(f3_from_f(reference_luminance), RGB_to_XYZ); + + const float Y_W = XYZ_w[1]; + + const f3 RGB_w = mult_f3_f33(XYZ_w, MATRIX_16); + + // Viewing condition dependent parameters + const float K = 1.f / (5.f * L_A + 1.f); + const float K4 = powf(K, 4.f); + const float N = Y_b / Y_W; + const float F_L = 0.2f * K4 * (5.f * L_A) + 0.1f * powf((1.f - K4), 2.f) * powf(5.f * L_A, 1.f/3.f); + const float z = 1.48f + sqrt(N); + + const f3 D_RGB = { + Y_W / RGB_w[0], + Y_W / RGB_w[1], + Y_W / RGB_w[2] + }; + + const f3 RGB_WC { + D_RGB[0] * RGB_w[0], + D_RGB[1] * RGB_w[1], + D_RGB[2] * RGB_w[2] + }; + + const f3 RGB_AW = { + panlrc_forward(RGB_WC[0], F_L), + panlrc_forward(RGB_WC[1], F_L), + panlrc_forward(RGB_WC[2], F_L) + }; + + const float A_w = ra * RGB_AW[0] + RGB_AW[1] + ba * RGB_AW[2]; + + const float F_L_W = powf(F_L, 0.42f); + const float A_w_J = (400.f * F_L_W) / (27.13f + F_L_W); + + p.XYZ_w = XYZ_w; + p.F_L = F_L; + p.z = z; + p.D_RGB = D_RGB; + p.A_w = A_w; + p.A_w_J = A_w_J; + + p.MATRIX_RGB_to_CAM16 = mult_f33_f33(RGBtoRGB_f33(P, CAM16::primaries), scale_f33(Identity_M33, f3_from_f(100.f))); + p.MATRIX_CAM16_to_RGB = invert_f33(p.MATRIX_RGB_to_CAM16); + + return p; +} + +Table3D make_gamut_table(const Primaries &P, float peakLuminance) +{ + const JMhParams params = init_JMhParams(P); + + Table3D gamutCuspTableUnsorted{}; + for (int i = 0; i < gamutCuspTableUnsorted.size; i++) + { + const float hNorm = (float) i / gamutCuspTableUnsorted.size; + const f3 HSV = {hNorm, 1., 1.}; + const f3 RGB = HSV_to_RGB(HSV); + const f3 scaledRGB = mult_f_f3(peakLuminance / reference_luminance, RGB); + const f3 JMh = RGB_to_JMh(scaledRGB, params); + + gamutCuspTableUnsorted.table[i][0] = JMh[0]; + gamutCuspTableUnsorted.table[i][1] = JMh[1]; + gamutCuspTableUnsorted.table[i][2] = JMh[2]; + } + + int minhIndex = 0; + for (int i = 0; i < gamutCuspTableUnsorted.size; i++) + { + if ( gamutCuspTableUnsorted.table[i][2] < gamutCuspTableUnsorted.table[minhIndex][2]) + minhIndex = i; + } + + Table3D gamutCuspTable{}; + for (int i = 0; i < gamutCuspTableUnsorted.size; i++) + { + gamutCuspTable.table[i + gamutCuspTable.base_index][0] = gamutCuspTableUnsorted.table[(minhIndex+i) % gamutCuspTableUnsorted.size][0]; + gamutCuspTable.table[i + gamutCuspTable.base_index][1] = gamutCuspTableUnsorted.table[(minhIndex+i) % gamutCuspTableUnsorted.size][1]; + gamutCuspTable.table[i + gamutCuspTable.base_index][2] = gamutCuspTableUnsorted.table[(minhIndex+i) % gamutCuspTableUnsorted.size][2]; + } + + // Copy last populated entry to first empty spot + gamutCuspTable.table[0][0] = gamutCuspTable.table[gamutCuspTable.base_index + gamutCuspTable.size-1][0]; + gamutCuspTable.table[0][1] = gamutCuspTable.table[gamutCuspTable.base_index + gamutCuspTable.size-1][1]; + gamutCuspTable.table[0][2] = gamutCuspTable.table[gamutCuspTable.base_index + gamutCuspTable.size-1][2]; + + // Copy first populated entry to last empty spot + gamutCuspTable.table[gamutCuspTable.base_index + gamutCuspTable.size][0] = gamutCuspTable.table[gamutCuspTable.base_index][0]; + gamutCuspTable.table[gamutCuspTable.base_index + gamutCuspTable.size][1] = gamutCuspTable.table[gamutCuspTable.base_index][1]; + gamutCuspTable.table[gamutCuspTable.base_index + gamutCuspTable.size][2] = gamutCuspTable.table[gamutCuspTable.base_index][2]; + + // Wrap the hues, to maintain monotonicity. These entries will fall outside [0.0, 360.0] + gamutCuspTable.table[0][2] = gamutCuspTable.table[0][2] - 360.f; + gamutCuspTable.table[gamutCuspTable.size+1][2] = gamutCuspTable.table[gamutCuspTable.size+1][2] + 360.f; + + return gamutCuspTable; +} + +bool any_below_zero(const f3 &rgb) +{ + return (rgb[0] < 0. || rgb[1] < 0. || rgb[2] < 0.); +} + +Table1D make_reach_m_table(const Primaries &P, float peakLuminance) +{ + const JMhParams params = init_JMhParams(P); + const float limit_J_max = Y_to_J(peakLuminance, params); + + Table1D gamutReachTable{}; + + for (int i = 0; i < gamutReachTable.size; i++) { + const float hue = (float) i; + + const float search_range = 50.f; + float low = 0.; + float high = low + search_range; + bool outside = false; + + while ((outside != true) & (high < 1300.f)) + { + const f3 searchJMh = {limit_J_max, high, hue}; + const f3 newLimitRGB = JMh_to_RGB(searchJMh, params); + outside = any_below_zero(newLimitRGB); + if (outside == false) + { + low = high; + high = high + search_range; + } + } + + while (high-low > 1e-2) + { + const float sampleM = (high + low) / 2.f; + const f3 searchJMh = {limit_J_max, sampleM, hue}; + const f3 newLimitRGB = JMh_to_RGB(searchJMh, params); + outside = any_below_zero(newLimitRGB); + if (outside) + { + high = sampleM; + } + else + { + low = sampleM; + } + } + + gamutReachTable.table[i] = high; + } + + return gamutReachTable; +} + +bool outside_hull(const f3 &rgb) +{ + // limit value, once we cross this value, we are outside of the top gamut shell + const float maxRGBtestVal = 1.0; + return rgb[0] > maxRGBtestVal || rgb[1] > maxRGBtestVal || rgb[2] > maxRGBtestVal; +} + +float get_focus_gain(float J, float cuspJ, float limit_J_max) +{ + const float thr = lerpf(cuspJ, limit_J_max, focus_gain_blend); + + if (J > thr) + { + // Approximate inverse required above threshold + float gain = (limit_J_max - thr) / std::max(0.0001f, (limit_J_max - std::min(limit_J_max, J))); + return powf(log10(gain), 1.f / focus_adjust_gain) + 1.f; + } + else + { + // Analytic inverse possible below cusp + return 1.f; + } +} + +float solve_J_intersect(float J, float M, float focusJ, float maxJ, float slope_gain) +{ + const float a = M / (focusJ * slope_gain); + float b = 0.f; + float c = 0.f; + float intersectJ = 0.f; + + if (J < focusJ) + { + b = 1.f - M / slope_gain; + } + else + { + b = - (1.f + M / slope_gain + maxJ * M / (focusJ * slope_gain)); + } + + if (J < focusJ) + { + c = -J; + } + else + { + c = maxJ * M / slope_gain + J; + } + + const float root = sqrt(b * b - 4.f * a * c); + + if (J < focusJ) + { + intersectJ = 2.f * c / (-b - root); + } + else + { + intersectJ = 2.f * c / (-b + root); + } + + return intersectJ; +} + +float smin(float a, float b, float s) +{ + const float h = std::max(s - std::abs(a - b), 0.f) / s; + return std::min(a, b) - h * h * h * s * (1.f / 6.f); +} + +f3 find_gamut_boundary_intersection(const f3 &JMh_s, const f2 &JM_cusp_in, float J_focus, float J_max, float slope_gain, float gamma_top, float gamma_bottom) +{ + const float s = std::max(0.000001f, smooth_cusps); + const f2 JM_cusp = { + JM_cusp_in[0], + JM_cusp_in[1] * (1.f + smooth_m * s) + }; + + const float J_intersect_source = solve_J_intersect(JMh_s[0], JMh_s[1], J_focus, J_max, slope_gain); + const float J_intersect_cusp = solve_J_intersect(JM_cusp[0], JM_cusp[1], J_focus, J_max, slope_gain); + + float slope = 0.f; + if (J_intersect_source < J_focus) + { + slope = J_intersect_source * (J_intersect_source - J_focus) / (J_focus * slope_gain); + } + else + { + slope = (J_max - J_intersect_source) * (J_intersect_source - J_focus) / (J_focus * slope_gain); + } + + const float M_boundary_lower = J_intersect_cusp * powf(J_intersect_source / J_intersect_cusp, 1.f / gamma_bottom) / (JM_cusp[0] / JM_cusp[1] - slope); + const float M_boundary_upper = JM_cusp[1] * (J_max - J_intersect_cusp) * powf((J_max - J_intersect_source) / (J_max - J_intersect_cusp), 1.f / gamma_top) / (slope * JM_cusp[1] + J_max - JM_cusp[0]); + const float M_boundary = JM_cusp[1] * smin(M_boundary_lower / JM_cusp[1], M_boundary_upper / JM_cusp[1], s); + const float J_boundary = J_intersect_source + slope * M_boundary; + + return {J_boundary, M_boundary, J_intersect_source}; +} + +f3 get_reach_boundary( + float J, + float M, + float h, + const f2 &JMcusp, + float focusJ, + float limit_J_max, + float model_gamma, + float focus_dist, + const ACES2::Table1D & reach_m_table +) +{ + const float reachMaxM = reach_m_from_table(h, reach_m_table); + + const float slope_gain = limit_J_max * focus_dist * get_focus_gain(J, JMcusp[0], limit_J_max); + + const float intersectJ = solve_J_intersect(J, M, focusJ, limit_J_max, slope_gain); + + float slope; + if (intersectJ < focusJ) + { + slope = intersectJ * (intersectJ - focusJ) / (focusJ * slope_gain); + } + else + { + slope = (limit_J_max - intersectJ) * (intersectJ - focusJ) / (focusJ * slope_gain); + } + + const float boundary = limit_J_max * powf(intersectJ / limit_J_max, model_gamma) * reachMaxM / (limit_J_max - slope * reachMaxM); + return {J, boundary, h}; +} + +float compression_function( + float v, + float thr, + float lim, + bool invert) +{ + float s = (lim - thr) * (1.f - thr) / (lim - 1.f); + float nd = (v - thr) / s; + + float vCompressed; + + if (invert) { + if (v < thr || lim <= 1.0001f || v > thr + s) { + vCompressed = v; + } else { + vCompressed = thr + s * (-nd / (nd - 1.f)); + } + } else { + if (v < thr || lim <= 1.0001f) { + vCompressed = v; + } else { + vCompressed = thr + s * nd / (1.f + nd); + } + } + + return vCompressed; +} + +f3 compressGamut(const f3 &JMh, float Jx, const ACES2::GamutCompressParams& p, bool invert) +{ + const float J = JMh[0]; + const float M = JMh[1]; + const float h = JMh[2]; + + if (M < 0.0001f || J > p.limit_J_max) + { + return {J, 0.f, h}; + } + else + { + const f2 project_from = {J, M}; + const f2 JMcusp = cusp_from_table(h, p.gamut_cusp_table); + const float focusJ = lerpf(JMcusp[0], p.mid_J, std::min(1.f, cusp_mid_blend - (JMcusp[0] / p.limit_J_max))); + const float slope_gain = p.limit_J_max * p.focus_dist * get_focus_gain(Jx, JMcusp[0], p.limit_J_max); + + const float gamma_top = hue_dependent_upper_hull_gamma(h, p.upper_hull_gamma_table); + const float gamma_bottom = p.lower_hull_gamma; + + const f3 boundaryReturn = find_gamut_boundary_intersection({J, M, h}, JMcusp, focusJ, p.limit_J_max, slope_gain, gamma_top, gamma_bottom); + const f2 JMboundary = {boundaryReturn[0], boundaryReturn[1]}; + const f2 project_to = {boundaryReturn[2], 0.f}; + + if (JMboundary[1] <= 0.0f) + { + return {J, 0.f, h}; + } + + const f3 reachBoundary = get_reach_boundary(JMboundary[0], JMboundary[1], h, JMcusp, focusJ, p.limit_J_max, p.model_gamma, p.focus_dist, p.reach_m_table); + + const float difference = std::max(1.0001f, reachBoundary[1] / JMboundary[1]); + const float threshold = std::max(compression_threshold, 1.f / difference); + + float v = project_from[1] / JMboundary[1]; + v = compression_function(v, threshold, difference, invert); + + const f2 JMcompressed { + project_to[0] + v * (JMboundary[0] - project_to[0]), + project_to[1] + v * (JMboundary[1] - project_to[1]) + }; + + return {JMcompressed[0], JMcompressed[1], h}; + } +} + +f3 gamut_compress_fwd(const f3 &JMh, const GamutCompressParams &p) +{ + return compressGamut(JMh, JMh[0], p, false); +} + +f3 gamut_compress_inv(const f3 &JMh, const GamutCompressParams &p) +{ + const f2 JMcusp = cusp_from_table(JMh[2], p.gamut_cusp_table); + float Jx = JMh[0]; + + f3 unCompressedJMh; + + // Analytic inverse below threshold + if (Jx <= lerpf(JMcusp[0], p.limit_J_max, focus_gain_blend)) + { + unCompressedJMh = compressGamut(JMh, Jx, p, true); + } + // Approximation above threshold + else + { + Jx = compressGamut(JMh, Jx, p, true)[0]; + unCompressedJMh = compressGamut(JMh, Jx, p, true); + } + + return unCompressedJMh; +} + +bool evaluate_gamma_fit( + const f2 &JMcusp, + const f3 testJMh[3], + float topGamma, + float peakLuminance, + float limit_J_max, + float mid_J, + float focus_dist, + float lower_hull_gamma, + const JMhParams &limitJMhParams) +{ + const float focusJ = lerpf(JMcusp[0], mid_J, std::min(1.f, cusp_mid_blend - (JMcusp[0] / limit_J_max))); + + for (size_t testIndex = 0; testIndex < 3; testIndex++) + { + const float slope_gain = limit_J_max * focus_dist * get_focus_gain(testJMh[testIndex][0], JMcusp[0], limit_J_max); + const f3 approxLimit = find_gamut_boundary_intersection(testJMh[testIndex], JMcusp, focusJ, limit_J_max, slope_gain, topGamma, lower_hull_gamma); + const f3 approximate_JMh = {approxLimit[0], approxLimit[1], testJMh[testIndex][2]}; + const f3 newLimitRGB = JMh_to_RGB(approximate_JMh, limitJMhParams); + const f3 newLimitRGBScaled = mult_f_f3(reference_luminance / peakLuminance, newLimitRGB); + + if (!outside_hull(newLimitRGBScaled)) + { + return false; + } + } + + return true; +} + +Table1D make_upper_hull_gamma( + const Table3D &gamutCuspTable, + float peakLuminance, + float limit_J_max, + float mid_J, + float focus_dist, + float lower_hull_gamma, + const JMhParams &limitJMhParams) +{ + const int test_count = 3; + const float testPositions[test_count] = {0.01f, 0.5f, 0.99f}; + + Table1D gammaTable{}; + Table1D gamutTopGamma{}; + + for (int i = 0; i < gammaTable.size; i++) + { + gammaTable.table[i] = -1.f; + + const float hue = (float) i; + const f2 JMcusp = cusp_from_table(hue, gamutCuspTable); + + f3 testJMh[test_count]{}; + for (int testIndex = 0; testIndex < test_count; testIndex++) + { + const float testJ = JMcusp[0] + ((limit_J_max - JMcusp[0]) * testPositions[testIndex]); + testJMh[testIndex] = { + testJ, + JMcusp[1], + hue + }; + } + + const float search_range = gammaSearchStep; + float low = gammaMinimum; + float high = low + search_range; + bool outside = false; + + while (!(outside) && (high < 5.f)) + { + const bool gammaFound = evaluate_gamma_fit(JMcusp, testJMh, high, peakLuminance, limit_J_max, mid_J, focus_dist, lower_hull_gamma, limitJMhParams); + if (!gammaFound) + { + low = high; + high = high + search_range; + } + else + { + outside = true; + } + } + + float testGamma = -1.f; + while ( (high-low) > gammaAccuracy) + { + testGamma = (high + low) / 2.f; + const bool gammaFound = evaluate_gamma_fit(JMcusp, testJMh, testGamma, peakLuminance, limit_J_max, mid_J, focus_dist, lower_hull_gamma, limitJMhParams); + if (gammaFound) + { + high = testGamma; + gammaTable.table[i] = high; + } + else + { + low = testGamma; + } + } + + // Duplicate gamma value to array, leaving empty entries at first and last position + gamutTopGamma.table[i+gamutTopGamma.base_index] = gammaTable.table[i]; + } + + // Copy last populated entry to first empty spot + gamutTopGamma.table[0] = gammaTable.table[gammaTable.size-1]; + + // Copy first populated entry to last empty spot + gamutTopGamma.table[gamutTopGamma.total_size-1] = gammaTable.table[0]; + + return gamutTopGamma; +} + +// Tonescale pre-calculations +ToneScaleParams init_ToneScaleParams(float peakLuminance) +{ + // Preset constants that set the desired behavior for the curve + const float n = peakLuminance; + + const float n_r = 100.0f; // normalized white in nits (what 1.0 should be) + const float g = 1.15f; // surround / contrast + const float c = 0.18f; // anchor for 18% grey + const float c_d = 10.013f; // output luminance of 18% grey (in nits) + const float w_g = 0.14f; // change in grey between different peak luminance + const float t_1 = 0.04f; // shadow toe or flare/glare compensation + const float r_hit_min = 128.f; // scene-referred value "hitting the roof" + const float r_hit_max = 896.f; // scene-referred value "hitting the roof" + + // Calculate output constants + const float r_hit = r_hit_min + (r_hit_max - r_hit_min) * (log(n/n_r)/log(10000.f/100.f)); + const float m_0 = (n / n_r); + const float m_1 = 0.5f * (m_0 + sqrt(m_0 * (m_0 + 4.f * t_1))); + const float u = powf((r_hit/m_1)/((r_hit/m_1)+1.f),g); + const float m = m_1 / u; + const float w_i = log(n/100.f)/log(2.f); + const float c_t = c_d/n_r * (1.f + w_i * w_g); + const float g_ip = 0.5f * (c_t + sqrt(c_t * (c_t + 4.f * t_1))); + const float g_ipp2 = -(m_1 * powf((g_ip/m),(1.f/g))) / (powf(g_ip/m , 1.f/g)-1.f); + const float w_2 = c / g_ipp2; + const float s_2 = w_2 * m_1; + const float u_2 = powf((r_hit/m_1)/((r_hit/m_1) + w_2), g); + const float m_2 = m_1 / u_2; + + ToneScaleParams TonescaleParams = { + n, + n_r, + g, + t_1, + c_t, + s_2, + u_2, + m_2 + }; + + return TonescaleParams; +} + +ChromaCompressParams init_ChromaCompressParams(float peakLuminance) +{ + const ToneScaleParams tsParams = init_ToneScaleParams(peakLuminance); + const JMhParams inputJMhParams = init_JMhParams(ACES_AP0::primaries); + + float limit_J_max = Y_to_J(peakLuminance, inputJMhParams); + + // Calculated chroma compress variables + const float log_peak = log10( tsParams.n / tsParams.n_r); + const float compr = chroma_compress + (chroma_compress * chroma_compress_fact) * log_peak; + const float sat = std::max(0.2f, chroma_expand - (chroma_expand * chroma_expand_fact) * log_peak); + const float sat_thr = chroma_expand_thr / tsParams.n; + const float model_gamma = 1.f / (surround[1] * (1.48f + sqrt(Y_b / L_A))); + + ChromaCompressParams params{}; + params.limit_J_max = limit_J_max; + params.model_gamma = model_gamma; + params.sat = sat; + params.sat_thr = sat_thr; + params.compr = compr; + params.chroma_compress_scale = powf(0.03379f * peakLuminance, 0.30596f) - 0.45135f; + params.reach_m_table = make_reach_m_table(ACES_AP1::primaries, peakLuminance); + return params; +} + +GamutCompressParams init_GamutCompressParams(float peakLuminance, const Primaries &limitingPrimaries) +{ + const ToneScaleParams tsParams = init_ToneScaleParams(peakLuminance); + const JMhParams inputJMhParams = init_JMhParams(ACES_AP0::primaries); + + float limit_J_max = Y_to_J(peakLuminance, inputJMhParams); + float mid_J = Y_to_J(tsParams.c_t * 100.f, inputJMhParams); + + // Calculated chroma compress variables + const float log_peak = log10( tsParams.n / tsParams.n_r); + const float model_gamma = 1.f / (surround[1] * (1.48f + sqrt(Y_b / L_A))); + const float focus_dist = focus_distance + focus_distance * focus_distance_scaling * log_peak; + const float lower_hull_gamma = 1.14f + 0.07f * log_peak; + + const JMhParams limitJMhParams = init_JMhParams(limitingPrimaries); + + GamutCompressParams params{}; + params.limit_J_max = limit_J_max; + params.mid_J = mid_J; + params.model_gamma = model_gamma; + params.focus_dist = focus_dist; + params.lower_hull_gamma = lower_hull_gamma; + params.reach_m_table = make_reach_m_table(ACES_AP1::primaries, peakLuminance); + params.gamut_cusp_table = make_gamut_table(limitingPrimaries, peakLuminance); + params.upper_hull_gamma_table = make_upper_hull_gamma( + params.gamut_cusp_table, + peakLuminance, + limit_J_max, + mid_J, + focus_dist, + lower_hull_gamma, + limitJMhParams); + + return params; +} + +} // namespace ACES2 + +} // OCIO namespace diff --git a/src/OpenColorIO/ops/fixedfunction/ACES2/Transform.h b/src/OpenColorIO/ops/fixedfunction/ACES2/Transform.h new file mode 100644 index 0000000000..ff3f969e5a --- /dev/null +++ b/src/OpenColorIO/ops/fixedfunction/ACES2/Transform.h @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright Contributors to the OpenColorIO Project. + +#ifndef INCLUDED_OCIO_ACES2_TRANSFORM_H +#define INCLUDED_OCIO_ACES2_TRANSFORM_H + +#include "Common.h" + +namespace OCIO_NAMESPACE +{ + +namespace ACES2 +{ + +JMhParams init_JMhParams(const Primaries &P); +ToneScaleParams init_ToneScaleParams(float peakLuminance); +ChromaCompressParams init_ChromaCompressParams(float peakLuminance); +GamutCompressParams init_GamutCompressParams(float peakLuminance, const Primaries &P); + +f3 RGB_to_JMh(const f3 &RGB, const JMhParams &p); +f3 JMh_to_RGB(const f3 &JMh, const JMhParams &p); + +f3 tonescale_chroma_compress_fwd(const f3 &JMh, const JMhParams &p, const ToneScaleParams &pt, const ChromaCompressParams &pc); +f3 tonescale_chroma_compress_inv(const f3 &JMh, const JMhParams &p, const ToneScaleParams &pt, const ChromaCompressParams &pc); + +f3 gamut_compress_fwd(const f3 &JMh, const GamutCompressParams &p); +f3 gamut_compress_inv(const f3 &JMh, const GamutCompressParams &p); + + +} // namespace ACES2 + +} // OCIO namespace + +#endif diff --git a/src/OpenColorIO/ops/fixedfunction/FixedFunctionOpCPU.cpp b/src/OpenColorIO/ops/fixedfunction/FixedFunctionOpCPU.cpp index a974cb7880..3cc15ecf6f 100644 --- a/src/OpenColorIO/ops/fixedfunction/FixedFunctionOpCPU.cpp +++ b/src/OpenColorIO/ops/fixedfunction/FixedFunctionOpCPU.cpp @@ -3,9 +3,11 @@ #include #include +#include #include +#include "ACES2/Transform.h" #include "BitDepthUtils.h" #include "MathUtils.h" #include "ops/fixedfunction/FixedFunctionOpCPU.h" @@ -126,6 +128,80 @@ class Renderer_ACES_GamutComp13_Inv : public Renderer_ACES_GamutComp13_Fwd void apply(const void * inImg, void * outImg, long numPixels) const override; }; +class Renderer_ACES_OutputTransform20 : public OpCPU +{ +public: + Renderer_ACES_OutputTransform20() = delete; + explicit Renderer_ACES_OutputTransform20(ConstFixedFunctionOpDataRcPtr & data); + + void apply(const void * inImg, void * outImg, long numPixels) const override; + +private: + void fwd(const void * inImg, void * outImg, long numPixels) const; + void inv(const void * inImg, void * outImg, long numPixels) const; + +protected: + bool m_fwd; + ACES2::JMhParams m_pIn; + ACES2::JMhParams m_pOut; + ACES2::ToneScaleParams m_t; + ACES2::ChromaCompressParams m_c; + ACES2::GamutCompressParams m_g; +}; + +class Renderer_ACES_RGB_TO_JMh_20 : public OpCPU +{ +public: + Renderer_ACES_RGB_TO_JMh_20() = delete; + explicit Renderer_ACES_RGB_TO_JMh_20(ConstFixedFunctionOpDataRcPtr & data); + + void apply(const void * inImg, void * outImg, long numPixels) const override; + +private: + void fwd(const void * inImg, void * outImg, long numPixels) const; + void inv(const void * inImg, void * outImg, long numPixels) const; + +protected: + bool m_fwd; + ACES2::JMhParams m_p; +}; + +class Renderer_ACES_TONESCALE_COMPRESS_20 : public OpCPU +{ +public: + Renderer_ACES_TONESCALE_COMPRESS_20() = delete; + explicit Renderer_ACES_TONESCALE_COMPRESS_20(ConstFixedFunctionOpDataRcPtr & data); + + void apply(const void * inImg, void * outImg, long numPixels) const override; + +private: + void fwd(const void * inImg, void * outImg, long numPixels) const; + void inv(const void * inImg, void * outImg, long numPixels) const; + +protected: + bool m_fwd; + ACES2::JMhParams m_p; + ACES2::ToneScaleParams m_t; + ACES2::ChromaCompressParams m_c; +}; + +class Renderer_ACES_GAMUT_COMPRESS_20 : public OpCPU +{ +public: + Renderer_ACES_GAMUT_COMPRESS_20() = delete; + explicit Renderer_ACES_GAMUT_COMPRESS_20(ConstFixedFunctionOpDataRcPtr & data); + + void apply(const void * inImg, void * outImg, long numPixels) const override; + +private: + void fwd(const void * inImg, void * outImg, long numPixels) const; + void inv(const void * inImg, void * outImg, long numPixels) const; + +protected: + bool m_fwd; + ACES2::GamutCompressParams m_g; +}; + class Renderer_REC2100_Surround : public OpCPU { public: @@ -791,6 +867,306 @@ void Renderer_ACES_GamutComp13_Inv::apply(const void * inImg, void * outImg, lon } } +Renderer_ACES_OutputTransform20::Renderer_ACES_OutputTransform20(ConstFixedFunctionOpDataRcPtr & data) + : OpCPU() +{ + m_fwd = FixedFunctionOpData::ACES_OUTPUT_TRANSFORM_20_FWD == data->getStyle(); + + const float peak_luminance = (float) data->getParams()[0]; + + const float lim_red_x = (float) data->getParams()[1]; + const float lim_red_y = (float) data->getParams()[2]; + const float lim_green_x = (float) data->getParams()[3]; + const float lim_green_y = (float) data->getParams()[4]; + const float lim_blue_x = (float) data->getParams()[5]; + const float lim_blue_y = (float) data->getParams()[6]; + const float lim_white_x = (float) data->getParams()[7]; + const float lim_white_y = (float) data->getParams()[8]; + + const Primaries lim_primaries = { + {lim_red_x , lim_red_y }, + {lim_green_x, lim_green_y}, + {lim_blue_x , lim_blue_y }, + {lim_white_x, lim_white_y} + }; + + m_pIn = ACES2::init_JMhParams(ACES_AP0::primaries); + m_pOut = ACES2::init_JMhParams(lim_primaries); + m_t = ACES2::init_ToneScaleParams(peak_luminance); + m_c = ACES2::init_ChromaCompressParams(peak_luminance); + m_g = ACES2::init_GamutCompressParams(peak_luminance, lim_primaries); +} + +void Renderer_ACES_OutputTransform20::apply(const void * inImg, void * outImg, long numPixels) const +{ + if (m_fwd) + { + fwd(inImg, outImg, numPixels); + } + else + { + inv(inImg, outImg, numPixels); + } +} + +void Renderer_ACES_OutputTransform20::fwd(const void * inImg, void * outImg, long numPixels) const +{ + const float * in = (const float *)inImg; + float * out = (float *)outImg; + + for(long idx=0; idxgetStyle(); + + const float red_x = (float) data->getParams()[0]; + const float red_y = (float) data->getParams()[1]; + const float green_x = (float) data->getParams()[2]; + const float green_y = (float) data->getParams()[3]; + const float blue_x = (float) data->getParams()[4]; + const float blue_y = (float) data->getParams()[5]; + const float white_x = (float) data->getParams()[6]; + const float white_y = (float) data->getParams()[7]; + + const Primaries primaries = { + {red_x , red_y }, + {green_x, green_y}, + {blue_x , blue_y }, + {white_x, white_y} + }; + + m_p = ACES2::init_JMhParams(primaries); +} + +void Renderer_ACES_RGB_TO_JMh_20::apply(const void * inImg, void * outImg, long numPixels) const +{ + if (m_fwd) + { + fwd(inImg, outImg, numPixels); + } + else + { + inv(inImg, outImg, numPixels); + } +} + +void Renderer_ACES_RGB_TO_JMh_20::fwd(const void * inImg, void * outImg, long numPixels) const +{ + const float * in = (const float *)inImg; + float * out = (float *)outImg; + + for(long idx=0; idxgetStyle(); + + const float peak_luminance = (float) data->getParams()[0]; + + m_p = ACES2::init_JMhParams(ACES_AP0::primaries); + m_t = ACES2::init_ToneScaleParams(peak_luminance); + m_c = ACES2::init_ChromaCompressParams(peak_luminance); +} + +void Renderer_ACES_TONESCALE_COMPRESS_20::apply(const void * inImg, void * outImg, long numPixels) const +{ + if (m_fwd) + { + fwd(inImg, outImg, numPixels); + } + else + { + inv(inImg, outImg, numPixels); + } +} + +void Renderer_ACES_TONESCALE_COMPRESS_20::fwd(const void * inImg, void * outImg, long numPixels) const +{ + const float * in = (const float *)inImg; + float * out = (float *)outImg; + + for(long idx=0; idxgetStyle(); + + const float peakLuminance = (float) data->getParams()[0]; + + const float red_x = (float) data->getParams()[1]; + const float red_y = (float) data->getParams()[2]; + const float green_x = (float) data->getParams()[3]; + const float green_y = (float) data->getParams()[4]; + const float blue_x = (float) data->getParams()[5]; + const float blue_y = (float) data->getParams()[6]; + const float white_x = (float) data->getParams()[7]; + const float white_y = (float) data->getParams()[8]; + + const Primaries limitingPrimaries = { + {red_x , red_y }, + {green_x, green_y}, + {blue_x , blue_y }, + {white_x, white_y} + }; + + m_g = ACES2::init_GamutCompressParams(peakLuminance, limitingPrimaries); +} + +void Renderer_ACES_GAMUT_COMPRESS_20::apply(const void * inImg, void * outImg, long numPixels) const +{ + if (m_fwd) + { + fwd(inImg, outImg, numPixels); + } + else + { + inv(inImg, outImg, numPixels); + } +} + +void Renderer_ACES_GAMUT_COMPRESS_20::fwd(const void * inImg, void * outImg, long numPixels) const +{ + const float * in = (const float *)inImg; + float * out = (float *)outImg; + + for(long idx=0; idx(func); } + + case FixedFunctionOpData::ACES_OUTPUT_TRANSFORM_20_FWD: + case FixedFunctionOpData::ACES_OUTPUT_TRANSFORM_20_INV: + { + // Sharing same renderer (param will be inverted to handle direction). + return std::make_shared(func); + } + + case FixedFunctionOpData::ACES_RGB_TO_JMh_20: + case FixedFunctionOpData::ACES_JMh_TO_RGB_20: + { + // Sharing same renderer (param will be inverted to handle direction). + return std::make_shared(func); + } + + case FixedFunctionOpData::ACES_TONESCALE_COMPRESS_20_FWD: + case FixedFunctionOpData::ACES_TONESCALE_COMPRESS_20_INV: + { + // Sharing same renderer (param will be inverted to handle direction). + return std::make_shared(func); + } + + case FixedFunctionOpData::ACES_GAMUT_COMPRESS_20_FWD: + case FixedFunctionOpData::ACES_GAMUT_COMPRESS_20_INV: + { + // Sharing same renderer (param will be inverted to handle direction). + return std::make_shared(func); + } + case FixedFunctionOpData::REC2100_SURROUND_FWD: case FixedFunctionOpData::REC2100_SURROUND_INV: { diff --git a/src/OpenColorIO/ops/fixedfunction/FixedFunctionOpData.cpp b/src/OpenColorIO/ops/fixedfunction/FixedFunctionOpData.cpp index ce835fd6a8..1926cc2eb0 100644 --- a/src/OpenColorIO/ops/fixedfunction/FixedFunctionOpData.cpp +++ b/src/OpenColorIO/ops/fixedfunction/FixedFunctionOpData.cpp @@ -1,6 +1,7 @@ // SPDX-License-Identifier: BSD-3-Clause // Copyright Contributors to the OpenColorIO Project. +#include #include #include @@ -11,34 +12,65 @@ namespace OCIO_NAMESPACE { +namespace +{ + void check_param_bounds(const std::string & name, double val, double low, double high) + { + if (val < low || val > high) + { + std::stringstream ss; + ss << "Parameter " << val << " (" << name << ") is outside valid range [" << low << "," << high << "]"; + throw Exception(ss.str().c_str()); + } + }; + + void check_param_no_frac(const std::string & name, double val) + { + if (floor(val) != val) + { + std::stringstream ss; + ss << "Parameter " << val << " (" << name << ") cannot include any fractional component"; + throw Exception(ss.str().c_str()); + } + }; +} + namespace DefaultValues { const int FLOAT_DECIMALS = 7; } -constexpr char ACES_RED_MOD_03_FWD_STR[] = "RedMod03Fwd"; -constexpr char ACES_RED_MOD_03_REV_STR[] = "RedMod03Rev"; -constexpr char ACES_RED_MOD_10_FWD_STR[] = "RedMod10Fwd"; -constexpr char ACES_RED_MOD_10_REV_STR[] = "RedMod10Rev"; -constexpr char ACES_GLOW_03_FWD_STR[] = "Glow03Fwd"; -constexpr char ACES_GLOW_03_REV_STR[] = "Glow03Rev"; -constexpr char ACES_GLOW_10_FWD_STR[] = "Glow10Fwd"; -constexpr char ACES_GLOW_10_REV_STR[] = "Glow10Rev"; -constexpr char ACES_DARK_TO_DIM_10_STR[] = "DarkToDim10"; -constexpr char ACES_DIM_TO_DARK_10_STR[] = "DimToDark10"; -constexpr char ACES_GAMUT_COMP_13_FWD_STR[]= "GamutComp13Fwd"; -constexpr char ACES_GAMUT_COMP_13_REV_STR[]= "GamutComp13Rev"; -constexpr char SURROUND_STR[] = "Surround"; // Old name for Rec2100SurroundFwd -constexpr char REC_2100_SURROUND_FWD_STR[] = "Rec2100SurroundFwd"; -constexpr char REC_2100_SURROUND_REV_STR[] = "Rec2100SurroundRev"; -constexpr char RGB_TO_HSV_STR[] = "RGB_TO_HSV"; -constexpr char HSV_TO_RGB_STR[] = "HSV_TO_RGB"; -constexpr char XYZ_TO_xyY_STR[] = "XYZ_TO_xyY"; -constexpr char xyY_TO_XYZ_STR[] = "xyY_TO_XYZ"; -constexpr char XYZ_TO_uvY_STR[] = "XYZ_TO_uvY"; -constexpr char uvY_TO_XYZ_STR[] = "uvY_TO_XYZ"; -constexpr char XYZ_TO_LUV_STR[] = "XYZ_TO_LUV"; -constexpr char LUV_TO_XYZ_STR[] = "LUV_TO_XYZ"; +constexpr char ACES_RED_MOD_03_FWD_STR[] = "RedMod03Fwd"; +constexpr char ACES_RED_MOD_03_REV_STR[] = "RedMod03Rev"; +constexpr char ACES_RED_MOD_10_FWD_STR[] = "RedMod10Fwd"; +constexpr char ACES_RED_MOD_10_REV_STR[] = "RedMod10Rev"; +constexpr char ACES_GLOW_03_FWD_STR[] = "Glow03Fwd"; +constexpr char ACES_GLOW_03_REV_STR[] = "Glow03Rev"; +constexpr char ACES_GLOW_10_FWD_STR[] = "Glow10Fwd"; +constexpr char ACES_GLOW_10_REV_STR[] = "Glow10Rev"; +constexpr char ACES_DARK_TO_DIM_10_STR[] = "DarkToDim10"; +constexpr char ACES_DIM_TO_DARK_10_STR[] = "DimToDark10"; +constexpr char ACES_GAMUT_COMP_13_FWD_STR[] = "GamutComp13Fwd"; +constexpr char ACES_GAMUT_COMP_13_REV_STR[] = "GamutComp13Rev"; +constexpr char ACES_OUTPUT_TRANSFORM_20_FWD_STR[] = "ACESOutputTransform20Fwd"; +constexpr char ACES_OUTPUT_TRANSFORM_20_INV_STR[] = "ACESOutputTransform20Inv"; +constexpr char ACES_RGB_TO_JMh_20_STR[] = "RGB_TO_JMh_20"; +constexpr char ACES_JMh_TO_RGB_20_STR[] = "JMh_TO_RGB_20"; +constexpr char ACES_TONESCALE_COMPRESS_20_FWD_STR[] = "ToneScaleCompress20Fwd"; +constexpr char ACES_TONESCALE_COMPRESS_20_INV_STR[] = "ToneScaleCompress20Inv"; +constexpr char ACES_GAMUT_COMPRESS_20_FWD_STR[] = "GamutCompress20Fwd"; +constexpr char ACES_GAMUT_COMPRESS_20_INV_STR[] = "GamutCompress20Inv"; +constexpr char SURROUND_STR[] = "Surround"; // Old name for Rec2100SurroundFwd +constexpr char REC_2100_SURROUND_FWD_STR[] = "Rec2100SurroundFwd"; +constexpr char REC_2100_SURROUND_REV_STR[] = "Rec2100SurroundRev"; +constexpr char RGB_TO_HSV_STR[] = "RGB_TO_HSV"; +constexpr char HSV_TO_RGB_STR[] = "HSV_TO_RGB"; +constexpr char XYZ_TO_xyY_STR[] = "XYZ_TO_xyY"; +constexpr char xyY_TO_XYZ_STR[] = "xyY_TO_XYZ"; +constexpr char XYZ_TO_uvY_STR[] = "XYZ_TO_uvY"; +constexpr char uvY_TO_XYZ_STR[] = "uvY_TO_XYZ"; +constexpr char XYZ_TO_LUV_STR[] = "XYZ_TO_LUV"; +constexpr char LUV_TO_XYZ_STR[] = "LUV_TO_XYZ"; // NOTE: Converts the enumeration value to its string representation (i.e. CLF reader). @@ -74,6 +106,22 @@ const char * FixedFunctionOpData::ConvertStyleToString(Style style, bool detaile return detailed ? "ACES_GamutComp13 (Forward)" : ACES_GAMUT_COMP_13_FWD_STR; case ACES_GAMUT_COMP_13_INV: return detailed ? "ACES_GamutComp13 (Inverse)" : ACES_GAMUT_COMP_13_REV_STR; + case ACES_OUTPUT_TRANSFORM_20_FWD: + return detailed ? "ACES_OutputTransform20 (Forward)" : ACES_OUTPUT_TRANSFORM_20_FWD_STR; + case ACES_OUTPUT_TRANSFORM_20_INV: + return detailed ? "ACES_OutputTransform20 (Inverse)" : ACES_OUTPUT_TRANSFORM_20_INV_STR; + case ACES_RGB_TO_JMh_20: + return ACES_RGB_TO_JMh_20_STR; + case ACES_JMh_TO_RGB_20: + return ACES_JMh_TO_RGB_20_STR; + case ACES_TONESCALE_COMPRESS_20_FWD: + return detailed ? "ACES_ToneScaleCompress20 (Forward)" : ACES_TONESCALE_COMPRESS_20_FWD_STR; + case ACES_TONESCALE_COMPRESS_20_INV: + return detailed ? "ACES_ToneScaleCompress20 (Inverse)" : ACES_TONESCALE_COMPRESS_20_INV_STR; + case ACES_GAMUT_COMPRESS_20_FWD: + return detailed ? "ACES_GamutCompress20 (Forward)" : ACES_GAMUT_COMPRESS_20_FWD_STR; + case ACES_GAMUT_COMPRESS_20_INV: + return detailed ? "ACES_GamutCompress20 (Inverse)" : ACES_GAMUT_COMPRESS_20_INV_STR; case REC2100_SURROUND_FWD: return detailed ? "REC2100_Surround (Forward)" : REC_2100_SURROUND_FWD_STR; case REC2100_SURROUND_INV: @@ -155,6 +203,38 @@ FixedFunctionOpData::Style FixedFunctionOpData::GetStyle(const char * name) { return ACES_GAMUT_COMP_13_INV; } + else if (0 == Platform::Strcasecmp(name, ACES_OUTPUT_TRANSFORM_20_FWD_STR)) + { + return ACES_OUTPUT_TRANSFORM_20_FWD; + } + else if (0 == Platform::Strcasecmp(name, ACES_OUTPUT_TRANSFORM_20_INV_STR)) + { + return ACES_OUTPUT_TRANSFORM_20_INV; + } + else if (0 == Platform::Strcasecmp(name, ACES_RGB_TO_JMh_20_STR)) + { + return ACES_RGB_TO_JMh_20; + } + else if (0 == Platform::Strcasecmp(name, ACES_JMh_TO_RGB_20_STR)) + { + return ACES_JMh_TO_RGB_20; + } + else if (0 == Platform::Strcasecmp(name, ACES_TONESCALE_COMPRESS_20_FWD_STR)) + { + return ACES_TONESCALE_COMPRESS_20_FWD; + } + else if (0 == Platform::Strcasecmp(name, ACES_TONESCALE_COMPRESS_20_INV_STR)) + { + return ACES_TONESCALE_COMPRESS_20_INV; + } + else if (0 == Platform::Strcasecmp(name, ACES_GAMUT_COMPRESS_20_FWD_STR)) + { + return ACES_GAMUT_COMPRESS_20_FWD; + } + else if (0 == Platform::Strcasecmp(name, ACES_GAMUT_COMPRESS_20_INV_STR)) + { + return ACES_GAMUT_COMPRESS_20_INV; + } else if (0 == Platform::Strcasecmp(name, SURROUND_STR) || 0 == Platform::Strcasecmp(name, REC_2100_SURROUND_FWD_STR)) { @@ -242,6 +322,26 @@ FixedFunctionOpData::Style FixedFunctionOpData::ConvertStyle(FixedFunctionStyle return isForward ? FixedFunctionOpData::ACES_GAMUT_COMP_13_FWD : FixedFunctionOpData::ACES_GAMUT_COMP_13_INV; } + case FIXED_FUNCTION_ACES_OUTPUT_TRANSFORM_20: + { + return isForward ? FixedFunctionOpData::ACES_OUTPUT_TRANSFORM_20_FWD : + FixedFunctionOpData::ACES_OUTPUT_TRANSFORM_20_INV; + } + case FIXED_FUNCTION_ACES_RGB_TO_JMH_20: + { + return isForward ? FixedFunctionOpData::ACES_RGB_TO_JMh_20 : + FixedFunctionOpData::ACES_JMh_TO_RGB_20; + } + case FIXED_FUNCTION_ACES_TONESCALE_COMPRESS_20: + { + return isForward ? FixedFunctionOpData::ACES_TONESCALE_COMPRESS_20_FWD : + FixedFunctionOpData::ACES_TONESCALE_COMPRESS_20_INV; + } + case FIXED_FUNCTION_ACES_GAMUT_COMPRESS_20: + { + return isForward ? FixedFunctionOpData::ACES_GAMUT_COMPRESS_20_FWD : + FixedFunctionOpData::ACES_GAMUT_COMPRESS_20_INV; + } case FIXED_FUNCTION_REC2100_SURROUND: { return isForward ? FixedFunctionOpData::REC2100_SURROUND_FWD : @@ -307,6 +407,22 @@ FixedFunctionStyle FixedFunctionOpData::ConvertStyle(FixedFunctionOpData::Style case FixedFunctionOpData::ACES_GAMUT_COMP_13_INV: return FIXED_FUNCTION_ACES_GAMUT_COMP_13; + case FixedFunctionOpData::ACES_OUTPUT_TRANSFORM_20_FWD: + case FixedFunctionOpData::ACES_OUTPUT_TRANSFORM_20_INV: + return FIXED_FUNCTION_ACES_OUTPUT_TRANSFORM_20; + + case FixedFunctionOpData::ACES_RGB_TO_JMh_20: + case FixedFunctionOpData::ACES_JMh_TO_RGB_20: + return FIXED_FUNCTION_ACES_RGB_TO_JMH_20; + + case FixedFunctionOpData::ACES_TONESCALE_COMPRESS_20_FWD: + case FixedFunctionOpData::ACES_TONESCALE_COMPRESS_20_INV: + return FIXED_FUNCTION_ACES_TONESCALE_COMPRESS_20; + + case FixedFunctionOpData::ACES_GAMUT_COMPRESS_20_FWD: + case FixedFunctionOpData::ACES_GAMUT_COMPRESS_20_INV: + return FIXED_FUNCTION_ACES_GAMUT_COMPRESS_20; + case FixedFunctionOpData::REC2100_SURROUND_FWD: case FixedFunctionOpData::REC2100_SURROUND_INV: return FIXED_FUNCTION_REC2100_SURROUND; @@ -381,33 +497,79 @@ void FixedFunctionOpData::validate() const const double thr_yellow = m_params[5]; const double power = m_params[6]; - auto check_bounds = [](const std::string & name, double val, double low, double high) - { - if (val < low || val > high) - { - std::stringstream ss; - ss << "Parameter " << val << " (" << name << ") is outside valid range [" << low << "," << high << "]"; - throw Exception(ss.str().c_str()); - } - }; - // Clamped to the smallest increment above 1 in half float precision for numerical stability. static constexpr double lim_low_bound = 1.001; static constexpr double lim_hi_bound = 65504.0; - check_bounds("lim_cyan", lim_cyan, lim_low_bound, lim_hi_bound); - check_bounds("lim_magenta", lim_magenta, lim_low_bound, lim_hi_bound); - check_bounds("lim_yellow", lim_yellow, lim_low_bound, lim_hi_bound); + check_param_bounds("lim_cyan", lim_cyan, lim_low_bound, lim_hi_bound); + check_param_bounds("lim_magenta", lim_magenta, lim_low_bound, lim_hi_bound); + check_param_bounds("lim_yellow", lim_yellow, lim_low_bound, lim_hi_bound); static constexpr double thr_low_bound = 0.0; // Clamped to the smallest increment below 1 in half float precision for numerical stability. static constexpr double thr_hi_bound = 0.9995; - check_bounds("thr_cyan", thr_cyan, thr_low_bound, thr_hi_bound); - check_bounds("thr_magenta", thr_magenta, thr_low_bound, thr_hi_bound); - check_bounds("thr_yellow", thr_yellow, thr_low_bound, thr_hi_bound); + check_param_bounds("thr_cyan", thr_cyan, thr_low_bound, thr_hi_bound); + check_param_bounds("thr_magenta", thr_magenta, thr_low_bound, thr_hi_bound); + check_param_bounds("thr_yellow", thr_yellow, thr_low_bound, thr_hi_bound); static constexpr double pwr_low_bound = 1.0; static constexpr double pwr_hi_bound = 65504.0; - check_bounds("power", power, pwr_low_bound, pwr_hi_bound); + check_param_bounds("power", power, pwr_low_bound, pwr_hi_bound); + } + else if (m_style == ACES_OUTPUT_TRANSFORM_20_FWD || m_style == ACES_OUTPUT_TRANSFORM_20_INV) + { + if (m_params.size() != 9) + { + std::stringstream ss; + ss << "The style '" << ConvertStyleToString(m_style, true) + << "' must have 9 parameters but " + << m_params.size() << " found."; + throw Exception(ss.str().c_str()); + } + + const double peak_luminance = m_params[0]; + check_param_bounds("peak_luminance", peak_luminance, 1, 10000); + check_param_no_frac("peak_luminance", peak_luminance); + } + else if (m_style == ACES_RGB_TO_JMh_20 || m_style == ACES_JMh_TO_RGB_20) + { + if (m_params.size() != 8) + { + std::stringstream ss; + ss << "The style '" << ConvertStyleToString(m_style, true) + << "' must have 8 parameters but " + << m_params.size() << " found."; + throw Exception(ss.str().c_str()); + } + } + else if (m_style == ACES_TONESCALE_COMPRESS_20_FWD || m_style == ACES_TONESCALE_COMPRESS_20_INV) + { + if (m_params.size() != 1) + { + std::stringstream ss; + ss << "The style '" << ConvertStyleToString(m_style, true) + << "' must have 1 parameters but " + << m_params.size() << " found."; + throw Exception(ss.str().c_str()); + } + + const double peak_luminance = m_params[0]; + check_param_bounds("peak_luminance", peak_luminance, 1, 10000); + check_param_no_frac("peak_luminance", peak_luminance); + } + else if (m_style == ACES_GAMUT_COMPRESS_20_FWD || m_style == ACES_GAMUT_COMPRESS_20_INV) + { + if (m_params.size() != 9) + { + std::stringstream ss; + ss << "The style '" << ConvertStyleToString(m_style, true) + << "' must have 9 parameters but " + << m_params.size() << " found."; + throw Exception(ss.str().c_str()); + } + + const double peak_luminance = m_params[0]; + check_param_bounds("peak_luminance", peak_luminance, 1, 10000); + check_param_no_frac("peak_luminance", peak_luminance); } else if (m_style==REC2100_SURROUND_FWD || m_style == REC2100_SURROUND_INV) { @@ -530,6 +692,47 @@ void FixedFunctionOpData::invert() noexcept setStyle(ACES_GAMUT_COMP_13_FWD); break; } + case ACES_OUTPUT_TRANSFORM_20_FWD: + { + setStyle(ACES_OUTPUT_TRANSFORM_20_INV); + break; + } + case ACES_OUTPUT_TRANSFORM_20_INV: + { + setStyle(ACES_OUTPUT_TRANSFORM_20_FWD); + break; + } + case ACES_RGB_TO_JMh_20: + { + setStyle(ACES_JMh_TO_RGB_20); + break; + } + case ACES_JMh_TO_RGB_20: + { + setStyle(ACES_RGB_TO_JMh_20); + break; + } + case ACES_TONESCALE_COMPRESS_20_FWD: + { + setStyle(ACES_TONESCALE_COMPRESS_20_INV); + break; + } + case ACES_TONESCALE_COMPRESS_20_INV: + { + setStyle(ACES_TONESCALE_COMPRESS_20_FWD); + break; + } + case ACES_GAMUT_COMPRESS_20_FWD: + { + setStyle(ACES_GAMUT_COMPRESS_20_INV); + break; + } + case ACES_GAMUT_COMPRESS_20_INV: + { + setStyle(ACES_GAMUT_COMPRESS_20_FWD); + break; + } + case REC2100_SURROUND_FWD: { setStyle(REC2100_SURROUND_INV); @@ -609,6 +812,10 @@ TransformDirection FixedFunctionOpData::getDirection() const noexcept case FixedFunctionOpData::ACES_GLOW_10_FWD: case FixedFunctionOpData::ACES_DARK_TO_DIM_10_FWD: case FixedFunctionOpData::ACES_GAMUT_COMP_13_FWD: + case FixedFunctionOpData::ACES_OUTPUT_TRANSFORM_20_FWD: + case FixedFunctionOpData::ACES_RGB_TO_JMh_20: + case FixedFunctionOpData::ACES_TONESCALE_COMPRESS_20_FWD: + case FixedFunctionOpData::ACES_GAMUT_COMPRESS_20_FWD: case FixedFunctionOpData::REC2100_SURROUND_FWD: case FixedFunctionOpData::RGB_TO_HSV: case FixedFunctionOpData::XYZ_TO_xyY: @@ -622,6 +829,10 @@ TransformDirection FixedFunctionOpData::getDirection() const noexcept case FixedFunctionOpData::ACES_GLOW_10_INV: case FixedFunctionOpData::ACES_DARK_TO_DIM_10_INV: case FixedFunctionOpData::ACES_GAMUT_COMP_13_INV: + case FixedFunctionOpData::ACES_OUTPUT_TRANSFORM_20_INV: + case FixedFunctionOpData::ACES_JMh_TO_RGB_20: + case FixedFunctionOpData::ACES_TONESCALE_COMPRESS_20_INV: + case FixedFunctionOpData::ACES_GAMUT_COMPRESS_20_INV: case FixedFunctionOpData::REC2100_SURROUND_INV: case FixedFunctionOpData::HSV_TO_RGB: case FixedFunctionOpData::xyY_TO_XYZ: diff --git a/src/OpenColorIO/ops/fixedfunction/FixedFunctionOpData.h b/src/OpenColorIO/ops/fixedfunction/FixedFunctionOpData.h index a640c66161..4b3fa99657 100644 --- a/src/OpenColorIO/ops/fixedfunction/FixedFunctionOpData.h +++ b/src/OpenColorIO/ops/fixedfunction/FixedFunctionOpData.h @@ -26,28 +26,36 @@ class FixedFunctionOpData : public OpData enum Style { - ACES_RED_MOD_03_FWD = 0, // Red modifier (ACES 0.3/0.7) - ACES_RED_MOD_03_INV, // Red modifier inverse (ACES 0.3/0.7) - ACES_RED_MOD_10_FWD, // Red modifier (ACES 1.0) - ACES_RED_MOD_10_INV, // Red modifier inverse (ACES v1.0) - ACES_GLOW_03_FWD, // Glow function (ACES 0.3/0.7) - ACES_GLOW_03_INV, // Glow function inverse (ACES 0.3/0.7) - ACES_GLOW_10_FWD, // Glow function (ACES 1.0) - ACES_GLOW_10_INV, // Glow function inverse (ACES 1.0) - ACES_DARK_TO_DIM_10_FWD, // Dark to dim surround correction (ACES 1.0) - ACES_DARK_TO_DIM_10_INV, // Dim to dark surround correction (ACES 1.0) - ACES_GAMUT_COMP_13_FWD, // Parametric Gamut Compression (ACES 1.3) - ACES_GAMUT_COMP_13_INV, // Parametric Gamut Compression inverse (ACES 1.3) - REC2100_SURROUND_FWD, // Rec.2100 surround correction (takes one double for the gamma param) - REC2100_SURROUND_INV, // Rec.2100 surround correction inverse (takes one gamma param) - RGB_TO_HSV, // Classic RGB to HSV function - HSV_TO_RGB, // Classic HSV to RGB function - XYZ_TO_xyY, // CIE XYZ to 1931 xy chromaticity coordinates - xyY_TO_XYZ, // Inverse of above - XYZ_TO_uvY, // CIE XYZ to 1976 u'v' chromaticity coordinates - uvY_TO_XYZ, // Inverse of above - XYZ_TO_LUV, // CIE XYZ to 1976 CIELUV colour space (D65 white) - LUV_TO_XYZ // Inverse of above + ACES_RED_MOD_03_FWD = 0, // Red modifier (ACES 0.3/0.7) + ACES_RED_MOD_03_INV, // Red modifier inverse (ACES 0.3/0.7) + ACES_RED_MOD_10_FWD, // Red modifier (ACES 1.0) + ACES_RED_MOD_10_INV, // Red modifier inverse (ACES v1.0) + ACES_GLOW_03_FWD, // Glow function (ACES 0.3/0.7) + ACES_GLOW_03_INV, // Glow function inverse (ACES 0.3/0.7) + ACES_GLOW_10_FWD, // Glow function (ACES 1.0) + ACES_GLOW_10_INV, // Glow function inverse (ACES 1.0) + ACES_DARK_TO_DIM_10_FWD, // Dark to dim surround correction (ACES 1.0) + ACES_DARK_TO_DIM_10_INV, // Dim to dark surround correction (ACES 1.0) + ACES_GAMUT_COMP_13_FWD, // Parametric Gamut Compression (ACES 1.3) + ACES_GAMUT_COMP_13_INV, // Parametric Gamut Compression inverse (ACES 1.3) + REC2100_SURROUND_FWD, // Rec.2100 surround correction (takes one double for the gamma param) + REC2100_SURROUND_INV, // Rec.2100 surround correction inverse (takes one gamma param) + RGB_TO_HSV, // Classic RGB to HSV function + HSV_TO_RGB, // Classic HSV to RGB function + XYZ_TO_xyY, // CIE XYZ to 1931 xy chromaticity coordinates + xyY_TO_XYZ, // Inverse of above + XYZ_TO_uvY, // CIE XYZ to 1976 u'v' chromaticity coordinates + uvY_TO_XYZ, // Inverse of above + XYZ_TO_LUV, // CIE XYZ to 1976 CIELUV colour space (D65 white) + LUV_TO_XYZ, // Inverse of above + ACES_OUTPUT_TRANSFORM_20_FWD, // ACES2 Output transform + ACES_OUTPUT_TRANSFORM_20_INV, // ACES2 Output transform (inverse) + ACES_RGB_TO_JMh_20, // ACES2 RGB to JMh + ACES_JMh_TO_RGB_20, // ACES2 JMh to RGB + ACES_TONESCALE_COMPRESS_20_FWD, // ACES2 Tonescale and chroma compression + ACES_TONESCALE_COMPRESS_20_INV, // ACES2 Tonescale and chroma compression (inv) + ACES_GAMUT_COMPRESS_20_FWD, // ACES2 Gamut compression + ACES_GAMUT_COMPRESS_20_INV // ACES2 Gamut compression (inv) }; static const char * ConvertStyleToString(Style style, bool detailed); diff --git a/src/OpenColorIO/ops/fixedfunction/FixedFunctionOpGPU.cpp b/src/OpenColorIO/ops/fixedfunction/FixedFunctionOpGPU.cpp index 20c1b1b1b4..7161deafb3 100644 --- a/src/OpenColorIO/ops/fixedfunction/FixedFunctionOpGPU.cpp +++ b/src/OpenColorIO/ops/fixedfunction/FixedFunctionOpGPU.cpp @@ -5,7 +5,9 @@ #include +#include "utils/StringUtils.h" #include "ops/fixedfunction/FixedFunctionOpGPU.h" +#include "ACES2/Transform.h" namespace OCIO_NAMESPACE @@ -347,6 +349,1305 @@ void Add_GamutComp_13_Inv_Shader(GpuShaderText & ss, ); } +void _Add_RGB_to_JMh_Shader( + GpuShaderCreatorRcPtr & shaderCreator, + GpuShaderText & ss, + const ACES2::JMhParams & p) +{ + const std::string pxl(shaderCreator->getPixelName()); + + ss.newLine() << ss.float3Decl("lms") << " = " << ss.mat3fMul(&p.MATRIX_RGB_to_CAM16[0], pxl + ".rgb") << ";"; + ss.newLine() << "lms = " << "lms * " << ss.float3Const(p.D_RGB[0], p.D_RGB[1], p.D_RGB[2]) << ";"; + + ss.newLine() << ss.float3Decl("F_L_v") << " = pow(" << p.F_L << " * abs(lms) / 100.0, " << ss.float3Const(0.42f) << ");"; + ss.newLine() << ss.float3Decl("rgb_a") << " = (400.0 * sign(lms) * F_L_v) / (27.13 + F_L_v);"; + + ss.newLine() << ss.floatDecl("A") << " = 2.0 * rgb_a.r + rgb_a.g + 0.05 * rgb_a.b;"; + ss.newLine() << ss.floatDecl("a") << " = rgb_a.r - 12.0 * rgb_a.g / 11.0 + rgb_a.b / 11.0;"; + ss.newLine() << ss.floatDecl("b") << " = (rgb_a.r + rgb_a.g - 2.0 * rgb_a.b) / 9.0;"; + + ss.newLine() << ss.floatDecl("J") << " = 100.0 * pow(A / " << p.A_w << ", " << ACES2::surround[1] << " * " << p.z << ");"; + + ss.newLine() << ss.floatDecl("M") << " = (J == 0.0) ? 0.0 : 43.0 * " << ACES2::surround[2] << " * sqrt(a * a + b * b);"; + + ss.newLine() << ss.floatDecl("h") << " = (a == 0.0) ? 0.0 : " << ss.atan2("b", "a") << " * 180.0 / 3.14159265358979;"; + ss.newLine() << "h = h - floor(h / 360.0) * 360.0;"; + ss.newLine() << "h = (h < 0.0) ? h + 360.0 : h;"; + + ss.newLine() << pxl << ".rgb = " << ss.float3Const("J", "M", "h") << ";"; +} + +void _Add_JMh_to_RGB_Shader( + GpuShaderCreatorRcPtr & shaderCreator, + GpuShaderText & ss, + const ACES2::JMhParams & p) +{ + const std::string pxl(shaderCreator->getPixelName()); + + ss.newLine() << ss.floatDecl("h") << " = " << pxl << ".b * 3.14159265358979 / 180.0;"; + + ss.newLine() << ss.floatDecl("scale") << " = " << pxl << ".g / (43.0 * " << ACES2::surround[2] << ");"; + ss.newLine() << ss.floatDecl("A") << " = " << p.A_w << " * pow(" << pxl << ".r / 100.0, 1.0 / (" << ACES2::surround[1] << " * " << p.z << "));"; + ss.newLine() << ss.floatDecl("a") << " = scale * cos(h);"; + ss.newLine() << ss.floatDecl("b") << " = scale * sin(h);"; + + ss.newLine() << ss.float3Decl("rgb_a") << ";"; + ss.newLine() << "rgb_a.r = (460.0 * A + 451.0 * a + 288.0 *b) / 1403.0;"; + ss.newLine() << "rgb_a.g = (460.0 * A - 891.0 * a - 261.0 *b) / 1403.0;"; + ss.newLine() << "rgb_a.b = (460.0 * A - 220.0 * a - 6300.0 *b) / 1403.0;"; + + ss.newLine() << ss.float3Decl("lms") << " = sign(rgb_a) * 100.0 / " << p.F_L << " * pow(27.13 * abs(rgb_a) / (400.0 - abs(rgb_a)), " << ss.float3Const(1.f / 0.42f) << ");"; + ss.newLine() << "lms = " << "lms / " << ss.float3Const(p.D_RGB[0], p.D_RGB[1], p.D_RGB[2]) << ";"; + + ss.newLine() << pxl << ".rgb = " << ss.mat3fMul(&p.MATRIX_CAM16_to_RGB[0], "lms") << ";"; +} + +std::string _Add_Reach_table( + GpuShaderCreatorRcPtr & shaderCreator, + unsigned resourceIndex, + const ACES2::Table1D & table) +{ + // Reserve name + std::ostringstream resName; + resName << shaderCreator->getResourcePrefix() + << std::string("_") + << std::string("reach_m_table") + << resourceIndex; + + // Note: Remove potentially problematic double underscores from GLSL resource names. + std::string name(resName.str()); + StringUtils::ReplaceInPlace(name, "__", "_"); + + // Register texture + GpuShaderDesc::TextureDimensions dimensions = GpuShaderDesc::TEXTURE_1D; + if (shaderCreator->getLanguage() == GPU_LANGUAGE_GLSL_ES_1_0 + || shaderCreator->getLanguage() == GPU_LANGUAGE_GLSL_ES_3_0 + || !shaderCreator->getAllowTexture1D()) + { + dimensions = GpuShaderDesc::TEXTURE_2D; + } + + shaderCreator->addTexture( + name.c_str(), + GpuShaderText::getSamplerName(name).c_str(), + table.size, + 1, + GpuShaderCreator::TEXTURE_RED_CHANNEL, + dimensions, + INTERP_NEAREST, + &(table.table[0])); + + + if (dimensions == GpuShaderDesc::TEXTURE_1D) + { + GpuShaderText ss(shaderCreator->getLanguage()); + ss.declareTex1D(name); + shaderCreator->addToDeclareShaderCode(ss.string().c_str()); + } + else + { + GpuShaderText ss(shaderCreator->getLanguage()); + ss.declareTex2D(name); + shaderCreator->addToDeclareShaderCode(ss.string().c_str()); + } + + // Sampler function + GpuShaderText ss(shaderCreator->getLanguage()); + + ss.newLine() << ss.floatKeyword() << " " << name << "_sample(float h)"; + ss.newLine() << "{"; + ss.indent(); + + ss.newLine() << ss.floatDecl("hwrap") << " = h;"; + ss.newLine() << "hwrap = hwrap - floor(hwrap / 360.0) * 360.0;"; + ss.newLine() << "hwrap = (hwrap < 0.0) ? hwrap + 360.0 : hwrap;"; + + ss.newLine() << ss.floatDecl("i_lo") << " = floor(hwrap);"; + ss.newLine() << ss.floatDecl("i_hi") << " = (i_lo + 1);"; + ss.newLine() << "i_hi = i_hi - floor(i_hi / 360.0) * 360.0;"; + + if (dimensions == GpuShaderDesc::TEXTURE_1D) + { + ss.newLine() << ss.floatDecl("lo") << " = " << ss.sampleTex1D(name, "(i_lo + 0.5) / 360.0") << ".r;"; + ss.newLine() << ss.floatDecl("hi") << " = " << ss.sampleTex1D(name, "(i_hi + 0.5) / 360.0") << ".r;"; + } + else + { + ss.newLine() << ss.floatDecl("lo") << " = " << ss.sampleTex2D(name, ss.float2Const("(i_lo + 0.5) / 360.0", "0.0")) << ".r;"; + ss.newLine() << ss.floatDecl("hi") << " = " << ss.sampleTex2D(name, ss.float2Const("(i_hi + 0.5) / 360.0", "0.5")) << ".r;"; + } + + ss.newLine() << ss.floatDecl("t") << " = (h - i_lo) / (i_hi - i_lo);"; + ss.newLine() << "return " << ss.lerp("lo", "hi", "t") << ";"; + + ss.dedent(); + ss.newLine() << "}"; + + shaderCreator->addToHelperShaderCode(ss.string().c_str()); + + return name; +} + +std::string _Add_Toe_func( + GpuShaderCreatorRcPtr & shaderCreator, + unsigned resourceIndex, + bool invert) +{ + // Reserve name + std::ostringstream resName; + resName << shaderCreator->getResourcePrefix() + << std::string("_") + << std::string("toe") + << (invert ? std::string("_inv") : std::string("_fwd")) + << resourceIndex; + + // Note: Remove potentially problematic double underscores from GLSL resource names. + std::string name(resName.str()); + StringUtils::ReplaceInPlace(name, "__", "_"); + + GpuShaderText ss(shaderCreator->getLanguage()); + + ss.newLine() << ss.floatKeyword() << " " << name << "(float x, float limit, float k1_in, float k2_in)"; + ss.newLine() << "{"; + ss.indent(); + + ss.newLine() << ss.floatDecl("k2") << " = max(k2_in, 0.001);"; + ss.newLine() << ss.floatDecl("k1") << " = sqrt(k1_in * k1_in + k2 * k2);"; + ss.newLine() << ss.floatDecl("k3") << " = (limit + k1) / (limit + k2);"; + + if (invert) + { + ss.newLine() << "return (x > limit) ? x : (x * x + k1 * x) / (k3 * (x + k2));"; + } + else + { + ss.newLine() << "return (x > limit) ? x : 0.5 * (k3 * x - k1 + sqrt((k3 * x - k1) * (k3 * x - k1) + 4.0 * k2 * k3 * x));"; + } + + ss.dedent(); + ss.newLine() << "}"; + + shaderCreator->addToHelperShaderCode(ss.string().c_str()); + + return name; +} + +void _Add_Tonescale_Compress_Fwd_Shader( + GpuShaderCreatorRcPtr & shaderCreator, + GpuShaderText & ss, + unsigned resourceIndex, + const ACES2::JMhParams & p, + const ACES2::ToneScaleParams & t, + const ACES2::ChromaCompressParams & c, + const std::string & reachName) +{ + std::string toeName = _Add_Toe_func(shaderCreator, resourceIndex, false); + + const std::string pxl(shaderCreator->getPixelName()); + + ss.newLine() << ss.floatDecl("J") << " = " << pxl << ".r;"; + ss.newLine() << ss.floatDecl("M") << " = " << pxl << ".g;"; + ss.newLine() << ss.floatDecl("h") << " = " << pxl << ".b;"; + + // Tonescale applied in Y (convert to and from J) + ss.newLine() << ss.floatDecl("A") << " = " << p.A_w_J << " * pow(abs(J) / 100.0, 1.0 / (" << ACES2::surround[1] << " * " << p.z << "));"; + ss.newLine() << ss.floatDecl("Y") << " = sign(J) * 100.0 / " << p.F_L << " * pow((27.13 * A) / (400.0 - A), 1.0 / 0.42) / 100.0;"; + + ss.newLine() << ss.floatDecl("f") << " = " << t.m_2 << " * pow(max(0.0, Y) / (Y + " << t.s_2 << "), " << t.g << ");"; + ss.newLine() << ss.floatDecl("Y_ts") << " = max(0.0, f * f / (f + " << t.t_1 << ")) * " << t.n_r << ";"; + + ss.newLine() << ss.floatDecl("F_L_Y") << " = pow(" << p.F_L << " * abs(Y_ts) / 100.0, 0.42);"; + ss.newLine() << ss.floatDecl("J_ts") << " = sign(Y_ts) * 100.0 * pow(((400.0 * F_L_Y) / (27.13 + F_L_Y)) / " << p.A_w_J << ", " << ACES2::surround[1] << " * " << p.z << ");"; + + // ChromaCompress + ss.newLine() << ss.floatDecl("M_cp") << " = M;"; + + ss.newLine() << "if (M != 0.0)"; + ss.newLine() << "{"; + ss.indent(); + + ss.newLine() << ss.floatDecl("nJ") << " = J_ts / " << c.limit_J_max << ";"; + ss.newLine() << ss.floatDecl("snJ") << " = max(0.0, 1.0 - nJ);"; + + // Mnorm + ss.newLine() << ss.floatDecl("Mnorm") << ";"; + ss.newLine() << "{"; + ss.indent(); + + ss.newLine() << ss.floatDecl("PI") << " = 3.14159265358979;"; + ss.newLine() << ss.floatDecl("h_rad") << " = h / 180.0 * PI;"; + ss.newLine() << ss.floatDecl("a") << " = cos(h_rad);"; + ss.newLine() << ss.floatDecl("b") << " = sin(h_rad);"; + ss.newLine() << ss.floatDecl("cos_hr2") << " = a * a - b * b;"; + ss.newLine() << ss.floatDecl("sin_hr2") << " = 2.0 * a * b;"; + ss.newLine() << ss.floatDecl("cos_hr3") << " = 4.0 * a * a * a - 3.0 * a;"; + ss.newLine() << ss.floatDecl("sin_hr3") << " = 3.0 * b - 4.0 * b * b * b;"; + ss.newLine() << ss.floatDecl("M") << " = 11.34072 * a + 16.46899 * cos_hr2 + 7.88380 * cos_hr3 + 14.66441 * b + -6.37224 * sin_hr2 + 9.19364 * sin_hr3 + 77.12896;"; + ss.newLine() << "Mnorm = M * " << c.chroma_compress_scale << ";"; + + ss.dedent(); + ss.newLine() << "}"; + + ss.newLine() << ss.floatDecl("reachM") << " = " << reachName << "_sample(h);"; + ss.newLine() << ss.floatDecl("limit") << " = pow(nJ, " << c.model_gamma << ") * reachM / Mnorm;"; + ss.newLine() << "M_cp = M * pow(J_ts / J, " << c.model_gamma << ");"; + ss.newLine() << "M_cp = M_cp / Mnorm;"; + + ss.newLine() << "M_cp = limit - " << toeName << "(limit - M_cp, limit - 0.001, snJ * " << c.sat << ", sqrt(nJ * nJ + " << c.sat_thr << "));"; + ss.newLine() << "M_cp = " << toeName << "(M_cp, limit, nJ * " << c.compr << ", snJ);"; + ss.newLine() << "M_cp = M_cp * Mnorm;"; + + ss.dedent(); + ss.newLine() << "}"; + + ss.newLine() << pxl << ".rgb = " << ss.float3Const("J_ts", "M_cp", "h") << ";"; +} + +void _Add_Tonescale_Compress_Inv_Shader( + GpuShaderCreatorRcPtr & shaderCreator, + GpuShaderText & ss, + unsigned resourceIndex, + const ACES2::JMhParams & p, + const ACES2::ToneScaleParams & t, + const ACES2::ChromaCompressParams & c, + const std::string & reachName) +{ + std::string toeName = _Add_Toe_func(shaderCreator, resourceIndex, true); + + const std::string pxl(shaderCreator->getPixelName()); + + ss.newLine() << ss.floatDecl("J_ts") << " = " << pxl << ".r;"; + ss.newLine() << ss.floatDecl("M_cp") << " = " << pxl << ".g;"; + ss.newLine() << ss.floatDecl("h") << " = " << pxl << ".b;"; + + // Inverse Tonescale applied in Y (convert to and from J) + ss.newLine() << ss.floatDecl("A") << " = " << p.A_w_J << " * pow(abs(J_ts) / 100.0, 1.0 / (" << ACES2::surround[1] << " * " << p.z << "));"; + ss.newLine() << ss.floatDecl("Y_ts") << " = sign(J_ts) * 100.0 / " << p.F_L << " * pow((27.13 * A) / (400.0 - A), 1.0 / 0.42) / 100.0;"; + + ss.newLine() << ss.floatDecl("Z") << " = max(0.0, min(" << t.n << " / (" << t.u_2 * t.n_r << "), Y_ts));"; + ss.newLine() << ss.floatDecl("ht") << " = (Z + sqrt(Z * (4.0 * " << t.t_1 << " + Z))) / 2.0;"; + ss.newLine() << ss.floatDecl("Y") << " = " << t.s_2 << " / (pow((" << t.m_2 << " / ht), (1.0 / " << t.g << ")) - 1.0);"; + + ss.newLine() << ss.floatDecl("F_L_Y") << " = pow(" << p.F_L << " * abs(Y * 100.0) / 100.0, 0.42);"; + ss.newLine() << ss.floatDecl("J") << " = sign(Y) * 100.0 * pow(((400.0 * F_L_Y) / (27.13 + F_L_Y)) / " << p.A_w_J << ", " << ACES2::surround[1] << " * " << p.z << ");"; + + // ChromaCompress + ss.newLine() << ss.floatDecl("M") << " = M_cp;"; + + ss.newLine() << "if (M_cp != 0.0)"; + ss.newLine() << "{"; + ss.indent(); + + ss.newLine() << ss.floatDecl("nJ") << " = J_ts / " << c.limit_J_max << ";"; + ss.newLine() << ss.floatDecl("snJ") << " = max(0.0, 1.0 - nJ);"; + + // Mnorm + ss.newLine() << ss.floatDecl("Mnorm") << ";"; + ss.newLine() << "{"; + ss.indent(); + + ss.newLine() << ss.floatDecl("PI") << " = 3.14159265358979;"; + ss.newLine() << ss.floatDecl("h_rad") << " = h / 180.0 * PI;"; + ss.newLine() << ss.floatDecl("a") << " = cos(h_rad);"; + ss.newLine() << ss.floatDecl("b") << " = sin(h_rad);"; + ss.newLine() << ss.floatDecl("cos_hr2") << " = a * a - b * b;"; + ss.newLine() << ss.floatDecl("sin_hr2") << " = 2.0 * a * b;"; + ss.newLine() << ss.floatDecl("cos_hr3") << " = 4.0 * a * a * a - 3.0 * a;"; + ss.newLine() << ss.floatDecl("sin_hr3") << " = 3.0 * b - 4.0 * b * b * b;"; + ss.newLine() << ss.floatDecl("M") << " = 11.34072 * a + 16.46899 * cos_hr2 + 7.88380 * cos_hr3 + 14.66441 * b + -6.37224 * sin_hr2 + 9.19364 * sin_hr3 + 77.12896;"; + ss.newLine() << "Mnorm = M * " << c.chroma_compress_scale << ";"; + + ss.dedent(); + ss.newLine() << "}"; + + ss.newLine() << ss.floatDecl("reachM") << " = " << reachName << "_sample(h);"; + ss.newLine() << ss.floatDecl("limit") << " = pow(nJ, " << c.model_gamma << ") * reachM / Mnorm;"; + + ss.newLine() << "M = M_cp / Mnorm;"; + ss.newLine() << "M = " << toeName << "(M, limit, nJ * " << c.compr << ", snJ);"; + ss.newLine() << "M = limit - " << toeName << "(limit - M, limit - 0.001, snJ * " << c.sat << ", sqrt(nJ * nJ + " << c.sat_thr << "));"; + ss.newLine() << "M = M * Mnorm;"; + ss.newLine() << "M = M * pow(J_ts / J, " << -c.model_gamma << ");"; + + ss.dedent(); + ss.newLine() << "}"; + + ss.newLine() << pxl << ".rgb = " << ss.float3Const("J", "M", "h") << ";"; +} + +std::string _Add_Cusp_table( + GpuShaderCreatorRcPtr & shaderCreator, + unsigned resourceIndex, + const ACES2::GamutCompressParams & g) +{ + // Reserve name + std::ostringstream resName; + resName << shaderCreator->getResourcePrefix() + << std::string("_") + << std::string("gamut_cusp_table") + << resourceIndex; + + // Note: Remove potentially problematic double underscores from GLSL resource names. + std::string name(resName.str()); + StringUtils::ReplaceInPlace(name, "__", "_"); + + // Register texture + GpuShaderDesc::TextureDimensions dimensions = GpuShaderDesc::TEXTURE_1D; + if (shaderCreator->getLanguage() == GPU_LANGUAGE_GLSL_ES_1_0 + || shaderCreator->getLanguage() == GPU_LANGUAGE_GLSL_ES_3_0 + || !shaderCreator->getAllowTexture1D()) + { + dimensions = GpuShaderDesc::TEXTURE_2D; + } + + shaderCreator->addTexture( + name.c_str(), + GpuShaderText::getSamplerName(name).c_str(), + g.gamut_cusp_table.total_size, + 1, + GpuShaderCreator::TEXTURE_RGB_CHANNEL, + dimensions, + INTERP_NEAREST, + &(g.gamut_cusp_table.table[0][0])); + + if (dimensions == GpuShaderDesc::TEXTURE_1D) + { + GpuShaderText ss(shaderCreator->getLanguage()); + ss.declareTex1D(name); + shaderCreator->addToDeclareShaderCode(ss.string().c_str()); + } + else + { + GpuShaderText ss(shaderCreator->getLanguage()); + ss.declareTex2D(name); + shaderCreator->addToDeclareShaderCode(ss.string().c_str()); + } + + // Sampler function + GpuShaderText ss(shaderCreator->getLanguage()); + + const std::string hues_array_name = name + "_hues_array"; + std::vector hues_array(g.gamut_cusp_table.total_size); + for (int i = 0; i < g.gamut_cusp_table.total_size; ++i) + { + hues_array[i] = g.gamut_cusp_table.table[i][2]; + } + ss.declareFloatArrayConst(hues_array_name, (int) hues_array.size(), hues_array.data()); + + ss.newLine() << ss.float2Keyword() << " " << name << "_sample(float h)"; + ss.newLine() << "{"; + ss.indent(); + + ss.newLine() << ss.floatDecl("i_lo") << " = 0;"; + ss.newLine() << ss.floatDecl("i_hi") << " = " << g.gamut_cusp_table.base_index + g.gamut_cusp_table.size << ";"; + + ss.newLine() << ss.floatDecl("hwrap") << " = h;"; + ss.newLine() << "hwrap = hwrap - floor(hwrap / 360.0) * 360.0;"; + ss.newLine() << "hwrap = (hwrap < 0.0) ? hwrap + 360.0 : hwrap;"; + ss.newLine() << ss.intDecl("i") << " = " << ss.intKeyword() << "(hwrap + " << g.gamut_cusp_table.base_index << ");"; + + ss.newLine() << "while (i_lo + 1 < i_hi)"; + ss.newLine() << "{"; + ss.indent(); + + ss.newLine() << ss.floatDecl("hcur") << " = " << hues_array_name << "[i];"; + + ss.newLine() << "if (h > hcur)"; + ss.newLine() << "{"; + ss.indent(); + ss.newLine() << "i_lo = i;"; + ss.dedent(); + ss.newLine() << "}"; + ss.newLine() << "else"; + ss.newLine() << "{"; + ss.indent(); + ss.newLine() << "i_hi = i;"; + ss.dedent(); + ss.newLine() << "}"; + ss.newLine() << "i = " << ss.intKeyword() << "((i_lo + i_hi) / 2.0);"; + + ss.dedent(); + ss.newLine() << "}"; + + if (dimensions == GpuShaderDesc::TEXTURE_1D) + { + ss.newLine() << ss.float3Decl("lo") << " = " << ss.sampleTex1D(name, std::string("(i_hi - 1 + 0.5) / ") + std::to_string(g.gamut_cusp_table.total_size)) << ".rgb;"; + ss.newLine() << ss.float3Decl("hi") << " = " << ss.sampleTex1D(name, std::string("(i_hi + 0.5) / ") + std::to_string(g.gamut_cusp_table.total_size)) << ".rgb;"; + } + else + { + ss.newLine() << ss.float3Decl("lo") << " = " << ss.sampleTex2D(name, ss.float2Const(std::string("(i_hi - 1 + 0.5) / ") + std::to_string(g.gamut_cusp_table.total_size), "0.5")) << ".rgb;"; + ss.newLine() << ss.float3Decl("hi") << " = " << ss.sampleTex2D(name, ss.float2Const(std::string("(i_hi + 0.5) / ") + std::to_string(g.gamut_cusp_table.total_size), "0.5")) << ".rgb;"; + } + + ss.newLine() << ss.floatDecl("t") << " = (h - lo.b) / (hi.b - lo.b);"; + ss.newLine() << ss.floatDecl("cuspJ") << " = " << ss.lerp("lo.r", "hi.r", "t") << ";"; + ss.newLine() << ss.floatDecl("cuspM") << " = " << ss.lerp("lo.g", "hi.g", "t") << ";"; + + ss.newLine() << "return " << ss.float2Const("cuspJ", "cuspM") << ";"; + + ss.dedent(); + ss.newLine() << "}"; + + shaderCreator->addToHelperShaderCode(ss.string().c_str()); + + return name; +} + +std::string _Add_Gamma_table( + GpuShaderCreatorRcPtr & shaderCreator, + unsigned resourceIndex, + const ACES2::GamutCompressParams & g) +{ + // Reserve name + std::ostringstream resName; + resName << shaderCreator->getResourcePrefix() + << std::string("_") + << std::string("upper_hull_gamma_table") + << resourceIndex; + + // Note: Remove potentially problematic double underscores from GLSL resource names. + std::string name(resName.str()); + StringUtils::ReplaceInPlace(name, "__", "_"); + + // Register texture + GpuShaderDesc::TextureDimensions dimensions = GpuShaderDesc::TEXTURE_1D; + if (shaderCreator->getLanguage() == GPU_LANGUAGE_GLSL_ES_1_0 + || shaderCreator->getLanguage() == GPU_LANGUAGE_GLSL_ES_3_0 + || !shaderCreator->getAllowTexture1D()) + { + dimensions = GpuShaderDesc::TEXTURE_2D; + } + + shaderCreator->addTexture( + name.c_str(), + GpuShaderText::getSamplerName(name).c_str(), + g.gamut_cusp_table.total_size, + 1, + GpuShaderCreator::TEXTURE_RED_CHANNEL, + dimensions, + INTERP_NEAREST, + &(g.upper_hull_gamma_table.table[0])); + + + if (dimensions == GpuShaderDesc::TEXTURE_1D) + { + GpuShaderText ss(shaderCreator->getLanguage()); + ss.declareTex1D(name); + shaderCreator->addToDeclareShaderCode(ss.string().c_str()); + } + else + { + GpuShaderText ss(shaderCreator->getLanguage()); + ss.declareTex2D(name); + shaderCreator->addToDeclareShaderCode(ss.string().c_str()); + } + + // Sampler function + GpuShaderText ss(shaderCreator->getLanguage()); + + ss.newLine() << ss.floatKeyword() << " " << name << "_sample(float h)"; + ss.newLine() << "{"; + ss.indent(); + + ss.newLine() << ss.floatDecl("hwrap") << " = h;"; + ss.newLine() << "hwrap = hwrap - floor(hwrap / 360.0) * 360.0;"; + ss.newLine() << "hwrap = (hwrap < 0.0) ? hwrap + 360.0 : hwrap;"; + + ss.newLine() << ss.floatDecl("i_lo") << " = floor(hwrap) + " << g.upper_hull_gamma_table.base_index << ";"; + ss.newLine() << ss.floatDecl("i_hi") << " = (i_lo + 1);"; + ss.newLine() << "i_hi = i_hi - floor(i_hi / 360.0) * 360.0;"; + + ss.newLine() << ss.floatDecl("base_hue") << " = i_lo - " << g.upper_hull_gamma_table.base_index << ";"; + + if (dimensions == GpuShaderDesc::TEXTURE_1D) + { + ss.newLine() << ss.floatDecl("lo") << " = " << ss.sampleTex1D(name, std::string("(i_lo + 0.5) / ") + std::to_string(g.upper_hull_gamma_table.total_size)) << ".r;"; + ss.newLine() << ss.floatDecl("hi") << " = " << ss.sampleTex1D(name, std::string("(i_hi + 0.5) / ") + std::to_string(g.upper_hull_gamma_table.total_size)) << ".r;"; + } + else + { + ss.newLine() << ss.floatDecl("lo") << " = " << ss.sampleTex2D(name, ss.float2Const(std::string("(i_lo + 0.5) / ") + std::to_string(g.upper_hull_gamma_table.total_size), "0.5")) << ".r;"; + ss.newLine() << ss.floatDecl("hi") << " = " << ss.sampleTex2D(name, ss.float2Const(std::string("(i_hi + 0.5) / ") + std::to_string(g.upper_hull_gamma_table.total_size), "0.5")) << ".r;"; + } + + ss.newLine() << ss.floatDecl("t") << " = hwrap - base_hue;"; + ss.newLine() << "return " << ss.lerp("lo", "hi", "t") << ";"; + + ss.dedent(); + ss.newLine() << "}"; + + shaderCreator->addToHelperShaderCode(ss.string().c_str()); + + return name; +} + +std::string _Add_Focus_Gain_func( + GpuShaderCreatorRcPtr & shaderCreator, + unsigned resourceIndex, + const ACES2::GamutCompressParams & g) +{ + // Reserve name + std::ostringstream resName; + resName << shaderCreator->getResourcePrefix() + << std::string("_") + << std::string("get_focus_gain") + << resourceIndex; + + // Note: Remove potentially problematic double underscores from GLSL resource names. + std::string name(resName.str()); + StringUtils::ReplaceInPlace(name, "__", "_"); + + GpuShaderText ss(shaderCreator->getLanguage()); + + ss.newLine() << ss.floatKeyword() << " " << name << "(float J, float cuspJ)"; + ss.newLine() << "{"; + ss.indent(); + + ss.newLine() << ss.floatDecl("thr") << " = " << ss.lerp("cuspJ", std::to_string(g.limit_J_max), std::to_string(ACES2::focus_gain_blend)) << ";"; + + ss.newLine() << "if (J > thr)"; + ss.newLine() << "{"; + ss.indent(); + ss.newLine() << ss.floatDecl("gain") << " = ( " << g.limit_J_max << " - thr) / max(0.0001, (" << g.limit_J_max << " - min(" << g.limit_J_max << ", J)));"; + ss.newLine() << "return pow(log(gain)/log(10.0), 1.0 / " << ACES2::focus_adjust_gain << ") + 1.0;"; + ss.dedent(); + ss.newLine() << "}"; + ss.newLine() << "else"; + ss.newLine() << "{"; + ss.indent(); + ss.newLine() << "return 1.0;"; + ss.dedent(); + ss.newLine() << "}"; + + ss.dedent(); + ss.newLine() << "}"; + + shaderCreator->addToHelperShaderCode(ss.string().c_str()); + + return name; +} + +std::string _Add_Solve_J_Intersect_func( + GpuShaderCreatorRcPtr & shaderCreator, + unsigned resourceIndex, + const ACES2::GamutCompressParams & g) +{ + // Reserve name + std::ostringstream resName; + resName << shaderCreator->getResourcePrefix() + << std::string("_") + << std::string("solve_J_intersect") + << resourceIndex; + + // Note: Remove potentially problematic double underscores from GLSL resource names. + std::string name(resName.str()); + StringUtils::ReplaceInPlace(name, "__", "_"); + + GpuShaderText ss(shaderCreator->getLanguage()); + + ss.newLine() << ss.floatKeyword() << " " << name << "(float J, float M, float focusJ, float slope_gain)"; + ss.newLine() << "{"; + ss.indent(); + + ss.newLine() << ss.floatDecl("a") << " = " << "M / (focusJ * slope_gain);"; + ss.newLine() << ss.floatDecl("b") << " = 0.0;"; + ss.newLine() << ss.floatDecl("c") << " = 0.0;"; + ss.newLine() << ss.floatDecl("intersectJ") << " = 0.0;"; + + ss.newLine() << "if (J < focusJ)"; + ss.newLine() << "{"; + ss.indent(); + ss.newLine() << "b = 1.0 - M / slope_gain;"; + ss.dedent(); + ss.newLine() << "}"; + ss.newLine() << "else"; + ss.newLine() << "{"; + ss.indent(); + ss.newLine() << "b = - (1.0 + M / slope_gain + " << g.limit_J_max << " * M / (focusJ * slope_gain));"; + ss.dedent(); + ss.newLine() << "}"; + + ss.newLine() << "if (J < focusJ)"; + ss.newLine() << "{"; + ss.indent(); + ss.newLine() << "c = -J;"; + ss.dedent(); + ss.newLine() << "}"; + ss.newLine() << "else"; + ss.newLine() << "{"; + ss.indent(); + ss.newLine() << "c = " << g.limit_J_max << " * M / slope_gain + J;"; + ss.dedent(); + ss.newLine() << "}"; + + ss.newLine() << ss.floatDecl("root") << " = sqrt(b * b - 4.0 * a * c);"; + + ss.newLine() << "if (J < focusJ)"; + ss.newLine() << "{"; + ss.indent(); + ss.newLine() << "intersectJ = 2.0 * c / (-b - root);"; + ss.dedent(); + ss.newLine() << "}"; + ss.newLine() << "else"; + ss.newLine() << "{"; + ss.indent(); + ss.newLine() << "intersectJ = 2.0 * c / (-b + root);"; + ss.dedent(); + ss.newLine() << "}"; + + ss.newLine() << "return intersectJ;"; + + ss.dedent(); + ss.newLine() << "}"; + + shaderCreator->addToHelperShaderCode(ss.string().c_str()); + + return name; +} + +std::string _Add_Find_Gamut_Boundary_Intersection_func( + GpuShaderCreatorRcPtr & shaderCreator, + unsigned resourceIndex, + const ACES2::GamutCompressParams & g, + const std::string & solveJIntersectName) +{ + // Reserve name + std::ostringstream resName; + resName << shaderCreator->getResourcePrefix() + << std::string("_") + << std::string("find_gamut_boundary_intersection") + << resourceIndex; + + // Note: Remove potentially problematic double underscores from GLSL resource names. + std::string name(resName.str()); + StringUtils::ReplaceInPlace(name, "__", "_"); + + GpuShaderText ss(shaderCreator->getLanguage()); + + ss.newLine() << ss.float3Keyword() << " " << name << "(" << ss.float3Keyword() << " JMh_s, " << ss.float2Keyword() << " JM_cusp_in, float J_focus, float slope_gain, float gamma_top, float gamma_bottom)"; + ss.newLine() << "{"; + ss.indent(); + + ss.newLine() << ss.float2Decl("JM_cusp") << " = " << ss.float2Const("JM_cusp_in.r", std::string("JM_cusp_in.g * (1.0 + ") + std::to_string(ACES2::smooth_m) + " * " + std::to_string(ACES2::smooth_cusps) + ")") << ";"; + + ss.newLine() << ss.floatDecl("J_intersect_source") << " = " << solveJIntersectName << "(JMh_s.r, JMh_s.g, J_focus, slope_gain);"; + ss.newLine() << ss.floatDecl("J_intersect_cusp") << " = " << solveJIntersectName << "(JM_cusp.r, JM_cusp.g, J_focus, slope_gain);"; + + ss.newLine() << ss.floatDecl("slope") << " = 0.0;"; + ss.newLine() << "if (J_intersect_source < J_focus)"; + ss.newLine() << "{"; + ss.indent(); + ss.newLine() << "slope = J_intersect_source * (J_intersect_source - J_focus) / (J_focus * slope_gain);"; + ss.dedent(); + ss.newLine() << "}"; + ss.newLine() << "else"; + ss.newLine() << "{"; + ss.indent(); + ss.newLine() << "slope = (" << g.limit_J_max << " - J_intersect_source) * (J_intersect_source - J_focus) / (J_focus * slope_gain);"; + ss.dedent(); + ss.newLine() << "}"; + + ss.newLine() << ss.floatDecl("M_boundary_lower ") << " = J_intersect_cusp * pow(J_intersect_source / J_intersect_cusp, 1.0 / gamma_bottom) / (JM_cusp.r / JM_cusp.g - slope);"; + ss.newLine() << ss.floatDecl("M_boundary_upper") << " = JM_cusp.g * (" << g.limit_J_max << " - J_intersect_cusp) * pow((" << g.limit_J_max << " - J_intersect_source) / (" << g.limit_J_max << " - J_intersect_cusp), 1.0 / gamma_top) / (slope * JM_cusp.g + " << g.limit_J_max << " - JM_cusp.r);"; + + ss.newLine() << ss.floatDecl("smin") << " = 0.0;"; + ss.newLine() << "{"; + ss.indent(); + ss.newLine() << ss.floatDecl("a") << " = M_boundary_lower / JM_cusp.g;"; + ss.newLine() << ss.floatDecl("b") << " = M_boundary_upper / JM_cusp.g;"; + ss.newLine() << ss.floatDecl("s") << " = " << ACES2::smooth_cusps << ";"; + + ss.newLine() << ss.floatDecl("h") << " = max(s - abs(a - b), 0.0) / s;"; + ss.newLine() << "smin = min(a, b) - h * h * h * s * (1.0 / 6.0);"; + + ss.dedent(); + ss.newLine() << "}"; + + ss.newLine() << ss.floatDecl("M_boundary") << " = JM_cusp.g * smin;"; + ss.newLine() << ss.floatDecl("J_boundary") << "= J_intersect_source + slope * M_boundary;"; + + ss.newLine() << "return " << ss.float3Const("J_boundary", "M_boundary", "J_intersect_source") << ";"; + + ss.dedent(); + ss.newLine() << "}"; + + shaderCreator->addToHelperShaderCode(ss.string().c_str()); + + return name; +} + +std::string _Add_Reach_Boundary_func( + GpuShaderCreatorRcPtr & shaderCreator, + unsigned resourceIndex, + const ACES2::GamutCompressParams & g, + const std::string & reachName, + const std::string & getFocusGainName, + const std::string & solveJIntersectName) +{ + // Reserve name + std::ostringstream resName; + resName << shaderCreator->getResourcePrefix() + << std::string("_") + << std::string("get_reach_boundary") + << resourceIndex; + + // Note: Remove potentially problematic double underscores from GLSL resource names. + std::string name(resName.str()); + StringUtils::ReplaceInPlace(name, "__", "_"); + + GpuShaderText ss(shaderCreator->getLanguage()); + + ss.newLine() << ss.float3Keyword() << " " << name << "(float J, float M, float h, " << ss.float2Keyword() << " JMcusp, float focusJ)"; + ss.newLine() << "{"; + ss.indent(); + + ss.newLine() << ss.floatDecl("reachMaxM") << " = " << reachName << "_sample(h);"; + ss.newLine() << ss.floatDecl("slope_gain") << " = " << g.limit_J_max << " * " << g.focus_dist << " * " << getFocusGainName << "(J, JMcusp.r);"; + ss.newLine() << ss.floatDecl("intersectJ") << " = " << solveJIntersectName << "(J, M, focusJ, slope_gain);"; + + ss.newLine() << ss.floatDecl("slope") << " = 0.0;"; + ss.newLine() << "if (intersectJ < focusJ)"; + ss.newLine() << "{"; + ss.indent(); + ss.newLine() << "slope = intersectJ * (intersectJ - focusJ) / (focusJ * slope_gain);"; + ss.dedent(); + ss.newLine() << "}"; + ss.newLine() << "else"; + ss.newLine() << "{"; + ss.indent(); + ss.newLine() << "slope = (" << g.limit_J_max << " - intersectJ) * (intersectJ - focusJ) / (focusJ * slope_gain);"; + ss.dedent(); + ss.newLine() << "}"; + + ss.newLine() << ss.floatDecl("boundary") << " = " << g.limit_J_max << " * pow(intersectJ / " << g.limit_J_max << ", " << g.model_gamma << ") * reachMaxM / (" << g.limit_J_max << " - slope * reachMaxM);"; + + ss.newLine() << "return " << ss.float3Const("J", "boundary", "h") << ";"; + + ss.dedent(); + ss.newLine() << "}"; + + shaderCreator->addToHelperShaderCode(ss.string().c_str()); + + return name; +} + +std::string _Add_Compression_func( + GpuShaderCreatorRcPtr & shaderCreator, + unsigned resourceIndex, + bool invert) +{ + // Reserve name + std::ostringstream resName; + resName << shaderCreator->getResourcePrefix() + << std::string("_") + << std::string("compression") + << (invert ? std::string("_inv") : std::string("_fwd")) + << resourceIndex; + + // Note: Remove potentially problematic double underscores from GLSL resource names. + std::string name(resName.str()); + StringUtils::ReplaceInPlace(name, "__", "_"); + + GpuShaderText ss(shaderCreator->getLanguage()); + + ss.newLine() << ss.floatKeyword() << " " << name << "(float v, float thr, float lim)"; + ss.newLine() << "{"; + ss.indent(); + + ss.newLine() << ss.floatDecl("s") << " = (lim - thr) * (1.0 - thr) / (lim - 1.0);"; + ss.newLine() << ss.floatDecl("nd") << " = (v - thr) / s;"; + + + ss.newLine() << ss.floatDecl("vCompressed") << " = 0.0;"; + + if (invert) + { + ss.newLine() << "if (v < thr || lim <= 1.0001 || v > thr + s)"; + ss.newLine() << "{"; + ss.indent(); + ss.newLine() << "vCompressed = v;"; + ss.dedent(); + ss.newLine() << "}"; + ss.newLine() << "else"; + ss.newLine() << "{"; + ss.indent(); + ss.newLine() << "vCompressed = thr + s * (-nd / (nd - 1));"; + ss.dedent(); + ss.newLine() << "}"; + } + else + { + ss.newLine() << "if (v < thr || lim <= 1.0001)"; + ss.newLine() << "{"; + ss.indent(); + ss.newLine() << "vCompressed = v;"; + ss.dedent(); + ss.newLine() << "}"; + ss.newLine() << "else"; + ss.newLine() << "{"; + ss.indent(); + ss.newLine() << "vCompressed = thr + s * nd / (1.0 + nd);"; + ss.dedent(); + ss.newLine() << "}"; + } + + ss.newLine() << "return vCompressed;"; + + ss.dedent(); + ss.newLine() << "}"; + + shaderCreator->addToHelperShaderCode(ss.string().c_str()); + + return name; +} + +std::string _Add_Compress_Gamut_func( + GpuShaderCreatorRcPtr & shaderCreator, + unsigned resourceIndex, + const ACES2::GamutCompressParams & g, + const std::string & cuspName, + const std::string & getFocusGainName, + const std::string & gammaName, + const std::string & findGamutBoundaryIntersectionName, + const std::string & getReachBoundaryName, + const std::string & compressionName) +{ + // Reserve name + std::ostringstream resName; + resName << shaderCreator->getResourcePrefix() + << std::string("_") + << std::string("gamut_compress") + << resourceIndex; + + // Note: Remove potentially problematic double underscores from GLSL resource names. + std::string name(resName.str()); + StringUtils::ReplaceInPlace(name, "__", "_"); + + GpuShaderText ss(shaderCreator->getLanguage()); + + ss.newLine() << ss.float3Keyword() << " " << name << "(" << ss.float3Keyword() << " JMh, float Jx)"; + ss.newLine() << "{"; + ss.indent(); + + ss.newLine() << ss.floatDecl("J") << " = JMh.r;"; + ss.newLine() << ss.floatDecl("M") << " = JMh.g;"; + ss.newLine() << ss.floatDecl("h") << " = JMh.b;"; + + ss.newLine() << "if (M < 0.0001 || J > " << g.limit_J_max << ")"; + ss.newLine() << "{"; + ss.indent(); + ss.newLine() << "return " << ss.float3Const("J", "0.0", "h") << ";"; + ss.dedent(); + ss.newLine() << "}"; + ss.newLine() << "else"; + ss.newLine() << "{"; + ss.indent(); + + ss.newLine() << ss.float2Decl("project_from") << " = " << ss.float2Const("J", "M") << ";"; + ss.newLine() << ss.float2Decl("JMcusp") << " = " << cuspName << "_sample(h);"; + + ss.newLine() << ss.floatDecl("focusJ") << " = " << ss.lerp("JMcusp.r", std::to_string(g.mid_J), std::string("min(1.0, ") + std::to_string(ACES2::cusp_mid_blend) + " - (JMcusp.r / " + std::to_string(g.limit_J_max)) << "));"; + ss.newLine() << ss.floatDecl("slope_gain") << " = " << g.limit_J_max << " * " << g.focus_dist << " * " << getFocusGainName << "(Jx, JMcusp.r);"; + + ss.newLine() << ss.floatDecl("gamma_top") << " = " << gammaName << "_sample(h);"; + ss.newLine() << ss.floatDecl("gamma_bottom") << " = " << g.lower_hull_gamma << ";"; + + ss.newLine() << ss.float3Decl("boundaryReturn") << " = " << findGamutBoundaryIntersectionName << "(" << ss.float3Const("J", "M", "h") << ", JMcusp, focusJ, slope_gain, gamma_top, gamma_bottom);"; + ss.newLine() << ss.float2Decl("JMboundary") << " = " << ss.float2Const("boundaryReturn.r", "boundaryReturn.g") << ";"; + ss.newLine() << ss.float2Decl("project_to") << " = " << ss.float2Const("boundaryReturn.b", "0.0") << ";"; + + ss.newLine() << "if (JMboundary.g <= 0.0)"; + ss.newLine() << "{"; + ss.indent(); + ss.newLine() << "return " << ss.float3Const("J", "0.0", "h") << ";"; + ss.dedent(); + ss.newLine() << "}"; + + ss.newLine() << ss.float3Decl("reachBoundary") << " = " << getReachBoundaryName << "(JMboundary.r, JMboundary.g, h, JMcusp, focusJ);"; + + ss.newLine() << ss.floatDecl("difference") << " = max(1.0001, reachBoundary.g / JMboundary.g);"; + ss.newLine() << ss.floatDecl("threshold") << " = max(" << ACES2::compression_threshold << ", 1.0 / difference);"; + + ss.newLine() << ss.floatDecl("v") << " = project_from.g / JMboundary.g;"; + ss.newLine() << "v = " << compressionName << "(v, threshold, difference);"; + + ss.newLine() << ss.float2Decl("JMcompressed") << " = " << ss.float2Const( + "project_to.r + v * (JMboundary.r - project_to.r)", + "project_to.g + v * (JMboundary.g - project_to.g)" + ) << ";"; + + ss.newLine() << "return " << ss.float3Const("JMcompressed.r", "JMcompressed.g", "h") << ";"; + + ss.dedent(); + ss.newLine() << "}"; + + ss.dedent(); + ss.newLine() << "}"; + + shaderCreator->addToHelperShaderCode(ss.string().c_str()); + + return name; +} + +void _Add_Gamut_Compress_Fwd_Shader( + GpuShaderCreatorRcPtr & shaderCreator, + GpuShaderText & ss, + unsigned int resourceIndex, + const ACES2::GamutCompressParams & g, + const std::string & reachName) +{ + std::string cuspName = _Add_Cusp_table(shaderCreator, resourceIndex, g); + std::string gammaName = _Add_Gamma_table(shaderCreator, resourceIndex, g); + std::string getFocusGainName = _Add_Focus_Gain_func(shaderCreator, resourceIndex, g); + std::string solveJIntersectName = _Add_Solve_J_Intersect_func(shaderCreator, resourceIndex, g); + std::string findGamutBoundaryIntersectionName = _Add_Find_Gamut_Boundary_Intersection_func(shaderCreator, resourceIndex, g, solveJIntersectName); + std::string getReachBoundaryName = _Add_Reach_Boundary_func(shaderCreator, resourceIndex, g, reachName, getFocusGainName, solveJIntersectName); + std::string compressionName = _Add_Compression_func(shaderCreator, resourceIndex, false); + std::string gamutCompressName = _Add_Compress_Gamut_func(shaderCreator, resourceIndex, g, cuspName, getFocusGainName, gammaName, findGamutBoundaryIntersectionName, getReachBoundaryName, compressionName); + + const std::string pxl(shaderCreator->getPixelName()); + + ss.newLine() << pxl << ".rgb = " << gamutCompressName << "(" << pxl << ".rgb, " << pxl << ".r);"; +} + +void _Add_Gamut_Compress_Inv_Shader( + GpuShaderCreatorRcPtr & shaderCreator, + GpuShaderText & ss, + unsigned int resourceIndex, + const ACES2::GamutCompressParams & g, + const std::string & reachName) +{ + std::string cuspName = _Add_Cusp_table(shaderCreator, resourceIndex, g); + std::string gammaName = _Add_Gamma_table(shaderCreator, resourceIndex, g); + std::string getFocusGainName = _Add_Focus_Gain_func(shaderCreator, resourceIndex, g); + std::string solveJIntersectName = _Add_Solve_J_Intersect_func(shaderCreator, resourceIndex, g); + std::string findGamutBoundaryIntersectionName = _Add_Find_Gamut_Boundary_Intersection_func(shaderCreator, resourceIndex, g, solveJIntersectName); + std::string getReachBoundaryName = _Add_Reach_Boundary_func(shaderCreator, resourceIndex, g, reachName, getFocusGainName, solveJIntersectName); + std::string compressionName = _Add_Compression_func(shaderCreator, resourceIndex, true); + std::string gamutCompressName = _Add_Compress_Gamut_func(shaderCreator, resourceIndex, g, cuspName, getFocusGainName, gammaName, findGamutBoundaryIntersectionName, getReachBoundaryName, compressionName); + + const std::string pxl(shaderCreator->getPixelName()); + + ss.newLine() << ss.float2Decl("JMcusp") << " = " << cuspName << "_sample(" << pxl << ".b);"; + ss.newLine() << ss.floatDecl("Jx") << " = " << pxl << ".r;"; + ss.newLine() << ss.float3Decl("unCompressedJMh") << ";"; + + // Analytic inverse below threshold + ss.newLine() << "if (Jx <= " << ss.lerp("JMcusp.r", std::to_string(g.limit_J_max), std::to_string(ACES2::focus_gain_blend)) << ")"; + ss.newLine() << "{"; + ss.indent(); + ss.newLine() << "unCompressedJMh = " << gamutCompressName << "(" << pxl << ".rgb, Jx);"; + ss.dedent(); + ss.newLine() << "}"; + // Approximation above threshold + ss.newLine() << "else"; + ss.newLine() << "{"; + ss.indent(); + ss.newLine() << "Jx = " << gamutCompressName << "(" << pxl << ".rgb, Jx).r;"; + ss.newLine() << "unCompressedJMh = " << gamutCompressName << "(" << pxl << ".rgb, Jx);"; + ss.dedent(); + ss.newLine() << "}"; + + ss.newLine() << pxl << ".rgb = unCompressedJMh;"; +} + +void Add_ACES_OutputTransform_Fwd_Shader( + GpuShaderCreatorRcPtr & shaderCreator, + GpuShaderText & ss, + const FixedFunctionOpData::Params & params) +{ + const float peak_luminance = (float) params[0]; + + const float red_x = (float) params[1]; + const float red_y = (float) params[2]; + const float green_x = (float) params[3]; + const float green_y = (float) params[4]; + const float blue_x = (float) params[5]; + const float blue_y = (float) params[6]; + const float white_x = (float) params[7]; + const float white_y = (float) params[8]; + + const Primaries in_primaries = ACES_AP0::primaries; + + const Primaries lim_primaries = { + {red_x , red_y }, + {green_x, green_y}, + {blue_x , blue_y }, + {white_x, white_y} + }; + + ACES2::JMhParams pIn = ACES2::init_JMhParams(in_primaries); + ACES2::JMhParams pLim = ACES2::init_JMhParams(lim_primaries); + ACES2::ToneScaleParams t = ACES2::init_ToneScaleParams(peak_luminance); + ACES2::ChromaCompressParams c = ACES2::init_ChromaCompressParams(peak_luminance); + const ACES2::GamutCompressParams g = ACES2::init_GamutCompressParams(peak_luminance, lim_primaries); + + unsigned resourceIndex = shaderCreator->getNextResourceIndex(); + + std::string reachName = _Add_Reach_table(shaderCreator, resourceIndex, g.reach_m_table); + + ss.newLine() << ""; + ss.newLine() << "// Add RGB to JMh"; + ss.newLine() << ""; + ss.newLine() << "{"; + ss.indent(); + _Add_RGB_to_JMh_Shader(shaderCreator, ss, pIn); + ss.dedent(); + ss.newLine() << "}"; + + ss.newLine() << ""; + ss.newLine() << "// Add ToneScale and ChromaCompress (fwd)"; + ss.newLine() << ""; + ss.newLine() << "{"; + ss.indent(); + _Add_Tonescale_Compress_Fwd_Shader(shaderCreator, ss, resourceIndex, pIn, t, c, reachName); + ss.dedent(); + ss.newLine() << "}"; + + ss.newLine() << ""; + ss.newLine() << "// Add GamutCompress (fwd)"; + ss.newLine() << ""; + ss.newLine() << "{"; + ss.indent(); + _Add_Gamut_Compress_Fwd_Shader(shaderCreator, ss, resourceIndex, g, reachName); + ss.dedent(); + ss.newLine() << "}"; + + ss.newLine() << ""; + ss.newLine() << "// Add JMh to RGB"; + ss.newLine() << ""; + ss.newLine() << "{"; + ss.indent(); + _Add_JMh_to_RGB_Shader(shaderCreator, ss, pLim); + ss.dedent(); + ss.newLine() << "}"; + +} + +void Add_ACES_OutputTransform_Inv_Shader( + GpuShaderCreatorRcPtr & shaderCreator, + GpuShaderText & ss, + const FixedFunctionOpData::Params & params) +{ + const float peak_luminance = (float) params[0]; + + const float red_x = (float) params[1]; + const float red_y = (float) params[2]; + const float green_x = (float) params[3]; + const float green_y = (float) params[4]; + const float blue_x = (float) params[5]; + const float blue_y = (float) params[6]; + const float white_x = (float) params[7]; + const float white_y = (float) params[8]; + + const Primaries in_primaries = ACES_AP0::primaries; + + const Primaries lim_primaries = { + {red_x , red_y }, + {green_x, green_y}, + {blue_x , blue_y }, + {white_x, white_y} + }; + + ACES2::JMhParams pIn = ACES2::init_JMhParams(in_primaries); + ACES2::JMhParams pLim = ACES2::init_JMhParams(lim_primaries); + ACES2::ToneScaleParams t = ACES2::init_ToneScaleParams(peak_luminance); + ACES2::ChromaCompressParams c = ACES2::init_ChromaCompressParams(peak_luminance); + const ACES2::GamutCompressParams g = ACES2::init_GamutCompressParams(peak_luminance, lim_primaries); + + unsigned resourceIndex = shaderCreator->getNextResourceIndex(); + + std::string reachName = _Add_Reach_table(shaderCreator, resourceIndex, c.reach_m_table); + + ss.newLine() << ""; + ss.newLine() << "// Add RGB to JMh"; + ss.newLine() << ""; + ss.newLine() << "{"; + ss.indent(); + _Add_RGB_to_JMh_Shader(shaderCreator, ss, pLim); + ss.dedent(); + ss.newLine() << "}"; + + ss.newLine() << ""; + ss.newLine() << "// Add GamutCompress (inv)"; + ss.newLine() << ""; + ss.newLine() << "{"; + ss.indent(); + _Add_Gamut_Compress_Inv_Shader(shaderCreator, ss, resourceIndex, g, reachName); + ss.dedent(); + ss.newLine() << "}"; + + ss.newLine() << ""; + ss.newLine() << "// Add ToneScale and ChromaCompress (inv)"; + ss.newLine() << ""; + ss.newLine() << "{"; + ss.indent(); + _Add_Tonescale_Compress_Inv_Shader(shaderCreator, ss, resourceIndex, pIn, t, c, reachName); + ss.dedent(); + ss.newLine() << "}"; + + ss.newLine() << ""; + ss.newLine() << "// Add JMh to RGB"; + ss.newLine() << ""; + ss.newLine() << "{"; + ss.indent(); + _Add_JMh_to_RGB_Shader(shaderCreator, ss, pIn); + ss.dedent(); + ss.newLine() << "}"; +} + +void Add_RGB_to_JMh_Shader( + GpuShaderCreatorRcPtr & shaderCreator, + GpuShaderText & ss, + const FixedFunctionOpData::Params & params) +{ + const float red_x = (float) params[0]; + const float red_y = (float) params[1]; + const float green_x = (float) params[2]; + const float green_y = (float) params[3]; + const float blue_x = (float) params[4]; + const float blue_y = (float) params[5]; + const float white_x = (float) params[6]; + const float white_y = (float) params[7]; + + const Primaries primaries = { + {red_x , red_y }, + {green_x, green_y}, + {blue_x , blue_y }, + {white_x, white_y} + }; + ACES2::JMhParams p = ACES2::init_JMhParams(primaries); + + _Add_RGB_to_JMh_Shader(shaderCreator, ss, p); +} + +void Add_JMh_to_RGB_Shader( + GpuShaderCreatorRcPtr & shaderCreator, + GpuShaderText & ss, + const FixedFunctionOpData::Params & params) +{ + const float red_x = (float) params[0]; + const float red_y = (float) params[1]; + const float green_x = (float) params[2]; + const float green_y = (float) params[3]; + const float blue_x = (float) params[4]; + const float blue_y = (float) params[5]; + const float white_x = (float) params[6]; + const float white_y = (float) params[7]; + + const Primaries primaries = { + {red_x , red_y }, + {green_x, green_y}, + {blue_x , blue_y }, + {white_x, white_y} + }; + + ACES2::JMhParams p = ACES2::init_JMhParams(primaries); + + _Add_JMh_to_RGB_Shader(shaderCreator, ss, p); +} + +void Add_Tonescale_Compress_Fwd_Shader( + GpuShaderCreatorRcPtr & shaderCreator, + GpuShaderText & ss, + const FixedFunctionOpData::Params & params) +{ + const float peak_luminance = (float) params[0]; + + ACES2::JMhParams p = ACES2::init_JMhParams(ACES_AP0::primaries); + ACES2::ToneScaleParams t = ACES2::init_ToneScaleParams(peak_luminance); + ACES2::ChromaCompressParams c = ACES2::init_ChromaCompressParams(peak_luminance); + + unsigned resourceIndex = shaderCreator->getNextResourceIndex(); + + std::string reachName = _Add_Reach_table(shaderCreator, resourceIndex, c.reach_m_table); + + _Add_Tonescale_Compress_Fwd_Shader(shaderCreator, ss, resourceIndex, p, t, c, reachName); +} + +void Add_Tonescale_Compress_Inv_Shader( + GpuShaderCreatorRcPtr & shaderCreator, + GpuShaderText & ss, + const FixedFunctionOpData::Params & params) +{ + const float peak_luminance = (float) params[0]; + + ACES2::JMhParams p = ACES2::init_JMhParams(ACES_AP0::primaries); + ACES2::ToneScaleParams t = ACES2::init_ToneScaleParams(peak_luminance); + ACES2::ChromaCompressParams c = ACES2::init_ChromaCompressParams(peak_luminance); + + unsigned resourceIndex = shaderCreator->getNextResourceIndex(); + + std::string reachName = _Add_Reach_table(shaderCreator, resourceIndex, c.reach_m_table); + + _Add_Tonescale_Compress_Inv_Shader(shaderCreator, ss, resourceIndex, p, t, c, reachName); +} + +void Add_Gamut_Compress_Fwd_Shader( + GpuShaderCreatorRcPtr & shaderCreator, + GpuShaderText & ss, + const FixedFunctionOpData::Params & params) +{ + const float peak_luminance = (float) params[0]; + + const float red_x = (float) params[1]; + const float red_y = (float) params[2]; + const float green_x = (float) params[3]; + const float green_y = (float) params[4]; + const float blue_x = (float) params[5]; + const float blue_y = (float) params[6]; + const float white_x = (float) params[7]; + const float white_y = (float) params[8]; + + const Primaries primaries = { + {red_x , red_y }, + {green_x, green_y}, + {blue_x , blue_y }, + {white_x, white_y} + }; + + const ACES2::GamutCompressParams g = ACES2::init_GamutCompressParams(peak_luminance, primaries); + + unsigned resourceIndex = shaderCreator->getNextResourceIndex(); + + std::string reachName = _Add_Reach_table(shaderCreator, resourceIndex, g.reach_m_table); + + _Add_Gamut_Compress_Fwd_Shader(shaderCreator, ss, resourceIndex, g, reachName); +} + +void Add_Gamut_Compress_Inv_Shader( + GpuShaderCreatorRcPtr & shaderCreator, + GpuShaderText & ss, + const FixedFunctionOpData::Params & params) +{ + const float peak_luminance = (float) params[0]; + + const float red_x = (float) params[1]; + const float red_y = (float) params[2]; + const float green_x = (float) params[3]; + const float green_y = (float) params[4]; + const float blue_x = (float) params[5]; + const float blue_y = (float) params[6]; + const float white_x = (float) params[7]; + const float white_y = (float) params[8]; + + const Primaries primaries = { + {red_x , red_y }, + {green_x, green_y}, + {blue_x , blue_y }, + {white_x, white_y} + }; + + const ACES2::GamutCompressParams g = ACES2::init_GamutCompressParams(peak_luminance, primaries); + + unsigned resourceIndex = shaderCreator->getNextResourceIndex(); + + std::string reachName = _Add_Reach_table(shaderCreator, resourceIndex, g.reach_m_table); + + _Add_Gamut_Compress_Inv_Shader(shaderCreator, ss, resourceIndex, g, reachName); +} + void Add_Surround_10_Fwd_Shader(GpuShaderCreatorRcPtr & shaderCreator, GpuShaderText & ss, float gamma) { const std::string pxl(shaderCreator->getPixelName()); @@ -622,6 +1923,46 @@ void GetFixedFunctionGPUShaderProgram(GpuShaderCreatorRcPtr & shaderCreator, ); break; } + case FixedFunctionOpData::ACES_OUTPUT_TRANSFORM_20_FWD: + { + Add_ACES_OutputTransform_Fwd_Shader(shaderCreator, ss, func->getParams()); + break; + } + case FixedFunctionOpData::ACES_OUTPUT_TRANSFORM_20_INV: + { + Add_ACES_OutputTransform_Inv_Shader(shaderCreator, ss, func->getParams()); + break; + } + case FixedFunctionOpData::ACES_RGB_TO_JMh_20: + { + Add_RGB_to_JMh_Shader(shaderCreator, ss, func->getParams()); + break; + } + case FixedFunctionOpData::ACES_JMh_TO_RGB_20: + { + Add_JMh_to_RGB_Shader(shaderCreator, ss, func->getParams()); + break; + } + case FixedFunctionOpData::ACES_TONESCALE_COMPRESS_20_FWD: + { + Add_Tonescale_Compress_Fwd_Shader(shaderCreator, ss, func->getParams()); + break; + } + case FixedFunctionOpData::ACES_TONESCALE_COMPRESS_20_INV: + { + Add_Tonescale_Compress_Inv_Shader(shaderCreator, ss, func->getParams()); + break; + } + case FixedFunctionOpData::ACES_GAMUT_COMPRESS_20_FWD: + { + Add_Gamut_Compress_Fwd_Shader(shaderCreator, ss, func->getParams()); + break; + } + case FixedFunctionOpData::ACES_GAMUT_COMPRESS_20_INV: + { + Add_Gamut_Compress_Inv_Shader(shaderCreator, ss, func->getParams()); + break; + } case FixedFunctionOpData::REC2100_SURROUND_FWD: { Add_Surround_Shader(shaderCreator, ss, (float) func->getParams()[0]); diff --git a/src/OpenColorIO/transforms/builtins/ACES.cpp b/src/OpenColorIO/transforms/builtins/ACES.cpp index d1e3321727..8f08c5829d 100644 --- a/src/OpenColorIO/transforms/builtins/ACES.cpp +++ b/src/OpenColorIO/transforms/builtins/ACES.cpp @@ -497,6 +497,73 @@ void Generate_roll_white_d65_ops(OpRcPtrVec & ops) } // namespace ACES_OUTPUT +namespace ACES2_OUTPUT +{ + void Generate_output_transform( + OpRcPtrVec & ops, + float peak_luminance, + const Primaries & limiting_pri, + const Primaries & encoding_pri, + float linear_scale, + bool scale_white) + { + // Clamp to AP1 + MatrixOpData::MatrixArrayPtr matrixToAP1 + = build_conversion_matrix(ACES_AP0::primaries, ACES_AP1::primaries, ADAPTATION_NONE); + CreateMatrixOp(ops, matrixToAP1, TRANSFORM_DIR_FORWARD); + + const float upperBound = 8.f * (128.f + 768.f * (std::log(peak_luminance / 100.f) / log(10000.f / 100.f))); + CreateRangeOp(ops, + 0., upperBound, + 0., upperBound, + TRANSFORM_DIR_FORWARD); + + CreateMatrixOp(ops, matrixToAP1, TRANSFORM_DIR_INVERSE); + + // Display rendering + CreateFixedFunctionOp(ops, FixedFunctionOpData::ACES_OUTPUT_TRANSFORM_20_FWD, { + peak_luminance, + limiting_pri.m_red.m_xy[0], limiting_pri.m_red.m_xy[1], + limiting_pri.m_grn.m_xy[0], limiting_pri.m_grn.m_xy[1], + limiting_pri.m_blu.m_xy[0], limiting_pri.m_blu.m_xy[1], + limiting_pri.m_wht.m_xy[0], limiting_pri.m_wht.m_xy[1], + }); + + // Post transform clamp + const double normPeakLuminance = peak_luminance / 100.f; + CreateRangeOp(ops, + 0., normPeakLuminance, + 0., normPeakLuminance, + TRANSFORM_DIR_FORWARD); + + // White point simulation + if (scale_white) + { + MatrixOpData::MatrixArrayPtr matrixLimToOut + = build_conversion_matrix(limiting_pri, encoding_pri, ADAPTATION_NONE); + + MatrixOpData::Offsets white(1.f, 1.f, 1.f, 0.f); + white = matrixLimToOut->inner(white); + + const double scale = 1. / std::max(std::max(white[0], white[1]), white[2]); + const double scale4[4] = { scale, scale, scale, 1. }; + CreateScaleOp(ops, scale4, TRANSFORM_DIR_FORWARD); + } + + // Linear scale factor + if (linear_scale != 1.f) + { + const double scale = linear_scale; + const double scale4[4] = { scale, scale, scale, 1. }; + CreateScaleOp(ops, scale4, TRANSFORM_DIR_FORWARD); + } + + MatrixOpData::MatrixArrayPtr matrixToXYZ + = build_conversion_matrix_to_XYZ_D65(limiting_pri, ADAPTATION_NONE); + CreateMatrixOp(ops, matrixToXYZ, TRANSFORM_DIR_FORWARD); + } + +} // namespace ACES2_OUTPUT // // Create the built-in transforms. @@ -1047,6 +1114,326 @@ void RegisterAll(BuiltinTransformRegistryImpl & registry) noexcept "Component of ACES Output Transforms for 108 nit HDR D65 cinema", ACES2065_1_to_CIE_XYZ_hdr_cinema_108nits_p3lim_1_1_Functor); } + + // + // ACES 2 OUTPUT TRANSFORMS + // + + struct ACES2OutputTransform + { + std::string name; + std::string desc; + float peak_luminance; + Primaries limiting_primaries; + Primaries encoding_primaries; + float linear_scale; + bool scale_white; + }; + + const std::vector aces2_output_transforms { + // + // D65 + // + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - SDR-100nit-REC709_2.0", + "Component of ACES 2 Output Transforms for 100 nit SDR Rec709", + 100.f, + REC709::primaries, + REC709::primaries, + 1.f, + false + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - SDR-100nit-P3-D65_2.0", + "Component of ACES 2 Output Transforms for 100 nit SDR P3-D65", + 100.f, + P3_D65::primaries, + P3_D65::primaries, + 1.f, + false + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-108nit-P3-D65_2.0", + "Component of ACES 2 Output Transforms for 108 nit HDR P3-D65", + 225.f, // = 108 * (100/48); + P3_D65::primaries, + P3_D65::primaries, + 0.48f, + false + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-300nit-P3-D65_2.0", + "Component of ACES 2 Output Transforms for 300 nit HDR P3-D65", + 625.f, // = 300 * (100/48); + P3_D65::primaries, + P3_D65::primaries, + 0.48f, + false + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-500nit-P3-D65_2.0", + "Component of ACES 2 Output Transforms for 500 nit HDR P3-D65", + 500.f, + P3_D65::primaries, + P3_D65::primaries, + 1.f, + false + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-1000nit-P3-D65_2.0", + "Component of ACES 2 Output Transforms for 1000 nit HDR P3-D65", + 1000.f, + P3_D65::primaries, + P3_D65::primaries, + 1.f, + false + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-2000nit-P3-D65_2.0", + "Component of ACES 2 Output Transforms for 2000 nit HDR P3-D65", + 2000.f, + P3_D65::primaries, + P3_D65::primaries, + 1.f, + false + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-4000nit-P3-D65_2.0", + "Component of ACES 2 Output Transforms for 4000 nit HDR P3-D65", + 4000.f, + P3_D65::primaries, + P3_D65::primaries, + 1.f, + false + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-500nit-REC2020_2.0", + "Component of ACES 2 Output Transforms for 500 nit HDR Rec2020", + 500.f, + REC2020::primaries, + REC2020::primaries, + 1.f, + false + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-1000nit-REC2020_2.0", + "Component of ACES 2 Output Transforms for 1000 nit HDR Rec2020", + 1000.f, + REC2020::primaries, + REC2020::primaries, + 1.f, + false + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-2000nit-REC2020_2.0", + "Component of ACES 2 Output Transforms for 2000 nit HDR Rec2020", + 2000.f, + REC2020::primaries, + REC2020::primaries, + 1.f, + false + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-4000nit-REC2020_2.0", + "Component of ACES 2 Output Transforms for 4000 nit HDR Rec2020", + 4000.f, + REC2020::primaries, + REC2020::primaries, + 1.f, + false + }, + // + // D60 + // + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - SDR-100nit-REC709-D60-in-REC709-D65_2.0", + "Component of ACES 2 Output Transforms for 100 nit SDR Rec709 simulating D60 white in Rec709", + 100.f, + REC709_D60::primaries, + REC709::primaries, + 1.f, + true + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - SDR-100nit-REC709-D60-in-P3-D65_2.0", + "Component of ACES 2 Output Transforms for 100 nit SDR Rec709 simulating D60 white in P3-D65", + 100.f, + REC709_D60::primaries, + P3_D65::primaries, + 1.f, + true + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - SDR-100nit-REC709-D60-in-REC2020-D65_2.0", + "Component of ACES 2 Output Transforms for 100 nit SDR Rec709 simulating D60 white in Rec2020", + 100.f, + REC709_D60::primaries, + REC2020::primaries, + 1.f, + true + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - SDR-100nit-P3-D60-in-P3-D65_2.0", + "Component of ACES 2 Output Transforms for 100 nit SDR P3-D60 simulating D60 white in P3-D65", + 100.f, + P3_D60::primaries, + P3_D65::primaries, + 1.f, + true + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - SDR-100nit-P3-D60-in-XYZ-E_2.0", + "Component of ACES 2 Output Transforms for 100 nit SDR P3-D60 simulating D60 white in XYZ-E", + 100.f, + P3_D60::primaries, + CIE_XYZ_ILLUM_E::primaries, + 1.f, + false + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-108nit-P3-D60-in-P3-D65_2.0", + "Component of ACES 2 Output Transforms for 108 nit HDR P3-D60 simulating D60 white in P3-D65", + 225.f, // = 108 * (100/48); + P3_D60::primaries, + P3_D65::primaries, + 0.48f, + true + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-300nit-P3-D60-in-XYZ-E_2.0", + "Component of ACES 2 Output Transforms for 300 nit HDR P3-D60 simulating D60 white in XYZ-E", + 625.f, // = 300 * (100/48); + P3_D60::primaries, + CIE_XYZ_ILLUM_E::primaries, + 0.48f, + true + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-500nit-P3-D60-in-P3-D65_2.0", + "Component of ACES 2 Output Transforms for 500 nit HDR P3-D60 simulating D60 white in P3-D65", + 500.f, + P3_D60::primaries, + P3_D65::primaries, + 1.f, + true + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-1000nit-P3-D60-in-P3-D65_2.0", + "Component of ACES 2 Output Transforms for 1000 nit HDR P3-D60 simulating D60 white in P3-D65", + 1000.f, + P3_D60::primaries, + P3_D65::primaries, + 1.f, + true + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-2000nit-P3-D60-in-P3-D65_2.0", + "Component of ACES 2 Output Transforms for 2000 nit HDR P3-D60 simulating D60 white in P3-D65", + 2000.f, + P3_D60::primaries, + P3_D65::primaries, + 1.f, + true + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-4000nit-P3-D60-in-P3-D65_2.0", + "Component of ACES 2 Output Transforms for 4000 nit HDR P3-D60 simulating D60 white in P3-D65", + 4000.f, + P3_D60::primaries, + P3_D65::primaries, + 1.f, + true + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-500nit-P3-D60-in-REC2020-D65_2.0", + "Component of ACES 2 Output Transforms for 500 nit HDR P3-D60 simulating D60 white in Rec2020", + 500.f, + P3_D60::primaries, + REC2020::primaries, + 1.f, + true + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-1000nit-P3-D60-in-REC2020-D65_2.0", + "Component of ACES 2 Output Transforms for 1000 nit HDR P3-D60 simulating D60 white in Rec2020", + 1000.f, + P3_D60::primaries, + REC2020::primaries, + 1.f, + true + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-2000nit-P3-D60-in-REC2020-D65_2.0", + "Component of ACES 2 Output Transforms for 2000 nit HDR P3-D60 simulating D60 white in Rec2020", + 2000.f, + P3_D60::primaries, + REC2020::primaries, + 1.f, + true + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-4000nit-P3-D60-in-REC2020-D65_2.0", + "Component of ACES 2 Output Transforms for 4000 nit HDR P3-D60 simulating D60 white in Rec2020", + 4000.f, + P3_D60::primaries, + REC2020::primaries, + 1.f, + true + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-500nit-REC2020-D60-in-REC2020-D65_2.0", + "Component of ACES 2 Output Transforms for 500 nit HDR Rec2020 simulating D60 white in Rec2020", + 500.f, + REC2020_D60::primaries, + REC2020::primaries, + 1.f, + true + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-1000nit-REC2020-D60-in-REC2020-D65_2.0", + "Component of ACES 2 Output Transforms for 1000 nit HDR Rec2020 simulating D60 white in Rec2020", + 1000.f, + REC2020_D60::primaries, + REC2020::primaries, + 1.f, + true + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-2000nit-REC2020-D60-in-REC2020-D65_2.0", + "Component of ACES 2 Output Transforms for 2000 nit HDR Rec2020 simulating D60 white in Rec2020", + 2000.f, + REC2020_D60::primaries, + REC2020::primaries, + 1.f, + true + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-4000nit-REC2020-D60-in-REC2020-D65_2.0", + "Component of ACES 2 Output Transforms for 4000 nit HDR Rec2020 simulating D60 white in Rec2020", + 4000.f, + REC2020_D60::primaries, + REC2020::primaries, + 1.f, + true + } + }; + + for (const auto& tr : aces2_output_transforms) + { + auto functor = [tr](OpRcPtrVec & ops) + { + ACES2_OUTPUT::Generate_output_transform( + ops, + tr.peak_luminance, + tr.limiting_primaries, + tr.encoding_primaries, + tr.linear_scale, + tr.scale_white + ); + }; + + registry.addBuiltin(tr.name.c_str(), tr.desc.c_str(), functor); + } } } // namespace ACES diff --git a/src/OpenColorIO/transforms/builtins/ColorMatrixHelpers.cpp b/src/OpenColorIO/transforms/builtins/ColorMatrixHelpers.cpp index a97f3d6fdf..3ab1dd13f2 100644 --- a/src/OpenColorIO/transforms/builtins/ColorMatrixHelpers.cpp +++ b/src/OpenColorIO/transforms/builtins/ColorMatrixHelpers.cpp @@ -51,6 +51,16 @@ static const Chromaticities wht_xy(0.3127, 0.3290); const Primaries primaries(red_xy, grn_xy, blu_xy, wht_xy); } +namespace REC709_D60 +{ +static const Chromaticities red_xy(0.64, 0.33 ); +static const Chromaticities grn_xy(0.30, 0.60 ); +static const Chromaticities blu_xy(0.15, 0.06 ); +static const Chromaticities wht_xy(0.32168, 0.33767); + +const Primaries primaries(red_xy, grn_xy, blu_xy, wht_xy); +} + namespace REC2020 { static const Chromaticities red_xy(0.708, 0.292 ); @@ -61,6 +71,16 @@ static const Chromaticities wht_xy(0.3127, 0.3290); const Primaries primaries(red_xy, grn_xy, blu_xy, wht_xy); } +namespace REC2020_D60 +{ +static const Chromaticities red_xy(0.708, 0.292 ); +static const Chromaticities grn_xy(0.170, 0.797 ); +static const Chromaticities blu_xy(0.131, 0.046 ); +static const Chromaticities wht_xy(0.32168, 0.33767); + +const Primaries primaries(red_xy, grn_xy, blu_xy, wht_xy); +} + namespace P3_DCI { static const Chromaticities red_xy(0.680, 0.320); diff --git a/src/OpenColorIO/transforms/builtins/ColorMatrixHelpers.h b/src/OpenColorIO/transforms/builtins/ColorMatrixHelpers.h index 30898312fc..323d5712ba 100644 --- a/src/OpenColorIO/transforms/builtins/ColorMatrixHelpers.h +++ b/src/OpenColorIO/transforms/builtins/ColorMatrixHelpers.h @@ -102,11 +102,21 @@ namespace REC709 extern const Primaries primaries; } +namespace REC709_D60 +{ +extern const Primaries primaries; +} + namespace REC2020 { extern const Primaries primaries; } +namespace REC2020_D60 +{ +extern const Primaries primaries; +} + namespace P3_DCI { extern const Primaries primaries; diff --git a/src/bindings/python/PyTypes.cpp b/src/bindings/python/PyTypes.cpp index 760d715587..2adff33c6c 100644 --- a/src/bindings/python/PyTypes.cpp +++ b/src/bindings/python/PyTypes.cpp @@ -583,6 +583,14 @@ 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_ACES_OUTPUT_TRANSFORM_20", FIXED_FUNCTION_ACES_OUTPUT_TRANSFORM_20, + DOC(PyOpenColorIO, FixedFunctionStyle, FIXED_FUNCTION_ACES_OUTPUT_TRANSFORM_20)) + .value("FIXED_FUNCTION_ACES_RGB_TO_JMH_20", FIXED_FUNCTION_ACES_RGB_TO_JMH_20, + DOC(PyOpenColorIO, FixedFunctionStyle, FIXED_FUNCTION_ACES_RGB_TO_JMH_20)) + .value("FIXED_FUNCTION_ACES_TONESCALE_COMPRESS_20", FIXED_FUNCTION_ACES_TONESCALE_COMPRESS_20, + DOC(PyOpenColorIO, FixedFunctionStyle, FIXED_FUNCTION_ACES_TONESCALE_COMPRESS_20)) + .value("FIXED_FUNCTION_ACES_GAMUT_COMPRESS_20", FIXED_FUNCTION_ACES_GAMUT_COMPRESS_20, + DOC(PyOpenColorIO, FixedFunctionStyle, FIXED_FUNCTION_ACES_GAMUT_COMPRESS_20)) .export_values(); py::enum_( diff --git a/tests/cpu/CMakeLists.txt b/tests/cpu/CMakeLists.txt index 91246fcc9e..66c32cb8a7 100755 --- a/tests/cpu/CMakeLists.txt +++ b/tests/cpu/CMakeLists.txt @@ -148,6 +148,7 @@ set(SOURCES ops/cdl/CDLOpCPU.cpp ops/cdl/CDLOpGPU.cpp ops/exposurecontrast/ExposureContrastOpGPU.cpp + ops/fixedfunction/ACES2/Transform.cpp ops/fixedfunction/FixedFunctionOpGPU.cpp ops/gamma/GammaOpGPU.cpp ops/gradingprimary/GradingPrimaryOpGPU.cpp diff --git a/tests/cpu/Config_tests.cpp b/tests/cpu/Config_tests.cpp index f0835eca82..89730fc908 100644 --- a/tests/cpu/Config_tests.cpp +++ b/tests/cpu/Config_tests.cpp @@ -2189,6 +2189,12 @@ const std::string PROFILE_V21 = "environment:\n" " {}\n"; +const std::string PROFILE_V24 = + "ocio_profile_version: 2.4\n" + "\n" + "environment:\n" + " {}\n"; + const std::string SIMPLE_PROFILE_A = "search_path: luts\n" "strictparsing: true\n" @@ -2199,6 +2205,19 @@ const std::string SIMPLE_PROFILE_A = " scene_linear: lnh\n" "\n"; +const std::string SIMPLE_PROFILE_B = + "search_path: luts\n" + "strictparsing: true\n" + "luma: [0.2126, 0.7152, 0.0722]\n" + "\n" + "roles:\n" + " aces_interchange: lnh\n" + " color_timing: log\n" + " compositing_log: log\n" + " default: raw\n" + " scene_linear: lnh\n" + "\n"; + const std::string SIMPLE_PROFILE_DISPLAYS_LOOKS = "displays:\n" " sRGB:\n" @@ -2284,6 +2303,9 @@ const std::string PROFILE_V2_START = PROFILE_V2 + SIMPLE_PROFILE_A + const std::string PROFILE_V21_START = PROFILE_V21 + SIMPLE_PROFILE_A + DEFAULT_RULES + SIMPLE_PROFILE_B_V2; + +const std::string PROFILE_V24_START = PROFILE_V24 + SIMPLE_PROFILE_B + + DEFAULT_RULES + SIMPLE_PROFILE_B_V2; } OCIO_ADD_TEST(Config, serialize_colorspace_displayview_transforms) @@ -4922,6 +4944,189 @@ OCIO_ADD_TEST(Config, fixed_function_serialization) OCIO_CHECK_THROW_WHAT(config = OCIO::Config::CreateFromStream(is), OCIO::Exception, "'FixedFunctionTransform' parsing failed: style value is missing."); } + + { + const std::string strEnd = + " from_scene_reference: !\n" + " children:\n" + " - ! {style: ACES2_OutputTransform, params: [100, 0.64, 0.33, 0.3, 0.6, 0.15, 0.06, 0.3127, 0.329]}\n" + " - ! {style: ACES2_OutputTransform, params: [100, 0.64, 0.33, 0.3, 0.6, 0.15, 0.06, 0.3127, 0.329], direction: inverse}\n" + " - ! {style: ACES2_RGB_TO_JMh, params: [0.64, 0.33, 0.3, 0.6, 0.15, 0.06, 0.3127, 0.329]}\n" + " - ! {style: ACES2_RGB_TO_JMh, params: [0.64, 0.33, 0.3, 0.6, 0.15, 0.06, 0.3127, 0.329], direction: inverse}\n" + " - ! {style: ACES2_TonescaleCompress, params: [100]}\n" + " - ! {style: ACES2_TonescaleCompress, params: [100], direction: inverse}\n" + " - ! {style: ACES2_GamutCompress, params: [100, 0.64, 0.33, 0.3, 0.6, 0.15, 0.06, 0.3127, 0.329]}\n" + " - ! {style: ACES2_GamutCompress, params: [100, 0.64, 0.33, 0.3, 0.6, 0.15, 0.06, 0.3127, 0.329], direction: inverse}\n"; + + const std::string str = PROFILE_V24_START + strEnd; + + std::istringstream is; + is.str(str); + + OCIO::ConstConfigRcPtr config; + + { + OCIO::LogGuard log; // Mute the experimental warnings. + + OCIO_CHECK_NO_THROW(config = OCIO::Config::CreateFromStream(is)); + OCIO_CHECK_NO_THROW(config->validate()); + + std::string expectedLog = +R"([OpenColorIO Warning]: FixedFunction style is experimental and may be removed in a future release: 'ACES2_OutputTransform'. +[OpenColorIO Warning]: FixedFunction style is experimental and may be removed in a future release: 'ACES2_OutputTransform'. +[OpenColorIO Warning]: FixedFunction style is experimental and may be removed in a future release: 'ACES2_RGB_TO_JMh'. +[OpenColorIO Warning]: FixedFunction style is experimental and may be removed in a future release: 'ACES2_RGB_TO_JMh'. +[OpenColorIO Warning]: FixedFunction style is experimental and may be removed in a future release: 'ACES2_TonescaleCompress'. +[OpenColorIO Warning]: FixedFunction style is experimental and may be removed in a future release: 'ACES2_TonescaleCompress'. +[OpenColorIO Warning]: FixedFunction style is experimental and may be removed in a future release: 'ACES2_GamutCompress'. +[OpenColorIO Warning]: FixedFunction style is experimental and may be removed in a future release: 'ACES2_GamutCompress'. +)"; + OCIO_CHECK_EQUAL(log.output(), expectedLog); + } + + { + OCIO::LogGuard log; // Mute the experimental warnings. + + // Write the config. + + std::stringstream ss; + OCIO_CHECK_NO_THROW(ss << *config.get()); + OCIO_CHECK_EQUAL(ss.str(), str); + } + } + + { + const std::string strEnd = + " from_scene_reference: !\n" + " children:\n" + " - ! {style: ACES2_OutputTransform, params: [100, 0.64, 0.33, 0.3, 0.6, 0.15, 0.06, 0.3127, 0.329]}\n" + " - ! {style: ACES2_OutputTransform, params: [100, 0.64, 0.33, 0.3, 0.6, 0.15, 0.06, 0.3127, 0.329], direction: inverse}\n"; + + const std::string str = PROFILE_V21_START + strEnd; + + OCIO::LogGuard log; // Mute the experimental warnings. + + std::istringstream is; + is.str(str); + + OCIO::ConstConfigRcPtr config; + OCIO_CHECK_THROW_WHAT(config = OCIO::Config::CreateFromStream(is), OCIO::Exception, + "Only config version 2.4 (or higher) can have FixedFunctionTransform style 'ACES_OUTPUT_TRANSFORM_20'."); + } + + { + const std::string strEnd = + " from_scene_reference: !\n" + " children:\n" + " - ! {style: ACES2_RGB_TO_JMh, params: [100, 0.64, 0.33, 0.3, 0.6, 0.15, 0.06, 0.3127, 0.329]}\n" + " - ! {style: ACES2_RGB_TO_JMh, params: [100, 0.64, 0.33, 0.3, 0.6, 0.15, 0.06, 0.3127, 0.329], direction: inverse}\n"; + + const std::string str = PROFILE_V21_START + strEnd; + + OCIO::LogGuard log; // Mute the experimental warnings. + + std::istringstream is; + is.str(str); + + OCIO::ConstConfigRcPtr config; + OCIO_CHECK_THROW_WHAT(config = OCIO::Config::CreateFromStream(is), OCIO::Exception, + "Only config version 2.4 (or higher) can have FixedFunctionTransform style 'ACES_RGB_TO_JMH_20'."); + } + + { + const std::string strEnd = + " from_scene_reference: !\n" + " children:\n" + " - ! {style: ACES2_TonescaleCompress, params: [100, 0.64, 0.33, 0.3, 0.6, 0.15, 0.06, 0.3127, 0.329]}\n" + " - ! {style: ACES2_TonescaleCompress, params: [100, 0.64, 0.33, 0.3, 0.6, 0.15, 0.06, 0.3127, 0.329], direction: inverse}\n"; + + const std::string str = PROFILE_V21_START + strEnd; + + OCIO::LogGuard log; // Mute the experimental warnings. + + std::istringstream is; + is.str(str); + + OCIO::ConstConfigRcPtr config; + OCIO_CHECK_THROW_WHAT(config = OCIO::Config::CreateFromStream(is), OCIO::Exception, + "Only config version 2.4 (or higher) can have FixedFunctionTransform style 'ACES_TONESCALE_COMPRESS_20'."); + } + + { + const std::string strEnd = + " from_scene_reference: !\n" + " children:\n" + " - ! {style: ACES2_GamutCompress, params: [100, 0.64, 0.33, 0.3, 0.6, 0.15, 0.06, 0.3127, 0.329]}\n" + " - ! {style: ACES2_GamutCompress, params: [100, 0.64, 0.33, 0.3, 0.6, 0.15, 0.06, 0.3127, 0.329], direction: inverse}\n"; + + const std::string str = PROFILE_V21_START + strEnd; + + OCIO::LogGuard log; // Mute the experimental warnings. + + std::istringstream is; + is.str(str); + + OCIO::ConstConfigRcPtr config; + OCIO_CHECK_THROW_WHAT(config = OCIO::Config::CreateFromStream(is), OCIO::Exception, + "Only config version 2.4 (or higher) can have FixedFunctionTransform style 'ACES_GAMUT_COMPRESS_20'."); + } + + { + const std::string strEnd = + " from_scene_reference: !\n" + " children:\n" + " - ! {style: ACES2_OutputTransform, params: []}\n"; + + const std::string str = PROFILE_V24_START + strEnd; + + OCIO::LogGuard log; // Mute the experimental warnings. + + std::istringstream is; + is.str(str); + + OCIO::ConstConfigRcPtr config; + OCIO_CHECK_NO_THROW(config = OCIO::Config::CreateFromStream(is)); + OCIO_CHECK_THROW_WHAT(config->validate(), OCIO::Exception, + "The style 'ACES_OutputTransform20 (Forward)' must have 9 parameters but 0 found."); + } + + { + const std::string strEnd = + " from_scene_reference: !\n" + " children:\n" + " - ! {style: ACES2_OutputTransform, params: [-1, 0.64, 0.33, 0.3, 0.6, 0.15, 0.06, 0.3127, 0.329]}\n"; + + const std::string str = PROFILE_V24_START + strEnd; + + OCIO::LogGuard log; // Mute the experimental warnings. + + std::istringstream is; + is.str(str); + + OCIO::ConstConfigRcPtr config; + OCIO_CHECK_NO_THROW(config = OCIO::Config::CreateFromStream(is)); + OCIO_CHECK_THROW_WHAT(config->validate(), OCIO::Exception, + "FixedFunctionTransform validation failed: Parameter -1 (peak_luminance) is outside valid range [1,10000]"); + } + + { + const std::string strEnd = + " from_scene_reference: !\n" + " children:\n" + " - ! {style: ACES2_OutputTransform, params: [100.5, 0.64, 0.33, 0.3, 0.6, 0.15, 0.06, 0.3127, 0.329]}\n"; + + const std::string str = PROFILE_V24_START + strEnd; + + OCIO::LogGuard log; // Mute the experimental warnings. + + std::istringstream is; + is.str(str); + + OCIO::ConstConfigRcPtr config; + OCIO_CHECK_NO_THROW(config = OCIO::Config::CreateFromStream(is)); + OCIO_CHECK_THROW_WHAT(config->validate(), OCIO::Exception, + "FixedFunctionTransform validation failed: Parameter 100.5 (peak_luminance) cannot include any fractional component"); + } } OCIO_ADD_TEST(Config, exposure_contrast_serialization) diff --git a/tests/cpu/ops/fixedfunction/FixedFunctionOpCPU_tests.cpp b/tests/cpu/ops/fixedfunction/FixedFunctionOpCPU_tests.cpp index 7e61872443..02d0d133f6 100644 --- a/tests/cpu/ops/fixedfunction/FixedFunctionOpCPU_tests.cpp +++ b/tests/cpu/ops/fixedfunction/FixedFunctionOpCPU_tests.cpp @@ -8,6 +8,7 @@ #include "testutils/UnitTest.h" #include "UnitTestUtils.h" +#include "ops/lut3d/Lut3DOp.h" namespace OCIO = OCIO_NAMESPACE; @@ -405,7 +406,524 @@ OCIO_ADD_TEST(FixedFunctionOpCPU, aces_gamut_map_13) 1e-6f, __LINE__); } -} +} + +OCIO_ADD_TEST(FixedFunctionOpCPU, aces_output_transform_20) +{ + const unsigned num_samples = 35; + + float input_32f[num_samples*4] = { + // ACEScg primaries and secondaries scaled by 4 + 2.781808965f, 0.179178253f, -0.022103530f, 1.0f, + 3.344523751f, 3.617862727f, -0.006002689f, 1.0f, + 0.562714786f, 3.438684474f, 0.016100841f, 1.0f, + 1.218191035f, 3.820821747f, 4.022103530f, 1.0f, + 0.655476249f, 0.382137273f, 4.006002689f, 1.0f, + 3.437285214f, 0.561315526f, 3.983899159f, 1.0f, + // OCIO test values + 0.110000000f, 0.020000000f, 0.040000000f, 0.5f, + 0.710000000f, 0.510000000f, 0.810000000f, 1.0f, + 0.430000000f, 0.820000000f, 0.710000000f, 0.0f, + // ColorChecker24 (SMPTE 2065-1 2021) + 0.118770000f, 0.087090000f, 0.058950000f, 1.0f, + 0.400020000f, 0.319160000f, 0.237360000f, 1.0f, + 0.184760000f, 0.203980000f, 0.313110000f, 1.0f, + 0.109010000f, 0.135110000f, 0.064930000f, 1.0f, + 0.266840000f, 0.246040000f, 0.409320000f, 1.0f, + 0.322830000f, 0.462080000f, 0.406060000f, 1.0f, + 0.386050000f, 0.227430000f, 0.057770000f, 1.0f, + 0.138220000f, 0.130370000f, 0.337030000f, 1.0f, + 0.302020000f, 0.137520000f, 0.127580000f, 1.0f, + 0.093100000f, 0.063470000f, 0.135250000f, 1.0f, + 0.348760000f, 0.436540000f, 0.106130000f, 1.0f, + 0.486550000f, 0.366850000f, 0.080610000f, 1.0f, + 0.087320000f, 0.074430000f, 0.272740000f, 1.0f, + 0.153660000f, 0.256920000f, 0.090710000f, 1.0f, + 0.217420000f, 0.070700000f, 0.051300000f, 1.0f, + 0.589190000f, 0.539430000f, 0.091570000f, 1.0f, + 0.309040000f, 0.148180000f, 0.274260000f, 1.0f, + 0.149010000f, 0.233780000f, 0.359390000f, 1.0f, + 0.866530000f, 0.867920000f, 0.858180000f, 1.0f, + 0.573560000f, 0.572560000f, 0.571690000f, 1.0f, + 0.353460000f, 0.353370000f, 0.353910000f, 1.0f, + 0.202530000f, 0.202430000f, 0.202870000f, 1.0f, + 0.094670000f, 0.095200000f, 0.096370000f, 1.0f, + 0.037450000f, 0.037660000f, 0.038950000f, 1.0f, + // Spectrally non-selective 18 % reflecting diffuser + 0.180000000f, 0.180000000f, 0.180000000f, 1.0f, + // Perfect reflecting diffuser + 0.977840000f, 0.977840000f, 0.977840000f, 1.0f, + }; + + float input2_32f[num_samples * 4]; + memcpy(&input2_32f[0], &input_32f[0], sizeof(float)*num_samples * 4); + + const float expected_32f[num_samples*4] = { + // ACEScg primaries and secondaries scaled by 4 + 4.965774059f, -0.032864563f, 0.041625995f, 1.0f, + 3.969441891f, 3.825784922f, -0.056133576f, 1.0f, + -0.075329021f, 3.688980103f, 0.270296901f, 1.0f, + -0.095423937f, 3.650517225f, 3.459972620f, 1.0f, + -0.028930068f, 0.196428135f, 2.796343565f, 1.0f, + 4.900805950f, -0.064376131f, 3.838256121f, 1.0f, + // OCIO test values + 0.096890204f, -0.001135312f, 0.018971510f, 0.5f, + 0.809614301f, 0.479856580f, 0.814239502f, 1.0f, + 0.107420206f, 0.920529068f, 0.726378500f, 0.0f, + // ColorChecker24 (SMPTE 2065-1 2021) + 0.115475260f, 0.050812904f, 0.030212952f, 1.0f, + 0.484879673f, 0.301042974f, 0.226768956f, 1.0f, + 0.098463766f, 0.160814658f, 0.277010560f, 1.0f, + 0.071130365f, 0.107334383f, 0.035097566f, 1.0f, + 0.207111493f, 0.198474824f, 0.375326216f, 1.0f, + 0.195447892f, 0.481111974f, 0.393299013f, 1.0f, + 0.571913838f, 0.196872935f, 0.041634772f, 1.0f, + 0.045791931f, 0.069875360f, 0.291233480f, 1.0f, + 0.424848706f, 0.083199009f, 0.102153838f, 1.0f, + 0.059589427f, 0.022219172f, 0.091246888f, 1.0f, + 0.360365510f, 0.478741467f, 0.086726837f, 1.0f, + 0.695662081f, 0.371994525f, 0.068298168f, 1.0f, + 0.011806309f, 0.021665439f, 0.199594811f, 1.0f, + 0.076526314f, 0.256237417f, 0.060564656f, 1.0f, + 0.300064564f, 0.023416257f, 0.030360471f, 1.0f, + 0.805484772f, 0.596903503f, 0.082996152f, 1.0f, + 0.388385952f, 0.079899102f, 0.245819211f, 1.0f, + 0.010952532f, 0.196105912f, 0.307181358f, 1.0f, + 0.921019495f, 0.921707213f, 0.912856042f, 1.0f, + 0.590192318f, 0.588423848f, 0.587825358f, 1.0f, + 0.337743521f, 0.337685764f, 0.338155121f, 1.0f, + 0.169265985f, 0.169178501f, 0.169557109f, 1.0f, + 0.058346048f, 0.059387825f, 0.060296260f, 1.0f, + 0.012581184f, 0.012947139f, 0.013654195f, 1.0f, + // Spectrally non-selective 18 % reflecting diffuser + 0.145115465f, 0.145115525f, 0.145115510f, 1.0f, + // Perfect reflecting diffuser + 1.041565657f, 1.041566014f, 1.041565657f, 1.0f, + }; + + OCIO::FixedFunctionOpData::Params params = { + // Peak luminance + 1000.f, + // P3D65 gamut + 0.680, 0.320, 0.265, 0.690, 0.150, 0.060, 0.3127, 0.3290 + }; + OCIO::ConstFixedFunctionOpDataRcPtr funcData + = std::make_shared(OCIO::FixedFunctionOpData::ACES_OUTPUT_TRANSFORM_20_FWD, + params); + + ApplyFixedFunction(&input2_32f[0], &expected_32f[0], num_samples, + funcData, + 1e-5f, + __LINE__); + + OCIO::ConstFixedFunctionOpDataRcPtr funcData2 + = std::make_shared(OCIO::FixedFunctionOpData::ACES_OUTPUT_TRANSFORM_20_INV, + params); + + ApplyFixedFunction(&input2_32f[0], &input_32f[0], num_samples, + funcData2, + 1e-4f, + __LINE__); +} + +OCIO_ADD_TEST(FixedFunctionOpCPU, aces_ot_20_rec709_100n_rt) +{ + const int lut_size = 8; + const int num_channels = 4; + int num_samples = lut_size * lut_size * lut_size; + std::vector input_32f(num_samples * num_channels, 0.f); + std::vector output_32f(num_samples * num_channels, 0.f); + + GenerateIdentityLut3D(input_32f.data(), lut_size, num_channels, OCIO::LUT3DORDER_FAST_RED); + + OCIO::FixedFunctionOpData::Params params = { + // Peak luminance + 100.f, + // Rec709 gamut + 0.6400, 0.3300, 0.3000, 0.6000, 0.1500, 0.0600, 0.3127, 0.3290 + }; + + OCIO::ConstFixedFunctionOpDataRcPtr funcData + = std::make_shared(OCIO::FixedFunctionOpData::ACES_OUTPUT_TRANSFORM_20_INV, + params); + + OCIO::ConstOpCPURcPtr op; + OCIO_CHECK_NO_THROW(op = OCIO::GetFixedFunctionCPURenderer(funcData)); + OCIO_CHECK_NO_THROW(op->apply(&input_32f[0], &output_32f[0], num_samples)); + + OCIO::ConstFixedFunctionOpDataRcPtr funcData2 + = std::make_shared(OCIO::FixedFunctionOpData::ACES_OUTPUT_TRANSFORM_20_FWD, + params); + + ApplyFixedFunction(&output_32f[0], &input_32f[0], num_samples, + funcData2, + 1e-3f, + __LINE__); +} + +OCIO_ADD_TEST(FixedFunctionOpCPU, aces_ot_20_p3d65_100n_rt) +{ + const int lut_size = 8; + const int num_channels = 4; + int num_samples = lut_size * lut_size * lut_size; + std::vector input_32f(num_samples * num_channels, 0.f); + std::vector output_32f(num_samples * num_channels, 0.f); + + GenerateIdentityLut3D(input_32f.data(), lut_size, num_channels, OCIO::LUT3DORDER_FAST_RED); + + OCIO::FixedFunctionOpData::Params params = { + // Peak luminance + 100.f, + // P3D65 gamut + 0.680, 0.320, 0.265, 0.690, 0.150, 0.060, 0.3127, 0.3290 + }; + + OCIO::ConstFixedFunctionOpDataRcPtr funcData + = std::make_shared(OCIO::FixedFunctionOpData::ACES_OUTPUT_TRANSFORM_20_INV, + params); + + OCIO::ConstOpCPURcPtr op; + OCIO_CHECK_NO_THROW(op = OCIO::GetFixedFunctionCPURenderer(funcData)); + OCIO_CHECK_NO_THROW(op->apply(&input_32f[0], &output_32f[0], num_samples)); + + OCIO::ConstFixedFunctionOpDataRcPtr funcData2 + = std::make_shared(OCIO::FixedFunctionOpData::ACES_OUTPUT_TRANSFORM_20_FWD, + params); + + ApplyFixedFunction(&output_32f[0], &input_32f[0], num_samples, + funcData2, + 1e-2f, + __LINE__); +} + +OCIO_ADD_TEST(FixedFunctionOpCPU, aces_ot_20_p3d65_1000n_rt) +{ + const int lut_size = 8; + const int num_channels = 4; + int num_samples = lut_size * lut_size * lut_size; + std::vector input_32f(num_samples * num_channels, 0.f); + std::vector output_32f(num_samples * num_channels, 0.f); + + GenerateIdentityLut3D(input_32f.data(), lut_size, num_channels, OCIO::LUT3DORDER_FAST_RED); + + const float normPeakLuminance = 10.f; + for (unsigned int i = 0; i < input_32f.size(); ++i) + { + input_32f[i] *= normPeakLuminance; + } + + OCIO::FixedFunctionOpData::Params params = { + // Peak luminance + 1000.f, + // P3D65 gamut + 0.680, 0.320, 0.265, 0.690, 0.150, 0.060, 0.3127, 0.3290 + }; + + OCIO::ConstFixedFunctionOpDataRcPtr funcData + = std::make_shared(OCIO::FixedFunctionOpData::ACES_OUTPUT_TRANSFORM_20_INV, + params); + + OCIO::ConstOpCPURcPtr op; + OCIO_CHECK_NO_THROW(op = OCIO::GetFixedFunctionCPURenderer(funcData)); + OCIO_CHECK_NO_THROW(op->apply(&input_32f[0], &output_32f[0], num_samples)); + + OCIO::ConstFixedFunctionOpDataRcPtr funcData2 + = std::make_shared(OCIO::FixedFunctionOpData::ACES_OUTPUT_TRANSFORM_20_FWD, + params); + + ApplyFixedFunction(&output_32f[0], &input_32f[0], num_samples, + funcData2, + 1e-3f, + __LINE__); +} + +OCIO_ADD_TEST(FixedFunctionOpCPU, aces_rgb_to_jmh_20) +{ + const unsigned num_samples = 27; + + // The following input values are processed and carried over to the next + // FixedFunctionOp test along the ACES2 output transform steps. + + float input_32f[num_samples*4] = { + // ACEScg primaries and secondaries scaled by 4 + 2.781808965f, 0.179178253f, -0.022103530f, 1.0f, + 3.344523751f, 3.617862727f, -0.006002689f, 1.0f, + 0.562714786f, 3.438684474f, 0.016100841f, 1.0f, + 1.218191035f, 3.820821747f, 4.022103530f, 1.0f, + 0.655476249f, 0.382137273f, 4.006002689f, 1.0f, + 3.437285214f, 0.561315526f, 3.983899159f, 1.0f, + // OCIO test values + 0.110000000f, 0.020000000f, 0.040000000f, 0.5f, + 0.710000000f, 0.510000000f, 0.810000000f, 1.0f, + 0.430000000f, 0.820000000f, 0.710000000f, 0.0f, + // ColorChecker24 (SMPTE 2065-1 2021) + 0.118770000f, 0.087090000f, 0.058950000f, 1.0f, + 0.400020000f, 0.319160000f, 0.237360000f, 1.0f, + 0.184760000f, 0.203980000f, 0.313110000f, 1.0f, + 0.109010000f, 0.135110000f, 0.064930000f, 1.0f, + 0.266840000f, 0.246040000f, 0.409320000f, 1.0f, + 0.322830000f, 0.462080000f, 0.406060000f, 1.0f, + 0.386050000f, 0.227430000f, 0.057770000f, 1.0f, + 0.138220000f, 0.130370000f, 0.337030000f, 1.0f, + 0.302020000f, 0.137520000f, 0.127580000f, 1.0f, + 0.093100000f, 0.063470000f, 0.135250000f, 1.0f, + 0.348760000f, 0.436540000f, 0.106130000f, 1.0f, + 0.486550000f, 0.366850000f, 0.080610000f, 1.0f, + 0.087320000f, 0.074430000f, 0.272740000f, 1.0f, + 0.153660000f, 0.256920000f, 0.090710000f, 1.0f, + 0.217420000f, 0.070700000f, 0.051300000f, 1.0f, + 0.589190000f, 0.539430000f, 0.091570000f, 1.0f, + 0.309040000f, 0.148180000f, 0.274260000f, 1.0f, + 0.149010000f, 0.233780000f, 0.359390000f, 1.0f, + }; + + float input2_32f[num_samples * 4]; + memcpy(&input2_32f[0], &input_32f[0], sizeof(float)*num_samples * 4); + + const float expected_32f[num_samples*4] = { + // ACEScg primaries and secondaries scaled by 4 + 107.480636597f, 206.827301025f, 25.025110245f, 1.0f, + 173.194076538f, 133.330886841f, 106.183448792f, 1.0f, + 139.210220337f, 191.922363281f, 147.056488037f, 1.0f, + 157.905166626f, 111.975311279f, 192.204727173f, 1.0f, + 79.229278564f, 100.424659729f, 268.442108154f, 1.0f, + 132.888137817f, 173.358779907f, 341.715240479f, 1.0f, + // OCIO test values + 26.112514496f, 42.523605347f, 4.173158169f, 0.5f, + 79.190460205f, 25.002300262f, 332.159759521f, 1.0f, + 81.912559509f, 39.754810333f, 182.925750732f, 0.0f, + // ColorChecker24 (SMPTE 2065-1 2021) + 33.924663544f, 12.254567146f, 38.146659851f, 1.0f, + 61.332393646f, 15.169423103f, 39.841842651f, 1.0f, + 47.191543579f, 11.839941978f, 249.107116699f, 1.0f, + 37.328300476f, 13.224150658f, 128.878036499f, 1.0f, + 53.465549469f, 13.121579170f, 285.658966064f, 1.0f, + 65.414512634f, 19.172147751f, 179.324264526f, 1.0f, + 55.711513519f, 37.182041168f, 50.924011230f, 1.0f, + 40.020961761f, 20.762512207f, 271.008331299f, 1.0f, + 47.704769135f, 35.791145325f, 13.975610733f, 1.0f, + 30.385913849f, 14.544739723f, 317.544281006f, 1.0f, + 64.222846985f, 33.487697601f, 119.145133972f, 1.0f, + 65.570358276f, 35.864013672f, 70.842193604f, 1.0f, + 31.800464630f, 23.920211792f, 273.228973389f, 1.0f, + 47.950405121f, 28.027387619f, 144.154159546f, 1.0f, + 38.440967560f, 42.604164124f, 17.892261505f, 1.0f, + 75.117736816f, 40.952045441f, 90.752044678f, 1.0f, + 49.311210632f, 33.812240601f, 348.832092285f, 1.0f, + 47.441757202f, 22.915655136f, 218.454376221f, 1.0f, + }; + + // ACES AP0 + OCIO::FixedFunctionOpData::Params params = {0.7347, 0.2653, 0.0000, 1.0000, 0.0001, -0.0770, 0.32168, 0.33767}; + OCIO::ConstFixedFunctionOpDataRcPtr funcData + = std::make_shared(OCIO::FixedFunctionOpData::ACES_RGB_TO_JMh_20, + params); + + ApplyFixedFunction(&input2_32f[0], &expected_32f[0], num_samples, + funcData, + 1e-5f, + __LINE__); + + OCIO::ConstFixedFunctionOpDataRcPtr funcData2 + = std::make_shared(OCIO::FixedFunctionOpData::ACES_JMh_TO_RGB_20, + params); + + ApplyFixedFunction(&input2_32f[0], &input_32f[0], num_samples, + funcData2, + 1e-4f, + __LINE__); +} + +OCIO_ADD_TEST(FixedFunctionOpCPU, aces_tonescale_compress_20) +{ + const unsigned num_samples = 27; + + float input_32f[num_samples*4] = { + // ACEScg primaries and secondaries scaled by 4 + 107.480636597f, 206.827301025f, 25.025110245f, 1.0f, + 173.194076538f, 133.330886841f, 106.183448792f, 1.0f, + 139.210220337f, 191.922363281f, 147.056488037f, 1.0f, + 157.905166626f, 111.975311279f, 192.204727173f, 1.0f, + 79.229278564f, 100.424659729f, 268.442108154f, 1.0f, + 132.888137817f, 173.358779907f, 341.715240479f, 1.0f, + // OCIO test values + 26.112514496f, 42.523605347f, 4.173158169f, 0.5f, + 79.190460205f, 25.002300262f, 332.159759521f, 1.0f, + 81.912559509f, 39.754810333f, 182.925750732f, 0.0f, + // ColorChecker24 (SMPTE 2065-1 2021) + 33.924663544f, 12.254567146f, 38.146659851f, 1.0f, + 61.332393646f, 15.169423103f, 39.841842651f, 1.0f, + 47.191543579f, 11.839941978f, 249.107116699f, 1.0f, + 37.328300476f, 13.224150658f, 128.878036499f, 1.0f, + 53.465549469f, 13.121579170f, 285.658966064f, 1.0f, + 65.414512634f, 19.172147751f, 179.324264526f, 1.0f, + 55.711513519f, 37.182041168f, 50.924011230f, 1.0f, + 40.020961761f, 20.762512207f, 271.008331299f, 1.0f, + 47.704769135f, 35.791145325f, 13.975610733f, 1.0f, + 30.385913849f, 14.544739723f, 317.544281006f, 1.0f, + 64.222846985f, 33.487697601f, 119.145133972f, 1.0f, + 65.570358276f, 35.864013672f, 70.842193604f, 1.0f, + 31.800464630f, 23.920211792f, 273.228973389f, 1.0f, + 47.950405121f, 28.027387619f, 144.154159546f, 1.0f, + 38.440967560f, 42.604164124f, 17.892261505f, 1.0f, + 75.117736816f, 40.952045441f, 90.752044678f, 1.0f, + 49.311210632f, 33.812240601f, 348.832092285f, 1.0f, + 47.441757202f, 22.915655136f, 218.454376221f, 1.0f, + }; + + float input2_32f[num_samples * 4]; + memcpy(&input2_32f[0], &input_32f[0], sizeof(float)*num_samples * 4); + + const float expected_32f[num_samples*4] = { + // ACEScg primaries and secondaries scaled by 4 + 110.702453613f, 211.251770020f, 25.025110245f, 1.0f, + 168.016815186f, 129.796249390f, 106.183448792f, 1.0f, + 140.814849854f, 193.459213257f, 147.056488037f, 1.0f, + 156.429519653f, 110.938514709f, 192.204727173f, 1.0f, + 80.456542969f, 98.490524292f, 268.442108154f, 1.0f, + 135.172195435f, 175.559280396f, 341.715240479f, 1.0f, + // OCIO test values + 18.187314987f, 33.819175720f, 4.173158169f, 0.5f, + 80.413116455f, 21.309329987f, 332.159759521f, 1.0f, + 83.447891235f, 37.852291107f, 182.925750732f, 0.0f, + // ColorChecker24 (SMPTE 2065-1 2021) + 27.411964417f, 13.382769585f, 38.146659851f, 1.0f, + 59.987670898f, 14.391894341f, 39.841842651f, 1.0f, + 43.298923492f, 12.199877739f, 249.107116699f, 1.0f, + 31.489658356f, 14.075142860f, 128.878036499f, 1.0f, + 50.749198914f, 12.731814384f, 285.658966064f, 1.0f, + 64.728637695f, 18.593795776f, 179.324264526f, 1.0f, + 53.399448395f, 37.394428253f, 50.924011230f, 1.0f, + 34.719596863f, 21.616765976f, 271.008331299f, 1.0f, + 43.910713196f, 36.788166046f, 13.975610733f, 1.0f, + 23.196525574f, 15.118354797f, 317.544281006f, 1.0f, + 63.348674774f, 33.283493042f, 119.145133972f, 1.0f, + 64.908889771f, 35.371044159f, 70.842193604f, 1.0f, + 24.876911163f, 23.143159866f, 273.228973389f, 1.0f, + 44.203376770f, 28.918329239f, 144.154159546f, 1.0f, + 32.824356079f, 43.447875977f, 17.892261505f, 1.0f, + 75.830871582f, 39.872474670f, 90.752044678f, 1.0f, + 45.823116302f, 34.652069092f, 348.832092285f, 1.0f, + 43.597240448f, 23.079078674f, 218.454376221f, 1.0f, + }; + + OCIO::FixedFunctionOpData::Params params = {1000.f}; + OCIO::ConstFixedFunctionOpDataRcPtr funcData + = std::make_shared(OCIO::FixedFunctionOpData::ACES_TONESCALE_COMPRESS_20_FWD, + params); + + ApplyFixedFunction(&input2_32f[0], &expected_32f[0], num_samples, + funcData, + 1e-5f, + __LINE__); + + OCIO::ConstFixedFunctionOpDataRcPtr funcData2 + = std::make_shared(OCIO::FixedFunctionOpData::ACES_TONESCALE_COMPRESS_20_INV, + params); + + ApplyFixedFunction(&input2_32f[0], &input_32f[0], num_samples, + funcData2, + 1e-4f, + __LINE__); +} + +OCIO_ADD_TEST(FixedFunctionOpCPU, aces_gamut_map_20) +{ + const unsigned num_samples = 27; + + float input_32f[num_samples*4] = { + // ACEScg primaries and secondaries scaled by 4 + 110.702453613f, 211.251770020f, 25.025110245f, 1.0f, + 168.016815186f, 129.796249390f, 106.183448792f, 1.0f, + 140.814849854f, 193.459213257f, 147.056488037f, 1.0f, + 156.429519653f, 110.938514709f, 192.204727173f, 1.0f, + 80.456542969f, 98.490524292f, 268.442108154f, 1.0f, + 135.172195435f, 175.559280396f, 341.715240479f, 1.0f, + // OCIO test values + 18.187314987f, 33.819175720f, 4.173158169f, 0.5f, + 80.413116455f, 21.309329987f, 332.159759521f, 1.0f, + 83.447891235f, 37.852291107f, 182.925750732f, 0.0f, + // ColorChecker24 (SMPTE 2065-1 2021) + 27.411964417f, 13.382769585f, 38.146659851f, 1.0f, + 59.987670898f, 14.391894341f, 39.841842651f, 1.0f, + 43.298923492f, 12.199877739f, 249.107116699f, 1.0f, + 31.489658356f, 14.075142860f, 128.878036499f, 1.0f, + 50.749198914f, 12.731814384f, 285.658966064f, 1.0f, + 64.728637695f, 18.593795776f, 179.324264526f, 1.0f, + 53.399448395f, 37.394428253f, 50.924011230f, 1.0f, + 34.719596863f, 21.616765976f, 271.008331299f, 1.0f, + 43.910713196f, 36.788166046f, 13.975610733f, 1.0f, + 23.196525574f, 15.118354797f, 317.544281006f, 1.0f, + 63.348674774f, 33.283493042f, 119.145133972f, 1.0f, + 64.908889771f, 35.371044159f, 70.842193604f, 1.0f, + 24.876911163f, 23.143159866f, 273.228973389f, 1.0f, + 44.203376770f, 28.918329239f, 144.154159546f, 1.0f, + 32.824356079f, 43.447875977f, 17.892261505f, 1.0f, + 75.830871582f, 39.872474670f, 90.752044678f, 1.0f, + 45.823116302f, 34.652069092f, 348.832092285f, 1.0f, + 43.597240448f, 23.079078674f, 218.454376221f, 1.0f, + }; + + float input2_32f[num_samples * 4]; + memcpy(&input2_32f[0], &input_32f[0], sizeof(float)*num_samples * 4); + + const float expected_32f[num_samples*4] = { + // ACEScg primaries and secondaries scaled by 4 + 107.831291199f, 174.252944946f, 25.025119781f, 1.0f, + 168.028198242f, 118.224960327f, 106.183464050f, 1.0f, + 140.030105591f, 127.177192688f, 147.056488037f, 1.0f, + 156.512435913f, 73.218856812f, 192.204727173f, 1.0f, + 79.378631592f, 72.613555908f, 268.442108154f, 1.0f, + 133.827835083f, 149.929809570f, 341.715240479f, 1.0f, + // OCIO test values + 18.194000244f, 33.312938690f, 4.173166752f, 0.5f, + 80.413116455f, 21.309329987f, 332.159759521f, 1.0f, + 83.467437744f, 37.305160522f, 182.925750732f, 0.0f, + // ColorChecker24 (SMPTE 2065-1 2021) + 27.411962509f, 13.382793427f, 38.146591187f, 1.0f, + 59.987670898f, 14.391893387f, 39.841842651f, 1.0f, + 43.298923492f, 12.199877739f, 249.107116699f, 1.0f, + 31.489658356f, 14.075142860f, 128.878036499f, 1.0f, + 50.749198914f, 12.731814384f, 285.658966064f, 1.0f, + 64.728637695f, 18.593795776f, 179.324264526f, 1.0f, + 53.399448395f, 37.394428253f, 50.924011230f, 1.0f, + 34.719596863f, 21.616765976f, 271.008331299f, 1.0f, + 43.910709381f, 36.788166046f, 13.975610733f, 1.0f, + 23.196525574f, 15.118361473f, 317.544250488f, 1.0f, + 63.348674774f, 33.283493042f, 119.145133972f, 1.0f, + 64.908889771f, 35.371044159f, 70.842193604f, 1.0f, + 24.876916885f, 23.143167496f, 273.229034424f, 1.0f, + 44.203376770f, 28.918329239f, 144.154159546f, 1.0f, + 32.824352264f, 43.447864532f, 17.892255783f, 1.0f, + 75.830871582f, 39.872474670f, 90.752044678f, 1.0f, + 45.823104858f, 34.652038574f, 348.832092285f, 1.0f, + 43.635551453f, 21.629474640f, 218.454376221f, 1.0f, + }; + + OCIO::FixedFunctionOpData::Params params = { + // Peak luminance + 1000.f, + // P3D65 gamut + 0.680, 0.320, 0.265, 0.690, 0.150, 0.060, 0.3127, 0.3290 + }; + OCIO::ConstFixedFunctionOpDataRcPtr funcData + = std::make_shared(OCIO::FixedFunctionOpData::ACES_GAMUT_COMPRESS_20_FWD, + params); + + ApplyFixedFunction(&input2_32f[0], &expected_32f[0], num_samples, + funcData, + 1e-5f, + __LINE__); + + OCIO::ConstFixedFunctionOpDataRcPtr funcData2 + = std::make_shared(OCIO::FixedFunctionOpData::ACES_GAMUT_COMPRESS_20_INV, + params); + + ApplyFixedFunction(&input2_32f[0], &input_32f[0], num_samples, + funcData2, + 1e-5f, + __LINE__); +} OCIO_ADD_TEST(FixedFunctionOpCPU, rec2100_surround) { diff --git a/tests/cpu/transforms/BuiltinTransform_tests.cpp b/tests/cpu/transforms/BuiltinTransform_tests.cpp index 32b8200b0b..52304d2a3f 100644 --- a/tests/cpu/transforms/BuiltinTransform_tests.cpp +++ b/tests/cpu/transforms/BuiltinTransform_tests.cpp @@ -429,6 +429,70 @@ AllValues UnitTestValues { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-CINEMA-108nit-7.2nit-P3lim_1.1", { { 0.5f, 0.4f, 0.3f }, { 0.22214814f, 0.21179835f, 0.15639816f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - SDR-100nit-REC709_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.26260212f, 0.25207470f, 0.20617338f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - SDR-100nit-P3-D65_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.26260212f, 0.25207472f, 0.20617332f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-108nit-P3-D65_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.16253406f, 0.15513624f, 0.12449740f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-300nit-P3-D65_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.20592399f, 0.19440515f, 0.15028581f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-500nit-P3-D65_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.41039306f, 0.38813826f, 0.30191866f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-1000nit-P3-D65_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.46536570f, 0.43852836f, 0.33688113f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-2000nit-P3-D65_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.51225936f, 0.48264506f, 0.37060050f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-4000nit-P3-D65_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.55653524f, 0.51967940f, 0.38678724f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-500nit-REC2020_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.41039258f, 0.38813800f, 0.30191845f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-1000nit-REC2020_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.46536540f, 0.43852820f, 0.33688095f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-2000nit-REC2020_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.51225930f, 0.48264477f, 0.37060022f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-4000nit-REC2020_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.55653550f, 0.51967950f, 0.38678730f } } }, + + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - SDR-100nit-REC709-D60-in-REC709-D65_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.25147703f, 0.24029444f, 0.18221131f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - SDR-100nit-REC709-D60-in-P3-D65_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.25373828f, 0.24245512f, 0.18384966f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - SDR-100nit-REC709-D60-in-REC2020-D65_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.25712878f, 0.24569483f, 0.18630630f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - SDR-100nit-P3-D60-in-P3-D65_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.25373834f, 0.24245517f, 0.18384990f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - SDR-100nit-P3-D60-in-XYZ-E_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.26332240f, 0.25161302f, 0.19079340f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-108nit-P3-D60-in-P3-D65_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.15705064f, 0.14920068f, 0.11100890f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-300nit-P3-D60-in-XYZ-E_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.20469205f, 0.19229384f, 0.13782679f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-500nit-P3-D60-in-P3-D65_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.39655724f, 0.37322620f, 0.26917280f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-1000nit-P3-D60-in-P3-D65_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.44968104f, 0.42165324f, 0.30032730f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-2000nit-P3-D60-in-P3-D65_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.49499428f, 0.46407104f, 0.33038715f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-4000nit-P3-D60-in-P3-D65_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.53778990f, 0.49960223f, 0.34477120f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-500nit-P3-D60-in-REC2020-D65_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.40185606f, 0.37821326f, 0.27276948f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-1000nit-P3-D60-in-REC2020-D65_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.45568960f, 0.42728746f, 0.30434040f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-2000nit-P3-D60-in-REC2020-D65_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.50160843f, 0.47027197f, 0.33480182f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-4000nit-P3-D60-in-REC2020-D65_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.54497580f, 0.50627790f, 0.34937808f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-500nit-REC2020-D60-in-REC2020-D65_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.40185624f, 0.37821335f, 0.27276933f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-1000nit-REC2020-D60-in-REC2020-D65_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.45568994f, 0.42728750f, 0.30434027f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-2000nit-REC2020-D60-in-REC2020-D65_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.50160870f, 0.47027220f, 0.33480210f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-4000nit-REC2020-D60-in-REC2020-D65_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.54497580f, 0.50627780f, 0.34937814f } } }, + { "APPLE_LOG_to_ACES2065-1", { { 0.5f, 0.4f, 0.3f }, { 0.153334766f, 0.083515430f, 0.032948254f } } }, { "CURVE - APPLE_LOG_to_LINEAR", diff --git a/tests/gpu/FixedFunctionOp_test.cpp b/tests/gpu/FixedFunctionOp_test.cpp index 2ed4bdc1a3..171f241207 100644 --- a/tests/gpu/FixedFunctionOp_test.cpp +++ b/tests/gpu/FixedFunctionOp_test.cpp @@ -305,6 +305,494 @@ OCIO_ADD_GPU_TEST(FixedFunction, style_aces_gamutcomp13_inv) test.setErrorThreshold(3e-6f); } +OCIO_ADD_GPU_TEST(FixedFunction, style_aces2_output_transform_fwd) +{ + const double data[9] = { + // Peak luminance + 1000.f, + // P3D65 gamut + 0.680, 0.320, 0.265, 0.690, 0.150, 0.060, 0.3127, 0.3290 + }; + OCIO::FixedFunctionTransformRcPtr func = + OCIO::FixedFunctionTransform::Create(OCIO::FIXED_FUNCTION_ACES_OUTPUT_TRANSFORM_20, &data[0], 9); + func->setDirection(OCIO::TRANSFORM_DIR_FORWARD); + + test.setProcessor(func); + + OCIOGPUTest::CustomValues values; + values.m_inputValues = + { + // ACEScg primaries and secondaries scaled by 4 + 2.781808965f, 0.179178253f, -0.022103530f, 1.0f, + 3.344523751f, 3.617862727f, -0.006002689f, 1.0f, + 0.562714786f, 3.438684474f, 0.016100841f, 1.0f, + 1.218191035f, 3.820821747f, 4.022103530f, 1.0f, + 0.655476249f, 0.382137273f, 4.006002689f, 1.0f, + 3.437285214f, 0.561315526f, 3.983899159f, 1.0f, + // OCIO test values + 0.110000000f, 0.020000000f, 0.040000000f, 0.5f, + 0.710000000f, 0.510000000f, 0.810000000f, 1.0f, + 0.430000000f, 0.820000000f, 0.710000000f, 0.0f, + // ColorChecker24 (SMPTE 2065-1 2021) + 0.118770000f, 0.087090000f, 0.058950000f, 1.0f, + 0.400020000f, 0.319160000f, 0.237360000f, 1.0f, + 0.184760000f, 0.203980000f, 0.313110000f, 1.0f, + 0.109010000f, 0.135110000f, 0.064930000f, 1.0f, + 0.266840000f, 0.246040000f, 0.409320000f, 1.0f, + 0.322830000f, 0.462080000f, 0.406060000f, 1.0f, + 0.386050000f, 0.227430000f, 0.057770000f, 1.0f, + 0.138220000f, 0.130370000f, 0.337030000f, 1.0f, + 0.302020000f, 0.137520000f, 0.127580000f, 1.0f, + 0.093100000f, 0.063470000f, 0.135250000f, 1.0f, + 0.348760000f, 0.436540000f, 0.106130000f, 1.0f, + 0.486550000f, 0.366850000f, 0.080610000f, 1.0f, + 0.087320000f, 0.074430000f, 0.272740000f, 1.0f, + 0.153660000f, 0.256920000f, 0.090710000f, 1.0f, + 0.217420000f, 0.070700000f, 0.051300000f, 1.0f, + 0.589190000f, 0.539430000f, 0.091570000f, 1.0f, + 0.309040000f, 0.148180000f, 0.274260000f, 1.0f, + 0.149010000f, 0.233780000f, 0.359390000f, 1.0f, + 0.866530000f, 0.867920000f, 0.858180000f, 1.0f, + 0.573560000f, 0.572560000f, 0.571690000f, 1.0f, + 0.353460000f, 0.353370000f, 0.353910000f, 1.0f, + 0.202530000f, 0.202430000f, 0.202870000f, 1.0f, + 0.094670000f, 0.095200000f, 0.096370000f, 1.0f, + 0.037450000f, 0.037660000f, 0.038950000f, 1.0f, + // Spectrally non-selective 18 % reflecting diffuser + 0.180000000f, 0.180000000f, 0.180000000f, 1.0f, + // Perfect reflecting diffuser + 0.977840000f, 0.977840000f, 0.977840000f, 1.0f, + }; + test.setCustomValues(values); + + test.setErrorThreshold(2e-5f); +} + +OCIO_ADD_GPU_TEST(FixedFunction, style_aces2_output_transform_inv) +{ + const double data[9] = { + // Peak luminance + 1000.f, + // P3D65 gamut + 0.680, 0.320, 0.265, 0.690, 0.150, 0.060, 0.3127, 0.3290 + }; + OCIO::FixedFunctionTransformRcPtr func = + OCIO::FixedFunctionTransform::Create(OCIO::FIXED_FUNCTION_ACES_OUTPUT_TRANSFORM_20, &data[0], 9); + func->setDirection(OCIO::TRANSFORM_DIR_INVERSE); + + test.setProcessor(func); + + OCIOGPUTest::CustomValues values; + values.m_inputValues = + { + // ACEScg primaries and secondaries scaled by 4 + 4.965774059f, -0.032864563f, 0.041625995f, 1.0f, + 3.969441891f, 3.825784922f, -0.056133576f, 1.0f, + -0.075329021f, 3.688980103f, 0.270296901f, 1.0f, + -0.095423937f, 3.650517225f, 3.459972620f, 1.0f, + -0.028930068f, 0.196428135f, 2.796343565f, 1.0f, + 4.900805950f, -0.064376131f, 3.838256121f, 1.0f, + // OCIO test values + 0.096890204f, -0.001135312f, 0.018971510f, 0.5f, + 0.809614301f, 0.479856580f, 0.814239502f, 1.0f, + 0.107420206f, 0.920529068f, 0.726378500f, 0.0f, + // ColorChecker24 (SMPTE 2065-1 2021) + 0.115475260f, 0.050812904f, 0.030212952f, 1.0f, + 0.484879673f, 0.301042974f, 0.226768956f, 1.0f, + 0.098463766f, 0.160814658f, 0.277010560f, 1.0f, + 0.071130365f, 0.107334383f, 0.035097566f, 1.0f, + 0.207111493f, 0.198474824f, 0.375326216f, 1.0f, + 0.195447892f, 0.481111974f, 0.393299013f, 1.0f, + 0.571913838f, 0.196872935f, 0.041634772f, 1.0f, + 0.045791931f, 0.069875360f, 0.291233480f, 1.0f, + 0.424848706f, 0.083199009f, 0.102153838f, 1.0f, + 0.059589427f, 0.022219172f, 0.091246888f, 1.0f, + 0.360365510f, 0.478741467f, 0.086726837f, 1.0f, + 0.695662081f, 0.371994525f, 0.068298168f, 1.0f, + 0.011806309f, 0.021665439f, 0.199594811f, 1.0f, + 0.076526314f, 0.256237417f, 0.060564656f, 1.0f, + 0.300064564f, 0.023416257f, 0.030360471f, 1.0f, + 0.805484772f, 0.596903503f, 0.082996152f, 1.0f, + 0.388385952f, 0.079899102f, 0.245819211f, 1.0f, + 0.010952532f, 0.196105912f, 0.307181358f, 1.0f, + 0.921019495f, 0.921707213f, 0.912856042f, 1.0f, + 0.590192318f, 0.588423848f, 0.587825358f, 1.0f, + 0.337743521f, 0.337685764f, 0.338155121f, 1.0f, + 0.169265985f, 0.169178501f, 0.169557109f, 1.0f, + 0.058346048f, 0.059387825f, 0.060296260f, 1.0f, + 0.012581184f, 0.012947139f, 0.013654195f, 1.0f, + // Spectrally non-selective 18 % reflecting diffuser + 0.145115465f, 0.145115525f, 0.145115510f, 1.0f, + // Perfect reflecting diffuser + 1.041565657f, 1.041566014f, 1.041565657f, 1.0f, + }; + test.setCustomValues(values); + + test.setErrorThreshold(1e-4f); +} + +OCIO_ADD_GPU_TEST(FixedFunction, style_aces2_output_transform_invfwd) +{ + const double data_inv[9] = { + // Peak luminance + 100.f, + // REC709 gamut + 0.64, 0.33, 0.3, 0.6, 0.15, 0.06, 0.3127, 0.329 + }; + OCIO::FixedFunctionTransformRcPtr func_inv = + OCIO::FixedFunctionTransform::Create(OCIO::FIXED_FUNCTION_ACES_OUTPUT_TRANSFORM_20, &data_inv[0], 9); + func_inv->setDirection(OCIO::TRANSFORM_DIR_INVERSE); + + const double data_fwd[9] = { + // Peak luminance + 1000.f, + // P3D65 gamut + 0.680, 0.320, 0.265, 0.690, 0.150, 0.060, 0.3127, 0.3290 + }; + + OCIO::FixedFunctionTransformRcPtr func_fwd = + OCIO::FixedFunctionTransform::Create(OCIO::FIXED_FUNCTION_ACES_OUTPUT_TRANSFORM_20, &data_fwd[0], 9); + + OCIO::GroupTransformRcPtr grp = OCIO::GroupTransform::Create(); + grp->appendTransform(func_inv); + grp->appendTransform(func_fwd); + + test.setProcessor(grp); + + test.setErrorThreshold(5e-4f); +} + +OCIO_ADD_GPU_TEST(FixedFunction, style_aces2_rgb_to_jmh_fwd) +{ + // ACES AP0 + const double data[8] = { 0.7347, 0.2653, 0.0000, 1.0000, 0.0001, -0.0770, 0.32168, 0.33767 }; + OCIO::FixedFunctionTransformRcPtr func = + OCIO::FixedFunctionTransform::Create(OCIO::FIXED_FUNCTION_ACES_RGB_TO_JMH_20, &data[0], 8); + func->setDirection(OCIO::TRANSFORM_DIR_FORWARD); + + test.setProcessor(func); + + OCIOGPUTest::CustomValues values; + values.m_inputValues = + { + // ACEScg primaries and secondaries scaled by 4 + 2.781808965f, 0.179178253f, -0.022103530f, 1.0f, + 3.344523751f, 3.617862727f, -0.006002689f, 1.0f, + 0.562714786f, 3.438684474f, 0.016100841f, 1.0f, + 1.218191035f, 3.820821747f, 4.022103530f, 1.0f, + 0.655476249f, 0.382137273f, 4.006002689f, 1.0f, + 3.437285214f, 0.561315526f, 3.983899159f, 1.0f, + // OCIO test values + 0.110000000f, 0.020000000f, 0.040000000f, 0.5f, + 0.710000000f, 0.510000000f, 0.810000000f, 1.0f, + 0.430000000f, 0.820000000f, 0.710000000f, 0.0f, + // ColorChecker24 (SMPTE 2065-1 2021) + 0.118770000f, 0.087090000f, 0.058950000f, 1.0f, + 0.400020000f, 0.319160000f, 0.237360000f, 1.0f, + 0.184760000f, 0.203980000f, 0.313110000f, 1.0f, + 0.109010000f, 0.135110000f, 0.064930000f, 1.0f, + 0.266840000f, 0.246040000f, 0.409320000f, 1.0f, + 0.322830000f, 0.462080000f, 0.406060000f, 1.0f, + 0.386050000f, 0.227430000f, 0.057770000f, 1.0f, + 0.138220000f, 0.130370000f, 0.337030000f, 1.0f, + 0.302020000f, 0.137520000f, 0.127580000f, 1.0f, + 0.093100000f, 0.063470000f, 0.135250000f, 1.0f, + 0.348760000f, 0.436540000f, 0.106130000f, 1.0f, + 0.486550000f, 0.366850000f, 0.080610000f, 1.0f, + 0.087320000f, 0.074430000f, 0.272740000f, 1.0f, + 0.153660000f, 0.256920000f, 0.090710000f, 1.0f, + 0.217420000f, 0.070700000f, 0.051300000f, 1.0f, + 0.589190000f, 0.539430000f, 0.091570000f, 1.0f, + 0.309040000f, 0.148180000f, 0.274260000f, 1.0f, + 0.149010000f, 0.233780000f, 0.359390000f, 1.0f, + }; + test.setCustomValues(values); + + test.setErrorThreshold(2e-4f); +} + +OCIO_ADD_GPU_TEST(FixedFunction, style_aces2_rgb_to_jmh_inv) +{ + // ACES AP0 + const double data[8] = { 0.7347, 0.2653, 0.0000, 1.0000, 0.0001, -0.0770, 0.32168, 0.33767 }; + OCIO::FixedFunctionTransformRcPtr func = + OCIO::FixedFunctionTransform::Create(OCIO::FIXED_FUNCTION_ACES_RGB_TO_JMH_20, &data[0], 8); + func->setDirection(OCIO::TRANSFORM_DIR_INVERSE); + + test.setProcessor(func); + + OCIOGPUTest::CustomValues values; + values.m_inputValues = + { + // ACEScg primaries and secondaries scaled by 4 + 107.480636597f, 206.827301025f, 25.025110245f, 1.0f, + 173.194076538f, 133.330886841f, 106.183448792f, 1.0f, + 139.210220337f, 191.922363281f, 147.056488037f, 1.0f, + 157.905166626f, 111.975311279f, 192.204727173f, 1.0f, + 79.229278564f, 100.424659729f, 268.442108154f, 1.0f, + 132.888137817f, 173.358779907f, 341.715240479f, 1.0f, + // OCIO test values + 26.112514496f, 42.523605347f, 4.173158169f, 0.5f, + 79.190460205f, 25.002300262f, 332.159759521f, 1.0f, + 81.912559509f, 39.754810333f, 182.925750732f, 0.0f, + // ColorChecker24 (SMPTE 2065-1 2021) + 33.924663544f, 12.254567146f, 38.146659851f, 1.0f, + 61.332393646f, 15.169423103f, 39.841842651f, 1.0f, + 47.191543579f, 11.839941978f, 249.107116699f, 1.0f, + 37.328300476f, 13.224150658f, 128.878036499f, 1.0f, + 53.465549469f, 13.121579170f, 285.658966064f, 1.0f, + 65.414512634f, 19.172147751f, 179.324264526f, 1.0f, + 55.711513519f, 37.182041168f, 50.924011230f, 1.0f, + 40.020961761f, 20.762512207f, 271.008331299f, 1.0f, + 47.704769135f, 35.791145325f, 13.975610733f, 1.0f, + 30.385913849f, 14.544739723f, 317.544281006f, 1.0f, + 64.222846985f, 33.487697601f, 119.145133972f, 1.0f, + 65.570358276f, 35.864013672f, 70.842193604f, 1.0f, + 31.800464630f, 23.920211792f, 273.228973389f, 1.0f, + 47.950405121f, 28.027387619f, 144.154159546f, 1.0f, + 38.440967560f, 42.604164124f, 17.892261505f, 1.0f, + 75.117736816f, 40.952045441f, 90.752044678f, 1.0f, + 49.311210632f, 33.812240601f, 348.832092285f, 1.0f, + 47.441757202f, 22.915655136f, 218.454376221f, 1.0f, + }; + test.setCustomValues(values); + + test.setErrorThreshold(1e-4f); +} + +OCIO_ADD_GPU_TEST(FixedFunction, style_aces2_tonescale_compress_fwd) +{ + const double data[1] = { 1000.f }; + OCIO::FixedFunctionTransformRcPtr func = + OCIO::FixedFunctionTransform::Create(OCIO::FIXED_FUNCTION_ACES_TONESCALE_COMPRESS_20, &data[0], 1); + func->setDirection(OCIO::TRANSFORM_DIR_FORWARD); + + test.setProcessor(func); + + OCIOGPUTest::CustomValues values; + values.m_inputValues = + { + // ACEScg primaries and secondaries scaled by 4 + 107.480636597f, 206.827301025f, 25.025110245f, 1.0f, + 173.194076538f, 133.330886841f, 106.183448792f, 1.0f, + 139.210220337f, 191.922363281f, 147.056488037f, 1.0f, + 157.905166626f, 111.975311279f, 192.204727173f, 1.0f, + 79.229278564f, 100.424659729f, 268.442108154f, 1.0f, + 132.888137817f, 173.358779907f, 341.715240479f, 1.0f, + // OCIO test values + 26.112514496f, 42.523605347f, 4.173158169f, 0.5f, + 79.190460205f, 25.002300262f, 332.159759521f, 1.0f, + 81.912559509f, 39.754810333f, 182.925750732f, 0.0f, + // ColorChecker24 (SMPTE 2065-1 2021) + 33.924663544f, 12.254567146f, 38.146659851f, 1.0f, + 61.332393646f, 15.169423103f, 39.841842651f, 1.0f, + 47.191543579f, 11.839941978f, 249.107116699f, 1.0f, + 37.328300476f, 13.224150658f, 128.878036499f, 1.0f, + 53.465549469f, 13.121579170f, 285.658966064f, 1.0f, + 65.414512634f, 19.172147751f, 179.324264526f, 1.0f, + 55.711513519f, 37.182041168f, 50.924011230f, 1.0f, + 40.020961761f, 20.762512207f, 271.008331299f, 1.0f, + 47.704769135f, 35.791145325f, 13.975610733f, 1.0f, + 30.385913849f, 14.544739723f, 317.544281006f, 1.0f, + 64.222846985f, 33.487697601f, 119.145133972f, 1.0f, + 65.570358276f, 35.864013672f, 70.842193604f, 1.0f, + 31.800464630f, 23.920211792f, 273.228973389f, 1.0f, + 47.950405121f, 28.027387619f, 144.154159546f, 1.0f, + 38.440967560f, 42.604164124f, 17.892261505f, 1.0f, + 75.117736816f, 40.952045441f, 90.752044678f, 1.0f, + 49.311210632f, 33.812240601f, 348.832092285f, 1.0f, + 47.441757202f, 22.915655136f, 218.454376221f, 1.0f, + 93.610260010f, 0.439610571f, 108.271926880f, 1.0f, + 77.237663269f, 0.131636351f, 33.296318054f, 1.0f, + 61.655914307f, 0.041985143f, 291.004058838f, 1.0f, + 47.493667603f, 0.048908804f, 297.386047363f, 1.0f, + 33.264842987f, 0.283808023f, 234.276382446f, 1.0f, + 21.467216492f, 0.409062684f, 255.025634766f, 1.0f, + // Spectrally non-selective 18 % reflecting diffuser + 44.938602448f, 0.000004705f, 299.357757568f, 1.0f, + // Perfect reflecting diffuser + 98.969635010f, 0.000083445f, 5.640549183f, 1.0f, + }; + test.setCustomValues(values); + + test.setErrorThreshold(1e-4f); +} + +OCIO_ADD_GPU_TEST(FixedFunction, style_aces2_tonescale_compress_inv) +{ + const double data[1] = { 1000.f }; + OCIO::FixedFunctionTransformRcPtr func = + OCIO::FixedFunctionTransform::Create(OCIO::FIXED_FUNCTION_ACES_TONESCALE_COMPRESS_20, &data[0], 1); + func->setDirection(OCIO::TRANSFORM_DIR_INVERSE); + + test.setProcessor(func); + + OCIOGPUTest::CustomValues values; + values.m_inputValues = + { + // ACEScg primaries and secondaries scaled by 4 + 110.702453613f, 211.251770020f, 25.025110245f, 1.0f, + 168.016815186f, 129.796249390f, 106.183448792f, 1.0f, + 140.814849854f, 193.459213257f, 147.056488037f, 1.0f, + 156.429519653f, 110.938514709f, 192.204727173f, 1.0f, + 80.456542969f, 98.490524292f, 268.442108154f, 1.0f, + 135.172195435f, 175.559280396f, 341.715240479f, 1.0f, + // OCIO test values + 18.187314987f, 33.819175720f, 4.173158169f, 0.5f, + 80.413116455f, 21.309329987f, 332.159759521f, 1.0f, + 83.447891235f, 37.852291107f, 182.925750732f, 0.0f, + // ColorChecker24 (SMPTE 2065-1 2021) + 27.411964417f, 13.382769585f, 38.146659851f, 1.0f, + 59.987670898f, 14.391894341f, 39.841842651f, 1.0f, + 43.298923492f, 12.199877739f, 249.107116699f, 1.0f, + 31.489658356f, 14.075142860f, 128.878036499f, 1.0f, + 50.749198914f, 12.731814384f, 285.658966064f, 1.0f, + 64.728637695f, 18.593795776f, 179.324264526f, 1.0f, + 53.399448395f, 37.394428253f, 50.924011230f, 1.0f, + 34.719596863f, 21.616765976f, 271.008331299f, 1.0f, + 43.910713196f, 36.788166046f, 13.975610733f, 1.0f, + 23.196525574f, 15.118354797f, 317.544281006f, 1.0f, + 63.348674774f, 33.283493042f, 119.145133972f, 1.0f, + 64.908889771f, 35.371044159f, 70.842193604f, 1.0f, + 24.876911163f, 23.143159866f, 273.228973389f, 1.0f, + 44.203376770f, 28.918329239f, 144.154159546f, 1.0f, + 32.824356079f, 43.447875977f, 17.892261505f, 1.0f, + 75.830871582f, 39.872474670f, 90.752044678f, 1.0f, + 45.823116302f, 34.652069092f, 348.832092285f, 1.0f, + 43.597240448f, 23.079078674f, 218.454376221f, 1.0f, + 96.212783813f, 0.322624743f, 108.271926880f, 1.0f, + 78.222122192f, 0.094044082f, 33.296318054f, 1.0f, + 60.364795685f, 0.031291425f, 291.004058838f, 1.0f, + 43.659111023f, 0.038717352f, 297.386047363f, 1.0f, + 26.623359680f, 0.269155562f, 234.276382446f, 1.0f, + 12.961384773f, 0.366550505f, 255.025634766f, 1.0f, + // Spectrally non-selective 18 % reflecting diffuser + 40.609165192f, 0.000000000f, 299.357757568f, 1.0f, + // Perfect reflecting diffuser + 101.899215698f, 0.000068110f, 5.640549183f, 1.0f, + }; + test.setCustomValues(values); + + test.setErrorThreshold(1e-4f); +} + +OCIO_ADD_GPU_TEST(FixedFunction, style_aces2_gamut_compress_fwd) +{ + const double data[9] = { + // Peak luminance + 1000.f, + // P3D65 gamut + 0.680, 0.320, 0.265, 0.690, 0.150, 0.060, 0.3127, 0.3290 + }; + OCIO::FixedFunctionTransformRcPtr func = + OCIO::FixedFunctionTransform::Create(OCIO::FIXED_FUNCTION_ACES_GAMUT_COMPRESS_20, &data[0], 9); + func->setDirection(OCIO::TRANSFORM_DIR_FORWARD); + + test.setProcessor(func); + + OCIOGPUTest::CustomValues values; + values.m_inputValues = + { + // ACEScg primaries and secondaries scaled by 4 + 110.702453613f, 211.251770020f, 25.025110245f, 1.0f, + 168.016815186f, 129.796249390f, 106.183448792f, 1.0f, + 140.814849854f, 193.459213257f, 147.056488037f, 1.0f, + 156.429519653f, 110.938514709f, 192.204727173f, 1.0f, + 80.456542969f, 98.490524292f, 268.442108154f, 1.0f, + 135.172195435f, 175.559280396f, 341.715240479f, 1.0f, + // OCIO test values + 18.187314987f, 33.819175720f, 4.173158169f, 0.5f, + 80.413116455f, 21.309329987f, 332.159759521f, 1.0f, + 83.447891235f, 37.852291107f, 182.925750732f, 0.0f, + // ColorChecker24 (SMPTE 2065-1 2021) + 27.411964417f, 13.382769585f, 38.146659851f, 1.0f, + 59.987670898f, 14.391894341f, 39.841842651f, 1.0f, + 43.298923492f, 12.199877739f, 249.107116699f, 1.0f, + 31.489658356f, 14.075142860f, 128.878036499f, 1.0f, + 50.749198914f, 12.731814384f, 285.658966064f, 1.0f, + 64.728637695f, 18.593795776f, 179.324264526f, 1.0f, + 53.399448395f, 37.394428253f, 50.924011230f, 1.0f, + 34.719596863f, 21.616765976f, 271.008331299f, 1.0f, + 43.910713196f, 36.788166046f, 13.975610733f, 1.0f, + 23.196525574f, 15.118354797f, 317.544281006f, 1.0f, + 63.348674774f, 33.283493042f, 119.145133972f, 1.0f, + 64.908889771f, 35.371044159f, 70.842193604f, 1.0f, + 24.876911163f, 23.143159866f, 273.228973389f, 1.0f, + 44.203376770f, 28.918329239f, 144.154159546f, 1.0f, + 32.824356079f, 43.447875977f, 17.892261505f, 1.0f, + 75.830871582f, 39.872474670f, 90.752044678f, 1.0f, + 45.823116302f, 34.652069092f, 348.832092285f, 1.0f, + 43.597240448f, 23.079078674f, 218.454376221f, 1.0f, + 96.212783813f, 0.322624743f, 108.271926880f, 1.0f, + 78.222122192f, 0.094044082f, 33.296318054f, 1.0f, + 60.364795685f, 0.031291425f, 291.004058838f, 1.0f, + 43.659111023f, 0.038717352f, 297.386047363f, 1.0f, + 26.623359680f, 0.269155562f, 234.276382446f, 1.0f, + 12.961384773f, 0.366550505f, 255.025634766f, 1.0f, + // Spectrally non-selective 18 % reflecting diffuser + 40.609165192f, 0.000000000f, 299.357757568f, 1.0f, + // Perfect reflecting diffuser + 101.899215698f, 0.000068110f, 5.640549183f, 1.0f, + }; + test.setCustomValues(values); + + test.setErrorThreshold(1e-4f); +} + +OCIO_ADD_GPU_TEST(FixedFunction, style_aces2_gamut_compress_inv) +{ + const double data[9] = { + // Peak luminance + 1000.f, + // P3D65 gamut + 0.680, 0.320, 0.265, 0.690, 0.150, 0.060, 0.3127, 0.3290 + }; + OCIO::FixedFunctionTransformRcPtr func = + OCIO::FixedFunctionTransform::Create(OCIO::FIXED_FUNCTION_ACES_GAMUT_COMPRESS_20, &data[0], 9); + func->setDirection(OCIO::TRANSFORM_DIR_INVERSE); + + test.setProcessor(func); + + OCIOGPUTest::CustomValues values; + values.m_inputValues = + { + // ACEScg primaries and secondaries scaled by 4 + 107.831291199f, 174.252944946f, 25.025119781f, 1.0f, + 168.028198242f, 118.224960327f, 106.183464050f, 1.0f, + 140.030105591f, 127.177192688f, 147.056488037f, 1.0f, + 156.512435913f, 73.218856812f, 192.204727173f, 1.0f, + 79.378631592f, 72.613555908f, 268.442108154f, 1.0f, + 133.827835083f, 149.929809570f, 341.715240479f, 1.0f, + // OCIO test values + 18.194000244f, 33.312938690f, 4.173166752f, 0.5f, + 80.413116455f, 21.309329987f, 332.159759521f, 1.0f, + 83.467437744f, 37.305160522f, 182.925750732f, 0.0f, + // ColorChecker24 (SMPTE 2065-1 2021) + 27.411962509f, 13.382793427f, 38.146591187f, 1.0f, + 59.987670898f, 14.391893387f, 39.841842651f, 1.0f, + 43.298923492f, 12.199877739f, 249.107116699f, 1.0f, + 31.489658356f, 14.075142860f, 128.878036499f, 1.0f, + 50.749198914f, 12.731814384f, 285.658966064f, 1.0f, + 64.728637695f, 18.593795776f, 179.324264526f, 1.0f, + 53.399448395f, 37.394428253f, 50.924011230f, 1.0f, + 34.719596863f, 21.616765976f, 271.008331299f, 1.0f, + 43.910709381f, 36.788166046f, 13.975610733f, 1.0f, + 23.196525574f, 15.118361473f, 317.544250488f, 1.0f, + 63.348674774f, 33.283493042f, 119.145133972f, 1.0f, + 64.908889771f, 35.371044159f, 70.842193604f, 1.0f, + 24.876916885f, 23.143167496f, 273.229034424f, 1.0f, + 44.203376770f, 28.918329239f, 144.154159546f, 1.0f, + 32.824352264f, 43.447864532f, 17.892255783f, 1.0f, + 75.830871582f, 39.872474670f, 90.752044678f, 1.0f, + 45.823104858f, 34.652038574f, 348.832092285f, 1.0f, + 43.635551453f, 21.629474640f, 218.454376221f, 1.0f, + }; + test.setCustomValues(values); + + // TODO: Improve inversion match? + test.setErrorThreshold(4e-4f); +} + // The next four tests run into a problem on some graphics cards where 0.0 * Inf = 0.0, // rather than the correct value of NaN. Therefore turning off TestInfinity for these tests.