diff --git a/include/nbl/asset/material_compiler3/CTrueIR.h b/include/nbl/asset/material_compiler3/CTrueIR.h index 7abff85593..b932eb9999 100644 --- a/include/nbl/asset/material_compiler3/CTrueIR.h +++ b/include/nbl/asset/material_compiler3/CTrueIR.h @@ -248,6 +248,12 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! } #define HASH_OPTIONALS_HASH(HANDLE) if (HANDLE) {HASH_REQUIREDS_HASH(HANDLE);} else {hasher << core::blake3_hash_t::EmptyInput();} + virtual _typed_pointer_type copy(CTrueIR* ir) const = 0; +#define COPY_DEFAULT_IMPL inline _typed_pointer_type copy(CTrueIR* ir) const override final \ + { \ + return CNodePool::copyNode > >(this,ir); \ + } + // Each node is final and immutable, has a precomputed hash for the whole subtree beneath it. // Debug info does not form part of the hash, so can get wildly replaced. core::blake3_hash_t hash = core::blake3_hash_t::EmptyInput(); @@ -341,6 +347,15 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! return true; } + inline _typed_pointer_type copy(CTrueIR* ir) const override final + { + auto& pool = ir->getObjectPool(); + const auto copyH = pool.emplace > >(getState()); + if (auto* const copy = pool.deref(copyH); copyH) + *copy = *this; + return copyH; + } + typed_pointer_type child[1] = {{}}; }; // Note that this is not a root node, its a flipped leaf! @@ -366,6 +381,9 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! typed_pointer_type contributor = {}; // if null then assumed to be 1 typed_pointer_type factor = {}; + + protected: + COPY_DEFAULT_IMPL }; // One BRDF or BTDF component of a layer is represented as // f(w_i,w_o) = Sum_i^N Product_j^{N_i} h_{ij}(w_i,w_o) l_i(w_i,w_o) @@ -405,6 +423,9 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! typed_pointer_type product = {}; // the rest node is ... _typed_pointer_type rest = {}; + + protected: + COPY_DEFAULT_IMPL }; // For codegen, we can compute total uncorrelated layering by convolving every `h_{ij}(w_i,w_o) l_i(w_i,w_o)` term in the layer above with layer below @@ -442,6 +463,9 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! _typed_pointer_type coated = {}; // optional, indicates a "sibling" transmission thats next to this one _typed_pointer_type next = {}; + + protected: + COPY_DEFAULT_IMPL }; // The oriented layer is a layer with already all the Etas reciprocated, etc. class COrientedLayer final : public obj_pool_type::INonTrivial, public INode @@ -465,6 +489,9 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! typed_pointer_type brdfTop = {}; // this node must be non-null until the last layer typed_pointer_type firstTransmission = {}; + + protected: + COPY_DEFAULT_IMPL }; // class IFactorLeaf : public IFactor @@ -653,6 +680,12 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! inline uint8_t getSpectralBins() const override final {return getKnotCount();} NBL_API2 void printDot(std::ostringstream& sstr, const core::string& selfID) const override final; + + protected: + inline _typed_pointer_type copy(CTrueIR* ir) const override final + { + return static_cast(this)->copy(ir->getObjectPool()); + } }; using CSpectralVariableFactor = CSpectralVariable; @@ -700,6 +733,9 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! // TODO: semantic flags/metadata (symmetries of the profile) NBL_API2 void printDot(std::ostringstream& sstr, const core::string& selfID) const override final; + + protected: + COPY_DEFAULT_IMPL }; // To use a bump map, the Material needs to be provided UVs (which can or can not have associated tangents and smooth normals), but that's the responsibility of backend. @@ -755,6 +791,9 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! inline const std::string_view getTypeName() const override {return TYPE_NAME_STR(CDeltaTransmission);} inline CDeltaTransmission() = default; + + protected: + COPY_DEFAULT_IMPL }; class IBxDFWithNDF : public IBxDF { @@ -786,6 +825,9 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! inline COrenNayar() = default; NBL_API2 void printDot(std::ostringstream& sstr, const core::string& selfID) const override final; + + protected: + COPY_DEFAULT_IMPL }; class CCookTorrance final : public obj_pool_type::INonTrivial, public IBxDFWithNDF { @@ -819,6 +861,9 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! // producing an estimator with just Masking and Shadowing function ratios. The reason is because we can simplify our IR by separating out // BRDFs and BTDFs components into separate expressions, and also importance sample much better. typed_pointer_type orientedRealEta = {}; + + protected: + COPY_DEFAULT_IMPL }; //! Parameter Nodes //! Basic factor nodes @@ -965,6 +1010,8 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! } return true; } + + uint32_t deepCopy(typed_pointer_type* out, const std::span> orig, const CTrueIR* srcIR=nullptr); // TODO: Optimization passes on the IR // It is the backend's job to handle: diff --git a/src/nbl/asset/material_compiler3/CTrueIR.cpp b/src/nbl/asset/material_compiler3/CTrueIR.cpp index 497bdf9f23..0731fbeb52 100644 --- a/src/nbl/asset/material_compiler3/CTrueIR.cpp +++ b/src/nbl/asset/material_compiler3/CTrueIR.cpp @@ -110,6 +110,210 @@ CTrueIR::SBasicNodes::SBasicNodes(CTrueIR* ir) } } +uint32_t CTrueIR::deepCopy(typed_pointer_type* out, + const std::span> orig, const CTrueIR* srcIR) +{ + auto& dstPool = getObjectPool(); + // if not explicitly other, then its ours + if (!srcIR) + srcIR = this; + const auto& srcPool = srcIR->getObjectPool(); + + core::vector> stack; + stack.reserve(orig.size() + 32); + for (const auto& o : orig) + stack.push_back(o); + // use a hashmap to not explore whole DAG + core::unordered_map, typed_pointer_type> substitutions; + while (!stack.empty()) + { + const auto entry = stack.back(); + const auto* const node = srcPool.deref(entry); + if (!node) // this is an error + return {}; + const auto nodeType = node->getFinalType(); + if (auto& copyH = substitutions[entry]; !copyH) + { + switch (nodeType) + { + case INode::EFinalType::CFactorCombiner: + { + const auto* combiner = dynamic_cast(node); + const uint8_t childCount = combiner->getState().childCount; + if (childCount) + { + for (uint8_t c = 0; c < childCount; c++) + { + const auto childH = combiner->getChildHandle(c); + if (auto child = srcPool.deref(childH); !child) + continue; // this is not an error + stack.push_back(childH); + } + } + break; + } + case INode::EFinalType::CContributorSum: + { + const auto* contributeSum = dynamic_cast(node); + if (contributeSum) + { + typed_pointer_type children[] = { contributeSum->product, contributeSum->rest }; + for (const auto childH : children) + { + if (auto child = srcPool.deref(childH); !child) + continue; + stack.push_back(childH); + } + } + break; + } + case INode::EFinalType::CCorellatedTransmission: + { + const auto* transmission = dynamic_cast(node); + if (transmission) + { + typed_pointer_type children[] = { transmission->btdf, transmission->brdfBottom, transmission->next }; + for (const auto childH : children) + { + if (auto child = srcPool.deref(childH); !child) + continue; + stack.push_back(childH); + } + //layerStack.push_back(transmission->coated); TODO: how to handle coated? + } + break; + } + case INode::EFinalType::CWeightedContributor: + { + const auto* contributor = dynamic_cast(node); + if (contributor) + { + typed_pointer_type children[] = { contributor->contributor, contributor->factor }; + for (const auto childH : children) + { + if (auto child = srcPool.deref(childH); !child) + continue; + stack.push_back(childH); + } + } + break; + } + case INode::EFinalType::CCookTorrance: + { + const auto* ct = dynamic_cast(node); + if (ct) + if (const auto eta = srcPool.deref(ct->orientedRealEta); eta) + stack.push_back(ct->orientedRealEta); + break; + } + default: + break; + } + + // copy copies everything including child handles + copyH = node->copy(this); + if (!copyH) + return {}; + } + else + { + auto* const copy = dstPool.deref(copyH); + auto setCopyChildren = [&](const typed_pointer_type& src, typed_pointer_type& cop) -> void + { + if (auto child = srcPool.deref(src); child) + { + auto found = substitutions.find(src); + assert(found != substitutions.end()); + cop = _static_cast>(found->second); + } + }; + switch (nodeType) + { + case INode::EFinalType::CFactorCombiner: + { + const auto* combiner = dynamic_cast(node); + const uint8_t childCount = combiner->getState().childCount; + if (childCount) + { + for (uint8_t c = 0; c < childCount; c++) + { + const auto childH = combiner->getChildHandle(c); + if (!childH) + continue; + auto found = substitutions.find(childH); + assert(found != substitutions.end()); + + const auto child = _static_cast>(found->second); + dynamic_cast(copy)->setChildHandle(c, child); + } + } + break; + } + case INode::EFinalType::CContributorSum: + { + const auto* contributeSum = dynamic_cast(node); + auto* copySum = dynamic_cast(copy); + if (contributeSum && copySum) + { + setCopyChildren(contributeSum->product, copySum->product); + setCopyChildren(contributeSum->product, copySum->product); + } + break; + } + case INode::EFinalType::CCorellatedTransmission: + { + const auto* transmission = dynamic_cast(node); + auto* copyTransmission = dynamic_cast(copy); + if (transmission && copyTransmission) + { + setCopyChildren(transmission->btdf, copyTransmission->btdf); + setCopyChildren(transmission->brdfBottom, copyTransmission->brdfBottom); + setCopyChildren(transmission->next, copyTransmission->next); + //layerStack.push_back(transmission->coated); TODO: how to handle coated? + } + break; + } + case INode::EFinalType::CWeightedContributor: + { + const auto* contributor = dynamic_cast(node); + auto* copyContributor = dynamic_cast(copy); + if (contributor && copyContributor) + { + setCopyChildren(contributor->contributor, copyContributor->contributor); + setCopyChildren(contributor->factor, copyContributor->factor); + } + break; + } + case INode::EFinalType::CCookTorrance: + { + const auto* ct = dynamic_cast(node); + auto* copyCt = dynamic_cast(copy); + if (ct && copyCt) + setCopyChildren(ct->orientedRealEta, copyCt->orientedRealEta); + break; + } + default: + break; + } + stack.pop_back(); + } + } + + uint32_t invalidCount = 0; + auto copies = out; + for (const auto& o : orig) + { + auto copy = substitutions[o]; + const auto* const node = dstPool.deref(copy); + if (!node) // this is invalid + invalidCount++; + else + *copies = copy; + copies++; + } + return invalidCount; +} + void CTrueIR::SDotPrinter::operator()(std::ostringstream& output) { output << "digraph {\n";