From cf0e12650df40172ad33e7759978735e1df37ba5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9drik=20Fuoco?= Date: Thu, 5 Jan 2023 18:19:48 -0500 Subject: [PATCH 1/6] Changing version release type for 2.1.3 official release. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Cédrik Fuoco --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 0d6e3a7b3f..7055882965 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,7 +34,7 @@ project(OpenColorIO LANGUAGES CXX C) # "dev", "beta1", rc1", etc or "" for an official release. -set(OpenColorIO_VERSION_RELEASE_TYPE "dev") +set(OpenColorIO_VERSION_RELEASE_TYPE "") ############################################################################### From dd43d66a6750dbd80ad5318ccd77d0abbfcf55b9 Mon Sep 17 00:00:00 2001 From: amyspark Date: Mon, 31 Oct 2022 15:41:09 -0300 Subject: [PATCH 2/6] MinGW: Work around lack of strtof_l for locale independent parsing (#1697) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: L. E. Segovia Signed-off-by: L. E. Segovia Co-authored-by: Doug Walker (cherry picked from commit 1e6b01cc23bb400c21326b1c97f27d553a9e7e30) Signed-off-by: Cédrik Fuoco --- src/utils/NumberUtils.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/utils/NumberUtils.h b/src/utils/NumberUtils.h index d0a7471f21..8db4cce096 100644 --- a/src/utils/NumberUtils.h +++ b/src/utils/NumberUtils.h @@ -99,7 +99,12 @@ really_inline from_chars_result from_chars(const char *first, const char *last, float #ifdef _WIN32 +#if defined(__MINGW32__) || defined(__MINGW64__) + // MinGW doesn't define strtof_l (clang/gcc) nor strtod_l (gcc)... + tempval = static_cast(_strtod_l (first, &endptr, loc.local)); +#else tempval = _strtof_l(first, &endptr, loc.local); +#endif #elif __APPLE__ // On OSX, strtod_l is for some reason drastically faster than strtof_l. tempval = static_cast(::strtod_l(first, &endptr, loc.local)); From f840905f6fa62f8fa969254d1c3371eca350a95f Mon Sep 17 00:00:00 2001 From: Doug Walker Date: Thu, 27 Oct 2022 20:33:58 -0400 Subject: [PATCH 3/6] Fix atan2 argument order for HLSL (#1712) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Doug Walker Signed-off-by: Doug Walker (cherry picked from commit 8a93946cb32a3b49b84b19b11cbdee3d920d003b) Signed-off-by: Cédrik Fuoco --- src/OpenColorIO/GpuShaderUtils.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/OpenColorIO/GpuShaderUtils.cpp b/src/OpenColorIO/GpuShaderUtils.cpp index 711bde8939..248810eab6 100644 --- a/src/OpenColorIO/GpuShaderUtils.cpp +++ b/src/OpenColorIO/GpuShaderUtils.cpp @@ -1072,8 +1072,10 @@ std::string GpuShaderText::atan2(const std::string & y, } case GPU_LANGUAGE_HLSL_DX11: { - // note: operand order is swapped in HLSL - kw << "atan2(" << x << ", " << y << ")"; + // note: Various internet sources claim that the x & y arguments need to be + // swapped for HLSL (relative to GLSL). However, recent testing on Windows + // has revealed that the argument order needs to be the same as GLSL. + kw << "atan2(" << y << ", " << x << ")"; break; } case LANGUAGE_OSL_1: From a5b91ce9454421195d2692fbc7f67a7f1e95e460 Mon Sep 17 00:00:00 2001 From: bartstyczen <93516126+bartstyczen@users.noreply.github.com> Date: Sat, 3 Dec 2022 01:56:03 +0100 Subject: [PATCH 4/6] replace texture2D function with texture for GLSL 1.3 (#1723) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Bart Styczen Signed-off-by: Bart Styczen Co-authored-by: Doug Walker (cherry picked from commit d5cedbf55188e618c55758e035d6d2a21c607932) Signed-off-by: Cédrik Fuoco --- src/OpenColorIO/GpuShaderUtils.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/OpenColorIO/GpuShaderUtils.cpp b/src/OpenColorIO/GpuShaderUtils.cpp index 248810eab6..5e7fa1d2eb 100644 --- a/src/OpenColorIO/GpuShaderUtils.cpp +++ b/src/OpenColorIO/GpuShaderUtils.cpp @@ -143,11 +143,15 @@ std::string getTexSample(GpuLanguage lang, switch (lang) { case GPU_LANGUAGE_GLSL_1_2: - case GPU_LANGUAGE_GLSL_1_3: { kw << "texture" << N << "D(" << samplerName << ", " << coords << ")"; break; } + case GPU_LANGUAGE_GLSL_1_3: + { + kw << "texture(" << samplerName << ", " << coords << ")"; + break; + } case GPU_LANGUAGE_GLSL_ES_1_0: { if (N == 1) { From 833a003ab69cbf17a165f4396f37d13a61dcbc4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9drik=20Fuoco?= <105517825+cedrik-fuoco-adsk@users.noreply.github.com> Date: Mon, 5 Dec 2022 20:16:27 -0500 Subject: [PATCH 5/6] Adsk contrib - Processor cache does not detect changes in cccid (#1726) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * For Looks that has a FileTransform and for Colorspace with FileTransfrom, add the CCCID to the processor's cache key for that transform. Signed-off-by: Cédrik Fuoco * Removing the workaround in the related unit tests and fixing the issue by adding the environment variable to the context using setStringVar. The processor's cache key is using the context cache ID which has all the context variables taken into account. Signed-off-by: Cédrik Fuoco * Now using addStringVars and creating a new context instead of reusing the one used for the filename. Signed-off-by: Cédrik Fuoco * Adding cccid to the context when there are no context variable. Signed-off-by: Cédrik Fuoco * Adding a few unit tests to test that the processor is different when changing the FileTransform's CCCID. Signed-off-by: Cédrik Fuoco * Using setStringVar to set CCNUM context variable in unit test. Signed-off-by: Cédrik Fuoco * Adding a test in FileTransform to test CollectContextVariables directly. Signed-off-by: Cédrik Fuoco * Minor tweaks for the unit test Signed-off-by: Cédrik Fuoco Signed-off-by: Cédrik Fuoco Co-authored-by: Doug Walker (cherry picked from commit 4fa775030a9b51ba6b9ebc09acd85887da4c02a8) Signed-off-by: Cédrik Fuoco --- src/OpenColorIO/transforms/FileTransform.cpp | 10 ++++ tests/cpu/Config_tests.cpp | 55 ++++++++++++++++++++ tests/cpu/transforms/FileTransform_tests.cpp | 52 ++++++++++++++++++ 3 files changed, 117 insertions(+) diff --git a/src/OpenColorIO/transforms/FileTransform.cpp b/src/OpenColorIO/transforms/FileTransform.cpp index 2419ba04d9..60ff9c83f7 100755 --- a/src/OpenColorIO/transforms/FileTransform.cpp +++ b/src/OpenColorIO/transforms/FileTransform.cpp @@ -240,6 +240,16 @@ bool CollectContextVariables(const Config &, usedContextVars->addStringVars(ctxFilepath); } + // Check if the CCCID is using a context variable and add it to the context if that's the case. + ContextRcPtr ctxCCCID = Context::Create(); + const char * cccid = tr.getCCCId(); + std::string resolvedCCCID = context.resolveStringVar(cccid, ctxCCCID); + if (0 != strcmp(resolvedCCCID.c_str(), cccid)) + { + foundContextVars = true; + usedContextVars->addStringVars(ctxCCCID); + } + return foundContextVars; } diff --git a/tests/cpu/Config_tests.cpp b/tests/cpu/Config_tests.cpp index 506c8b5c98..3e3719ace5 100644 --- a/tests/cpu/Config_tests.cpp +++ b/tests/cpu/Config_tests.cpp @@ -7610,6 +7610,61 @@ OCIO_ADD_TEST(Config, context_variables_typical_use_cases) cfg->getProcessor(ctx2, "cs1", "cs2").get()); } } + + + // Case 7 - Context variables in the FileTransform's CCCID. + { + static const std::string CONFIG = + "ocio_profile_version: 2\n" + "\n" + "environment:\n" + " CCPREFIX: cc\n" + "\n" + "search_path: " + OCIO::GetTestFilesDir() + "\n" + "\n" + "roles:\n" + " default: cs1\n" + "\n" + "displays:\n" + " disp1:\n" + " - ! {name: view1, colorspace: cs2}\n" + "\n" + "colorspaces:\n" + " - !\n" + " name: cs1\n" + "\n" + " - !\n" + " name: cs2\n" + " from_scene_reference: ! {src: cdl_test1.ccc, cccid: $CCPREFIX00$CCNUM}\n"; + + std::istringstream iss; + iss.str(CONFIG); + + OCIO::ConfigRcPtr cfg; + OCIO_CHECK_NO_THROW(cfg = OCIO::Config::CreateFromStream(iss)->createEditableCopy()); + OCIO_CHECK_NO_THROW(cfg->validate()); + + OCIO::ConstTransformRcPtr ctf = cfg->getColorSpace("cs2")->getTransform( + OCIO::COLORSPACE_DIR_FROM_REFERENCE + ); + OCIO_REQUIRE_ASSERT(ctf); + + OCIO::ContextRcPtr ctx = cfg->getCurrentContext()->createEditableCopy(); + + ctx->setStringVar("CCNUM", "01"); + OCIO::ConstProcessorRcPtr p1 = cfg->getProcessor(ctx, ctf, OCIO::TRANSFORM_DIR_FORWARD); + + ctx->setStringVar("CCNUM", "02"); + OCIO::ConstProcessorRcPtr p2 = cfg->getProcessor(ctx, ctf, OCIO::TRANSFORM_DIR_FORWARD); + + ctx->setStringVar("CCNUM", "03"); + OCIO::ConstProcessorRcPtr p3 = cfg->getProcessor(ctx, ctf, OCIO::TRANSFORM_DIR_FORWARD); + + // All three processors should be different. + OCIO_CHECK_NE(p1.get(), p2.get()); + OCIO_CHECK_NE(p1.get(), p3.get()); + OCIO_CHECK_NE(p2.get(), p3.get()); + } } OCIO_ADD_TEST(Config, virtual_display) diff --git a/tests/cpu/transforms/FileTransform_tests.cpp b/tests/cpu/transforms/FileTransform_tests.cpp index 738962fbde..7f60737b45 100644 --- a/tests/cpu/transforms/FileTransform_tests.cpp +++ b/tests/cpu/transforms/FileTransform_tests.cpp @@ -431,4 +431,56 @@ OCIO_ADD_TEST(FileTransform, context_variables) // A basic check to validate that context variables are correctly used. OCIO_CHECK_NO_THROW(cfg->getProcessor(ctx, file, OCIO::TRANSFORM_DIR_FORWARD)); + + + { + // Case 4 - The 'cccid' now contains a context variable + static const std::string CONFIG = + "ocio_profile_version: 2\n" + "\n" + "environment:\n" + " CCPREFIX: cc\n" + " CCNUM: 02\n" + "\n" + "search_path: " + OCIO::GetTestFilesDir() + "\n" + "\n" + "roles:\n" + " default: cs1\n" + "\n" + "displays:\n" + " disp1:\n" + " - ! {name: view1, colorspace: cs2}\n" + "\n" + "colorspaces:\n" + " - !\n" + " name: cs1\n" + "\n" + " - !\n" + " name: cs2\n" + " from_scene_reference: ! {src: cdl_test1.ccc, cccid: $CCPREFIX00$CCNUM}\n"; + + std::istringstream iss; + iss.str(CONFIG); + + OCIO::ConstConfigRcPtr cfg; + OCIO_CHECK_NO_THROW(cfg = OCIO::Config::CreateFromStream(iss)); + OCIO_CHECK_NO_THROW(cfg->validate()); + + ctx = cfg->getCurrentContext()->createEditableCopy(); + OCIO_CHECK_NO_THROW(ctx->setStringVar("CCNUM", "01")); + + usedContextVars = OCIO::Context::Create(); // New & empty instance. + OCIO::ConstTransformRcPtr tr1 = cfg->getColorSpace("cs2")->getTransform( + OCIO::COLORSPACE_DIR_FROM_REFERENCE + ); + OCIO::ConstFileTransformRcPtr fTr1 = OCIO::DynamicPtrCast(tr1); + OCIO_CHECK_ASSERT(fTr1); + + OCIO_CHECK_ASSERT(CollectContextVariables(*cfg, *ctx, *fTr1, usedContextVars)); + OCIO_CHECK_EQUAL(2, usedContextVars->getNumStringVars()); + OCIO_CHECK_EQUAL(std::string("CCPREFIX"), usedContextVars->getStringVarNameByIndex(0)); + OCIO_CHECK_EQUAL(std::string("cc"), usedContextVars->getStringVarByIndex(0)); + OCIO_CHECK_EQUAL(std::string("CCNUM"), usedContextVars->getStringVarNameByIndex(1)); + OCIO_CHECK_EQUAL(std::string("01"), usedContextVars->getStringVarByIndex(1)); + } } From 0ea6d5e46013df5eae36a13fb30779ffb6049edb Mon Sep 17 00:00:00 2001 From: Doug Walker Date: Wed, 4 Jan 2023 16:00:36 -0500 Subject: [PATCH 6/6] Fix inverse Lut1D optimization bug (#1743) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Doug Walker Signed-off-by: Doug Walker (cherry picked from commit 5152635a189f05e85af8a7bff62fb348ec293d97) Signed-off-by: Cédrik Fuoco --- src/OpenColorIO/OpOptimizers.cpp | 34 +++++++++- src/OpenColorIO/ops/lut1d/Lut1DOpData.cpp | 80 +++++++++++++++++++++++ src/OpenColorIO/ops/lut1d/Lut1DOpData.h | 2 + tests/cpu/OpOptimizers_tests.cpp | 57 ++++++++++++++++ 4 files changed, 172 insertions(+), 1 deletion(-) diff --git a/src/OpenColorIO/OpOptimizers.cpp b/src/OpenColorIO/OpOptimizers.cpp index 8b7e2c2fc7..7788bfb164 100755 --- a/src/OpenColorIO/OpOptimizers.cpp +++ b/src/OpenColorIO/OpOptimizers.cpp @@ -12,6 +12,7 @@ #include "Op.h" #include "ops/lut1d/Lut1DOp.h" #include "ops/lut3d/Lut3DOp.h" +#include "ops/range/RangeOp.h" namespace OCIO_NAMESPACE { @@ -241,7 +242,38 @@ int RemoveInverseOps(OpRcPtrVec & opVec, OptimizationFlags oFlags) // When a pair of inverse ops is removed, we want the optimized ops to give the // same result as the original. For certain ops such as Lut1D or Log this may // mean inserting a Range to emulate the clamping done by the original ops. - auto replacedBy = op1->getIdentityReplacement(); + + OpRcPtr replacedBy; + if (type1 == OpData::Lut1DType) + { + // Lut1D gets special handling so that both halfs of the pair are available. + // Only the inverse LUT has the values needed to generate the replacement. + + ConstLut1DOpDataRcPtr lut1 = OCIO_DYNAMIC_POINTER_CAST(op1->data()); + ConstLut1DOpDataRcPtr lut2 = OCIO_DYNAMIC_POINTER_CAST(op2->data()); + + OpDataRcPtr opData = lut1->getPairIdentityReplacement(lut2); + + OpRcPtrVec ops; + if (opData->getType() == OpData::MatrixType) + { + // No-op that will be optimized. + auto mat = OCIO_DYNAMIC_POINTER_CAST(opData); + CreateMatrixOp(ops, mat, TRANSFORM_DIR_FORWARD); + } + else if (opData->getType() == OpData::RangeType) + { + // Clamping op. + auto range = OCIO_DYNAMIC_POINTER_CAST(opData); + CreateRangeOp(ops, range, TRANSFORM_DIR_FORWARD); + } + replacedBy = ops[0]; + } + else + { + replacedBy = op1->getIdentityReplacement(); + } + replacedBy->finalize(); if (replacedBy->isNoOp()) { diff --git a/src/OpenColorIO/ops/lut1d/Lut1DOpData.cpp b/src/OpenColorIO/ops/lut1d/Lut1DOpData.cpp index 79d016f469..eb4a014f6b 100644 --- a/src/OpenColorIO/ops/lut1d/Lut1DOpData.cpp +++ b/src/OpenColorIO/ops/lut1d/Lut1DOpData.cpp @@ -280,6 +280,86 @@ OpDataRcPtr Lut1DOpData::getIdentityReplacement() const return res; } +OpDataRcPtr Lut1DOpData::getPairIdentityReplacement(ConstLut1DOpDataRcPtr & lut2) const +{ + OpDataRcPtr res; + if (isInputHalfDomain()) + { + // TODO: If a half-domain LUT has a flat spot, it would be more appropriate + // to use a Range, since some areas would be clamped in a round-trip. + // Currently leaving this a Matrix since it is a potential work-around + // for situations where you want a pair identity of LUTs to be totally + // removed, even if it omits some clamping at extreme values. + res = std::make_shared(); + } + else + { + // Note that the ops have been finalized by the time this is called, + // Therefore, for an inverse Lut1D, it means initializeFromForward() has + // been called and so any reversals have been converted to flat regions. + // Therefore, the first and last LUT entries are the extreme values and + // the ComponentProperties has been initialized, but only for the op + // whose direction is INVERSE. + const Lut1DOpData * invLut = (m_direction == TRANSFORM_DIR_INVERSE) + ? this: lut2.get(); + const ComponentProperties & redProperties = invLut->getRedProperties(); + const unsigned long length = invLut->getArray().getLength(); + + // If the start or end of the LUT contains a flat region, that will cause + // a round-trip to be limited. + + double minValue = 0.; + double maxValue = 1.; + switch (m_direction) + { + case TRANSFORM_DIR_FORWARD: // Fwd Lut1D -> Inv Lut1D + { + // A round-trip in this order will impose at least a clamp to [0,1] + // based on what happens entering the first Fwd Lut1D. However, the + // clamping may be to an even narrower range if there are flat regions. + // + // The flat region limitation is imposed based on the where it falls + // relative to the [0,1] input domain. + + // TODO: A RangeOp has one min & max for all channels, whereas a Lut1D may + // have three independent channels. Potentially could look at all chans + // and take the extrema of each? For now, just using the first channel. + const unsigned long minIndex = redProperties.startDomain; + const unsigned long maxIndex = redProperties.endDomain; + + minValue = (double)minIndex / (length - 1); + maxValue = (double)maxIndex / (length - 1); + break; + } + case TRANSFORM_DIR_INVERSE: // Inv Lut1D -> Fwd Lut1D + { + // A round-trip in this order will impose a clamp, but it may be to + // bounds outside of [0,1] since the Fwd LUT may contain values outside + // [0,1] and so the Inv LUT will accept inputs on that extended range. + // + // The flat region limitation is imposed based on the output range. + + const bool isIncreasing = redProperties.isIncreasing; + const unsigned long maxChannels = invLut->getArray().getMaxColorComponents(); + const unsigned long lastValIndex = (length - 1) * maxChannels; + // Note that the array for the invLut has had initializeFromForward() + // done and so any reversals have been converted to flat regions and + // the extrema are at the beginning & end of the LUT. + const Array::Values & lutValues = invLut->getArray().getValues(); + + // TODO: Currently only basing this on the red channel. + minValue = isIncreasing ? lutValues[0] : lutValues[lastValIndex]; + maxValue = isIncreasing ? lutValues[lastValIndex] : lutValues[0]; + break; + } + } + + res = std::make_shared(minValue, maxValue, + minValue, maxValue); + } + return res; +} + void Lut1DOpData::setInputHalfDomain(bool isHalfDomain) noexcept { m_halfFlags = (isHalfDomain) ? diff --git a/src/OpenColorIO/ops/lut1d/Lut1DOpData.h b/src/OpenColorIO/ops/lut1d/Lut1DOpData.h index ea68072a5a..80b25a79a9 100644 --- a/src/OpenColorIO/ops/lut1d/Lut1DOpData.h +++ b/src/OpenColorIO/ops/lut1d/Lut1DOpData.h @@ -179,6 +179,8 @@ class Lut1DOpData : public OpData OpDataRcPtr getIdentityReplacement() const override; + OpDataRcPtr getPairIdentityReplacement(ConstLut1DOpDataRcPtr & lut2) const; + inline const ComponentProperties & getRedProperties() const { return m_componentProperties[0]; diff --git a/tests/cpu/OpOptimizers_tests.cpp b/tests/cpu/OpOptimizers_tests.cpp index b3305f4def..6f606fd6e8 100644 --- a/tests/cpu/OpOptimizers_tests.cpp +++ b/tests/cpu/OpOptimizers_tests.cpp @@ -645,6 +645,63 @@ OCIO_ADD_TEST(OpOptimizers, lut1d_identity_replacement) } } +OCIO_ADD_TEST(OpOptimizers, lut1d_identity_replacement_order) +{ + // See issue #1737, https://github.com/AcademySoftwareFoundation/OpenColorIO/issues/1737. + + // This CTF contains a single LUT1D, inverse direction, normal (not half) domain. + // It contains values from -6 to +3.4. + const std::string fileName("lut1d_inverse_gpu.ctf"); + OCIO::ContextRcPtr context = OCIO::Context::Create(); + + OCIO::OpRcPtrVec inv_ops; + OCIO_CHECK_NO_THROW(OCIO::BuildOpsTest(inv_ops, fileName, context, + // FWD direction simply means don't swap the direction, the + // file contains an inverse LUT1D and leave it that way. + OCIO::TRANSFORM_DIR_FORWARD)); + OCIO::OpRcPtrVec fwd_ops; + OCIO_CHECK_NO_THROW(OCIO::BuildOpsTest(fwd_ops, fileName, context, + OCIO::TRANSFORM_DIR_INVERSE)); + + // Check forward LUT1D followed by inverse LUT1D. + { + OCIO::OpRcPtrVec fwd_inv_ops = fwd_ops; + fwd_inv_ops += inv_ops; + + OCIO_CHECK_NO_THROW(fwd_inv_ops.finalize()); + OCIO_CHECK_NO_THROW(fwd_inv_ops.optimize(OCIO::OPTIMIZATION_NONE)); + OCIO_CHECK_EQUAL(fwd_inv_ops.size(), 2); // no optmization was done + + OCIO::OpRcPtrVec optOps = fwd_inv_ops.clone(); + OCIO_CHECK_NO_THROW(optOps.finalize()); + OCIO_CHECK_NO_THROW(optOps.optimize(OCIO::OPTIMIZATION_DEFAULT)); + OCIO_CHECK_EQUAL(optOps.size(), 1); + OCIO_CHECK_EQUAL(optOps[0]->getInfo(), ""); + + // Compare renders. + CompareRender(fwd_inv_ops, optOps, __LINE__, 1e-6f); + } + + // Check inverse LUT1D followed by forward LUT1D. + { + OCIO::OpRcPtrVec inv_fwd_ops = inv_ops; + inv_fwd_ops += fwd_ops; + + OCIO_CHECK_NO_THROW(inv_fwd_ops.finalize()); + OCIO_CHECK_NO_THROW(inv_fwd_ops.optimize(OCIO::OPTIMIZATION_NONE)); + OCIO_CHECK_EQUAL(inv_fwd_ops.size(), 2); // no optmization was done + + OCIO::OpRcPtrVec optOps = inv_fwd_ops.clone(); + OCIO_CHECK_NO_THROW(optOps.finalize()); + OCIO_CHECK_NO_THROW(optOps.optimize(OCIO::OPTIMIZATION_DEFAULT)); + OCIO_CHECK_EQUAL(optOps.size(), 1); + OCIO_CHECK_EQUAL(optOps[0]->getInfo(), ""); + + // Compare renders. + CompareRender(inv_fwd_ops, optOps, __LINE__, 1e-6f); + } +} + OCIO_ADD_TEST(OpOptimizers, lut1d_half_domain_keep_prior_range) { // A half-domain LUT should not allow removal of a prior range op.