diff --git a/include/nbl/asset/material_compiler3/CTrueIR.h b/include/nbl/asset/material_compiler3/CTrueIR.h index 496ae3487f..2814fec51e 100644 --- a/include/nbl/asset/material_compiler3/CTrueIR.h +++ b/include/nbl/asset/material_compiler3/CTrueIR.h @@ -12,7 +12,6 @@ #include "nbl/asset/format/EColorSpace.h" #include "nbl/asset/ICPUImageView.h" - namespace nbl::asset::material_compiler3 { @@ -20,6 +19,7 @@ namespace nbl::asset::material_compiler3 // They appear "flipped upside down", its expected our backends will evaluate contributors first, and then bother with the attenuators. class CTrueIR : public CNodePool // TODO: turn into an asset! { + using block_allocator_type = CNodePool::obj_pool_type::block_allocator_type; template using _typed_pointer_type = CNodePool::obj_pool_type::mem_pool_type::typed_pointer_type; @@ -232,11 +232,34 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! return hasher.operator core::blake3_hash_t(); } + // Only sane child count allowed + virtual uint8_t getChildCount() const = 0; + inline _typed_pointer_type getChildHandle(const uint8_t ix) + { + if (ix < getChildCount()) + return getChildHandle_impl(ix); + return {}; + } + inline _typed_pointer_type getChildHandle(const uint8_t ix) const + { + auto retval = const_cast(this)->getChildHandle(ix); + return retval; + } + virtual inline std::string_view getChildName_impl(const uint8_t ix) const { return ""; } virtual inline void printDot(std::ostringstream& sstr, const core::string& selfID) const {} protected: friend class CTrueIR; + // child managment + virtual inline _typed_pointer_type getChildHandle_impl(const uint8_t ix) const { assert(false); return {}; } + inline void setChild(const uint8_t ix, _typed_pointer_type newChild) + { + assert(ix < getChildCount()); + setChild_impl(ix, newChild); + } + virtual inline void setChild_impl(const uint8_t ix, _typed_pointer_type newChild) { assert(false); } + inline bool recomputeHash(const obj_pool_type& pool) { hash = computeHash(pool); @@ -251,6 +274,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(); @@ -316,16 +345,12 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! padding = std::bit_cast(state); } + inline uint8_t getChildCount() const override final { return getState().childCount; } + // Only sane child count allowed - inline typed_pointer_type getChildHandle(const uint8_t ix) const - { - if (ix handle) { - if (ix getChildHandle_impl(const uint8_t ix) const override final { return child[ix]; } + inline void setChild_impl(const uint8_t ix, _typed_pointer_type newChild) override final { child[ix] = block_allocator_type::_static_cast(newChild); } + + 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) + { + for (uint64_t c = 0; c < getState().childCount; c++) + { + auto childHandle = child[c]; + if (auto* const _child = pool.deref(childHandle); _child) + copy->child[c] = block_allocator_type::_static_cast(_child->copy(ir)); + } + } + return copyH; + } + typed_pointer_type child[1] = {{}}; }; // Note that this is not a root node, its a flipped leaf! @@ -362,6 +406,8 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! public: inline EFinalType getFinalType() const override {return EFinalType::CWeightedContributor;} + inline uint8_t getChildCount() const override final { return 2; } + inline const std::string_view getTypeName() const override {return TYPE_NAME_STR(CWeightedContributor);} inline std::string_view getChildName_impl(const uint8_t ix) const override final { return ix ? "factor" : "contributor"; } @@ -369,6 +415,23 @@ 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 + + inline typed_pointer_type getChildHandle_impl(const uint8_t ix) const override final + { + if (ix) + return factor; + return contributor; + } + inline void setChild_impl(const uint8_t ix, _typed_pointer_type newChild) override final + { + if (ix) + factor = block_allocator_type::_static_cast(newChild); + else + contributor = block_allocator_type::_static_cast(newChild); + } }; // 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) @@ -401,6 +464,8 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! public: inline EFinalType getFinalType() const override {return EFinalType::CContributorSum;} + inline uint8_t getChildCount() const override final { return 2; } + inline const std::string_view getTypeName() const override {return TYPE_NAME_STR(CContributorSum);} inline std::string_view getChildName_impl(const uint8_t ix) const override final { return ix ? "rest" : "product"; } @@ -408,6 +473,23 @@ 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 + + inline typed_pointer_type getChildHandle_impl(const uint8_t ix) const override final + { + if (ix) + return rest; + return product; + } + inline void setChild_impl(const uint8_t ix, _typed_pointer_type newChild) override final + { + if (ix) + rest = block_allocator_type::_static_cast(newChild); + else + product = block_allocator_type::_static_cast(newChild); + } }; // 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 @@ -431,8 +513,23 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! public: inline EFinalType getFinalType() const override {return EFinalType::CCorellatedTransmission;} + inline uint8_t getChildCount() const override final { return 4; } + inline const std::string_view getTypeName() const override {return TYPE_NAME_STR(CCorellatedTransmission);} - inline std::string_view getChildName_impl(const uint8_t ix) const override final { return ix ? (ix > 1 ? "next" : "brdfBottom") : "btdf"; } + inline std::string_view getChildName_impl(const uint8_t ix) const override final + { + switch (ix) + { + case 1: + return "brdfBottom"; + case 2: + return "coated"; + case 3: + return "next"; + default: + return "btdf"; + } + } // you can set the children later inline CCorellatedTransmission() = default; @@ -445,6 +542,41 @@ 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 + + inline typed_pointer_type getChildHandle_impl(const uint8_t ix) const override final + { + switch (ix) + { + case 1: + return brdfBottom; + case 2: + return coated; + case 3: + return next; + default: + return btdf; + } + } + inline void setChild_impl(const uint8_t ix, _typed_pointer_type newChild) override final + { + switch (ix) + { + case 1: + brdfBottom = block_allocator_type::_static_cast(newChild); + break; + case 2: + coated = block_allocator_type::_static_cast(newChild); + break; + case 3: + next = block_allocator_type::_static_cast(newChild); + break; + default: + btdf = block_allocator_type::_static_cast(newChild); + } + } }; // The oriented layer is a layer with already all the Etas reciprocated, etc. class COrientedLayer final : public obj_pool_type::INonTrivial, public INode @@ -459,6 +591,8 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! public: inline EFinalType getFinalType() const override {return EFinalType::COrientedLayer;} + inline uint8_t getChildCount() const override final { return 2; } + inline const std::string_view getTypeName() const override {return TYPE_NAME_STR(COrientedLayer);} // you can set the children later @@ -468,6 +602,24 @@ 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 + + inline typed_pointer_type getChildHandle_impl(const uint8_t ix) const override final + { + if (ix) + return firstTransmission; + else + return brdfTop; + } + inline void setChild_impl(const uint8_t ix, _typed_pointer_type newChild) override final + { + if (ix) + firstTransmission = block_allocator_type::_static_cast(newChild); + else + brdfTop = block_allocator_type::_static_cast(newChild); + } }; // class IFactorLeaf : public IFactor @@ -663,10 +815,18 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! return EFinalType::CSpectralVariable; } + inline uint8_t getChildCount() const override final { return 0; } + // 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; @@ -696,6 +856,8 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! public: inline EFinalType getFinalType() const override {return EFinalType::CEmitter;} + inline uint8_t getChildCount() const override final { return 0; } + inline const std::string_view getTypeName() const override {return TYPE_NAME_STR(CEmitter);} inline bool isEmitter() const override {return true;} @@ -714,6 +876,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. @@ -766,9 +931,14 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! public: inline EFinalType getFinalType() const override {return EFinalType::CDeltaTransmission;} + inline uint8_t getChildCount() const override final { return 0; } + inline const std::string_view getTypeName() const override {return TYPE_NAME_STR(CDeltaTransmission);} inline CDeltaTransmission() = default; + + protected: + COPY_DEFAULT_IMPL }; class IBxDFWithNDF : public IBxDF { @@ -795,11 +965,16 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! inline EFinalType getFinalType() const override {return EFinalType::COrenNayar;} + inline uint8_t getChildCount() const override final { return 0; } + inline const std::string_view getTypeName() const override {return TYPE_NAME_STR(COrenNayar);} 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 { @@ -817,7 +992,10 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! public: inline EFinalType getFinalType() const override {return EFinalType::CCookTorrance;} + inline uint8_t getChildCount() const override final { return 1; } + inline const std::string_view getTypeName() const override {return TYPE_NAME_STR(CCookTorrance);} + inline std::string_view getChildName_impl(const uint8_t ix) const override final { return "orientedRealEta"; } inline CCookTorrance() = default; @@ -833,6 +1011,12 @@ 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 + + inline typed_pointer_type getChildHandle_impl(const uint8_t ix) const override final { return orientedRealEta; } + inline void setChild_impl(const uint8_t ix, _typed_pointer_type newChild) override final { orientedRealEta = block_allocator_type::_static_cast(newChild); } }; //! Basic factor nodes // Effective transparency = exp2(log2(perpTransmittance)*thickness/dot(refract(V,X,eta),X)) = exp2(log2(perpTransmittance)*thickness*inversesqrt(1.f+(LdotX-1)*rcpEta)) @@ -851,6 +1035,8 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! public: inline EFinalType getFinalType() const override {return EFinalType::CBeer;} + inline uint8_t getChildCount() const override final { return 2; } + inline const std::string_view getTypeName() const override {return TYPE_NAME_STR(CBeer);} inline std::string_view getChildName_impl(const uint8_t ix) const override final {return ix ? "Thickness":"Perpendicular\\nTransmittance";} @@ -863,6 +1049,23 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! typed_pointer_type thickness = {}; // can be worked out by analyzing what we point to, but not needed uint8_t channels = 3; + + protected: + COPY_DEFAULT_IMPL + + inline typed_pointer_type getChildHandle_impl(const uint8_t ix) const override final + { + if (ix) + return thickness; + return perpTransmittance; + } + inline void setChild_impl(const uint8_t ix, _typed_pointer_type newChild) override final + { + if (ix) + thickness = block_allocator_type::_static_cast(newChild); + else + perpTransmittance = block_allocator_type::_static_cast(newChild); + } }; class CFresnel final : public obj_pool_type::INonTrivial, public IFactorLeaf { @@ -885,6 +1088,8 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! public: inline EFinalType getFinalType() const override {return EFinalType::CFresnel;} + inline uint8_t getChildCount() const override final { return 2; } + inline const std::string_view getTypeName() const override {return TYPE_NAME_STR(CFresnel);} inline std::string_view getChildName_impl(const uint8_t ix) const override final {return ix ? "Imaginary":"Real";} @@ -899,6 +1104,23 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! uint8_t reciprocateEtas : 1 = false; // can be worked out by analyzing what we point to, but not needed uint8_t channels : 7 = 3; + + protected: + COPY_DEFAULT_IMPL + + inline typed_pointer_type getChildHandle_impl(const uint8_t ix) const override final + { + if (ix) + return orientedImagEta; + return orientedRealEta; + } + inline void setChild_impl(const uint8_t ix, _typed_pointer_type newChild) override final + { + if (ix) + orientedImagEta = block_allocator_type::_static_cast(newChild); + else + orientedRealEta = block_allocator_type::_static_cast(newChild); + } }; class CThinInfiniteScatterCorrection final : public obj_pool_type::INonTrivial, public IFactorLeaf { @@ -917,6 +1139,8 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! public: inline EFinalType getFinalType() const override {return EFinalType::CThinInfiniteScatterCorrection;} + inline uint8_t getChildCount() const override final { return 3; } + inline const std::string_view getTypeName() const override {return TYPE_NAME_STR(CThinInfiniteScatterCorrection);} inline std::string_view getChildName_impl(const uint8_t ix) const override final {return ix ? (ix>1 ? "reflectanceBottom":"extinction"):"reflectanceTop";} @@ -931,7 +1155,29 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! typed_pointer_type reflectanceBottom = {}; // can be worked out by analyzing what we point to, but not needed uint8_t channels = 3; + + protected: + COPY_DEFAULT_IMPL + + inline typed_pointer_type getChildHandle_impl(const uint8_t ix) const override final + { + if (ix > 1) + return reflectanceBottom; + if (ix) + return extinction; + return reflectanceTop; + } + inline void setChild_impl(const uint8_t ix, _typed_pointer_type newChild) override final + { + if (ix > 1) + reflectanceBottom = block_allocator_type::_static_cast(newChild); + if (ix) + extinction = block_allocator_type::_static_cast(newChild); + else + reflectanceTop = block_allocator_type::_static_cast(newChild); + } }; +#undef COPY_DEFAULT_IMPL #undef TYPE_NAME_STR #undef HASH_THE_HASH @@ -1075,6 +1321,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..3874b9472b 100644 --- a/src/nbl/asset/material_compiler3/CTrueIR.cpp +++ b/src/nbl/asset/material_compiler3/CTrueIR.cpp @@ -110,6 +110,76 @@ 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); + assert(!node); + const auto childCount = node->getChildCount(); + if (auto& copyH = substitutions[entry]; !copyH) + { + uint8_t pushedChildren = 0; + for (uint8_t c = 0; c < childCount; c++) + { + const auto childH = node->getChildHandle(c); + if (auto child = srcPool.deref(childH); !child) + continue; // this is not an error + stack.push_back(childH); + pushedChildren++; + } + + // copy copies everything including child handles + copyH = node->copy(this); + if (!copyH) // node invalid so pop stack until copy and all added children removed + for (uint8_t i = 0; i < pushedChildren + 1; i++) + stack.pop_back(); + } + else + { + auto* const copy = dstPool.deref(copyH); + for (uint8_t c = 0; c < childCount; c++) + { + const auto childH = node->getChildHandle(c); + if (!childH) + continue; + auto found = substitutions.find(childH); + assert(found != substitutions.end()); + copy->setChild(c, found->second); + } + 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"; @@ -129,10 +199,12 @@ void CTrueIR::SDotPrinter::operator()(std::ostringstream& output) if (!node) continue; output << "\n\t" << m_ir->getLabelledNodeID(entry); - auto printChildren = [&](std::span> children, const INode* node)->void { - uint32_t childIx = 0u; - for (const auto childHandle : children) + const auto childCount = node->getChildCount(); + if (childCount) + { + for (auto childIx = 0; childIx < childCount; childIx++) { + const auto childHandle = node->getChildHandle(childIx); if (const auto child = m_ir->getObjectPool().deref(childHandle); child) { output << "\n\t" << nodeID << " -> " << m_ir->getNodeID(childHandle) << "[label=\"" << node->getChildName_impl(childIx) << "\"]"; @@ -142,84 +214,7 @@ void CTrueIR::SDotPrinter::operator()(std::ostringstream& output) nodeStack.push_back(childHandle); visitedNodes.insert(childHandle); } - childIx++; - } - }; - switch (node->getFinalType()) - { - case INode::EFinalType::CFactorCombiner: - { - const auto* combiner = dynamic_cast(node); - const auto state = combiner->getState(); - const auto childCount = state.childCount; - if (childCount) - { - for (auto childIx = 0; childIx < childCount; childIx++) - { - const auto childHandle = combiner->getChildHandle(childIx); - if (const auto child = m_ir->getObjectPool().deref(childHandle); child) - { - output << "\n\t" << nodeID << " -> " << m_ir->getNodeID(childHandle); - const auto visited = visitedNodes.find(childHandle); - if (visited != visitedNodes.end()) - continue; - nodeStack.push_back(childHandle); - visitedNodes.insert(childHandle); - } - } - } - break; - } - case INode::EFinalType::CContributorSum: - { - const auto* contributeSum = dynamic_cast(node); - if (contributeSum) - { - typed_pointer_type children[] = {contributeSum->product, contributeSum->rest}; - printChildren(children, node); } - break; - } - case INode::EFinalType::CCorellatedTransmission: - { - const auto* transmission = dynamic_cast(node); - if (transmission) - { - typed_pointer_type children[] = { transmission->btdf, transmission->brdfBottom, transmission->next }; - printChildren(children, node); - layerStack.push_back(transmission->coated); - } - break; - } - case INode::EFinalType::CWeightedContributor: - { - const auto* contributor = dynamic_cast(node); - if (contributor) - { - typed_pointer_type children[] = { contributor->contributor, contributor->factor }; - printChildren(children, node); - } - break; - } - case INode::EFinalType::CCookTorrance: - { - const auto* ct = dynamic_cast(node); - if (ct) - { - if (const auto eta = m_ir->getObjectPool().deref(ct->orientedRealEta); eta) - { - output << "\n\t" << nodeID << " -> " << m_ir->getNodeID(ct->orientedRealEta) << "[label=\"orientedRealEta\"]"; - const auto visited = visitedNodes.find(ct->orientedRealEta); - if (visited != visitedNodes.end()) - continue; - nodeStack.push_back(ct->orientedRealEta); - visitedNodes.insert(ct->orientedRealEta); - } - } - break; - } - default: - break; } // special printing node->printDot(output, nodeID);