diff --git a/src/OpenColorIO/NamedTransform.cpp b/src/OpenColorIO/NamedTransform.cpp index 19c6d8d67c..45bd1298bc 100644 --- a/src/OpenColorIO/NamedTransform.cpp +++ b/src/OpenColorIO/NamedTransform.cpp @@ -86,7 +86,7 @@ void NamedTransformImpl::addAlias(const char * alias) noexcept { if (!StringUtils::Contain(m_aliases, alias)) { - m_aliases.push_back(alias); + m_aliases.emplace_back(alias); } } } @@ -279,7 +279,7 @@ std::ostream & operator<< (std::ostream & os, const NamedTransform & t) StringUtils::StringVec categories; for (int i = 0; i < t.getNumCategories(); ++i) { - categories.push_back(t.getCategory(i)); + categories.emplace_back(t.getCategory(i)); } os << ", categories=[" << StringUtils::Join(categories, ',') << "]"; } diff --git a/src/OpenColorIO/Op.cpp b/src/OpenColorIO/Op.cpp index 7e95baecfc..6bc32061d7 100755 --- a/src/OpenColorIO/Op.cpp +++ b/src/OpenColorIO/Op.cpp @@ -1,11 +1,10 @@ // SPDX-License-Identifier: BSD-3-Clause // Copyright Contributors to the OpenColorIO Project. -#include +#include +#include #include -#include - #include #include "Logging.h" @@ -26,41 +25,11 @@ namespace OCIO_NAMESPACE { -bool OpCPU::isDynamic() const -{ - return false; -} - -bool OpCPU::hasDynamicProperty(DynamicPropertyType /* type */) const -{ - return false; -} - DynamicPropertyRcPtr OpCPU::getDynamicProperty(DynamicPropertyType /* type */) const { throw Exception("Op does not implement dynamic property."); } -OpData::OpData() - : m_metadata() -{ } - -OpData::OpData(const OpData & rhs) - : m_metadata() -{ - *this = rhs; -} - -OpData & OpData::operator=(const OpData & rhs) -{ - if (this != &rhs) - { - m_metadata = rhs.m_metadata; - } - - return *this; -} - OpDataRcPtr OpData::getIdentityReplacement() const { return std::make_shared(); @@ -70,39 +39,6 @@ void OpData::getSimplerReplacement(OpDataVec & /* ops */) const { } -bool OpData::equals(const OpData & other) const -{ - if (this == &other) return true; - - // Ignore metadata. - return getType() == other.getType(); -} - -const std::string & OpData::getID() const -{ - return m_metadata.getAttributeValueString(METADATA_ID); -} - -void OpData::setID(const std::string & id) -{ - return m_metadata.setID(id.c_str()); -} - -const std::string & OpData::getName() const -{ - return m_metadata.getAttributeValueString(METADATA_NAME); -} - -void OpData::setName(const std::string & name) -{ - return m_metadata.setName(name.c_str()); -} - -bool operator==(const OpData & lhs, const OpData & rhs) -{ - return lhs.equals(rhs); -} - const char * GetTypeName(OpData::Type type) { switch (type) @@ -179,6 +115,8 @@ OpRcPtr Op::getIdentityReplacement() const { auto opData = m_data->getIdentityReplacement(); OpRcPtrVec ops; + ops.reserve(1); + if (opData->getType() == OpData::MatrixType) { // No-op that will be optimized. @@ -198,41 +136,20 @@ OpRcPtr Op::getIdentityReplacement() const << std::string(GetTypeName(opData->getType())) << "."; throw Exception(oss.str().c_str()); } - return ops[0]; + return std::move(ops[0]); } void Op::getSimplerReplacement(OpRcPtrVec & ops) const { OpDataVec opDataVec; m_data->getSimplerReplacement(opDataVec); + ops.reserve(ops.size() + opDataVec.size()); for (const auto & opData : opDataVec) { CreateOpVecFromOpData(ops, opData, TRANSFORM_DIR_FORWARD); } } -OpRcPtrVec::OpRcPtrVec() - : m_metadata() -{ -} - -OpRcPtrVec::OpRcPtrVec(const OpRcPtrVec & v) - : OpRcPtrVec() -{ - *this = v; -} - -OpRcPtrVec & OpRcPtrVec::operator=(const OpRcPtrVec & v) -{ - if(this!=&v) - { - m_ops = v.m_ops; - m_metadata = v.m_metadata; - } - - return *this; -} - OpRcPtrVec & OpRcPtrVec::operator+=(const OpRcPtrVec & v) { if (this != &v) @@ -248,70 +165,6 @@ OpRcPtrVec & OpRcPtrVec::operator+=(const OpRcPtrVec & v) } } -OpRcPtrVec::iterator OpRcPtrVec::erase(OpRcPtrVec::const_iterator position) -{ - return m_ops.erase(position); -} - -OpRcPtrVec::iterator OpRcPtrVec::erase(OpRcPtrVec::const_iterator first, - OpRcPtrVec::const_iterator last) -{ - return m_ops.erase(first, last); -} - -void OpRcPtrVec::insert(OpRcPtrVec::const_iterator position, - OpRcPtrVec::const_iterator first, - OpRcPtrVec::const_iterator last) -{ - m_ops.insert(position, first, last); -} - -void OpRcPtrVec::push_back(const OpRcPtrVec::value_type & val) -{ - m_ops.push_back(val); -} - -OpRcPtrVec::const_reference OpRcPtrVec::back() const -{ - return m_ops.back(); -} - -OpRcPtrVec::const_reference OpRcPtrVec::front() const -{ - return m_ops.front(); -} - -bool OpRcPtrVec::isNoOp() const noexcept -{ - for (const auto & op : m_ops) - { - if(!op->isNoOp()) return false; - } - - return true; -} - -bool OpRcPtrVec::hasChannelCrosstalk() const noexcept -{ - return m_ops.end() != std::find_if(m_ops.begin(), - m_ops.end(), - [](const OpRcPtr & op) { return op->hasChannelCrosstalk(); } ); -} - -bool OpRcPtrVec::isDynamic() const noexcept -{ - return m_ops.end() != std::find_if(m_ops.begin(), - m_ops.end(), - [](const OpRcPtr & op) { return op->isDynamic(); } ); -} - -bool OpRcPtrVec::hasDynamicProperty(DynamicPropertyType type) const noexcept -{ - return m_ops.end() != std::find_if(m_ops.begin(), - m_ops.end(), - [type](const OpRcPtr & op) { return op->hasDynamicProperty(type); } ); -} - DynamicPropertyRcPtr OpRcPtrVec::getDynamicProperty(DynamicPropertyType type) const { for (const auto & op : m_ops) @@ -328,10 +181,11 @@ DynamicPropertyRcPtr OpRcPtrVec::getDynamicProperty(DynamicPropertyType type) co OpRcPtrVec OpRcPtrVec::clone() const { OpRcPtrVec cloned; + cloned.reserve(m_ops.size()); for (const auto & op : m_ops) { - cloned.push_back(op->clone()); + cloned.emplace_back(op->clone()); } return cloned; @@ -340,6 +194,7 @@ OpRcPtrVec OpRcPtrVec::clone() const OpRcPtrVec OpRcPtrVec::invert() const { OpRcPtrVec inverted; + inverted.reserve(m_ops.size()); OpRcPtrVec::const_reverse_iterator iter = m_ops.rbegin(); OpRcPtrVec::const_reverse_iterator end = m_ops.rend(); @@ -361,14 +216,6 @@ OpRcPtrVec OpRcPtrVec::invert() const return inverted; } -void OpRcPtrVec::validate() const -{ - for (auto & op : m_ops) - { - op->validate(); - } -} - namespace { template @@ -473,16 +320,14 @@ std::ostream& operator<< (std::ostream & os, const Op & op) std::string SerializeOpVec(const OpRcPtrVec & ops, int indent) { std::ostringstream oss; + const std::string indentStr(indent, ' '); - for (OpRcPtrVec::size_type idx = 0, size = ops.size(); idx < size; ++idx) + OpRcPtrVec::size_type idx = 0; + for (const auto & op : ops) { - const OpRcPtr & op = ops[idx]; - - oss << pystring::mul(" ", indent); - oss << "Op " << idx << ": " << *op << " "; - oss << op->getCacheID(); - - oss << "\n"; + oss << indentStr << "Op " << idx << ": " << *op << " " + << op->getCacheID() << "\n"; + ++idx; } return oss.str(); diff --git a/src/OpenColorIO/Op.h b/src/OpenColorIO/Op.h index 895c426d80..6dce4109b9 100644 --- a/src/OpenColorIO/Op.h +++ b/src/OpenColorIO/Op.h @@ -5,8 +5,9 @@ #ifndef INCLUDED_OCIO_OP_H #define INCLUDED_OCIO_OP_H -#include +#include #include +#include #include @@ -45,8 +46,8 @@ class OpCPU // the 1D LUT CPU Op where the finalization depends on input and output bit depths. virtual void apply(const void * inImg, void * outImg, long numPixels) const = 0; - virtual bool isDynamic() const; - virtual bool hasDynamicProperty(DynamicPropertyType type) const; + virtual bool isDynamic() const { return false; } + virtual bool hasDynamicProperty([[maybe_unused]] DynamicPropertyType type) const { return false; } virtual DynamicPropertyRcPtr getDynamicProperty(DynamicPropertyType type) const; }; @@ -118,18 +119,18 @@ class OpData }; public: - OpData(); - OpData(const OpData & rhs); + OpData() = default; + OpData(const OpData & rhs) : m_metadata(rhs.m_metadata) {} OpData(OpData && rhs) = delete; - OpData & operator=(const OpData & rhs); + OpData & operator=(const OpData & rhs) { if (this != &rhs) { m_metadata = rhs.m_metadata; } return *this; } OpData & operator=(OpData && rhs) = delete; virtual ~OpData() = default; - const std::string & getID() const; - void setID(const std::string & id); + const std::string & getID() const { return m_metadata.getAttributeValueString(METADATA_ID); } + void setID(const std::string & id) { m_metadata.setID(id.c_str()); } - const std::string & getName() const; - void setName(const std::string & name); + const std::string & getName() const { return m_metadata.getAttributeValueString(METADATA_NAME); } + void setName(const std::string & name) { m_metadata.setName(name.c_str()); } virtual void validate() const = 0; @@ -154,7 +155,7 @@ class OpData // returns true if the op's output does not combine input channels virtual bool hasChannelCrosstalk() const = 0; - virtual bool equals(const OpData & other) const; + virtual bool equals(const OpData & other) const { if (this == &other) return true; return getType() == other.getType(); } // This should yield a string of not unreasonable length. virtual std::string getCacheID() const = 0; @@ -170,7 +171,10 @@ class OpData FormatMetadataImpl m_metadata; }; -bool operator==(const OpData & lhs, const OpData & rhs); +inline bool operator==(const OpData & lhs, const OpData & rhs) +{ + return lhs.equals(rhs); +} const char * GetTypeName(OpData::Type type); @@ -324,31 +328,37 @@ class OpRcPtrVec typedef Type::reference reference; typedef Type::const_reference const_reference; - OpRcPtrVec(); + OpRcPtrVec() = default; ~OpRcPtrVec() {} - OpRcPtrVec(const OpRcPtrVec & v); - OpRcPtrVec & operator=(const OpRcPtrVec & v); + OpRcPtrVec(const OpRcPtrVec & v) = default; + OpRcPtrVec & operator=(const OpRcPtrVec & v) = default; // Note: It copies elements i.e. no clone. OpRcPtrVec & operator+=(const OpRcPtrVec & v); size_type size() const { return m_ops.size(); } + size_type capacity() const noexcept { return m_ops.capacity(); } + size_type max_size() const noexcept { return m_ops.max_size(); } iterator begin() noexcept { return m_ops.begin(); } const_iterator begin() const noexcept { return m_ops.begin(); } + const_iterator cbegin() const noexcept { return m_ops.cbegin(); } iterator end() noexcept { return m_ops.end(); } const_iterator end() const noexcept { return m_ops.end(); } + const_iterator cend() const noexcept { return m_ops.cend(); } reverse_iterator rbegin() noexcept { return m_ops.rbegin(); } const_reverse_iterator rbegin() const noexcept { return m_ops.rbegin(); } + const_reverse_iterator crbegin() const noexcept { return m_ops.crbegin(); } reverse_iterator rend() noexcept { return m_ops.rend(); } const_reverse_iterator rend() const noexcept { return m_ops.rend(); } + const_reverse_iterator crend() const noexcept { return m_ops.crend(); } const OpRcPtr & operator[](size_type idx) const { return m_ops[idx]; } OpRcPtr & operator[](size_type idx) { return m_ops[idx]; } - iterator erase(const_iterator position); - iterator erase(const_iterator first, const_iterator last); + iterator erase(const_iterator position) { return m_ops.erase(position); } + iterator erase(const_iterator first, const_iterator last) { return m_ops.erase(first, last); } // Insert at the 'position' the elements from the range ['first', 'last'[ // respecting the element's order. Inserting elements at a given position @@ -358,26 +368,42 @@ class OpRcPtrVec // in an empty list appends elements from the range ['first', 'last'[. // // Note: It copies elements i.e. no clone. - void insert(const_iterator position, const_iterator first, const_iterator last); + template + void insert(const_iterator position, InputIt first, InputIt last) + { + m_ops.insert(position, first, last); + } void clear() noexcept { m_ops.clear(); } bool empty() const noexcept { return m_ops.empty(); } - void push_back(const value_type & val); + void reserve(size_type n) { m_ops.reserve(n); } + void shrink_to_fit() { m_ops.shrink_to_fit(); } + void resize(size_type count) { m_ops.resize(count); } + void resize(size_type count, const value_type & value) { m_ops.resize(count, value); } - const_reference back() const; - const_reference front() const; + void push_back(const value_type & val) { m_ops.push_back(val); } + void push_back(value_type && val) { m_ops.push_back(std::move(val)); } + + template + reference emplace_back(Args&&... args) + { + return m_ops.emplace_back(std::forward(args)...); + } + + const_reference back() const { return m_ops.back(); } + const_reference front() const { return m_ops.front(); } // The following methods provide helpers for basic Op behaviors. FormatMetadataImpl & getFormatMetadata() { return m_metadata; } const FormatMetadataImpl & getFormatMetadata() const { return m_metadata; } - bool isNoOp() const noexcept; - bool hasChannelCrosstalk() const noexcept; + bool isNoOp() const noexcept { return std::all_of(m_ops.begin(), m_ops.end(), [](const auto & op) { return op->isNoOp(); }); } + bool hasChannelCrosstalk() const noexcept { return std::any_of(m_ops.begin(), m_ops.end(), [](const auto & op) { return op->hasChannelCrosstalk(); }); } - bool isDynamic() const noexcept; - bool hasDynamicProperty(DynamicPropertyType type) const noexcept; + bool isDynamic() const noexcept { return std::any_of(m_ops.begin(), m_ops.end(), [](const auto & op) { return op->isDynamic(); }); } + bool hasDynamicProperty(DynamicPropertyType type) const noexcept { return std::any_of(m_ops.begin(), m_ops.end(), [type](const auto & op) { return op->hasDynamicProperty(type); }); } DynamicPropertyRcPtr getDynamicProperty(DynamicPropertyType type) const; void validateDynamicProperties(); @@ -386,7 +412,7 @@ class OpRcPtrVec // Note: The elements are cloned. OpRcPtrVec invert() const; - void validate() const; + void validate() const { for (auto & op : m_ops) { op->validate(); } } std::string getCacheID() const; diff --git a/src/OpenColorIO/OpOptimizers.cpp b/src/OpenColorIO/OpOptimizers.cpp index 164123f413..d8fc7724ff 100755 --- a/src/OpenColorIO/OpOptimizers.cpp +++ b/src/OpenColorIO/OpOptimizers.cpp @@ -4,6 +4,9 @@ #include #include #include +#include +#include +#include #include @@ -71,67 +74,63 @@ bool IsCombineEnabled(OpData::Type type, OptimizationFlags flags) constexpr int MAX_OPTIMIZATION_PASSES = 80; -int RemoveNoOpTypes(OpRcPtrVec & opVec) +size_t RemoveNoOpTypes(OpRcPtrVec & opVec, [[maybe_unused]] OptimizationFlags flags) { - int count = 0; - - OpRcPtrVec::const_iterator iter = opVec.begin(); - while (iter != opVec.end()) - { - ConstOpRcPtr o = (*iter); - if (o->data()->getType() == OpData::NoOpType) - { - iter = opVec.erase(iter); - ++count; - } - else - { - ++iter; - } - } - + // TODO: with c++20 could use std::erase_if ? + auto newEnd = std::remove_if(opVec.begin(), opVec.end(), [](const auto & o) { + return o->isNoOpType(); + }); + + size_t count = std::distance(newEnd, opVec.end()); + opVec.erase(newEnd, opVec.end()); return count; } // Ops are preserved, dynamic properties are made non-dynamic. -void RemoveDynamicProperties(OpRcPtrVec & opVec) +size_t RemoveDynamicProperties(OpRcPtrVec & opVec, OptimizationFlags oFlags) { - const size_t nbOps = opVec.size(); - for (size_t i = 0; i < nbOps; ++i) + size_t count = 0; + const auto removeDynamic = HasFlag(oFlags, OPTIMIZATION_NO_DYNAMIC_PROPERTIES); + if (!removeDynamic) { - auto & op = opVec[i]; + return count; + } + + std::for_each(opVec.begin(), opVec.end(), [&count](auto & op) { if (op->isDynamic()) { // Optimization flag is tested before. auto replacedBy = op->clone(); replacedBy->removeDynamicProperties(); - opVec[i] = replacedBy; + op = std::move(replacedBy); + ++count; } - } + }); + return count; } -int RemoveNoOps(OpRcPtrVec & opVec) +size_t RemoveNoOps(OpRcPtrVec & opVec, OptimizationFlags oFlags) { - int count = 0; - OpRcPtrVec::const_iterator iter = opVec.begin(); - while (iter != opVec.end()) + const bool optimizeIdentity = HasFlag(oFlags, OPTIMIZATION_IDENTITY); + if (!optimizeIdentity) { - if ((*iter)->isNoOp()) - { - iter = opVec.erase(iter); - ++count; - } - else - { - ++iter; - } + return 0; } + + // TODO: with c++20 could use std::erase_if ? + auto newEnd = std::remove_if(opVec.begin(), opVec.end(), [](const auto& op) + { + return op->isNoOp(); + }); + + size_t count = std::distance(newEnd, opVec.end()); + opVec.erase(newEnd, opVec.end()); return count; } void FinalizeOps(OpRcPtrVec & opVec) { - for (auto op : opVec) + for (const auto &op : opVec) { // Prepare LUT 1D for inversion and ensure Matrix & Range are forward. op->finalize(); @@ -140,222 +139,252 @@ void FinalizeOps(OpRcPtrVec & opVec) // Some rather complex ops can get replaced based on their data by simpler ops. // For instance CDL that does not use power will get replaced. -int ReplaceOps(OpRcPtrVec & opVec) +size_t ReplaceOps(OpRcPtrVec & opVec, [[maybe_unused]] OptimizationFlags oFlags) { - int count = 0; - int firstindex = 0; // this must be a signed int + size_t count = 0; + const bool replaceOps = HasFlag(oFlags, OPTIMIZATION_SIMPLIFY_OPS); + if (!replaceOps) + { + return count; + } + OpRcPtrVec tmpops; + OpRcPtrVec newOpVec; + bool rebuilding = false; - while (firstindex < static_cast(opVec.size())) + for (size_t i = 0; i < opVec.size(); ++i) { tmpops.clear(); - ConstOpRcPtr op = opVec[firstindex]; - op->getSimplerReplacement(tmpops); + opVec[i]->getSimplerReplacement(tmpops); if (!tmpops.empty()) { - FinalizeOps(tmpops); - - // Erase the initial op we've replaced. - opVec.erase(opVec.begin() + firstindex, opVec.begin() + firstindex + 1); - - // Insert the new ops at this location. - opVec.insert(opVec.begin() + firstindex, tmpops.begin(), tmpops.end()); + if (!rebuilding) + { + rebuilding = true; + // Defer allocation until the first replacement to prevent + // large, unnecessary heap allocations on no-op optimizer passes. + newOpVec.reserve(opVec.size()); + + // Catch up any unmodified elements prior to this replacement + for (size_t j = 0; j < i; ++j) + { + newOpVec.emplace_back(std::move(opVec[j])); + } + } - // We've done something so increment the count! + FinalizeOps(tmpops); + for (auto & newOp : tmpops) + { + newOpVec.emplace_back(std::move(newOp)); + } ++count; } - ++firstindex; + else + { + if (rebuilding) + { + newOpVec.emplace_back(std::move(opVec[i])); + } + } + + } + + if (rebuilding) + { + opVec = std::move(newOpVec); } return count; } -int ReplaceIdentityOps(OpRcPtrVec & opVec, OptimizationFlags oFlags) +size_t ReplaceIdentityOps(OpRcPtrVec & opVec, OptimizationFlags oFlags) { - int count = 0; + size_t count = 0; // Remove any identity ops (other than gamma). const bool optIdentity = HasFlag(oFlags, OPTIMIZATION_IDENTITY); // Remove identity gamma ops (handled separately to give control over negative // alpha clamping). const bool optIdGamma = HasFlag(oFlags, OPTIMIZATION_IDENTITY_GAMMA); - if (optIdentity || optIdGamma) + if (!optIdentity && !optIdGamma) { - const size_t nbOps = opVec.size(); - for (size_t i = 0; i < nbOps; ++i) + return count; + } + + for (auto & op : opVec) + { + const auto type = std::as_const(*op).data()->getType(); + if (type != OpData::RangeType && // Do not replace a range identity. + ((type == OpData::GammaType && optIdGamma) || + (type != OpData::GammaType && optIdentity)) && + op->isIdentity()) { - ConstOpRcPtr op = opVec[i]; - const auto type = op->data()->getType(); - if (type != OpData::RangeType && // Do not replace a range identity. - ((type == OpData::GammaType && optIdGamma) || - (type != OpData::GammaType && optIdentity)) && - op->isIdentity()) - { - // Optimization flag is tested before. - auto replacedBy = op->getIdentityReplacement(); - replacedBy->finalize(); - opVec[i] = replacedBy; - ++count; - } + // Optimization flag is tested before. + auto replacedBy = op->getIdentityReplacement(); + replacedBy->finalize(); + op = std::move(replacedBy); + ++count; } } return count; } -int RemoveInverseOps(OpRcPtrVec & opVec, OptimizationFlags oFlags) +size_t RemoveInverseOps(OpRcPtrVec & opVec, OptimizationFlags oFlags) { - int count = 0; - int firstindex = 0; // this must be a signed int + size_t count = 0; + size_t writeIdx = 0; + const size_t numOps = opVec.size(); - while (firstindex < (static_cast(opVec.size()) - 1)) + for (size_t readIdx = 0; readIdx < numOps; ++readIdx) { - ConstOpRcPtr op1 = opVec[firstindex]; - ConstOpRcPtr op2 = opVec[firstindex + 1]; - const auto type1 = op1->data()->getType(); - const auto type2 = op2->data()->getType(); - // The common case of inverse ops is to have a deep nesting: - // ..., A, B, B', A', ... - // - // Consider the above, when firstindex reaches B: - // - // | - // ..., A, B, B', A', ... - // - // We will remove B and B'. - // Firstindex remains pointing at the original location: - // - // | - // ..., A, A', ... - // - // We then decrement firstindex by 1, - // to backstep and reconsider the A, A' case: - // - // | <-- firstindex decremented - // ..., A, A', ... - // + auto & op = opVec[readIdx]; - if (type1 == type2 && - IsPairInverseEnabled(type1, oFlags) && - op1->isInverse(op2)) + if (writeIdx > 0) { - // 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. - - 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. + ConstOpRcPtr lastOp = opVec[writeIdx - 1]; + ConstOpRcPtr constOp = op; + const auto type1 = lastOp->data()->getType(); + const auto type2 = constOp->data()->getType(); - ConstLut1DOpDataRcPtr lut1 = OCIO_DYNAMIC_POINTER_CAST(op1->data()); - ConstLut1DOpDataRcPtr lut2 = OCIO_DYNAMIC_POINTER_CAST(op2->data()); + // The common case of inverse ops is to have a deep nesting: + // ..., A, B, B', A', ... + // + // By treating the processed portion of the vector as a stack (`writeIdx`), + // popping an element automatically exposes `A` to be reconsidered against `A'` + // on the next loop iteration. - OpDataRcPtr opData = lut1->getPairIdentityReplacement(lut2); + if (type1 == type2 && + IsPairInverseEnabled(type1, oFlags) && + lastOp->isInverse(constOp)) + { + // 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. - OpRcPtrVec ops; - if (opData->getType() == OpData::MatrixType) + OpRcPtr replacedBy; + if (type1 == OpData::Lut1DType) { - // No-op that will be optimized. - auto mat = OCIO_DYNAMIC_POINTER_CAST(opData); - CreateMatrixOp(ops, mat, TRANSFORM_DIR_FORWARD); + // 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(lastOp->data()); + ConstLut1DOpDataRcPtr lut2 = OCIO_DYNAMIC_POINTER_CAST(constOp->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 = std::move(ops[0]); } - else if (opData->getType() == OpData::RangeType) + else { - // Clamping op. - auto range = OCIO_DYNAMIC_POINTER_CAST(opData); - CreateRangeOp(ops, range, TRANSFORM_DIR_FORWARD); + replacedBy = lastOp->getIdentityReplacement(); } - replacedBy = ops[0]; - } - else - { - replacedBy = op1->getIdentityReplacement(); - } - replacedBy->finalize(); - if (replacedBy->isNoOp()) - { - opVec.erase(opVec.begin() + firstindex, opVec.begin() + firstindex + 2); - firstindex = std::max(0, firstindex - 1); - } - else - { - // Forward + inverse does clamp. - opVec[firstindex] = replacedBy; - opVec.erase(opVec.begin() + firstindex + 1); - ++firstindex; + replacedBy->finalize(); + if (replacedBy->isNoOp()) + { + // Pop the last element off the stack to naturally backstep + --writeIdx; + } + else + { + // Forward + inverse does clamp. + opVec[writeIdx - 1] = std::move(replacedBy); + } + ++count; + continue; } - ++count; } - else + + // Push the current op to the stack if it wasn't cancelled out + if (writeIdx != readIdx) { - ++firstindex; + opVec[writeIdx] = std::move(op); } + ++writeIdx; + } + + if (count > 0) + { + // Drop any unused elements at the tail in a single O(N) pass + opVec.erase(opVec.begin() + writeIdx, opVec.end()); } return count; } -int CombineOps(OpRcPtrVec & opVec, OptimizationFlags oFlags) +size_t CombineOps(OpRcPtrVec & opVec, OptimizationFlags oFlags) { - int count = 0; - int firstindex = 0; // this must be a signed int + auto it = std::adjacent_find(opVec.begin(), opVec.end(), + [oFlags](const auto & ptr1, const auto & ptr2) { + ConstOpRcPtr op2 = ptr2; + return IsCombineEnabled(std::as_const(*ptr1).data()->getType(), oFlags) && ptr1->canCombineWith(op2); + }); - OpRcPtrVec tmpops; - - while (firstindex < (static_cast(opVec.size()) - 1)) + if (it != opVec.end()) { - ConstOpRcPtr op1 = opVec[firstindex]; - ConstOpRcPtr op2 = opVec[firstindex + 1]; - const auto type1 = op1->data()->getType(); + OpRcPtrVec tmpops; + ConstOpRcPtr op2 = *(it + 1); + + (*it)->combineWith(tmpops, op2); + FinalizeOps(tmpops); + + // The tmpops may have any number of ops in it: (0, 1, 2, ...). + // (Size 0 would occur only if the combination results in a no-op, + // for example, a pair of matrices that compose into a no-op are + // returned as empty rather than as an identity matrix.) + // + // No matter the number, we need to swap them in for the original ops. - if (IsCombineEnabled(type1, oFlags) && op1->canCombineWith(op2)) + const size_t numNewOps = tmpops.size(); + if (numNewOps == 0) { - tmpops.clear(); - op1->combineWith(tmpops, op2); - FinalizeOps(tmpops); - - // The tmpops may have any number of ops in it: (0, 1, 2, ...). - // (Size 0 would occur only if the combination results in a no-op, - // for example, a pair of matrices that compose into a no-op are - // returned as empty rather than as an identity matrix.) - // - // No matter the number, we need to swap them in for the original ops. - - // Erase the initial two ops we've combined. - opVec.erase(opVec.begin() + firstindex, opVec.begin() + firstindex + 2); - - // Insert the new ops (which may be empty) at this location. - opVec.insert(opVec.begin() + firstindex, tmpops.begin(), tmpops.end()); - - // Decrement firstindex by 1, - // to backstep and reconsider the A, A' case. - // See RemoveInverseOps for the full discussion of - // why this is appropriate. - firstindex = std::max(0, firstindex - 1); - - // We've done something so increment the count! - ++count; - - // Break, since combining ops is less desirable than other optimization options. - // For example, it is preferable to remove a pair of ops using RemoveInverseOps - // rather than combining them. Consider this example: - // Lut1D A --> Matrix B --> Matrix C --> Lut1D Ainv - // If Matrix B & C are not pair inverses but do combine into an identity, then - // CombineOps would compose Lut1D A & Ainv, into a new Lut1D rather than - // allowing another round of optimization which would remove them as inverses. - break; + opVec.erase(it, it + 2); } - else + else if (numNewOps == 1) { - ++firstindex; + *it = std::move(tmpops[0]); + opVec.erase(it + 1); } + else if (numNewOps == 2) + { + *it = std::move(tmpops[0]); + *(it + 1) = std::move(tmpops[1]); + } + else + { + *it = std::move(tmpops[0]); + *(it + 1) = std::move(tmpops[1]); + opVec.insert(it + 2, + std::make_move_iterator(tmpops.begin() + 2), + std::make_move_iterator(tmpops.end())); } + + + // Return 1 since combining ops is less desirable than other optimization options. + // For example, it is preferable to remove a pair of ops using RemoveInverseOps + // rather than combining them. Consider this example: + // Lut1D A --> Matrix B --> Matrix C --> Lut1D Ainv + // If Matrix B & C are not pair inverses but do combine into an identity, then + // CombineOps would compose Lut1D A & Ainv, into a new Lut1D rather than + // allowing another round of optimization which would remove them as inverses. + return 1; } - return count; + return 0; } // Replace any Lut1D or Lut3D that specify inverse evaluation with a faster forward approximation. @@ -366,15 +395,20 @@ int CombineOps(OpRcPtrVec & opVec, OptimizationFlags oFlags) // LUT is used and so this is quite accurate even for scene-linear values, but for Lut3D the baked // version is more of an approximation. The default optimization level uses the FAST method since // it is the only one available on both CPU and GPU. -int ReplaceInverseLuts(OpRcPtrVec & opVec) +size_t ReplaceInverseLuts(OpRcPtrVec & opVec, OptimizationFlags oFlags) { - int count = 0; + size_t count = 0; + const bool fastLut = HasFlag(oFlags, OPTIMIZATION_LUT_INV_FAST); + if (!fastLut) + { + return count; + } - const size_t nbOps = opVec.size(); - for (size_t i = 0; i < nbOps; ++i) + OpRcPtrVec tmpops; + + for (auto & op : opVec) { - ConstOpRcPtr op = opVec[i]; - auto opData = op->data(); + auto opData = std::as_const(*op).data(); const auto type = opData->getType(); if (type == OpData::Lut1DType) { @@ -382,10 +416,10 @@ int ReplaceInverseLuts(OpRcPtrVec & opVec) if (lutData->getDirection() == TRANSFORM_DIR_INVERSE) { auto invLutData = MakeFastLut1DFromInverse(lutData); - OpRcPtrVec tmpops; + tmpops.clear(); CreateLut1DOp(tmpops, invLutData, TRANSFORM_DIR_FORWARD); FinalizeOps(tmpops); - opVec[i] = tmpops[0]; + op = std::move(tmpops[0]); ++count; } } @@ -395,67 +429,44 @@ int ReplaceInverseLuts(OpRcPtrVec & opVec) if (lutData->getDirection() == TRANSFORM_DIR_INVERSE) { auto invLutData = MakeFastLut3DFromInverse(lutData); - OpRcPtrVec tmpops; + tmpops.clear(); CreateLut3DOp(tmpops, invLutData, TRANSFORM_DIR_FORWARD); FinalizeOps(tmpops); - opVec[i] = tmpops[0]; + op = std::move(tmpops[0]); ++count; } } } return count; - } -int RemoveLeadingClampIdentity(OpRcPtrVec & opVec) +size_t RemoveLeadingClampIdentity(OpRcPtrVec & opVec) { - int count = 0; - OpRcPtrVec::const_iterator iter = opVec.begin(); - while (iter != opVec.end()) - { - ConstOpRcPtr o = (*iter); - auto oData = o->data(); - if (oData->getType() == OpData::RangeType && oData->isIdentity()) - { - iter++; - ++count; - } - else - { - break; - } - } - if (count != 0) + auto it = std::find_if_not(opVec.begin(), opVec.end(), [](const auto & op) { + auto oData = std::as_const(*op).data(); + return oData->getType() == OpData::RangeType && oData->isIdentity(); + }); + + size_t count = std::distance(opVec.begin(), it); + if (count > 0) { - OpRcPtrVec::const_iterator iter = opVec.begin() + count; - opVec.erase(opVec.begin(), iter); + opVec.erase(opVec.begin(), it); } return count; } -int RemoveTrailingClampIdentity(OpRcPtrVec & opVec) +size_t RemoveTrailingClampIdentity(OpRcPtrVec & opVec) { - int count = 0; - int current = static_cast(opVec.size()) - 1; - while (current >= 0) + // Note the use of reverse iterators + auto rit = std::find_if_not(opVec.rbegin(), opVec.rend(), [](const auto & op) { + auto oData = std::as_const(*op).data(); + return oData->getType() == OpData::RangeType && oData->isIdentity(); + }); + + size_t count = std::distance(opVec.rbegin(), rit); + if (count > 0) { - ConstOpRcPtr o = opVec[current]; - auto oData = o->data(); - if (oData->getType() == OpData::RangeType && oData->isIdentity()) - { - ++count; - --current; - } - else - { - break; - } - } - - if (count != 0) - { - OpRcPtrVec::const_iterator iter = opVec.begin() + (current + 1); - opVec.erase(iter, opVec.end()); + opVec.erase(rit.base(), opVec.end()); } return count; } @@ -470,33 +481,25 @@ int RemoveTrailingClampIdentity(OpRcPtrVec & opVec) // pixels. Rather than convert to float and apply the power function on each // pixel, it's better to build a 1024 entry LUT and just do a look-up. // -unsigned FindSeparablePrefix(const OpRcPtrVec & ops) +size_t FindSeparablePrefix(const OpRcPtrVec & ops) { - unsigned prefixLen = 0; - // Loop over the ops until we get to one that cannot be combined. // // Note: For some ops such as Matrix and CDL, the separability depends upon // the parameters. - for (const auto & op : ops) - { + auto it = std::find_if(ops.begin(), ops.end(), [](const auto & op) { // In OCIO, the hasChannelCrosstalk method returns false for separable ops. - if (op->hasChannelCrosstalk() || op->isDynamic()) - { - break; - } + return op->hasChannelCrosstalk() || op->isDynamic(); + }); - // Op is separable, keep going. - prefixLen++; - } + size_t prefixLen = std::distance(ops.begin(), it); // If the only op is a 1D LUT, there is actually nothing to optimize // so set the length to 0. (This also avoids an infinite loop.) // (If it is an inverse 1D LUT, proceed since we want to replace it with a 1D LUT.) if (prefixLen == 1) { - ConstOpRcPtr constOp0 = ops[0]; - auto opData = constOp0->data(); + auto opData = std::as_const(*ops[0]).data(); if (opData->getType() == OpData::Lut1DType) { auto lutData = OCIO_DYNAMIC_POINTER_CAST(opData); @@ -510,31 +513,22 @@ unsigned FindSeparablePrefix(const OpRcPtrVec & ops) // Some ops are so fast that it may not make sense to replace just one of those. // E.g., if it's just a single matrix, it may not be faster to replace it with a LUT. // So make sure there are some more expensive ops to combine. - unsigned expensiveOps = 0U; - for (unsigned i = 0; i < prefixLen; ++i) - { - auto op = ops[i]; - + bool hasExpensiveOps = std::any_of(ops.begin(), ops.begin() + prefixLen, [](const auto & op) { if (op->hasChannelCrosstalk()) { // Non-separable ops (should never get here). throw Exception("Non-separable op."); } - ConstOpRcPtr constOp = op; - if (constOp->data()->getType() == OpData::MatrixType - || constOp->data()->getType() == OpData::RangeType) - { - // Potentially separable, but inexpensive ops. - // TODO: Perhaps a LUT is faster once the conversion to float is considered? - } - else - { - expensiveOps++; - } - } + const auto type = std::as_const(*op).data()->getType(); + + // Potentially separable, but inexpensive ops are not counted. + // TODO: Perhaps a LUT is faster once the conversion to float is considered? - if (expensiveOps == 0) + return type != OpData::MatrixType && type != OpData::RangeType; + }); + + if (!hasExpensiveOps) { return 0; } @@ -564,16 +558,17 @@ void OptimizeSeparablePrefix(OpRcPtrVec & ops, BitDepth in) return; } - const unsigned prefixLen = FindSeparablePrefix(ops); + const auto prefixLen = FindSeparablePrefix(ops); if (prefixLen == 0) { return; // Nothing to do. } OpRcPtrVec prefixOps; - for (unsigned i = 0; i < prefixLen; ++i) + prefixOps.reserve(prefixLen); + for (size_t i = 0; i < prefixLen; ++i) { - prefixOps.push_back(ops[i]->clone()); + prefixOps.emplace_back(ops[i]->clone()); } // Make a domain for the LUT. (Will be half-domain for target == 16f.) @@ -583,16 +578,48 @@ void OptimizeSeparablePrefix(OpRcPtrVec & ops, BitDepth in) // Note: This sets the outBitDepth of newDomain to match prefixOps. Lut1DOpData::ComposeVec(newDomain, prefixOps); - // Remove the prefix ops. - ops.erase(ops.begin(), ops.begin() + prefixLen); - // Insert the new LUT to replace the prefix ops. OpRcPtrVec lutOps; + lutOps.reserve(1); CreateLut1DOp(lutOps, newDomain, TRANSFORM_DIR_FORWARD); FinalizeOps(lutOps); - ops.insert(ops.begin(), lutOps.begin(), lutOps.end()); + const auto numNewOps = lutOps.size(); + const auto elementsToOverwrite = std::min(numNewOps, prefixLen); + + for (size_t i = 0; i < elementsToOverwrite; ++i) + { + ops[i] = std::move(lutOps[i]); + } + + if (numNewOps < prefixLen) + { + ops.erase(ops.begin() + numNewOps, ops.begin() + prefixLen); + } + else if (numNewOps > prefixLen) + { + ops.insert(ops.begin() + prefixLen, + std::make_move_iterator(lutOps.begin() + prefixLen), + std::make_move_iterator(lutOps.end())); + } } + +size_t PerformOptimisation(size_t (*Operation)(OpRcPtrVec &, OptimizationFlags), OpRcPtrVec & opVec, OptimizationFlags oFlags, bool debugLoggingEnabled, std::string_view operationName) +{ + const size_t ops_removed = Operation(opVec, oFlags); + if (debugLoggingEnabled) + { + std::ostringstream os; + os << operationName << " - " << ops_removed << " optimisations found\n"; + if (ops_removed != 0) + { + os << SerializeOpVec(opVec, 4); + } + LogDebug(os.str()); + } + return ops_removed; +} + } // namespace void OpRcPtrVec::finalize() @@ -615,34 +642,35 @@ void OpRcPtrVec::optimize(OptimizationFlags oFlags) return; } - if (IsDebugLoggingEnabled()) + const bool debugLoggingEnabled = IsDebugLoggingEnabled(); + if (debugLoggingEnabled) { - std::ostringstream oss; - oss << std::endl - << "**" << std::endl - << "Optimizing Op Vec..." << std::endl - << SerializeOpVec(*this, 4) << std::endl; - - LogDebug(oss.str()); + const std::string message = "\n**\nOptimizing Op Vec...\n" + SerializeOpVec(*this, 4); + LogDebug(message); } const auto originalSize = size(); + size_t total_noops = 0; + size_t total_replacedops = 0; + size_t total_identityops = 0; + size_t total_inverseops = 0; + size_t total_combines = 0; + size_t total_inverses = 0; + int passes = 1; // NoOpType can be removed (facilitates conversion to a CPU/GPUProcessor). - const int total_nooptype = RemoveNoOpTypes(*this); + const auto total_nooptype = PerformOptimisation(RemoveNoOpTypes, *this, oFlags, debugLoggingEnabled, "RemoveNoOpTypes"); if (oFlags == OPTIMIZATION_NONE) { - if (IsDebugLoggingEnabled()) + if (debugLoggingEnabled) { - OpRcPtrVec::size_type finalSize = size(); + const auto finalSize = size(); std::ostringstream os; - os << "**" << std::endl; - os << "Optimized "; - os << originalSize << "->" << finalSize << ", 1 pass, "; - os << total_nooptype << " no-op types removed\n"; - os << SerializeOpVec(*this, 4); + os << "**\nOptimized " << originalSize << "->" << finalSize << ", " << passes << " pass, " + << total_nooptype << " no-op types removed\n" + << SerializeOpVec(*this, 4); LogDebug(os.str()); } @@ -651,106 +679,94 @@ void OpRcPtrVec::optimize(OptimizationFlags oFlags) // Keep dynamic ops using their default values. Remove the ability to modify // them dynamically. - const auto removeDynamic = HasFlag(oFlags, OPTIMIZATION_NO_DYNAMIC_PROPERTIES); - if (removeDynamic) - { - RemoveDynamicProperties(*this); - } - - // As the input and output bit-depths represent the color processing - // request and they may be altered by the following optimizations, - // preserve their values. - - int total_noops = 0; - int total_replacedops = 0; - int total_identityops = 0; - int total_inverseops = 0; - int total_combines = 0; - int total_inverses = 0; - int passes = 0; - - const bool optimizeIdentity = HasFlag(oFlags, OPTIMIZATION_IDENTITY); - const bool replaceOps = HasFlag(oFlags, OPTIMIZATION_SIMPLIFY_OPS); - - const bool fastLut = HasFlag(oFlags, OPTIMIZATION_LUT_INV_FAST); + const auto total_dynamicOps = PerformOptimisation(RemoveDynamicProperties, *this, oFlags, debugLoggingEnabled, "RemoveDynamicProperties"); while (passes <= MAX_OPTIMIZATION_PASSES) { + if (debugLoggingEnabled) + { + const auto message = "Starting pass " + std::to_string(passes); + LogDebug(message); + } // Remove all ops for which isNoOp is true, including identity matrices. - int noops = optimizeIdentity ? RemoveNoOps(*this) : 0; + const auto noops = PerformOptimisation(RemoveNoOps, *this, oFlags, debugLoggingEnabled, "RemoveNoOps"); + total_noops += noops; // Replace all complex ops with simpler ops (e.g., a CDL which only scales with a matrix). // Note this might increase the number of ops. - int replacedOps = replaceOps ? ReplaceOps(*this) : 0; + const auto replacedOps = PerformOptimisation(ReplaceOps, *this, oFlags, debugLoggingEnabled, "ReplaceOps"); + total_replacedops += replacedOps; // Replace all complex identities with simpler ops (e.g., an identity Lut1D with a range). - int identityops = ReplaceIdentityOps(*this, oFlags); + const auto identityops = PerformOptimisation(ReplaceIdentityOps, *this, oFlags, debugLoggingEnabled, "ReplaceIdentityOps"); + total_identityops += identityops; // Remove all adjacent pairs of ops that are inverses of each other. - int inverseops = RemoveInverseOps(*this, oFlags); + const auto inverseops = PerformOptimisation(RemoveInverseOps, *this, oFlags, debugLoggingEnabled, "RemoveInverseOps"); + total_inverseops += inverseops; // Combine a pair of ops, for example multiply two adjacent Matrix ops. // (Combines at most one pair on each iteration.) - int combines = CombineOps(*this, oFlags); + const auto combines = PerformOptimisation(CombineOps, *this, oFlags, debugLoggingEnabled, "CombineOps"); + total_combines += combines; - if (noops + identityops + inverseops + combines == 0) + if (noops + replacedOps + identityops + inverseops + combines == 0) { // No optimization progress was made, so stop trying. If requested, replace any // inverse LUTs with faster forward LUTs and do another pass to see if more // optimization is possible. - if (fastLut) - { - const int inverses = ReplaceInverseLuts(*this); - if (inverses == 0) - { - break; - } + const auto inverses = PerformOptimisation(ReplaceInverseLuts, *this, oFlags, debugLoggingEnabled, "ReplaceInverseLuts"); + total_inverses += inverses; - total_inverses += inverses; - } - else + if (inverses == 0) { break; } } - total_noops += noops; - total_replacedops += replacedOps; - total_identityops += identityops; - total_inverseops += inverseops; - total_combines += combines; + if (debugLoggingEnabled) + { + std::ostringstream os; + os << "Pass " << passes << " summary: " + << noops << " no-op removed, " + << replacedOps << " ops replaced, " + << identityops << " identity ops replaced, " + << inverseops << " inverse op pairs removed, " + << combines << " ops combined."; + LogDebug(os.str()); + } ++passes; } - if (passes == MAX_OPTIMIZATION_PASSES) + if (debugLoggingEnabled && (passes == MAX_OPTIMIZATION_PASSES)) { std::ostringstream os; - os << "The max number of passes, " << passes << ", "; - os << "was reached during optimization. This is likely a sign "; - os << "that either the complexity of the color transform is "; - os << "very high, or that some internal optimizers are in conflict "; - os << "(undo-ing / redo-ing the other's results)."; + os << "The max number of passes, " << passes << ", " + "was reached during optimization. This is likely a sign " + "that either the complexity of the color transform is " + "very high, or that some internal optimizers are in conflict " + "(undo-ing / redo-ing the other's results)."; LogDebug(os.str()); } - if (IsDebugLoggingEnabled()) + if (debugLoggingEnabled) { - OpRcPtrVec::size_type finalSize = size(); + const auto finalSize = size(); std::ostringstream os; - os << "**" << std::endl; - os << "Optimized "; - os << originalSize << "->" << finalSize << ", "; - os << passes << " passes, "; - os << total_nooptype << " no-op types removed, "; - os << total_noops << " no-ops removed, "; - os << total_replacedops << " ops replaced, "; - os << total_identityops << " identity ops replaced, "; - os << total_inverseops << " inverse op pairs removed, "; - os << total_combines << " ops combined, "; - os << total_inverses << " ops inverted\n"; - os << SerializeOpVec(*this, 4); + os << "**\nOptimized " + << originalSize << "->" << finalSize << ", " + << passes << " passes, " + << total_nooptype << " no-op types removed, " + << total_dynamicOps << " dynamic-ops made static, " + << total_noops << " no-ops removed, " + << total_replacedops << " ops replaced, " + << total_identityops << " identity ops replaced, " + << total_inverseops << " inverse op pairs removed, " + << total_combines << " ops combined, " + << total_inverses << " ops inverted\n" + << SerializeOpVec(*this, 4); LogDebug(os.str()); } } @@ -777,4 +793,3 @@ void OpRcPtrVec::optimizeForBitdepth(const BitDepth & inBitDepth, } } // namespace OCIO_NAMESPACE - diff --git a/src/OpenColorIO/ParseUtils.cpp b/src/OpenColorIO/ParseUtils.cpp index 47d4f2641a..6759b2d0d4 100644 --- a/src/OpenColorIO/ParseUtils.cpp +++ b/src/OpenColorIO/ParseUtils.cpp @@ -771,12 +771,12 @@ StringUtils::StringVec SplitStringEnvStyle(const std::string & str) foundComma != std::string::npos ? ',' : ':' ); if(nameEndPos > currentPos) { - outputvec.push_back(s.substr(currentPos, nameEndPos - currentPos)); + outputvec.emplace_back(s.substr(currentPos, nameEndPos - currentPos)); currentPos = nameEndPos + 1; } else { - outputvec.push_back(""); + outputvec.emplace_back(""); currentPos += 1; } } @@ -784,7 +784,7 @@ StringUtils::StringVec SplitStringEnvStyle(const std::string & str) else { // If there is no comma or colon, consider the string as a single element. - outputvec.push_back(s); + outputvec.emplace_back(s); } for ( auto & val : outputvec ) diff --git a/src/OpenColorIO/Processor.cpp b/src/OpenColorIO/Processor.cpp index 992701855f..ea4b441ca7 100755 --- a/src/OpenColorIO/Processor.cpp +++ b/src/OpenColorIO/Processor.cpp @@ -93,7 +93,7 @@ const char * ProcessorMetadata::getLook(int index) const void ProcessorMetadata::addLook(const char * look) { - getImpl()->looks.push_back(look); + getImpl()->looks.emplace_back(look); } ////////////////////////////////////////////////////////////////////////// diff --git a/src/OpenColorIO/ViewingRules.cpp b/src/OpenColorIO/ViewingRules.cpp index 2db7d1e499..543681e26f 100644 --- a/src/OpenColorIO/ViewingRules.cpp +++ b/src/OpenColorIO/ViewingRules.cpp @@ -156,7 +156,7 @@ ViewingRules::Impl & ViewingRules::Impl::operator=(const ViewingRules::Impl & rh for (const auto & rule : rhs.m_rules) { - m_rules.push_back(rule->clone()); + m_rules.emplace_back(rule->clone()); } } @@ -387,15 +387,14 @@ void ViewingRules::insertRule(size_t ruleIndex, const char * name) m_impl->validateNewRule(ruleName.c_str()); - auto newRule = std::make_shared(ruleName.c_str()); if (ruleIndex == getNumEntries()) { - m_impl->m_rules.push_back(newRule); + m_impl->m_rules.emplace_back(std::make_shared(ruleName.c_str())); } else { m_impl->validatePosition(ruleIndex); - m_impl->m_rules.insert(m_impl->m_rules.begin() + ruleIndex, newRule); + m_impl->m_rules.insert(m_impl->m_rules.begin() + ruleIndex, std::make_shared(ruleName.c_str())); } } diff --git a/src/OpenColorIO/ops/cdl/CDLOp.cpp b/src/OpenColorIO/ops/cdl/CDLOp.cpp index bd0511ea43..b6d96df8a2 100644 --- a/src/OpenColorIO/ops/cdl/CDLOp.cpp +++ b/src/OpenColorIO/ops/cdl/CDLOp.cpp @@ -177,7 +177,7 @@ void CreateCDLOp(OpRcPtrVec & ops, cdl = cdl->inverse(); } - ops.push_back(std::make_shared(cdl)); + ops.emplace_back(std::make_shared(cdl)); } /////////////////////////////////////////////////////////////////////////// diff --git a/src/OpenColorIO/ops/cdl/CDLOpData.cpp b/src/OpenColorIO/ops/cdl/CDLOpData.cpp index fa214be887..cca445ccd4 100644 --- a/src/OpenColorIO/ops/cdl/CDLOpData.cpp +++ b/src/OpenColorIO/ops/cdl/CDLOpData.cpp @@ -361,8 +361,7 @@ void CDLOpData::getSimplerReplacement(OpDataVec & tmpops) const if (isClamping()) { // Same in both directions. - auto range = std::make_shared(0., 1., 0., 1.); - tmpops.push_back(range); + tmpops.emplace_back(std::make_shared(0., 1., 0., 1.)); } static constexpr double lumaCoef3[3]{ 0.2126, 0.7152, 0.0722 }; @@ -383,8 +382,7 @@ void CDLOpData::getSimplerReplacement(OpDataVec & tmpops) const // Clamping if (isClamping()) { - auto range = std::make_shared(0., 1., 0., 1.); - tmpops.push_back(range); + tmpops.emplace_back( std::make_shared(0., 1., 0., 1.)); } if (getDirection() == TRANSFORM_DIR_INVERSE) diff --git a/src/OpenColorIO/ops/exponent/ExponentOp.cpp b/src/OpenColorIO/ops/exponent/ExponentOp.cpp index e10eb56b71..c5142a763c 100644 --- a/src/OpenColorIO/ops/exponent/ExponentOp.cpp +++ b/src/OpenColorIO/ops/exponent/ExponentOp.cpp @@ -312,7 +312,7 @@ void CreateExponentOp(OpRcPtrVec & ops, { case TRANSFORM_DIR_FORWARD: { - ops.push_back(std::make_shared(expData)); + ops.emplace_back(std::make_shared(expData)); break; } case TRANSFORM_DIR_INVERSE: @@ -330,7 +330,7 @@ void CreateExponentOp(OpRcPtrVec & ops, } } ExponentOpDataRcPtr expInv = std::make_shared(values); - ops.push_back(std::make_shared(expInv)); + ops.emplace_back(std::make_shared(expInv)); break; } } diff --git a/src/OpenColorIO/ops/exposurecontrast/ExposureContrastOp.cpp b/src/OpenColorIO/ops/exposurecontrast/ExposureContrastOp.cpp index fc5d1cd4e4..1462ae336d 100644 --- a/src/OpenColorIO/ops/exposurecontrast/ExposureContrastOp.cpp +++ b/src/OpenColorIO/ops/exposurecontrast/ExposureContrastOp.cpp @@ -180,13 +180,13 @@ void CreateExposureContrastOp(OpRcPtrVec & ops, { case TRANSFORM_DIR_FORWARD: { - ops.push_back(std::make_shared(data)); + ops.emplace_back(std::make_shared(data)); break; } case TRANSFORM_DIR_INVERSE: { ExposureContrastOpDataRcPtr dataInv = data->inverse(); - ops.push_back(std::make_shared(dataInv)); + ops.emplace_back(std::make_shared(dataInv)); break; } } diff --git a/src/OpenColorIO/ops/fixedfunction/FixedFunctionOp.cpp b/src/OpenColorIO/ops/fixedfunction/FixedFunctionOp.cpp index 4f9569ef1c..5694bdfeb1 100644 --- a/src/OpenColorIO/ops/fixedfunction/FixedFunctionOp.cpp +++ b/src/OpenColorIO/ops/fixedfunction/FixedFunctionOp.cpp @@ -157,7 +157,7 @@ void CreateFixedFunctionOp(OpRcPtrVec & ops, func = func->inverse(); } - ops.push_back(std::make_shared(func)); + ops.emplace_back(std::make_shared(func)); } /////////////////////////////////////////////////////////////////////////// diff --git a/src/OpenColorIO/ops/gamma/GammaOp.cpp b/src/OpenColorIO/ops/gamma/GammaOp.cpp index 9d6afccc7d..a0c7c63e27 100644 --- a/src/OpenColorIO/ops/gamma/GammaOp.cpp +++ b/src/OpenColorIO/ops/gamma/GammaOp.cpp @@ -141,7 +141,7 @@ void CreateGammaOp(OpRcPtrVec & ops, gamma = gamma->inverse(); } - ops.push_back(std::make_shared(gamma)); + ops.emplace_back(std::make_shared(gamma)); } /////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/OpenColorIO/ops/gradinghuecurve/GradingHueCurveOp.cpp b/src/OpenColorIO/ops/gradinghuecurve/GradingHueCurveOp.cpp index 1915301319..c59e1abd21 100644 --- a/src/OpenColorIO/ops/gradinghuecurve/GradingHueCurveOp.cpp +++ b/src/OpenColorIO/ops/gradinghuecurve/GradingHueCurveOp.cpp @@ -211,7 +211,7 @@ void CreateGradingHueCurveOp(OpRcPtrVec & ops, curve = curve->inverse(); } - ops.push_back(std::make_shared(curve)); + ops.emplace_back(std::make_shared(curve)); } /////////////////////////////////////////////////////////////////////////// diff --git a/src/OpenColorIO/ops/gradingprimary/GradingPrimaryOp.cpp b/src/OpenColorIO/ops/gradingprimary/GradingPrimaryOp.cpp index 5bb2f960bf..f1cdea8b8f 100644 --- a/src/OpenColorIO/ops/gradingprimary/GradingPrimaryOp.cpp +++ b/src/OpenColorIO/ops/gradingprimary/GradingPrimaryOp.cpp @@ -215,7 +215,7 @@ void CreateGradingPrimaryOp(OpRcPtrVec & ops, prim = prim->inverse(); } - ops.push_back(std::make_shared(prim)); + ops.emplace_back(std::make_shared(prim)); } /////////////////////////////////////////////////////////////////////////// diff --git a/src/OpenColorIO/ops/gradingrgbcurve/GradingRGBCurveOp.cpp b/src/OpenColorIO/ops/gradingrgbcurve/GradingRGBCurveOp.cpp index 598ae28e67..52b5c3f271 100644 --- a/src/OpenColorIO/ops/gradingrgbcurve/GradingRGBCurveOp.cpp +++ b/src/OpenColorIO/ops/gradingrgbcurve/GradingRGBCurveOp.cpp @@ -215,7 +215,7 @@ void CreateGradingRGBCurveOp(OpRcPtrVec & ops, curve = curve->inverse(); } - ops.push_back(std::make_shared(curve)); + ops.emplace_back(std::make_shared(curve)); } /////////////////////////////////////////////////////////////////////////// diff --git a/src/OpenColorIO/ops/gradingtone/GradingToneOp.cpp b/src/OpenColorIO/ops/gradingtone/GradingToneOp.cpp index 5968210e71..f09278a209 100644 --- a/src/OpenColorIO/ops/gradingtone/GradingToneOp.cpp +++ b/src/OpenColorIO/ops/gradingtone/GradingToneOp.cpp @@ -209,7 +209,7 @@ void CreateGradingToneOp(OpRcPtrVec & ops, tone = tone->inverse(); } - ops.push_back(std::make_shared(tone)); + ops.emplace_back(std::make_shared(tone)); } /////////////////////////////////////////////////////////////////////////// diff --git a/src/OpenColorIO/ops/log/LogOp.cpp b/src/OpenColorIO/ops/log/LogOp.cpp index 8eeff9b3b2..f15691ad5b 100644 --- a/src/OpenColorIO/ops/log/LogOp.cpp +++ b/src/OpenColorIO/ops/log/LogOp.cpp @@ -130,13 +130,13 @@ void CreateLogOp(OpRcPtrVec & ops, { auto opData = std::make_shared(base, logSlope, logOffset, linSlope, linOffset, direction); - ops.push_back(std::make_shared(opData)); + ops.emplace_back(std::make_shared(opData)); } void CreateLogOp(OpRcPtrVec & ops, double base, TransformDirection direction) { auto opData = std::make_shared(base, direction); - ops.push_back(std::make_shared(opData)); + ops.emplace_back(std::make_shared(opData)); } void CreateLogOp(OpRcPtrVec & ops, @@ -149,7 +149,7 @@ void CreateLogOp(OpRcPtrVec & ops, log = log->inverse(); } - ops.push_back(std::make_shared(log)); + ops.emplace_back(std::make_shared(log)); } diff --git a/src/OpenColorIO/ops/log/LogUtils.cpp b/src/OpenColorIO/ops/log/LogUtils.cpp index 77548acfe6..e023337774 100644 --- a/src/OpenColorIO/ops/log/LogUtils.cpp +++ b/src/OpenColorIO/ops/log/LogUtils.cpp @@ -3,6 +3,7 @@ #include #include +#include #include diff --git a/src/OpenColorIO/ops/lut1d/Lut1DOp.cpp b/src/OpenColorIO/ops/lut1d/Lut1DOp.cpp index 412462414b..8e5737baf4 100644 --- a/src/OpenColorIO/ops/lut1d/Lut1DOp.cpp +++ b/src/OpenColorIO/ops/lut1d/Lut1DOp.cpp @@ -123,8 +123,7 @@ void Lut1DOp::combineWith(OpRcPtrVec & ops, ConstOpRcPtr & secondOp) const const auto compFlag = Lut1DOpData::COMPOSE_RESAMPLE_BIG; auto thisLut = lut1DData(); Lut1DOpDataRcPtr result = Lut1DOpData::Compose(thisLut, secondLut, compFlag); - auto composedOp = std::make_shared(result); - ops.push_back(composedOp); + ops.emplace_back(std::make_shared(result)); } bool Lut1DOp::hasChannelCrosstalk() const @@ -188,7 +187,7 @@ void CreateLut1DOp(OpRcPtrVec & ops, lutData = lut->inverse(); } - ops.push_back(std::make_shared(lutData)); + ops.emplace_back(std::make_shared(lutData)); } void GenerateIdentityLut1D(float* img, int numElements, int numChannels) diff --git a/src/OpenColorIO/ops/lut3d/Lut3DOp.cpp b/src/OpenColorIO/ops/lut3d/Lut3DOp.cpp index 4f0de76f4d..741f95a699 100644 --- a/src/OpenColorIO/ops/lut3d/Lut3DOp.cpp +++ b/src/OpenColorIO/ops/lut3d/Lut3DOp.cpp @@ -175,8 +175,7 @@ void Lut3DOp::combineWith(OpRcPtrVec & ops, ConstOpRcPtr & secondOp) const auto secondLut = typedRcPtr->lut3DData(); auto thisLut = lut3DData(); auto composed = Lut3DOpData::Compose(thisLut, secondLut); - auto composedOp = std::make_shared(composed); - ops.push_back(composedOp); + ops.emplace_back(std::make_shared(composed)); } bool Lut3DOp::hasChannelCrosstalk() const @@ -229,7 +228,7 @@ void CreateLut3DOp(OpRcPtrVec & ops, Lut3DOpDataRcPtr & lut, TransformDirection lutData = lut->inverse(); } - ops.push_back(std::make_shared(lutData)); + ops.emplace_back(std::make_shared(lutData)); } void CreateLut3DTransform(GroupTransformRcPtr & group, ConstOpRcPtr & op) diff --git a/src/OpenColorIO/ops/matrix/MatrixOp.cpp b/src/OpenColorIO/ops/matrix/MatrixOp.cpp index b593983128..1928700632 100644 --- a/src/OpenColorIO/ops/matrix/MatrixOp.cpp +++ b/src/OpenColorIO/ops/matrix/MatrixOp.cpp @@ -288,7 +288,7 @@ void CreateIdentityMatrixOp(OpRcPtrVec & ops, TransformDirection direction) matrix[15] = 1.0; const double offset[4] = { 0.0, 0.0, 0.0, 0.0 }; - ops.push_back(std::make_shared(matrix, + ops.emplace_back(std::make_shared(matrix, offset, direction)); } @@ -348,14 +348,14 @@ void CreateMatrixOp(OpRcPtrVec & ops, MatrixOpDataRcPtr & matrix, TransformDirec mat->setDirection(newDir); } - ops.push_back(std::make_shared(mat)); + ops.emplace_back(std::make_shared(mat)); } void CreateIdentityMatrixOp(OpRcPtrVec & ops) { MatrixOpDataRcPtr mat = MatrixOpData::CreateDiagonalMatrix(1.0); - ops.push_back(std::make_shared(mat)); + ops.emplace_back(std::make_shared(mat)); } /////////////////////////////////////////////////////////////////////////// diff --git a/src/OpenColorIO/ops/noop/NoOps.cpp b/src/OpenColorIO/ops/noop/NoOps.cpp index cf71b1fde5..690c0b9a08 100644 --- a/src/OpenColorIO/ops/noop/NoOps.cpp +++ b/src/OpenColorIO/ops/noop/NoOps.cpp @@ -103,7 +103,7 @@ bool DefinesGpuAllocation(const OpRcPtr & op) void CreateGpuAllocationNoOp(OpRcPtrVec & ops, const AllocationData & allocationData) { - ops.push_back( std::make_shared(allocationData) ); + ops.emplace_back( std::make_shared(allocationData) ); } @@ -233,7 +233,7 @@ void PartitionGPUOps(OpRcPtrVec & gpuPreOps, { for(unsigned int i=0; iclone() ); + gpuPreOps.emplace_back( ops[i]->clone() ); } } // Analytical -> 3D LUT -> analytical @@ -242,7 +242,7 @@ void PartitionGPUOps(OpRcPtrVec & gpuPreOps, // Handle analytical shader block before start index. for(int i=0; iclone() ); + gpuPreOps.emplace_back( ops[i]->clone() ); } // Get the GPU Allocation at the cross-over point @@ -275,13 +275,13 @@ void PartitionGPUOps(OpRcPtrVec & gpuPreOps, // Handle cpu lattice processing for(int i=gpuLut3DOpStartIndex; i<=gpuLut3DOpEndIndex; ++i) { - gpuLatticeOps.push_back( ops[i]->clone() ); + gpuLatticeOps.emplace_back( ops[i]->clone() ); } // And then handle the gpu post processing for(int i=gpuLut3DOpEndIndex+1; i<(int)ops.size(); ++i) { - gpuPostOps.push_back( ops[i]->clone() ); + gpuPostOps.emplace_back( ops[i]->clone() ); } } } @@ -365,7 +365,7 @@ std::string FileNoOp::getCacheID() const void CreateFileNoOp(OpRcPtrVec & ops, const std::string & fileReference) { - ops.push_back( std::make_shared(fileReference) ); + ops.emplace_back( std::make_shared(fileReference) ); } @@ -449,7 +449,7 @@ std::string LookNoOp::getCacheID() const void CreateLookNoOp(OpRcPtrVec & ops, const std::string & look) { - ops.push_back( std::make_shared(look) ); + ops.emplace_back( std::make_shared(look) ); } } // namespace OCIO_NAMESPACE diff --git a/src/OpenColorIO/ops/range/RangeOp.cpp b/src/OpenColorIO/ops/range/RangeOp.cpp index f708908fbd..0dd63f9b19 100644 --- a/src/OpenColorIO/ops/range/RangeOp.cpp +++ b/src/OpenColorIO/ops/range/RangeOp.cpp @@ -238,7 +238,7 @@ void CreateRangeOp(OpRcPtrVec & ops, RangeOpDataRcPtr & rangeData, TransformDire range->setDirection(newDir); } - ops.push_back(std::make_shared(range)); + ops.emplace_back(std::make_shared(range)); } /////////////////////////////////////////////////////////////////////////// diff --git a/tests/cpu/OpOptimizers_tests.cpp b/tests/cpu/OpOptimizers_tests.cpp index 5b2f771a72..5cb81f3fc9 100644 --- a/tests/cpu/OpOptimizers_tests.cpp +++ b/tests/cpu/OpOptimizers_tests.cpp @@ -311,9 +311,12 @@ OCIO_ADD_TEST(OpOptimizers, combine_ops) 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); + auto count = OCIO::CombineOps(ops, OCIO::OPTIMIZATION_ALL); // CombineOps removes at most one pair on each call, repeat to combine all pairs. - OCIO::CombineOps(ops, OCIO::OPTIMIZATION_ALL); + OCIO_CHECK_EQUAL(count, 1); + OCIO_CHECK_EQUAL(ops.size(), 2); + count = OCIO::CombineOps(ops, OCIO::OPTIMIZATION_ALL); + OCIO_CHECK_EQUAL(count, 1); OCIO_CHECK_EQUAL(ops.size(), 1); } @@ -325,7 +328,9 @@ OCIO_ADD_TEST(OpOptimizers, combine_ops) 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); + auto count = OCIO::CombineOps(ops, OCIO::OPTIMIZATION_ALL); + // Note: the number of optimisations is 1 even though they both get removed + OCIO_CHECK_EQUAL(count, 1); OCIO_CHECK_EQUAL(ops.size(), 0); } @@ -355,8 +360,11 @@ OCIO_ADD_TEST(OpOptimizers, combine_ops) OCIO_CHECK_EQUAL(ops.size(), 5); OCIO::CombineOps(ops, OCIO::OPTIMIZATION_ALL); // CombineOps removes at most one pair on each call, repeat to combine all pairs. + OCIO_CHECK_EQUAL(ops.size(), 4); OCIO::CombineOps(ops, OCIO::OPTIMIZATION_ALL); + OCIO_CHECK_EQUAL(ops.size(), 3); OCIO::CombineOps(ops, OCIO::OPTIMIZATION_ALL); + OCIO_CHECK_EQUAL(ops.size(), 2); OCIO::CombineOps(ops, OCIO::OPTIMIZATION_ALL); OCIO_CHECK_EQUAL(ops.size(), 1); } @@ -374,6 +382,7 @@ OCIO_ADD_TEST(OpOptimizers, combine_ops) OCIO_CHECK_EQUAL(ops.size(), 4); OCIO::CombineOps(ops, OCIO::OPTIMIZATION_ALL); // CombineOps removes at most one pair on each call, repeat to combine all pairs. + OCIO_CHECK_EQUAL(ops.size(), 2); OCIO::CombineOps(ops, OCIO::OPTIMIZATION_ALL); OCIO_CHECK_EQUAL(ops.size(), 0); } @@ -1315,6 +1324,41 @@ OCIO_ADD_TEST(OpOptimizers, dynamic_ops) } // 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_EQUAL(OCIO::RemoveDynamicProperties(ops, OCIO::OPTIMIZATION_DEFAULT), 0); + OCIO_REQUIRE_EQUAL(ops.size(), 3); + + OCIO_CHECK_ASSERT(ops[1]->isDynamic()); + + OCIO_CHECK_EQUAL(ops[0]->getInfo(), ""); + OCIO_CHECK_EQUAL(ops[1]->getInfo(), ""); + OCIO_CHECK_EQUAL(ops[2]->getInfo(), ""); + + // It does get optimized if flag is set. + OCIO_CHECK_ASSERT(HasFlag(OCIO::OPTIMIZATION_ALL, OCIO::OPTIMIZATION_NO_DYNAMIC_PROPERTIES)); + OCIO_CHECK_NO_THROW(ops.finalize()); + OCIO_CHECK_EQUAL(OCIO::RemoveDynamicProperties(ops, OCIO::OPTIMIZATION_ALL), 1); + OCIO_REQUIRE_EQUAL(ops.size(), 3); + + OCIO_CHECK_ASSERT(!ops[1]->isDynamic()); + } + { OCIO::OpRcPtrVec ops; OCIO_CHECK_NO_THROW(OCIO::CreateMatrixOp(ops, matrix, OCIO::TRANSFORM_DIR_FORWARD)); @@ -1554,7 +1598,7 @@ OCIO_ADD_TEST(OpOptimizers, opt_prefix_test1) // First one is the file no op. OCIO_CHECK_EQUAL(ops.size(), 12); - OCIO_CHECK_NO_THROW(OCIO::RemoveNoOpTypes(ops)); + OCIO_CHECK_NO_THROW(OCIO::RemoveNoOpTypes(ops, OCIO::OPTIMIZATION_ALL)); OCIO_CHECK_EQUAL(ops.size(), 11); diff --git a/tests/cpu/Op_tests.cpp b/tests/cpu/Op_tests.cpp index d6c1fb3c00..0db960d3ef 100644 --- a/tests/cpu/Op_tests.cpp +++ b/tests/cpu/Op_tests.cpp @@ -1,6 +1,7 @@ // SPDX-License-Identifier: BSD-3-Clause // Copyright Contributors to the OpenColorIO Project. +#include #include "Op.cpp"