Signed-off-by: Doug Walker <doug.walker@autodesk.com> Signed-off-by: Doug Walker <doug.walker@autodesk.com>
1563 lines
61 KiB
C++
1563 lines
61 KiB
C++
// SPDX-License-Identifier: BSD-3-Clause
|
|
// Copyright Contributors to the OpenColorIO Project.
|
|
|
|
|
|
#include "OpOptimizers.cpp"
|
|
|
|
#include "ops/cdl/CDLOp.h"
|
|
#include "ops/exponent/ExponentOp.h"
|
|
#include "ops/exposurecontrast/ExposureContrastOp.h"
|
|
#include "ops/fixedfunction/FixedFunctionOp.h"
|
|
#include "ops/gamma/GammaOp.h"
|
|
#include "ops/log/LogOp.h"
|
|
#include "ops/matrix/MatrixOp.h"
|
|
#include "ops/range/RangeOp.h"
|
|
#include "testutils/UnitTest.h"
|
|
#include "transforms/FileTransform.h"
|
|
#include "UnitTestUtils.h"
|
|
|
|
namespace OCIO = OCIO_NAMESPACE;
|
|
|
|
|
|
namespace
|
|
{
|
|
OCIO::OptimizationFlags AllBut(OCIO::OptimizationFlags notFlag)
|
|
{
|
|
return static_cast<OCIO::OptimizationFlags>(OCIO::OPTIMIZATION_ALL & ~notFlag);
|
|
}
|
|
|
|
void CompareRender(OCIO::OpRcPtrVec & ops1, OCIO::OpRcPtrVec & ops2,
|
|
unsigned line, float errorThreshold,
|
|
bool forceAlphaInRange = false)
|
|
{
|
|
std::vector<float> img1 = {
|
|
0.778f, 0.824f, 0.885f, 0.153f,
|
|
0.044f, 0.014f, 0.088f, 0.999f,
|
|
0.488f, 0.381f, 0.f, 0.f,
|
|
1.000f, 1.52e-4f, 0.0229f, 1.f,
|
|
0.f, -0.1f, -2.f, -0.1f,
|
|
2.f, 1.9f, 0.f, 2.f };
|
|
|
|
if (forceAlphaInRange)
|
|
{
|
|
img1[19] = 0.f;
|
|
}
|
|
|
|
std::vector<float> img2 = img1;
|
|
|
|
const long nbPixels = (long)img1.size() / 4;
|
|
|
|
for (const auto & op : ops1)
|
|
{
|
|
op->apply(&img1[0], &img1[0], nbPixels);
|
|
}
|
|
|
|
for (const auto & op : ops2)
|
|
{
|
|
op->apply(&img2[0], &img2[0], nbPixels);
|
|
}
|
|
|
|
for (size_t idx = 0; idx < img1.size(); ++idx)
|
|
{
|
|
OCIO_CHECK_CLOSE_FROM(img1[idx], img2[idx], errorThreshold, line);
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
OCIO_ADD_TEST(OpOptimizers, remove_leading_clamp_identity)
|
|
{
|
|
OCIO::OpRcPtrVec ops;
|
|
|
|
auto range = std::make_shared<OCIO::RangeOpData>(0., 1., 0., 1.);
|
|
auto range2 = std::make_shared<OCIO::RangeOpData>(0., 1., 0., 2.);
|
|
auto matrix = std::make_shared<OCIO::MatrixOpData>();
|
|
|
|
OCIO_CHECK_NO_THROW(OCIO::CreateRangeOp(ops, range, OCIO::TRANSFORM_DIR_FORWARD));
|
|
OCIO_CHECK_NO_THROW(OCIO::CreateRangeOp(ops, range, OCIO::TRANSFORM_DIR_FORWARD));
|
|
OCIO_CHECK_NO_THROW(OCIO::CreateRangeOp(ops, range, OCIO::TRANSFORM_DIR_FORWARD));
|
|
OCIO_CHECK_NO_THROW(OCIO::CreateMatrixOp(ops, matrix, OCIO::TRANSFORM_DIR_FORWARD));
|
|
OCIO_CHECK_EQUAL(ops.size(), 4);
|
|
OCIO::RemoveLeadingClampIdentity(ops);
|
|
OCIO_REQUIRE_EQUAL(ops.size(), 1);
|
|
OCIO::ConstOpRcPtr o0 = ops[0];
|
|
OCIO_CHECK_EQUAL(o0->data()->getType(), OCIO::OpData::MatrixType);
|
|
ops.clear();
|
|
|
|
OCIO_CHECK_NO_THROW(OCIO::CreateRangeOp(ops, range, OCIO::TRANSFORM_DIR_FORWARD));
|
|
OCIO_CHECK_NO_THROW(OCIO::CreateRangeOp(ops, range, OCIO::TRANSFORM_DIR_FORWARD));
|
|
OCIO_CHECK_NO_THROW(OCIO::CreateRangeOp(ops, range, OCIO::TRANSFORM_DIR_FORWARD));
|
|
OCIO_CHECK_EQUAL(ops.size(), 3);
|
|
OCIO::RemoveLeadingClampIdentity(ops);
|
|
OCIO_CHECK_EQUAL(ops.size(), 0);
|
|
ops.clear();
|
|
|
|
OCIO_CHECK_NO_THROW(OCIO::CreateRangeOp(ops, range, OCIO::TRANSFORM_DIR_FORWARD));
|
|
OCIO_CHECK_NO_THROW(OCIO::CreateMatrixOp(ops, matrix, OCIO::TRANSFORM_DIR_FORWARD));
|
|
OCIO_CHECK_NO_THROW(OCIO::CreateRangeOp(ops, range, OCIO::TRANSFORM_DIR_FORWARD));
|
|
OCIO_CHECK_NO_THROW(OCIO::CreateRangeOp(ops, range, OCIO::TRANSFORM_DIR_FORWARD));
|
|
OCIO_CHECK_EQUAL(ops.size(), 4);
|
|
OCIO::RemoveLeadingClampIdentity(ops);
|
|
OCIO_REQUIRE_EQUAL(ops.size(), 3);
|
|
o0 = ops[0];
|
|
OCIO::ConstOpRcPtr o1 = ops[1];
|
|
OCIO::ConstOpRcPtr o2 = ops[2];
|
|
OCIO_CHECK_EQUAL(o0->data()->getType(), OCIO::OpData::MatrixType);
|
|
OCIO_CHECK_EQUAL(o1->data()->getType(), OCIO::OpData::RangeType);
|
|
OCIO_CHECK_EQUAL(o2->data()->getType(), OCIO::OpData::RangeType);
|
|
|
|
ops.clear();
|
|
|
|
// First range is not an identity, nothing to remove.
|
|
OCIO_CHECK_NO_THROW(OCIO::CreateRangeOp(ops, range2, OCIO::TRANSFORM_DIR_FORWARD));
|
|
OCIO_CHECK_NO_THROW(OCIO::CreateMatrixOp(ops, matrix, OCIO::TRANSFORM_DIR_FORWARD));
|
|
OCIO_CHECK_NO_THROW(OCIO::CreateRangeOp(ops, range, OCIO::TRANSFORM_DIR_FORWARD));
|
|
OCIO_CHECK_NO_THROW(OCIO::CreateRangeOp(ops, range, OCIO::TRANSFORM_DIR_FORWARD));
|
|
OCIO_CHECK_EQUAL(ops.size(), 4);
|
|
OCIO::RemoveLeadingClampIdentity(ops);
|
|
OCIO_REQUIRE_EQUAL(ops.size(), 4);
|
|
o0 = ops[0];
|
|
o1 = ops[1];
|
|
o2 = ops[2];
|
|
OCIO::ConstOpRcPtr o3 = ops[3];
|
|
OCIO_CHECK_EQUAL(o0->data()->getType(), OCIO::OpData::RangeType);
|
|
OCIO_CHECK_EQUAL(o1->data()->getType(), OCIO::OpData::MatrixType);
|
|
OCIO_CHECK_EQUAL(o2->data()->getType(), OCIO::OpData::RangeType);
|
|
OCIO_CHECK_EQUAL(o3->data()->getType(), OCIO::OpData::RangeType);
|
|
ops.clear();
|
|
|
|
OCIO_CHECK_NO_THROW(OCIO::CreateRangeOp(ops, range, OCIO::TRANSFORM_DIR_FORWARD));
|
|
OCIO_CHECK_NO_THROW(OCIO::CreateRangeOp(ops, range, OCIO::TRANSFORM_DIR_FORWARD));
|
|
OCIO_CHECK_NO_THROW(OCIO::CreateRangeOp(ops, range2, OCIO::TRANSFORM_DIR_FORWARD));
|
|
OCIO_CHECK_NO_THROW(OCIO::CreateMatrixOp(ops, matrix, OCIO::TRANSFORM_DIR_FORWARD));
|
|
OCIO_CHECK_EQUAL(ops.size(), 4);
|
|
OCIO::RemoveLeadingClampIdentity(ops);
|
|
OCIO_REQUIRE_EQUAL(ops.size(), 2);
|
|
o0 = ops[0];
|
|
o1 = ops[1];
|
|
OCIO_CHECK_EQUAL(o0->data()->getType(), OCIO::OpData::RangeType);
|
|
OCIO_CHECK_EQUAL(o1->data()->getType(), OCIO::OpData::MatrixType);
|
|
ops.clear();
|
|
}
|
|
|
|
OCIO_ADD_TEST(OpOptimizers, remove_trailing_clamp_identity)
|
|
{
|
|
OCIO::OpRcPtrVec ops;
|
|
|
|
auto range = std::make_shared<OCIO::RangeOpData>(0., 1., 0., 1.);
|
|
auto range2 = std::make_shared<OCIO::RangeOpData>(0., 1., 0., 2.);
|
|
auto matrix = std::make_shared<OCIO::MatrixOpData>();
|
|
|
|
OCIO_CHECK_NO_THROW(OCIO::CreateMatrixOp(ops, matrix, OCIO::TRANSFORM_DIR_FORWARD));
|
|
OCIO_CHECK_NO_THROW(OCIO::CreateRangeOp(ops, range, OCIO::TRANSFORM_DIR_FORWARD));
|
|
OCIO_CHECK_NO_THROW(OCIO::CreateRangeOp(ops, range, OCIO::TRANSFORM_DIR_FORWARD));
|
|
OCIO_CHECK_NO_THROW(OCIO::CreateRangeOp(ops, range, OCIO::TRANSFORM_DIR_FORWARD));
|
|
OCIO_CHECK_EQUAL(ops.size(), 4);
|
|
OCIO::RemoveTrailingClampIdentity(ops);
|
|
OCIO_REQUIRE_EQUAL(ops.size(), 1);
|
|
OCIO::ConstOpRcPtr o0 = ops[0];
|
|
OCIO_CHECK_EQUAL(o0->data()->getType(), OCIO::OpData::MatrixType);
|
|
ops.clear();
|
|
|
|
OCIO_CHECK_NO_THROW(OCIO::CreateRangeOp(ops, range, OCIO::TRANSFORM_DIR_FORWARD));
|
|
OCIO_CHECK_NO_THROW(OCIO::CreateRangeOp(ops, range, OCIO::TRANSFORM_DIR_FORWARD));
|
|
OCIO_CHECK_NO_THROW(OCIO::CreateRangeOp(ops, range, OCIO::TRANSFORM_DIR_FORWARD));
|
|
OCIO_CHECK_EQUAL(ops.size(), 3);
|
|
OCIO::RemoveTrailingClampIdentity(ops);
|
|
OCIO_CHECK_EQUAL(ops.size(), 0);
|
|
ops.clear();
|
|
|
|
OCIO_CHECK_NO_THROW(OCIO::CreateRangeOp(ops, range, OCIO::TRANSFORM_DIR_FORWARD));
|
|
OCIO_CHECK_NO_THROW(OCIO::CreateMatrixOp(ops, matrix, OCIO::TRANSFORM_DIR_FORWARD));
|
|
OCIO_CHECK_NO_THROW(OCIO::CreateRangeOp(ops, range, OCIO::TRANSFORM_DIR_FORWARD));
|
|
OCIO_CHECK_NO_THROW(OCIO::CreateRangeOp(ops, range, OCIO::TRANSFORM_DIR_FORWARD));
|
|
OCIO_CHECK_EQUAL(ops.size(), 4);
|
|
OCIO::RemoveTrailingClampIdentity(ops);
|
|
OCIO_REQUIRE_EQUAL(ops.size(), 2);
|
|
o0 = ops[0];
|
|
OCIO::ConstOpRcPtr o1 = ops[1];
|
|
OCIO_CHECK_EQUAL(o0->data()->getType(), OCIO::OpData::RangeType);
|
|
OCIO_CHECK_EQUAL(o1->data()->getType(), OCIO::OpData::MatrixType);
|
|
ops.clear();
|
|
|
|
// Last range is not an identity, nothing to remove.
|
|
OCIO_CHECK_NO_THROW(OCIO::CreateRangeOp(ops, range2, OCIO::TRANSFORM_DIR_FORWARD));
|
|
OCIO_CHECK_NO_THROW(OCIO::CreateMatrixOp(ops, matrix, OCIO::TRANSFORM_DIR_FORWARD));
|
|
OCIO_CHECK_NO_THROW(OCIO::CreateRangeOp(ops, range, OCIO::TRANSFORM_DIR_FORWARD));
|
|
OCIO_CHECK_NO_THROW(OCIO::CreateRangeOp(ops, range2, OCIO::TRANSFORM_DIR_FORWARD));
|
|
OCIO_CHECK_EQUAL(ops.size(), 4);
|
|
OCIO::RemoveTrailingClampIdentity(ops);
|
|
OCIO_REQUIRE_EQUAL(ops.size(), 4);
|
|
o0 = ops[0];
|
|
o1 = ops[1];
|
|
OCIO::ConstOpRcPtr o2 = ops[2];
|
|
OCIO::ConstOpRcPtr o3 = ops[3];
|
|
OCIO_CHECK_EQUAL(o0->data()->getType(), OCIO::OpData::RangeType);
|
|
OCIO_CHECK_EQUAL(o1->data()->getType(), OCIO::OpData::MatrixType);
|
|
OCIO_CHECK_EQUAL(o2->data()->getType(), OCIO::OpData::RangeType);
|
|
OCIO_CHECK_EQUAL(o3->data()->getType(), OCIO::OpData::RangeType);
|
|
ops.clear();
|
|
|
|
OCIO_CHECK_NO_THROW(OCIO::CreateRangeOp(ops, range2, OCIO::TRANSFORM_DIR_FORWARD));
|
|
OCIO_CHECK_NO_THROW(OCIO::CreateMatrixOp(ops, matrix, OCIO::TRANSFORM_DIR_FORWARD));
|
|
OCIO_CHECK_NO_THROW(OCIO::CreateRangeOp(ops, range, OCIO::TRANSFORM_DIR_FORWARD));
|
|
OCIO_CHECK_NO_THROW(OCIO::CreateRangeOp(ops, range, OCIO::TRANSFORM_DIR_FORWARD));
|
|
OCIO_CHECK_EQUAL(ops.size(), 4);
|
|
OCIO::RemoveTrailingClampIdentity(ops);
|
|
OCIO_REQUIRE_EQUAL(ops.size(), 2);
|
|
o0 = ops[0];
|
|
o1 = ops[1];
|
|
OCIO_CHECK_EQUAL(o0->data()->getType(), OCIO::OpData::RangeType);
|
|
OCIO_CHECK_EQUAL(o1->data()->getType(), OCIO::OpData::MatrixType);
|
|
ops.clear();
|
|
}
|
|
|
|
OCIO_ADD_TEST(OpOptimizers, remove_inverse_ops)
|
|
{
|
|
OCIO::OpRcPtrVec ops;
|
|
|
|
auto func = std::make_shared<OCIO::FixedFunctionOpData>(
|
|
OCIO::FixedFunctionOpData::ACES_RED_MOD_03_FWD);
|
|
|
|
const double logSlope[3] = {0.18, 0.18, 0.18};
|
|
const double linSlope[3] = {2.0, 2.0, 2.0};
|
|
const double linOffset[3] = {0.1, 0.1, 0.1};
|
|
const double base = 10.0;
|
|
const double logOffset[3] = {1.0, 1.0, 1.0};
|
|
|
|
OCIO::CreateFixedFunctionOp(ops, func, OCIO::TRANSFORM_DIR_FORWARD);
|
|
OCIO::CreateLogOp(ops, base, logSlope, logOffset, linSlope, linOffset,
|
|
OCIO::TRANSFORM_DIR_INVERSE);
|
|
OCIO::CreateLogOp(ops, base, logSlope, logOffset, linSlope, linOffset,
|
|
OCIO::TRANSFORM_DIR_FORWARD);
|
|
OCIO::CreateFixedFunctionOp(ops, func, OCIO::TRANSFORM_DIR_INVERSE);
|
|
OCIO_CHECK_EQUAL(ops.size(), 4);
|
|
|
|
// Inverse + forward log are optimized as no-op then forward and inverse exponent are
|
|
// optimized as no-op within the same call.
|
|
OCIO::RemoveInverseOps(ops, OCIO::OPTIMIZATION_ALL);
|
|
OCIO_CHECK_EQUAL(ops.size(), 0);
|
|
ops.clear();
|
|
|
|
OCIO::CreateFixedFunctionOp(ops, func, OCIO::TRANSFORM_DIR_FORWARD);
|
|
OCIO::CreateLogOp(ops, base, logSlope, logOffset, linSlope, linOffset,
|
|
OCIO::TRANSFORM_DIR_FORWARD);
|
|
OCIO::CreateLogOp(ops, base, logSlope, logOffset, linSlope, linOffset,
|
|
OCIO::TRANSFORM_DIR_INVERSE);
|
|
OCIO::CreateFixedFunctionOp(ops, func, OCIO::TRANSFORM_DIR_INVERSE);
|
|
OCIO_CHECK_EQUAL(ops.size(), 4);
|
|
|
|
// Forward + inverse log are optimized as a clamping range that stays between
|
|
// forward and inverse exponents.
|
|
OCIO::RemoveInverseOps(ops, OCIO::OPTIMIZATION_ALL);
|
|
OCIO_REQUIRE_EQUAL(ops.size(), 3);
|
|
OCIO_CHECK_EQUAL(ops[0]->getInfo(), "<FixedFunctionOp>");
|
|
OCIO_CHECK_EQUAL(ops[1]->getInfo(), "<RangeOp>");
|
|
OCIO_CHECK_EQUAL(ops[2]->getInfo(), "<FixedFunctionOp>");
|
|
ops.clear();
|
|
|
|
OCIO::CreateFixedFunctionOp(ops, func, OCIO::TRANSFORM_DIR_FORWARD);
|
|
OCIO::CreateFixedFunctionOp(ops, func, OCIO::TRANSFORM_DIR_INVERSE);
|
|
OCIO::CreateLogOp(ops, base, logSlope, logOffset, linSlope, linOffset,
|
|
OCIO::TRANSFORM_DIR_INVERSE);
|
|
OCIO::CreateLogOp(ops, base, logSlope, logOffset, linSlope, linOffset,
|
|
OCIO::TRANSFORM_DIR_FORWARD);
|
|
OCIO::CreateFixedFunctionOp(ops, func, OCIO::TRANSFORM_DIR_FORWARD);
|
|
OCIO_CHECK_EQUAL(ops.size(), 5);
|
|
|
|
OCIO::RemoveInverseOps(ops, OCIO::OPTIMIZATION_ALL);
|
|
OCIO_REQUIRE_EQUAL(ops.size(), 1);
|
|
|
|
OCIO_CHECK_EQUAL(ops[0]->getInfo(), "<FixedFunctionOp>");
|
|
}
|
|
|
|
OCIO_ADD_TEST(OpOptimizers, combine_ops)
|
|
{
|
|
double m1[4] = {2.0, 2.0, 2.0, 1.0};
|
|
double m2[4] = {0.5, 0.5, 0.5, 1.0};
|
|
double m3[4] = {0.6, 0.6, 0.6, 1.0};
|
|
double m4[4] = {0.7, 0.7, 0.7, 1.0};
|
|
|
|
const double exp[4] = {1.2, 1.3, 1.4, 1.5};
|
|
|
|
{
|
|
OCIO::OpRcPtrVec ops;
|
|
OCIO::CreateScaleOp(ops, m1, OCIO::TRANSFORM_DIR_FORWARD);
|
|
|
|
OCIO_CHECK_EQUAL(ops.size(), 1);
|
|
OCIO::CombineOps(ops, OCIO::OPTIMIZATION_ALL);
|
|
OCIO_CHECK_EQUAL(ops.size(), 1);
|
|
}
|
|
|
|
{
|
|
OCIO::OpRcPtrVec ops;
|
|
OCIO::CreateScaleOp(ops, m1, OCIO::TRANSFORM_DIR_FORWARD);
|
|
OCIO::CreateScaleOp(ops, m3, OCIO::TRANSFORM_DIR_FORWARD);
|
|
|
|
OCIO_CHECK_EQUAL(ops.size(), 2);
|
|
OCIO::CombineOps(ops, AllBut(OCIO::OPTIMIZATION_COMP_MATRIX));
|
|
OCIO_CHECK_EQUAL(ops.size(), 2);
|
|
OCIO::CombineOps(ops, OCIO::OPTIMIZATION_ALL);
|
|
OCIO_CHECK_EQUAL(ops.size(), 1);
|
|
}
|
|
|
|
{
|
|
OCIO::OpRcPtrVec ops;
|
|
OCIO::CreateScaleOp(ops, m1, OCIO::TRANSFORM_DIR_FORWARD);
|
|
OCIO::CreateScaleOp(ops, m3, OCIO::TRANSFORM_DIR_FORWARD);
|
|
OCIO::CreateScaleOp(ops, m4, OCIO::TRANSFORM_DIR_FORWARD);
|
|
|
|
OCIO_CHECK_EQUAL(ops.size(), 3);
|
|
OCIO::CombineOps(ops, AllBut(OCIO::OPTIMIZATION_COMP_MATRIX));
|
|
OCIO_CHECK_EQUAL(ops.size(), 3);
|
|
OCIO::CombineOps(ops, OCIO::OPTIMIZATION_ALL);
|
|
OCIO_CHECK_EQUAL(ops.size(), 1);
|
|
}
|
|
|
|
{
|
|
OCIO::OpRcPtrVec ops;
|
|
OCIO::CreateScaleOp(ops, m1, OCIO::TRANSFORM_DIR_FORWARD);
|
|
OCIO::CreateScaleOp(ops, m2, OCIO::TRANSFORM_DIR_FORWARD);
|
|
|
|
OCIO_CHECK_EQUAL(ops.size(), 2);
|
|
OCIO::CombineOps(ops, AllBut(OCIO::OPTIMIZATION_COMP_MATRIX));
|
|
OCIO_CHECK_EQUAL(ops.size(), 2);
|
|
OCIO::CombineOps(ops, OCIO::OPTIMIZATION_ALL);
|
|
OCIO_CHECK_EQUAL(ops.size(), 0);
|
|
}
|
|
|
|
{
|
|
OCIO::OpRcPtrVec ops;
|
|
OCIO::CreateScaleOp(ops, m1, OCIO::TRANSFORM_DIR_FORWARD);
|
|
OCIO::CreateScaleOp(ops, m1, OCIO::TRANSFORM_DIR_INVERSE);
|
|
|
|
OCIO_CHECK_NO_THROW(ops.finalize());
|
|
OCIO_CHECK_EQUAL(ops.size(), 2);
|
|
OCIO::CombineOps(ops, AllBut(OCIO::OPTIMIZATION_COMP_MATRIX));
|
|
OCIO_CHECK_EQUAL(ops.size(), 2);
|
|
OCIO::CombineOps(ops, OCIO::OPTIMIZATION_ALL);
|
|
OCIO_CHECK_EQUAL(ops.size(), 0);
|
|
}
|
|
|
|
{
|
|
OCIO::OpRcPtrVec ops;
|
|
OCIO::CreateScaleOp(ops, m1, OCIO::TRANSFORM_DIR_FORWARD);
|
|
OCIO::CreateScaleOp(ops, m1, OCIO::TRANSFORM_DIR_FORWARD);
|
|
OCIO::CreateScaleOp(ops, m1, OCIO::TRANSFORM_DIR_FORWARD);
|
|
OCIO::CreateScaleOp(ops, m1, OCIO::TRANSFORM_DIR_FORWARD);
|
|
OCIO::CreateScaleOp(ops, m1, OCIO::TRANSFORM_DIR_FORWARD);
|
|
|
|
OCIO_CHECK_EQUAL(ops.size(), 5);
|
|
OCIO::CombineOps(ops, AllBut(OCIO::OPTIMIZATION_COMP_MATRIX));
|
|
OCIO_CHECK_EQUAL(ops.size(), 5);
|
|
OCIO::CombineOps(ops, OCIO::OPTIMIZATION_ALL);
|
|
OCIO_CHECK_EQUAL(ops.size(), 1);
|
|
}
|
|
|
|
{
|
|
OCIO::OpRcPtrVec ops;
|
|
OCIO::CreateExponentOp(ops, exp, OCIO::TRANSFORM_DIR_FORWARD);
|
|
OCIO::CreateScaleOp(ops, m1, OCIO::TRANSFORM_DIR_FORWARD);
|
|
OCIO::CreateScaleOp(ops, m2, OCIO::TRANSFORM_DIR_FORWARD);
|
|
OCIO::CreateExponentOp(ops, exp, OCIO::TRANSFORM_DIR_INVERSE);
|
|
|
|
OCIO_CHECK_NO_THROW(ops.finalize());
|
|
OCIO_CHECK_EQUAL(ops.size(), 4);
|
|
OCIO::CombineOps(ops, AllBut(OCIO::OPTIMIZATION_COMP_MATRIX));
|
|
OCIO_CHECK_EQUAL(ops.size(), 4);
|
|
OCIO::CombineOps(ops, OCIO::OPTIMIZATION_ALL);
|
|
OCIO_CHECK_EQUAL(ops.size(), 0);
|
|
}
|
|
}
|
|
|
|
OCIO_ADD_TEST(OpOptimizers, non_optimizable)
|
|
{
|
|
OCIO::OpRcPtrVec ops;
|
|
// Create non identity Matrix.
|
|
const double m44[16] = { 2., 0., 0., 0.,
|
|
0., 1., 0., 0.,
|
|
0., 0., 1., 0.,
|
|
0., 0., 0., 1. };
|
|
const double offset4[4] = { 0., 0., 0., 0. };
|
|
OCIO::CreateMatrixOffsetOp(ops, m44, offset4, OCIO::TRANSFORM_DIR_FORWARD);
|
|
|
|
OCIO_CHECK_EQUAL(ops.size(), 1);
|
|
|
|
OCIO_CHECK_NO_THROW(ops.finalize());
|
|
OCIO_CHECK_NO_THROW(ops.optimize(OCIO::OPTIMIZATION_DEFAULT));
|
|
|
|
OCIO_REQUIRE_EQUAL(ops.size(), 1);
|
|
|
|
OCIO::ConstOpRcPtr op = ops[0];
|
|
OCIO_REQUIRE_ASSERT(op);
|
|
auto mat = OCIO_DYNAMIC_POINTER_CAST<const OCIO::MatrixOpData>(op->data());
|
|
OCIO_REQUIRE_ASSERT(mat);
|
|
|
|
OCIO_CHECK_EQUAL(mat->getArray().getValues()[0], 2.);
|
|
OCIO_CHECK_ASSERT(mat->isDiagonal());
|
|
}
|
|
|
|
OCIO_ADD_TEST(OpOptimizers, optimizable)
|
|
{
|
|
OCIO::OpRcPtrVec ops;
|
|
// Create identity Matrix.
|
|
double m44[16] = { 1., 0., 0., 0.,
|
|
0., 1., 0., 0.,
|
|
0., 0., 1., 0.,
|
|
0., 0., 0., 1. };
|
|
const double offset4[4] = { 0., 0., 0., 0. };
|
|
OCIO::CreateMatrixOffsetOp(ops, m44, offset4, OCIO::TRANSFORM_DIR_FORWARD);
|
|
|
|
OCIO_CHECK_EQUAL(ops.size(), 1);
|
|
|
|
OCIO_CHECK_NO_THROW(ops.finalize());
|
|
OCIO_CHECK_NO_THROW(ops.optimize(OCIO::OPTIMIZATION_DEFAULT));
|
|
|
|
// Identity matrix is a no-op and is removed. CPU processor will re-add an identity matrix
|
|
// if there are no ops left.
|
|
OCIO_CHECK_EQUAL(ops.size(), 0);
|
|
ops.clear();
|
|
|
|
// Add identity matrix.
|
|
OCIO::CreateMatrixOffsetOp(ops, m44, offset4, OCIO::TRANSFORM_DIR_FORWARD);
|
|
|
|
// No more an 'identity matrix'.
|
|
m44[0] = 2.;
|
|
m44[1] = 2.;
|
|
OCIO::CreateMatrixOffsetOp(ops, m44, offset4, OCIO::TRANSFORM_DIR_FORWARD);
|
|
|
|
OCIO_CHECK_EQUAL(ops.size(), 2);
|
|
|
|
OCIO_CHECK_NO_THROW(ops.finalize());
|
|
OCIO_CHECK_NO_THROW(ops.optimize(OCIO::OPTIMIZATION_DEFAULT));
|
|
|
|
OCIO_REQUIRE_EQUAL(ops.size(), 1);
|
|
|
|
OCIO::ConstOpRcPtr op = ops[0];
|
|
OCIO_REQUIRE_ASSERT(op);
|
|
auto mat = OCIO_DYNAMIC_POINTER_CAST<const OCIO::MatrixOpData>(op->data());
|
|
OCIO_REQUIRE_ASSERT(mat);
|
|
OCIO_CHECK_ASSERT(!mat->isIdentity());
|
|
OCIO_CHECK_ASSERT(!mat->isDiagonal());
|
|
}
|
|
|
|
OCIO_ADD_TEST(OpOptimizers, optimization)
|
|
{
|
|
// This is a transform consisting of a Lut1d, Matrix, Matrix, Lut1d.
|
|
// The matrices and luts are inverses of one another, so when they are
|
|
// composed they become identities which are then replaced.
|
|
// So this one test actually tests quite a lot of the optimize and compose
|
|
// functionality.
|
|
|
|
const std::string fileName("opt_test1.ctf");
|
|
OCIO::OpRcPtrVec ops;
|
|
OCIO::ContextRcPtr context = OCIO::Context::Create();
|
|
OCIO_CHECK_NO_THROW(OCIO::BuildOpsTest(ops, fileName, context,
|
|
OCIO::TRANSFORM_DIR_FORWARD));
|
|
|
|
// First one is the file no op.
|
|
OCIO_CHECK_EQUAL(ops.size(), 5);
|
|
|
|
OCIO_CHECK_NO_THROW(ops.finalize());
|
|
OCIO_CHECK_NO_THROW(ops.optimize(OCIO::OPTIMIZATION_NONE));
|
|
OCIO_CHECK_EQUAL(ops.size(), 4);
|
|
|
|
OCIO::OpRcPtrVec optOps = ops.clone();
|
|
OCIO_CHECK_EQUAL(optOps.size(), 4);
|
|
OCIO_CHECK_NO_THROW(optOps.finalize());
|
|
OCIO_CHECK_NO_THROW(optOps.optimize(OCIO::OPTIMIZATION_DEFAULT));
|
|
|
|
OCIO_REQUIRE_EQUAL(optOps.size(), 1);
|
|
OCIO_CHECK_EQUAL(optOps[0]->getInfo(), "<RangeOp>");
|
|
|
|
// Compare renders.
|
|
CompareRender(ops, optOps, __LINE__, 1e-6f);
|
|
}
|
|
|
|
OCIO_ADD_TEST(OpOptimizers, optimization2)
|
|
{
|
|
// This transform has the following ops:
|
|
// 1 Lut1D, half domain, effectively an identity
|
|
// 2 Matrix, bit depth conversion identity
|
|
// 3 Matrix, bit depth conversion identity
|
|
// 4 Matrix, almost identity
|
|
// 5 Range, clamp identity
|
|
// 6 Lut1D, half domain, raw halfs, identity
|
|
// 7 Lut1D, raw halfs, identity
|
|
// 8 Matrix, not identity
|
|
// 9 Matrix, not identity
|
|
// 10 Lut1D, almost identity
|
|
// 11 Lut1D, almost identity that composes to an identity with the previous one
|
|
// 12 Lut3D, not identity
|
|
// 13 Lut3D, not identity but composes to an identity with the previous one
|
|
|
|
const std::string fileName("opt_test2.ctf");
|
|
OCIO::OpRcPtrVec ops;
|
|
OCIO::ContextRcPtr context = OCIO::Context::Create();
|
|
OCIO_CHECK_NO_THROW(OCIO::BuildOpsTest(ops, fileName, context,
|
|
OCIO::TRANSFORM_DIR_FORWARD));
|
|
|
|
// First one is the file no op.
|
|
OCIO_CHECK_EQUAL(ops.size(), 14);
|
|
|
|
OCIO_CHECK_NO_THROW(ops.finalize());
|
|
OCIO_CHECK_NO_THROW(ops.optimize(OCIO::OPTIMIZATION_NONE));
|
|
OCIO_CHECK_EQUAL(ops.size(), 13);
|
|
|
|
OCIO::OpRcPtrVec optOps = ops.clone();
|
|
OCIO_CHECK_EQUAL(optOps.size(), 13);
|
|
|
|
// No need to remove OPTIMIZATION_COMP_SEPARABLE_PREFIX because optimization is for F32.
|
|
OCIO_CHECK_NO_THROW(optOps.finalize());
|
|
OCIO_CHECK_NO_THROW(optOps.optimize(OCIO::OPTIMIZATION_GOOD));
|
|
OCIO_REQUIRE_EQUAL(optOps.size(), 4);
|
|
|
|
// Op 1 is exactly an identity except for the first value which is 0.000001. Since the
|
|
// outDepth=16i, this gets normalized by 1/65536, which puts it well under the noise
|
|
// threshold so it's optimized it out as an identity.
|
|
|
|
// Ops 2 & 3 are identities and removed.
|
|
// This is op 4.
|
|
OCIO_CHECK_EQUAL(optOps[0]->getInfo(), "<MatrixOffsetOp>");
|
|
// Op 5 is a clamp identity and removed.
|
|
// Ops 6 & 7 are identities and are replaced by ranges, one is removed.
|
|
OCIO_CHECK_EQUAL(optOps[1]->getInfo(), "<RangeOp>");
|
|
// This is op 8 composed with op 9.
|
|
OCIO_CHECK_EQUAL(optOps[2]->getInfo(), "<MatrixOffsetOp>");
|
|
// Ops 10 & 11 composed become an identity, is replaced with a range,
|
|
// which is then removed as a clamp identity
|
|
// This is op 12 composed with op 13. It is an identity.
|
|
// NB: We don't try to detect Lut3DOp identities.
|
|
OCIO_CHECK_EQUAL(optOps[3]->getInfo(), "<Lut3DOp>");
|
|
|
|
// Compare renders.
|
|
CompareRender(ops, optOps, __LINE__, 1e-6f);
|
|
}
|
|
|
|
OCIO_ADD_TEST(OpOptimizers, lut1d_identities)
|
|
{
|
|
// This transform has the following ops:
|
|
// 1 Lut1D, identity
|
|
// 2 Lut1D, identity
|
|
// 3 Lut1D, not quite identity
|
|
// 4 Lut1D, half-domain identity (note 16i outDepth)
|
|
// 5 Lut1D, half-domain not an identity (values clamped due to rawHalfs encoding)
|
|
// 6 Lut1D, identity
|
|
|
|
const std::string fileName("lut1d_identity_test.ctf");
|
|
OCIO::OpRcPtrVec ops;
|
|
OCIO::ContextRcPtr context = OCIO::Context::Create();
|
|
OCIO_CHECK_NO_THROW(OCIO::BuildOpsTest(ops, fileName, context,
|
|
OCIO::TRANSFORM_DIR_FORWARD));
|
|
|
|
// First one is the file no op.
|
|
OCIO_REQUIRE_EQUAL(ops.size(), 7);
|
|
|
|
OCIO_CHECK_ASSERT(ops[1]->isIdentity());
|
|
OCIO_CHECK_ASSERT(ops[2]->isIdentity());
|
|
OCIO_CHECK_ASSERT(false == ops[3]->isIdentity());
|
|
OCIO_CHECK_ASSERT(ops[4]->isIdentity());
|
|
OCIO_CHECK_ASSERT(false == ops[5]->isIdentity());
|
|
OCIO_CHECK_ASSERT(ops[6]->isIdentity());
|
|
|
|
OCIO_CHECK_NO_THROW(ops.finalize());
|
|
OCIO_CHECK_NO_THROW(ops.optimize(OCIO::OPTIMIZATION_NONE));
|
|
|
|
OCIO::OpRcPtrVec optOps = ops.clone();
|
|
OCIO_CHECK_EQUAL(optOps.size(), 6);
|
|
|
|
OCIO_CHECK_NO_THROW(optOps.finalize());
|
|
OCIO_CHECK_NO_THROW(optOps.optimize(OCIO::OPTIMIZATION_DEFAULT));
|
|
|
|
OCIO_REQUIRE_EQUAL(optOps.size(), 2);
|
|
|
|
// The first two LUTs should get detected as identities and replaced with ranges which then
|
|
// get removed as clamp identities. LUT 3 is not identity and is not removed.
|
|
// The next LUT should also get detected as an identity, and replaced with a matrix (rather
|
|
// than a range since it is half-domain) which is then optimized out. The next LUT is almost
|
|
// an identity except the large values are clamped due to the rawHalfs encoding, so it is not
|
|
// removed. The final LUT is a normal domain to be replaced with a range. The two Luts get
|
|
// combined into a single Lut with a standard domain.
|
|
|
|
OCIO_CHECK_EQUAL(optOps[0]->getInfo(), "<Lut1DOp>");
|
|
OCIO_CHECK_EQUAL(optOps[1]->getInfo(), "<RangeOp>");
|
|
|
|
OCIO::ConstOpRcPtr lutOp = optOps[0];
|
|
auto lutData = OCIO::DynamicPtrCast<const OCIO::Lut1DOpData>(lutOp->data());
|
|
OCIO_REQUIRE_ASSERT(lutData);
|
|
OCIO_CHECK_EQUAL(lutData->getArray().getLength(), 65536);
|
|
OCIO_CHECK_ASSERT(!lutData->isInputHalfDomain());
|
|
|
|
// Now check that the optimized transform renders the same as the original.
|
|
// TODO: Shall investigate why this test requires a bigger error.
|
|
CompareRender(ops, optOps, __LINE__, 3e-4f);
|
|
}
|
|
|
|
OCIO_ADD_TEST(OpOptimizers, lut1d_identity_replacement)
|
|
{
|
|
// Test that an identity Lut1D becomes a range but a half-domain becomes a matrix.
|
|
{
|
|
auto lutData = std::make_shared<OCIO::Lut1DOpData>(3);
|
|
OCIO_CHECK_ASSERT(lutData->isIdentity());
|
|
|
|
OCIO::OpRcPtrVec ops;
|
|
OCIO::CreateLut1DOp(ops, lutData, OCIO::TRANSFORM_DIR_FORWARD);
|
|
|
|
OCIO_CHECK_EQUAL(ops.size(), 1);
|
|
|
|
OCIO_CHECK_NO_THROW(ops.finalize());
|
|
OCIO_CHECK_NO_THROW(ops.optimize(OCIO::OPTIMIZATION_DEFAULT));
|
|
OCIO_REQUIRE_EQUAL(ops.size(), 1);
|
|
|
|
OCIO_CHECK_EQUAL(ops[0]->getInfo(), "<RangeOp>");
|
|
}
|
|
{
|
|
// By setting the filterNaNs argument to true, the constructor replaces NaN values with 0
|
|
// and this causes the LUT to technically no longer be an identity since the values are no
|
|
// longer exactly what is in a half float.
|
|
OCIO::Lut1DOpDataRcPtr lutData
|
|
= std::make_shared<OCIO::Lut1DOpData>(OCIO::Lut1DOpData::LUT_INPUT_OUTPUT_HALF_CODE,
|
|
65536, true);
|
|
lutData->setFileOutputBitDepth(OCIO::BIT_DEPTH_F32);
|
|
OCIO_CHECK_ASSERT(!lutData->isIdentity());
|
|
OCIO_CHECK_ASSERT(lutData->isInputHalfDomain());
|
|
}
|
|
{
|
|
// By default, this constructor creates an 'identity lut'.
|
|
OCIO::Lut1DOpDataRcPtr lutData
|
|
= std::make_shared<OCIO::Lut1DOpData>(OCIO::Lut1DOpData::LUT_INPUT_OUTPUT_HALF_CODE,
|
|
65536, false);
|
|
lutData->setFileOutputBitDepth(OCIO::BIT_DEPTH_F32);
|
|
OCIO_CHECK_ASSERT(lutData->isIdentity());
|
|
OCIO_CHECK_ASSERT(lutData->isInputHalfDomain());
|
|
|
|
OCIO::OpRcPtrVec ops;
|
|
OCIO::CreateLut1DOp(ops, lutData, OCIO::TRANSFORM_DIR_FORWARD);
|
|
|
|
OCIO_CHECK_EQUAL(ops.size(), 1);
|
|
|
|
OCIO_CHECK_NO_THROW(ops.finalize());
|
|
OCIO_CHECK_NO_THROW(ops.optimize(OCIO::OPTIMIZATION_DEFAULT));
|
|
|
|
// Half domain LUT 1d is a no-op.
|
|
// CPU processor will add an identity matrix.
|
|
OCIO_CHECK_EQUAL(ops.size(), 0);
|
|
}
|
|
}
|
|
|
|
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(), "<RangeOp>");
|
|
|
|
// 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(), "<RangeOp>");
|
|
|
|
// 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.
|
|
|
|
OCIO::OpRcPtrVec ops;
|
|
OCIO::CreateRangeOp(ops, 0., 1., 0., 1., OCIO::TRANSFORM_DIR_FORWARD);
|
|
|
|
OCIO::Lut1DOpDataRcPtr lutData
|
|
= std::make_shared<OCIO::Lut1DOpData>(OCIO::Lut1DOpData::LUT_INPUT_OUTPUT_HALF_CODE,
|
|
65536, false);
|
|
lutData->setFileOutputBitDepth(OCIO::BIT_DEPTH_F32);
|
|
|
|
// Add no-op LUT.
|
|
OCIO::CreateLut1DOp(ops, lutData, OCIO::TRANSFORM_DIR_INVERSE);
|
|
|
|
// Add another LUT.
|
|
lutData = lutData->clone();
|
|
auto & lutArray = lutData->getArray();
|
|
for (auto & val : lutArray.getValues())
|
|
{
|
|
val = -val;
|
|
}
|
|
OCIO::CreateLut1DOp(ops, lutData, OCIO::TRANSFORM_DIR_FORWARD);
|
|
|
|
OCIO_CHECK_NO_THROW(ops.finalize());
|
|
OCIO_CHECK_EQUAL(ops.size(), 3);
|
|
|
|
OCIO::OpRcPtrVec optOps = ops.clone();
|
|
OCIO_CHECK_EQUAL(optOps.size(), 3);
|
|
OCIO_CHECK_NO_THROW(optOps.finalize());
|
|
OCIO_CHECK_NO_THROW(optOps.optimize(OCIO::OPTIMIZATION_DEFAULT));
|
|
OCIO_CHECK_EQUAL(optOps.size(), 2);
|
|
|
|
OCIO_CHECK_EQUAL(optOps[0]->getInfo(), "<RangeOp>");
|
|
OCIO_CHECK_EQUAL(optOps[1]->getInfo(), "<Lut1DOp>");
|
|
|
|
// Now check that the optimized transform renders the same as the original.
|
|
CompareRender(ops, optOps, __LINE__, 1e-6f);
|
|
}
|
|
|
|
OCIO_ADD_TEST(OpOptimizers, range_composition)
|
|
{
|
|
const double emptyValue = OCIO::RangeOpData::EmptyValue();
|
|
{
|
|
// Two identity clamp negs ranges are collapsed into one.
|
|
OCIO::OpRcPtrVec ops;
|
|
OCIO::CreateRangeOp(ops, 0., emptyValue, 0., emptyValue, OCIO::TRANSFORM_DIR_FORWARD);
|
|
OCIO::CreateRangeOp(ops, 0., emptyValue, 0., emptyValue, OCIO::TRANSFORM_DIR_FORWARD);
|
|
|
|
OCIO_CHECK_NO_THROW(ops.finalize());
|
|
OCIO_CHECK_EQUAL(ops.size(), 2);
|
|
|
|
OCIO::OpRcPtrVec optOps = ops.clone();
|
|
OCIO_CHECK_NO_THROW(optOps.finalize());
|
|
OCIO_CHECK_NO_THROW(optOps.optimize(OCIO::OPTIMIZATION_DEFAULT));
|
|
OCIO_REQUIRE_EQUAL(optOps.size(), 1);
|
|
OCIO_CHECK_EQUAL(optOps[0]->getInfo(), "<RangeOp>");
|
|
CompareRender(ops, optOps, __LINE__, 1e-6f);
|
|
}
|
|
{
|
|
// Non identity ranges are combined.
|
|
OCIO::OpRcPtrVec ops;
|
|
OCIO::CreateRangeOp(ops, 0.1, emptyValue, 0.1, emptyValue, OCIO::TRANSFORM_DIR_FORWARD);
|
|
OCIO::CreateRangeOp(ops, 0.2, emptyValue, 0.2, emptyValue, OCIO::TRANSFORM_DIR_FORWARD);
|
|
|
|
OCIO_CHECK_NO_THROW(ops.finalize());
|
|
OCIO_CHECK_EQUAL(ops.size(), 2);
|
|
|
|
OCIO::OpRcPtrVec optOps = ops.clone();
|
|
OCIO_CHECK_NO_THROW(optOps.finalize());
|
|
OCIO_CHECK_NO_THROW(optOps.optimize(OCIO::OPTIMIZATION_DEFAULT));
|
|
OCIO_REQUIRE_EQUAL(optOps.size(), 1);
|
|
OCIO_CHECK_EQUAL(optOps[0]->getInfo(), "<RangeOp>");
|
|
CompareRender(ops, optOps, __LINE__, 1e-6f);
|
|
}
|
|
{
|
|
// Non identity ranges are combined.
|
|
OCIO::OpRcPtrVec ops;
|
|
OCIO::CreateRangeOp(ops, 0.1, emptyValue, 0.1, emptyValue, OCIO::TRANSFORM_DIR_FORWARD);
|
|
OCIO::CreateRangeOp(ops, 0.1, emptyValue, 0.1, emptyValue, OCIO::TRANSFORM_DIR_INVERSE);
|
|
|
|
OCIO_CHECK_NO_THROW(ops.finalize());
|
|
OCIO_REQUIRE_EQUAL(ops.size(), 2);
|
|
|
|
OCIO::OpRcPtrVec optOps = ops.clone();
|
|
OCIO_CHECK_NO_THROW(optOps.finalize());
|
|
OCIO_CHECK_NO_THROW(optOps.optimize(OCIO::OPTIMIZATION_DEFAULT));
|
|
OCIO_REQUIRE_EQUAL(optOps.size(), 1);
|
|
OCIO_CHECK_EQUAL(optOps[0]->getInfo(), "<RangeOp>");
|
|
CompareRender(ops, optOps, __LINE__, 1e-6f);
|
|
}
|
|
{
|
|
// Non identity ranges are combined.
|
|
OCIO::OpRcPtrVec ops;
|
|
OCIO::CreateRangeOp(ops, -0.1, emptyValue, -0.1, emptyValue, OCIO::TRANSFORM_DIR_FORWARD);
|
|
OCIO::CreateRangeOp(ops, -0.1, emptyValue, -0.1, emptyValue, OCIO::TRANSFORM_DIR_INVERSE);
|
|
|
|
OCIO_CHECK_NO_THROW(ops.finalize());
|
|
OCIO_REQUIRE_EQUAL(ops.size(), 2);
|
|
|
|
OCIO::OpRcPtrVec optOps = ops.clone();
|
|
OCIO_CHECK_NO_THROW(optOps.finalize());
|
|
OCIO_CHECK_NO_THROW(optOps.optimize(OCIO::OPTIMIZATION_DEFAULT));
|
|
OCIO_REQUIRE_EQUAL(optOps.size(), 1);
|
|
OCIO_CHECK_EQUAL(optOps[0]->getInfo(), "<RangeOp>");
|
|
CompareRender(ops, optOps, __LINE__, 1e-6f);
|
|
}
|
|
{
|
|
// A clamp negs range is dropped before a more restrictive one.
|
|
OCIO::OpRcPtrVec ops;
|
|
OCIO::CreateRangeOp(ops, 0., emptyValue, 0., emptyValue, OCIO::TRANSFORM_DIR_FORWARD);
|
|
OCIO::CreateRangeOp(ops, 0.1, 2., 0., 2., OCIO::TRANSFORM_DIR_FORWARD);
|
|
|
|
OCIO_CHECK_NO_THROW(ops.finalize());
|
|
OCIO_CHECK_EQUAL(ops.size(), 2);
|
|
|
|
OCIO::OpRcPtrVec optOps = ops.clone();
|
|
OCIO_CHECK_NO_THROW(optOps.finalize());
|
|
OCIO_CHECK_NO_THROW(optOps.optimize(OCIO::OPTIMIZATION_DEFAULT));
|
|
OCIO_REQUIRE_EQUAL(optOps.size(), 1);
|
|
OCIO_CHECK_EQUAL(optOps[0]->getInfo(), "<RangeOp>");
|
|
CompareRender(ops, optOps, __LINE__, 1e-6f);
|
|
}
|
|
{
|
|
OCIO::OpRcPtrVec ops;
|
|
OCIO::CreateRangeOp(ops, 0., 0.5, 0., 0.5, OCIO::TRANSFORM_DIR_FORWARD);
|
|
OCIO::CreateRangeOp(ops, 0.1, 1., 0.1, 1., OCIO::TRANSFORM_DIR_FORWARD);
|
|
|
|
OCIO_CHECK_NO_THROW(ops.finalize());
|
|
OCIO_CHECK_EQUAL(ops.size(), 2);
|
|
|
|
OCIO::OpRcPtrVec optOps = ops.clone();
|
|
OCIO_CHECK_NO_THROW(optOps.finalize());
|
|
OCIO_CHECK_NO_THROW(optOps.optimize(OCIO::OPTIMIZATION_DEFAULT));
|
|
OCIO_REQUIRE_EQUAL(optOps.size(), 1);
|
|
OCIO_CHECK_EQUAL(optOps[0]->getInfo(), "<RangeOp>");
|
|
CompareRender(ops, optOps, __LINE__, 1e-6f);
|
|
}
|
|
{
|
|
OCIO::OpRcPtrVec ops;
|
|
OCIO::CreateRangeOp(ops, 0., 0.6, 0., 0.5, OCIO::TRANSFORM_DIR_FORWARD);
|
|
OCIO::CreateRangeOp(ops, 0.1, 1., 0.2, 1., OCIO::TRANSFORM_DIR_FORWARD);
|
|
OCIO::CreateRangeOp(ops, 0., 1., 0., 2., OCIO::TRANSFORM_DIR_FORWARD);
|
|
|
|
OCIO_CHECK_NO_THROW(ops.finalize());
|
|
OCIO_CHECK_EQUAL(ops.size(), 3);
|
|
|
|
OCIO::OpRcPtrVec optOps = ops.clone();
|
|
OCIO_CHECK_NO_THROW(optOps.finalize());
|
|
OCIO_CHECK_NO_THROW(optOps.optimize(OCIO::OPTIMIZATION_DEFAULT));
|
|
OCIO_REQUIRE_EQUAL(optOps.size(), 1);
|
|
OCIO_CHECK_EQUAL(optOps[0]->getInfo(), "<RangeOp>");
|
|
CompareRender(ops, optOps, __LINE__, 1e-6f);
|
|
}
|
|
{
|
|
OCIO::OpRcPtrVec ops;
|
|
OCIO::CreateRangeOp(ops, 0., 0.5, 0., 0.5, OCIO::TRANSFORM_DIR_FORWARD);
|
|
OCIO::CreateRangeOp(ops, 0.6, 1., 0.6, 1., OCIO::TRANSFORM_DIR_FORWARD);
|
|
|
|
OCIO_CHECK_NO_THROW(ops.finalize());
|
|
OCIO_CHECK_EQUAL(ops.size(), 2);
|
|
|
|
OCIO::OpRcPtrVec optOps = ops.clone();
|
|
// Two Ranges with non-overlapping pass regions are replaced with a clamp to a constant.
|
|
OCIO_CHECK_NO_THROW(optOps.finalize());
|
|
OCIO_CHECK_NO_THROW(optOps.optimize(OCIO::OPTIMIZATION_DEFAULT));
|
|
OCIO_REQUIRE_EQUAL(optOps.size(), 1);
|
|
OCIO_CHECK_EQUAL(optOps[0]->getInfo(), "<RangeOp>");
|
|
CompareRender(ops, optOps, __LINE__, 1e-6f);
|
|
}
|
|
{
|
|
OCIO::OpRcPtrVec ops;
|
|
OCIO::CreateRangeOp(ops, 0.6, 1., 0.6, 1., OCIO::TRANSFORM_DIR_FORWARD);
|
|
OCIO::CreateRangeOp(ops, 0., 0.5, 0., 0.5, OCIO::TRANSFORM_DIR_FORWARD);
|
|
|
|
OCIO_CHECK_NO_THROW(ops.finalize());
|
|
OCIO_CHECK_EQUAL(ops.size(), 2);
|
|
|
|
OCIO::OpRcPtrVec optOps = ops.clone();
|
|
// Ranges can not be combined out domain of the first does not intersect
|
|
// with in domain of the second.
|
|
OCIO_CHECK_NO_THROW(optOps.finalize());
|
|
OCIO_CHECK_NO_THROW(optOps.optimize(OCIO::OPTIMIZATION_DEFAULT));
|
|
OCIO_REQUIRE_EQUAL(optOps.size(), 1);
|
|
OCIO_CHECK_EQUAL(optOps[0]->getInfo(), "<RangeOp>");
|
|
CompareRender(ops, optOps, __LINE__, 1e-6f);
|
|
}
|
|
{
|
|
OCIO::OpRcPtrVec ops;
|
|
OCIO::CreateRangeOp(ops, 0., 0.6, 0., 0.5, OCIO::TRANSFORM_DIR_FORWARD);
|
|
OCIO::CreateRangeOp(ops, 0.1, 1., 0.2, 1., OCIO::TRANSFORM_DIR_FORWARD);
|
|
OCIO::CreateRangeOp(ops, 0.1, 1., 0.2, 1., OCIO::TRANSFORM_DIR_INVERSE);
|
|
OCIO::CreateRangeOp(ops, 0., 1., 0., 2., OCIO::TRANSFORM_DIR_FORWARD);
|
|
OCIO::CreateRangeOp(ops, 0.1, 0.8, 0.2, 1., OCIO::TRANSFORM_DIR_INVERSE);
|
|
OCIO::CreateRangeOp(ops, 0.2, 0.6, 0.1, 0.7, OCIO::TRANSFORM_DIR_INVERSE);
|
|
|
|
OCIO_CHECK_NO_THROW(ops.finalize());
|
|
OCIO_CHECK_EQUAL(ops.size(), 6);
|
|
|
|
OCIO::OpRcPtrVec optOps = ops.clone();
|
|
OCIO_CHECK_NO_THROW(optOps.finalize());
|
|
OCIO_CHECK_NO_THROW(optOps.optimize(OCIO::OPTIMIZATION_DEFAULT));
|
|
OCIO_REQUIRE_EQUAL(optOps.size(), 1);
|
|
OCIO_CHECK_EQUAL(optOps[0]->getInfo(), "<RangeOp>");
|
|
CompareRender(ops, optOps, __LINE__, 1e-6f);
|
|
}
|
|
}
|
|
|
|
OCIO_ADD_TEST(OpOptimizers, invlut_pair_identities)
|
|
{
|
|
// The file contains an InverseLUT1D and LUT1D, both with the same array, followed by
|
|
// an InverseLUT3D and LUT3D, also both with the same array. The pairs should each get
|
|
// replaced by a range and then the ranges should be combined.
|
|
const std::string fileName("lut_inv_pairs.ctf");
|
|
OCIO::OpRcPtrVec ops;
|
|
OCIO::ContextRcPtr context = OCIO::Context::Create();
|
|
OCIO_CHECK_NO_THROW(OCIO::BuildOpsTest(ops, fileName, context,
|
|
OCIO::TRANSFORM_DIR_FORWARD));
|
|
|
|
// First one is the file no op.
|
|
OCIO_CHECK_EQUAL(ops.size(), 5);
|
|
|
|
// Remove no ops & finalize for computation.
|
|
OCIO_CHECK_NO_THROW(ops.finalize());
|
|
OCIO_CHECK_NO_THROW(ops.optimize(OCIO::OPTIMIZATION_NONE));
|
|
|
|
OCIO_CHECK_EQUAL(ops.size(), 4);
|
|
|
|
OCIO::OpRcPtrVec optOps = ops.clone();
|
|
OCIO_CHECK_EQUAL(optOps.size(), 4);
|
|
OCIO_CHECK_NO_THROW(optOps.finalize());
|
|
OCIO_CHECK_NO_THROW(optOps.optimize(OCIO::OPTIMIZATION_DEFAULT));
|
|
|
|
OCIO_REQUIRE_EQUAL(optOps.size(), 1);
|
|
OCIO_CHECK_EQUAL(optOps[0]->getInfo(), "<RangeOp>");
|
|
// (A range that clamps on either side is not a no-op.)
|
|
OCIO_CHECK_ASSERT(!optOps[0]->isNoOp());
|
|
|
|
// Now check that the optimized transform renders the same as the original.
|
|
CompareRender(ops, optOps, __LINE__, 1e-6f);
|
|
}
|
|
|
|
OCIO_ADD_TEST(OpOptimizers, mntr_identities)
|
|
{
|
|
// Forward and inverse monitor transforms should become an identity.
|
|
const std::string fileName("mntr_srgb_identity.ctf");
|
|
OCIO::OpRcPtrVec ops;
|
|
OCIO::ContextRcPtr context = OCIO::Context::Create();
|
|
OCIO_CHECK_NO_THROW(OCIO::BuildOpsTest(ops, fileName, context,
|
|
OCIO::TRANSFORM_DIR_FORWARD));
|
|
|
|
// First one is the file no op.
|
|
OCIO_CHECK_EQUAL(ops.size(), 5);
|
|
|
|
// Remove no ops & finalize for computation.
|
|
OCIO_CHECK_NO_THROW(ops.finalize());
|
|
OCIO_CHECK_NO_THROW(ops.optimize(OCIO::OPTIMIZATION_NONE));
|
|
|
|
OCIO_CHECK_EQUAL(ops.size(), 4);
|
|
|
|
OCIO_CHECK_NO_THROW(ops.finalize());
|
|
OCIO_CHECK_NO_THROW(ops.optimize(OCIO::OPTIMIZATION_DEFAULT));
|
|
|
|
// Identity transform.
|
|
OCIO_CHECK_EQUAL(ops.size(), 0);
|
|
}
|
|
|
|
OCIO_ADD_TEST(OpOptimizers, gamma_comp)
|
|
{
|
|
// This transform has a pair of gammas separated by an identity matrix
|
|
// that should compose into a single (non-identity) gamma that then should
|
|
// be identified as a pair identity with another gamma and be replaced with
|
|
// a clamp-negs range.
|
|
|
|
const std::string fileName("gamma_comp_test.ctf");
|
|
OCIO::OpRcPtrVec ops;
|
|
OCIO::ContextRcPtr context = OCIO::Context::Create();
|
|
OCIO_CHECK_NO_THROW(OCIO::BuildOpsTest(ops, fileName, context,
|
|
OCIO::TRANSFORM_DIR_FORWARD));
|
|
|
|
// First one is the file no op.
|
|
OCIO_CHECK_EQUAL(ops.size(), 5);
|
|
|
|
// Remove no ops & finalize for computation.
|
|
OCIO_CHECK_NO_THROW(ops.finalize());
|
|
OCIO_CHECK_NO_THROW(ops.optimize(OCIO::OPTIMIZATION_NONE));
|
|
|
|
OCIO_CHECK_EQUAL(ops.size(), 4);
|
|
|
|
OCIO::OpRcPtrVec optOps = ops.clone();
|
|
OCIO::OpRcPtrVec optOps_noComp = ops.clone();
|
|
OCIO::OpRcPtrVec optOps_noIdentity = ops.clone();
|
|
|
|
OCIO_CHECK_EQUAL(optOps_noComp.size(), 4);
|
|
OCIO_CHECK_NO_THROW(optOps_noComp.finalize());
|
|
OCIO_CHECK_NO_THROW(optOps_noComp.optimize(AllBut(OCIO::OPTIMIZATION_COMP_GAMMA)));
|
|
// Identity matrix is removed but gamma are not combined.
|
|
OCIO_CHECK_EQUAL(optOps_noComp.size(), 3);
|
|
OCIO_CHECK_EQUAL(optOps_noComp[0]->getInfo(), "<GammaOp>");
|
|
OCIO_CHECK_EQUAL(optOps_noComp[1]->getInfo(), "<GammaOp>");
|
|
OCIO_CHECK_EQUAL(optOps_noComp[2]->getInfo(), "<GammaOp>");
|
|
|
|
CompareRender(ops, optOps_noComp, __LINE__, 1e-6f);
|
|
|
|
OCIO_CHECK_EQUAL(optOps_noIdentity.size(), 4);
|
|
OCIO_CHECK_NO_THROW(optOps_noIdentity.finalize());
|
|
OCIO_CHECK_NO_THROW(optOps_noIdentity.optimize(AllBut(OCIO::OPTIMIZATION_IDENTITY_GAMMA)));
|
|
// Identity matrix is removed and gammas are combined into an identity gamma
|
|
// but it is not removed.
|
|
OCIO_CHECK_EQUAL(optOps_noIdentity.size(), 1);
|
|
OCIO_CHECK_EQUAL(optOps_noIdentity[0]->getInfo(), "<GammaOp>");
|
|
|
|
CompareRender(ops, optOps_noIdentity, __LINE__, 5e-5f);
|
|
|
|
OCIO_CHECK_NO_THROW(optOps.finalize());
|
|
OCIO_CHECK_NO_THROW(optOps.optimize(OCIO::OPTIMIZATION_DEFAULT));
|
|
|
|
// Identity matrix is removed and gammas combined and optimized as a range.
|
|
OCIO_REQUIRE_EQUAL(optOps.size(), 1);
|
|
OCIO_CHECK_EQUAL(optOps[0]->getInfo(), "<RangeOp>");
|
|
|
|
// Now check that the optimized transform renders the same as the original.
|
|
// TODO: Gamma is clamping alpha, and Range does not.
|
|
CompareRender(ops, optOps, __LINE__, 1e-4f, true);
|
|
}
|
|
|
|
OCIO_ADD_TEST(OpOptimizers, gamma_comp_identity)
|
|
{
|
|
OCIO::OpRcPtrVec ops;
|
|
|
|
OCIO::GammaOpData::Params params1 = { 0.45 };
|
|
OCIO::GammaOpData::Params paramsA = { 1. };
|
|
|
|
auto gamma1 = std::make_shared<OCIO::GammaOpData>(OCIO::GammaOpData::BASIC_FWD,
|
|
params1, params1, params1, paramsA);
|
|
|
|
OCIO::GammaOpData::Params params2 = { 1. / 0.45 };
|
|
|
|
auto gamma2 = std::make_shared<OCIO::GammaOpData>(OCIO::GammaOpData::BASIC_FWD,
|
|
params2, params2, params2, paramsA);
|
|
|
|
OCIO_CHECK_NO_THROW(OCIO::CreateGammaOp(ops, gamma1, OCIO::TRANSFORM_DIR_FORWARD));
|
|
OCIO_CHECK_NO_THROW(OCIO::CreateGammaOp(ops, gamma2, OCIO::TRANSFORM_DIR_FORWARD));
|
|
|
|
OCIO_CHECK_NO_THROW(ops.finalize());
|
|
OCIO_CHECK_EQUAL(ops.size(), 2);
|
|
|
|
OCIO::OpRcPtrVec optOps = ops.clone();
|
|
|
|
// BASIC gamma are composed resulting into identity, that get optimized as a range.
|
|
OCIO_CHECK_NO_THROW(optOps.finalize());
|
|
OCIO_CHECK_NO_THROW(optOps.optimize(OCIO::OPTIMIZATION_DEFAULT));
|
|
|
|
OCIO_REQUIRE_EQUAL(optOps.size(), 1);
|
|
OCIO_CHECK_EQUAL(optOps[0]->getInfo(), "<RangeOp>");
|
|
|
|
ops.clear();
|
|
optOps.clear();
|
|
|
|
params1 = { 2., 0.5 };
|
|
params2 = { 2., 0.6 };
|
|
paramsA = { 1., 0. };
|
|
gamma1 = std::make_shared<OCIO::GammaOpData>(OCIO::GammaOpData::MONCURVE_FWD,
|
|
params1, params1, params1, paramsA);
|
|
gamma2 = std::make_shared<OCIO::GammaOpData>(OCIO::GammaOpData::MONCURVE_FWD,
|
|
params2, params2, params2, paramsA);
|
|
|
|
OCIO_CHECK_NO_THROW(OCIO::CreateGammaOp(ops, gamma1, OCIO::TRANSFORM_DIR_FORWARD));
|
|
OCIO_CHECK_NO_THROW(OCIO::CreateGammaOp(ops, gamma2, OCIO::TRANSFORM_DIR_FORWARD));
|
|
|
|
OCIO_CHECK_NO_THROW(ops.finalize());
|
|
OCIO_CHECK_EQUAL(ops.size(), 2);
|
|
|
|
optOps = ops.clone();
|
|
|
|
// MONCURVE composition is not supported yet.
|
|
OCIO_CHECK_NO_THROW(optOps.finalize());
|
|
OCIO_CHECK_NO_THROW(optOps.optimize(OCIO::OPTIMIZATION_DEFAULT));
|
|
|
|
OCIO_REQUIRE_EQUAL(optOps.size(), 2);
|
|
OCIO_CHECK_EQUAL(optOps[0]->getInfo(), "<GammaOp>");
|
|
OCIO_CHECK_EQUAL(optOps[1]->getInfo(), "<GammaOp>");
|
|
}
|
|
|
|
OCIO_ADD_TEST(OpOptimizers, log_identities)
|
|
{
|
|
// Log fwd and rev transforms should become a range.
|
|
// This transform has two pair of LogOps separated by an identity matrix
|
|
// that should optimize into a range.
|
|
|
|
const std::string fileName("log_identities.ctf");
|
|
OCIO::OpRcPtrVec ops;
|
|
OCIO::ContextRcPtr context = OCIO::Context::Create();
|
|
OCIO_CHECK_NO_THROW(OCIO::BuildOpsTest(ops, fileName, context,
|
|
OCIO::TRANSFORM_DIR_FORWARD));
|
|
|
|
|
|
// First one is the file no op.
|
|
OCIO_CHECK_EQUAL(ops.size(), 6);
|
|
|
|
// Remove no ops & finalize for computation.
|
|
OCIO_CHECK_NO_THROW(ops.finalize());
|
|
OCIO_CHECK_NO_THROW(ops.optimize(OCIO::OPTIMIZATION_NONE));
|
|
|
|
OCIO_CHECK_EQUAL(ops.size(), 5);
|
|
|
|
OCIO::OpRcPtrVec optOps = ops.clone();
|
|
OCIO::OpRcPtrVec optOpsOff = ops.clone();
|
|
|
|
OCIO_CHECK_EQUAL(optOpsOff.size(), 5);
|
|
OCIO_CHECK_NO_THROW(optOpsOff.finalize());
|
|
OCIO_CHECK_NO_THROW(optOpsOff.optimize(AllBut(OCIO::OPTIMIZATION_PAIR_IDENTITY_LOG)));
|
|
// Only the identity matrix is optimized.
|
|
OCIO_CHECK_EQUAL(optOpsOff.size(), 4);
|
|
|
|
OCIO_CHECK_EQUAL(optOps.size(), 5);
|
|
OCIO_CHECK_NO_THROW(optOps.finalize());
|
|
OCIO_CHECK_NO_THROW(optOps.optimize(OCIO::OPTIMIZATION_DEFAULT));
|
|
// Identity matrix is optimized and forward/inverse log are combined.
|
|
OCIO_REQUIRE_EQUAL(optOps.size(), 1);
|
|
|
|
// (A range that clamps on either side is not a no-op.)
|
|
OCIO_CHECK_ASSERT(!optOps[0]->isNoOp());
|
|
OCIO_CHECK_EQUAL(optOps[0]->getInfo(), "<RangeOp>");
|
|
|
|
// Now check that the optimized transform renders the same as the original.
|
|
CompareRender(ops, optOps, __LINE__, 1e-4f);
|
|
}
|
|
|
|
OCIO_ADD_TEST(OpOptimizers, range_lut)
|
|
{
|
|
// Non-identity range before a Lut1D should not be removed.
|
|
|
|
const std::string fileName("range_lut.ctf");
|
|
OCIO::OpRcPtrVec ops;
|
|
OCIO::ContextRcPtr context = OCIO::Context::Create();
|
|
OCIO_CHECK_NO_THROW(OCIO::BuildOpsTest(ops, fileName, context,
|
|
OCIO::TRANSFORM_DIR_FORWARD));
|
|
|
|
// First one is the file no op.
|
|
OCIO_CHECK_EQUAL(ops.size(), 3);
|
|
|
|
// Remove no ops & finalize for computation.
|
|
OCIO_CHECK_NO_THROW(ops.finalize());
|
|
OCIO_CHECK_NO_THROW(ops.optimize(OCIO::OPTIMIZATION_NONE));
|
|
|
|
OCIO_CHECK_EQUAL(ops.size(), 2);
|
|
|
|
OCIO::OpRcPtrVec optOps = ops.clone();
|
|
OCIO_CHECK_EQUAL(optOps.size(), 2);
|
|
OCIO_CHECK_NO_THROW(optOps.finalize());
|
|
OCIO_CHECK_NO_THROW(optOps.optimize(OCIO::OPTIMIZATION_DEFAULT));
|
|
OCIO_REQUIRE_EQUAL(optOps.size(), 2);
|
|
OCIO_CHECK_EQUAL(optOps[0]->getInfo(), "<RangeOp>");
|
|
OCIO_CHECK_EQUAL(optOps[1]->getInfo(), "<Lut1DOp>");
|
|
|
|
// Now check that the optimized transform renders the same as the original.
|
|
CompareRender(ops, optOps, __LINE__, 1e-6f);
|
|
}
|
|
|
|
OCIO_ADD_TEST(OpOptimizers, dynamic_ops)
|
|
{
|
|
// Non-identity matrix.
|
|
OCIO::MatrixOpDataRcPtr matrix = std::make_shared<OCIO::MatrixOpData>();
|
|
matrix->setArrayValue(0, 2.);
|
|
|
|
// Identity exposure contrast.
|
|
OCIO::ExposureContrastOpDataRcPtr exposure = std::make_shared<OCIO::ExposureContrastOpData>();
|
|
|
|
OCIO::ExposureContrastOpDataRcPtr exposureDyn = exposure->clone();
|
|
exposureDyn->getExposureProperty()->makeDynamic();
|
|
|
|
// Test with non dynamic exposure contrast.
|
|
{
|
|
OCIO::OpRcPtrVec ops;
|
|
OCIO_CHECK_NO_THROW(OCIO::CreateMatrixOp(ops, matrix, OCIO::TRANSFORM_DIR_FORWARD));
|
|
OCIO_CHECK_NO_THROW(OCIO::CreateExposureContrastOp(ops, exposure,
|
|
OCIO::TRANSFORM_DIR_FORWARD));
|
|
OCIO_REQUIRE_EQUAL(ops.size(), 2);
|
|
OCIO_CHECK_ASSERT(!ops[0]->isIdentity());
|
|
OCIO_CHECK_ASSERT(ops[1]->isIdentity());
|
|
|
|
OCIO_CHECK_NO_THROW(ops.finalize());
|
|
OCIO_CHECK_NO_THROW(ops.optimize(OCIO::OPTIMIZATION_DEFAULT));
|
|
// It gets optimized.
|
|
OCIO_REQUIRE_EQUAL(ops.size(), 1);
|
|
OCIO_CHECK_EQUAL(ops[0]->getInfo(), "<MatrixOffsetOp>");
|
|
}
|
|
|
|
// Test with dynamic exposure contrast.
|
|
{
|
|
OCIO::OpRcPtrVec ops;
|
|
OCIO_CHECK_NO_THROW(OCIO::CreateMatrixOp(ops, matrix, OCIO::TRANSFORM_DIR_FORWARD));
|
|
OCIO_CHECK_NO_THROW(OCIO::CreateExposureContrastOp(ops, exposureDyn,
|
|
OCIO::TRANSFORM_DIR_FORWARD));
|
|
OCIO_CHECK_NO_THROW(OCIO::CreateMatrixOp(ops, matrix, OCIO::TRANSFORM_DIR_FORWARD));
|
|
OCIO_REQUIRE_EQUAL(ops.size(), 3);
|
|
OCIO_CHECK_ASSERT(!ops[0]->isIdentity());
|
|
|
|
// Exposure contrast is dynamic.
|
|
OCIO_CHECK_ASSERT(ops[1]->isDynamic());
|
|
OCIO_CHECK_ASSERT(!ops[1]->isIdentity());
|
|
|
|
OCIO_CHECK_ASSERT(!ops[2]->isIdentity());
|
|
|
|
// It does not get optimized with default flags (OPTIMIZATION_NO_DYNAMIC_PROPERTIES off).
|
|
OCIO_CHECK_NO_THROW(ops.finalize());
|
|
OCIO_CHECK_NO_THROW(ops.optimize(OCIO::OPTIMIZATION_DEFAULT));
|
|
OCIO_REQUIRE_EQUAL(ops.size(), 3);
|
|
OCIO_CHECK_EQUAL(ops[0]->getInfo(), "<MatrixOffsetOp>");
|
|
OCIO_CHECK_EQUAL(ops[1]->getInfo(), "<ExposureContrastOp>");
|
|
OCIO_CHECK_EQUAL(ops[2]->getInfo(), "<MatrixOffsetOp>");
|
|
|
|
// It does get optimized if flag is set.
|
|
// OPTIMIZATION_ALL includes OPTIMIZATION_NO_DYNAMIC_PROPERTIES.
|
|
// Exposure contrast will get optimized and the 2 matrix will be composed.
|
|
OCIO_CHECK_NO_THROW(ops.finalize());
|
|
OCIO_CHECK_NO_THROW(ops.optimize(OCIO::OPTIMIZATION_ALL));
|
|
OCIO_REQUIRE_EQUAL(ops.size(), 1);
|
|
OCIO_CHECK_EQUAL(ops[0]->getInfo(), "<MatrixOffsetOp>");
|
|
}
|
|
}
|
|
|
|
OCIO_ADD_TEST(OpOptimizers, gamma_prefix)
|
|
{
|
|
OCIO::OpRcPtrVec originalOps;
|
|
|
|
OCIO::GammaOpData::Params params1 = {2.6};
|
|
OCIO::GammaOpData::Params paramsA = {1.};
|
|
|
|
OCIO::GammaOpDataRcPtr gamma1
|
|
= std::make_shared<OCIO::GammaOpData>(OCIO::GammaOpData::BASIC_REV,
|
|
params1, params1, params1, paramsA);
|
|
|
|
OCIO_CHECK_NO_THROW(OCIO::CreateGammaOp(originalOps, gamma1, OCIO::TRANSFORM_DIR_FORWARD));
|
|
OCIO_REQUIRE_EQUAL(originalOps.size(), 1);
|
|
|
|
OCIO::OpRcPtrVec optimizedOps = originalOps.clone();
|
|
|
|
// Optimize it.
|
|
OCIO_CHECK_NO_THROW(optimizedOps.finalize());
|
|
OCIO_CHECK_NO_THROW(optimizedOps.optimize(OCIO::OPTIMIZATION_DEFAULT));
|
|
OCIO_CHECK_NO_THROW(optimizedOps.optimizeForBitdepth(OCIO::BIT_DEPTH_UINT16,
|
|
OCIO::BIT_DEPTH_F32,
|
|
OCIO::OPTIMIZATION_COMP_SEPARABLE_PREFIX));
|
|
|
|
// Validate the result.
|
|
|
|
OCIO_REQUIRE_EQUAL(optimizedOps.size(), 1);
|
|
|
|
OCIO::ConstOpRcPtr o1 = optimizedOps[0];
|
|
OCIO::ConstLut1DOpDataRcPtr oData1 = OCIO::DynamicPtrCast<const OCIO::Lut1DOpData>(o1->data());
|
|
OCIO_REQUIRE_ASSERT(oData1);
|
|
OCIO_CHECK_EQUAL(oData1->getType(), OCIO::OpData::Lut1DType);
|
|
OCIO_CHECK_EQUAL(oData1->getArray().getLength(), 65536);
|
|
originalOps.clear();
|
|
|
|
// However, if the input bit depth is F32, it should not be optimized.
|
|
|
|
OCIO::GammaOpDataRcPtr gamma2
|
|
= std::make_shared<OCIO::GammaOpData>(OCIO::GammaOpData::BASIC_REV,
|
|
params1, params1, params1, paramsA);
|
|
|
|
OCIO_CHECK_NO_THROW(OCIO::CreateGammaOp(originalOps, gamma2, OCIO::TRANSFORM_DIR_FORWARD));
|
|
OCIO_REQUIRE_EQUAL(originalOps.size(), 1);
|
|
|
|
// Optimize it.
|
|
OCIO_CHECK_NO_THROW(originalOps.finalize());
|
|
OCIO_CHECK_NO_THROW(originalOps.optimize(OCIO::OPTIMIZATION_DEFAULT));
|
|
|
|
OCIO_REQUIRE_EQUAL(originalOps.size(), 1);
|
|
OCIO::ConstOpRcPtr o2 = originalOps[0];
|
|
OCIO_CHECK_EQUAL(o2->data()->getType(), OCIO::OpData::GammaType);
|
|
}
|
|
|
|
OCIO_ADD_TEST(OpOptimizers, multi_op_prefix)
|
|
{
|
|
// Test prefix optimization of a complex transform.
|
|
|
|
OCIO::OpRcPtrVec originalOps;
|
|
|
|
OCIO::MatrixOpDataRcPtr matrix = std::make_shared<OCIO::MatrixOpData>();
|
|
matrix->setArrayValue(0, 2.);
|
|
|
|
OCIO_CHECK_NO_THROW(OCIO::CreateMatrixOp(originalOps, matrix, OCIO::TRANSFORM_DIR_FORWARD));
|
|
OCIO_REQUIRE_EQUAL(originalOps.size(), 1);
|
|
|
|
OCIO::RangeOpDataRcPtr range
|
|
= std::make_shared<OCIO::RangeOpData>(0., 1., -1000./65535., 66000./65535);
|
|
|
|
OCIO_CHECK_NO_THROW(OCIO::CreateRangeOp(originalOps, range, OCIO::TRANSFORM_DIR_FORWARD));
|
|
OCIO_REQUIRE_EQUAL(originalOps.size(), 2);
|
|
|
|
OCIO::OpRcPtrVec optimizedOps = originalOps.clone();
|
|
|
|
// Nothing to optimize.
|
|
OCIO_CHECK_NO_THROW(optimizedOps.finalize());
|
|
OCIO_CHECK_NO_THROW(optimizedOps.optimize(OCIO::OPTIMIZATION_DEFAULT));
|
|
OCIO_CHECK_NO_THROW(optimizedOps.optimizeForBitdepth(OCIO::BIT_DEPTH_UINT8,
|
|
OCIO::BIT_DEPTH_F32,
|
|
OCIO::OPTIMIZATION_COMP_SEPARABLE_PREFIX));
|
|
|
|
// Validate ops are unchanged.
|
|
|
|
OCIO_REQUIRE_EQUAL(optimizedOps.size(), 2U);
|
|
|
|
std::string originalID;
|
|
std::string optimizedID;
|
|
OCIO_CHECK_NO_THROW(originalID = originalOps[0]->getCacheID());
|
|
OCIO_CHECK_NO_THROW(optimizedID = optimizedOps[0]->getCacheID());
|
|
|
|
OCIO_CHECK_EQUAL(originalID, optimizedID);
|
|
|
|
OCIO_CHECK_NO_THROW(originalID = originalOps[1]->getCacheID());
|
|
OCIO_CHECK_NO_THROW(optimizedID = optimizedOps[1]->getCacheID());
|
|
|
|
OCIO_CHECK_EQUAL(originalID, optimizedID);
|
|
|
|
// Add more ops to originalOps.
|
|
const OCIO::CDLOpData::ChannelParams slope(1.35, 1.1, 0.071);
|
|
const OCIO::CDLOpData::ChannelParams offset(0.05, -0.23, 0.11);
|
|
const OCIO::CDLOpData::ChannelParams power(1.27, 0.81, 0.2);
|
|
const double saturation = 1.;
|
|
|
|
OCIO::CDLOpDataRcPtr cdl
|
|
= std::make_shared<OCIO::CDLOpData>(OCIO::CDLOpData::CDL_V1_2_FWD,
|
|
slope, offset, power, saturation);
|
|
|
|
OCIO_CHECK_NO_THROW(OCIO::CreateCDLOp(originalOps, cdl, OCIO::TRANSFORM_DIR_FORWARD));
|
|
|
|
OCIO_CHECK_NO_THROW(originalOps.finalize());
|
|
OCIO_REQUIRE_EQUAL(originalOps.size(), 3);
|
|
|
|
optimizedOps = originalOps.clone();
|
|
|
|
// Optimize it.
|
|
OCIO_CHECK_NO_THROW(optimizedOps.finalize());
|
|
OCIO_CHECK_NO_THROW(optimizedOps.optimize(OCIO::OPTIMIZATION_DEFAULT));
|
|
OCIO_CHECK_NO_THROW(optimizedOps.optimizeForBitdepth(OCIO::BIT_DEPTH_UINT8,
|
|
OCIO::BIT_DEPTH_F32,
|
|
OCIO::OPTIMIZATION_COMP_SEPARABLE_PREFIX));
|
|
|
|
// Validate the result.
|
|
|
|
OCIO_REQUIRE_EQUAL(optimizedOps.size(), 1U);
|
|
|
|
OCIO::ConstOpRcPtr o = optimizedOps[0];
|
|
OCIO::ConstLut1DOpDataRcPtr oData = OCIO::DynamicPtrCast<const OCIO::Lut1DOpData>(o->data());
|
|
OCIO_CHECK_EQUAL(oData->getType(), OCIO::OpData::Lut1DType);
|
|
OCIO_CHECK_EQUAL(oData->getArray().getLength(), 256);
|
|
|
|
// Make sure originalOps are ready to render.
|
|
OCIO_CHECK_NO_THROW(originalOps.finalize());
|
|
|
|
// Although finalized for UINT8, the transform may still be evaluated at 32f to verify that
|
|
// it is a good approximation to the original.
|
|
CompareRender(originalOps, optimizedOps, __LINE__, 5e-5f);
|
|
}
|
|
|
|
OCIO_ADD_TEST(OpOptimizers, dyn_properties_prefix)
|
|
{
|
|
// Test prefix optimization of a complex transform.
|
|
|
|
OCIO::OpRcPtrVec originalOps;
|
|
|
|
OCIO::MatrixOpDataRcPtr matrix = std::make_shared<OCIO::MatrixOpData>();
|
|
matrix->setArrayValue(0, 2.);
|
|
|
|
OCIO_CHECK_NO_THROW(OCIO::CreateMatrixOp(originalOps, matrix, OCIO::TRANSFORM_DIR_FORWARD));
|
|
OCIO_REQUIRE_EQUAL(originalOps.size(), 1);
|
|
|
|
OCIO::ExposureContrastOpDataRcPtr exposure = std::make_shared<OCIO::ExposureContrastOpData>();
|
|
|
|
exposure->setExposure(1.2);
|
|
exposure->setPivot(0.5);
|
|
|
|
OCIO_CHECK_NO_THROW(
|
|
OCIO::CreateExposureContrastOp(originalOps, exposure, OCIO::TRANSFORM_DIR_FORWARD));
|
|
OCIO_REQUIRE_EQUAL(originalOps.size(), 2);
|
|
|
|
exposure = exposure->clone();
|
|
exposure->getExposureProperty()->makeDynamic();
|
|
|
|
OCIO_CHECK_NO_THROW(
|
|
OCIO::CreateExposureContrastOp(originalOps, exposure, OCIO::TRANSFORM_DIR_FORWARD));
|
|
OCIO_REQUIRE_EQUAL(originalOps.size(), 3);
|
|
|
|
OCIO::OpRcPtrVec optimizedOps = originalOps.clone();
|
|
|
|
// Optimize it.
|
|
OCIO_CHECK_NO_THROW(optimizedOps.finalize());
|
|
OCIO_CHECK_NO_THROW(optimizedOps.optimize(OCIO::OPTIMIZATION_DEFAULT));
|
|
OCIO_CHECK_NO_THROW(optimizedOps.optimizeForBitdepth(OCIO::BIT_DEPTH_UINT8,
|
|
OCIO::BIT_DEPTH_F32,
|
|
OCIO::OPTIMIZATION_COMP_SEPARABLE_PREFIX));
|
|
|
|
// Validate the result.
|
|
|
|
OCIO_REQUIRE_EQUAL(optimizedOps.size(), 2U);
|
|
|
|
OCIO::ConstOpRcPtr o = optimizedOps[0];
|
|
OCIO::ConstLut1DOpDataRcPtr oData = OCIO::DynamicPtrCast<const OCIO::Lut1DOpData>(o->data());
|
|
OCIO_CHECK_ASSERT(oData);
|
|
OCIO_CHECK_EQUAL(oData->getType(), OCIO::OpData::Lut1DType);
|
|
OCIO_CHECK_EQUAL(oData->getArray().getLength(), 256);
|
|
OCIO_CHECK_CLOSE(oData->getArray()[255 * 3 + 0], 4.5947948f, 1.e-6f);
|
|
OCIO_CHECK_CLOSE(oData->getArray()[255 * 3 + 1], 2.2973969f, 1.e-6f);
|
|
OCIO_CHECK_CLOSE(oData->getArray()[255 * 3 + 2], 2.2973969f, 1.e-6f);
|
|
|
|
o = optimizedOps[1];
|
|
OCIO::ConstExposureContrastOpDataRcPtr exp =
|
|
OCIO::DynamicPtrCast<const OCIO::ExposureContrastOpData>(o->data());
|
|
|
|
OCIO_CHECK_ASSERT(exp);
|
|
OCIO_CHECK_EQUAL(exp->getType(), OCIO::OpData::ExposureContrastType);
|
|
OCIO_CHECK_ASSERT(exp->isDynamic());
|
|
}
|
|
|
|
OCIO_ADD_TEST(OpOptimizers, opt_prefix_test1)
|
|
{
|
|
const std::string fileName("opt_prefix_test1.ctf");
|
|
OCIO::OpRcPtrVec ops;
|
|
OCIO::ContextRcPtr context = OCIO::Context::Create();
|
|
OCIO_CHECK_NO_THROW(OCIO::BuildOpsTest(ops, fileName, context,
|
|
OCIO::TRANSFORM_DIR_FORWARD));
|
|
|
|
// First one is the file no op.
|
|
OCIO_CHECK_EQUAL(ops.size(), 12);
|
|
|
|
OCIO_CHECK_NO_THROW(OCIO::RemoveNoOpTypes(ops));
|
|
|
|
OCIO_CHECK_EQUAL(ops.size(), 11);
|
|
|
|
OCIO::OpRcPtrVec optOps = ops.clone();
|
|
OCIO_CHECK_EQUAL(optOps.size(), 11);
|
|
// Ignore dynamic properties.
|
|
OCIO_CHECK_NO_THROW(optOps.finalize());
|
|
OCIO_CHECK_NO_THROW(optOps.optimize(OCIO::OPTIMIZATION_ALL));
|
|
OCIO_CHECK_NO_THROW(optOps.optimizeForBitdepth(OCIO::BIT_DEPTH_F16, OCIO::BIT_DEPTH_F32,
|
|
OCIO::OPTIMIZATION_COMP_SEPARABLE_PREFIX));
|
|
|
|
OCIO_REQUIRE_EQUAL(optOps.size(), 3);
|
|
|
|
OCIO::ConstOpRcPtr o0 = optOps[0];
|
|
OCIO::ConstOpRcPtr o1 = optOps[1];
|
|
OCIO::ConstOpRcPtr o2 = optOps[2];
|
|
|
|
OCIO_CHECK_EQUAL(o0->data()->getType(), OCIO::OpData::Lut1DType);
|
|
OCIO_CHECK_EQUAL(o1->data()->getType(), OCIO::OpData::MatrixType);
|
|
OCIO_CHECK_EQUAL(o2->data()->getType(), OCIO::OpData::GammaType);
|
|
|
|
auto lut0 = OCIO_DYNAMIC_POINTER_CAST<const OCIO::Lut1DOpData>(o0->data());
|
|
OCIO_CHECK_ASSERT(!lut0->isIdentity());
|
|
OCIO_CHECK_EQUAL(lut0->getArray().getLength(), 65536u);
|
|
}
|
|
|
|
OCIO_ADD_TEST(OpOptimizers, replace_ops)
|
|
{
|
|
auto cdlData = std::make_shared<OCIO::CDLOpData>();
|
|
OCIO::CDLOpData::ChannelParams newOffsetParams(0.09);
|
|
cdlData->setOffsetParams(newOffsetParams);
|
|
cdlData->setSaturation(1.23);
|
|
cdlData->setSlopeParams(OCIO::CDLOpData::ChannelParams(0.8, 0.9, 1.1));
|
|
cdlData->setStyle(OCIO::CDLOpData::CDL_NO_CLAMP_FWD);
|
|
|
|
OCIO::OpRcPtrVec originalOps;
|
|
|
|
OCIO_CHECK_NO_THROW(
|
|
OCIO::CreateCDLOp(originalOps, cdlData, OCIO::TRANSFORM_DIR_FORWARD));
|
|
OCIO_REQUIRE_EQUAL(originalOps.size(), 1);
|
|
|
|
OCIO::OpRcPtrVec optimizedOps = originalOps.clone();
|
|
|
|
// Verify that default optimization includes replacing ops.
|
|
OCIO_CHECK_ASSERT(OCIO::HasFlag(OCIO::OPTIMIZATION_DEFAULT,
|
|
OCIO::OPTIMIZATION_SIMPLIFY_OPS));
|
|
|
|
// Optimize it: CDL is replaced by a matrix.
|
|
OCIO_CHECK_NO_THROW(optimizedOps.finalize());
|
|
OCIO_CHECK_NO_THROW(optimizedOps.optimize(OCIO::OPTIMIZATION_DEFAULT));
|
|
OCIO_REQUIRE_EQUAL(optimizedOps.size(), 1);
|
|
|
|
OCIO::ConstOpRcPtr o = optimizedOps[0];
|
|
auto oData = o->data();
|
|
OCIO_CHECK_EQUAL(oData->getType(), OCIO::OpData::MatrixType);
|
|
|
|
// No optimization: keep CDL.
|
|
optimizedOps = originalOps.clone();
|
|
OCIO_CHECK_NO_THROW(optimizedOps.finalize());
|
|
OCIO_REQUIRE_EQUAL(optimizedOps.size(), 1);
|
|
|
|
o = optimizedOps[0];
|
|
oData = o->data();
|
|
OCIO_CHECK_EQUAL(oData->getType(), OCIO::OpData::CDLType);
|
|
|
|
// Only replace. CDL is replaced by 2 matrices, one for offset and slope, one for saturation.
|
|
// Default optimization would combine them.
|
|
optimizedOps = originalOps.clone();
|
|
OCIO_CHECK_NO_THROW(optimizedOps.finalize());
|
|
OCIO_CHECK_NO_THROW(optimizedOps.optimize(OCIO::OPTIMIZATION_SIMPLIFY_OPS));
|
|
OCIO_REQUIRE_EQUAL(optimizedOps.size(), 2);
|
|
|
|
o = optimizedOps[0];
|
|
oData = o->data();
|
|
OCIO_CHECK_EQUAL(oData->getType(), OCIO::OpData::MatrixType);
|
|
|
|
o = optimizedOps[1];
|
|
oData = o->data();
|
|
OCIO_CHECK_EQUAL(oData->getType(), OCIO::OpData::MatrixType);
|
|
|
|
// Use clamping style.
|
|
cdlData->setStyle(OCIO::CDLOpData::CDL_V1_2_FWD);
|
|
|
|
optimizedOps.clear();
|
|
|
|
OCIO_CHECK_NO_THROW(
|
|
OCIO::CreateCDLOp(optimizedOps, cdlData, OCIO::TRANSFORM_DIR_FORWARD));
|
|
OCIO_REQUIRE_EQUAL(optimizedOps.size(), 1);
|
|
|
|
// Optimize it: CDL replaced by 2 matrices and 2 clamps.
|
|
OCIO_CHECK_NO_THROW(optimizedOps.finalize());
|
|
OCIO_CHECK_NO_THROW(optimizedOps.optimize(OCIO::OPTIMIZATION_DEFAULT));
|
|
OCIO_REQUIRE_EQUAL(optimizedOps.size(), 4);
|
|
|
|
o = optimizedOps[0];
|
|
oData = o->data();
|
|
OCIO_CHECK_EQUAL(oData->getType(), OCIO::OpData::MatrixType);
|
|
|
|
o = optimizedOps[1];
|
|
oData = o->data();
|
|
OCIO_CHECK_EQUAL(oData->getType(), OCIO::OpData::RangeType);
|
|
|
|
o = optimizedOps[2];
|
|
oData = o->data();
|
|
OCIO_CHECK_EQUAL(oData->getType(), OCIO::OpData::MatrixType);
|
|
|
|
o = optimizedOps[3];
|
|
oData = o->data();
|
|
OCIO_CHECK_EQUAL(oData->getType(), OCIO::OpData::RangeType);
|
|
|
|
// With a non-identity power.
|
|
cdlData->setPowerParams(OCIO::CDLOpData::ChannelParams(1, 1, 1.0001));
|
|
|
|
optimizedOps.clear();
|
|
|
|
OCIO_CHECK_NO_THROW(
|
|
OCIO::CreateCDLOp(optimizedOps, cdlData, OCIO::TRANSFORM_DIR_FORWARD));
|
|
OCIO_REQUIRE_EQUAL(optimizedOps.size(), 1);
|
|
|
|
// Optimize it: CDL is not replaced.
|
|
OCIO_CHECK_NO_THROW(optimizedOps.finalize());
|
|
OCIO_CHECK_NO_THROW(optimizedOps.optimize(OCIO::OPTIMIZATION_DEFAULT));
|
|
OCIO_REQUIRE_EQUAL(optimizedOps.size(), 1);
|
|
|
|
o = optimizedOps[0];
|
|
oData = o->data();
|
|
OCIO_CHECK_EQUAL(oData->getType(), OCIO::OpData::CDLType);
|
|
}
|