From 559b9be58817390b593adbb2d7af2be487e00510 Mon Sep 17 00:00:00 2001 From: devshgraphicsprogramming Date: Fri, 15 May 2026 02:30:58 +0200 Subject: [PATCH 01/16] draft --- .../asset/material_compiler3/CFrontendIR.cpp | 60 +++++++++++++------ 1 file changed, 43 insertions(+), 17 deletions(-) diff --git a/src/nbl/asset/material_compiler3/CFrontendIR.cpp b/src/nbl/asset/material_compiler3/CFrontendIR.cpp index 5af5f25e01..fcf0352474 100644 --- a/src/nbl/asset/material_compiler3/CFrontendIR.cpp +++ b/src/nbl/asset/material_compiler3/CFrontendIR.cpp @@ -984,14 +984,36 @@ auto CFrontendIR::SAdd2IRSession::makeContributors(const CFrontendIR::typed_poin irChain.emplace_back().factor = {.handle=varH,.monochrome=monochrome}; break; } - case ast_expr_type_e::Other: + case ast_expr_type_e::Other: // the way this is written, we'd really benefit from a more "intimate" enforcing of the structure of Fresnel, Beer, etc. being identical between AST and IR { // astStack.pop_back(); - // TODO: We need to start a new `canonicalSum` and save a link to it in the current IR - args.logger.log("Unsupported AST Expression type \"%s\"",ELL_ERROR,system::to_string(astExprType).c_str()); - printSubtree(nodeH); - return (headH=errorRetval); + // shouldn't invalidate iterator, but underline our vector changes + pEntry = nullptr; + // do not hash yet! the children are invalid! + const auto funcH = static_cast(node)->createIRNode(btdfSubtree,srcAST,tmpIR.get()); + auto* const func = irPool.deref(funcH); + if (!func) + { + args.logger.log("Failed to Function Other IR Contributor from AST",ELL_ERROR); + printSubtree(nodeH); + return (headH=errorRetval); + } + // need to start a new `canonicalSum` for every input term of the function + // insert back to front so we get good `it` at the end + for (uint8_t c=node->getChildCount(); c;) + { + const auto astChild = node->getChildHandle(--c); + // record in the IR node child, the original AST child + func->setChildUnsafe(c,astChild); + // only add sum expressions for non-null children + if (!astChild) + continue; + it = canonicalSum.emplace(it); + it->astStack.push_back(astChild); + } + // what to do about monochrome? + irChain.emplace_back().factor = {.handle=funcH/*,.monochrome*/}; break; } default: @@ -1040,6 +1062,7 @@ auto CFrontendIR::SAdd2IRSession::makeContributors(const CFrontendIR::typed_poin if (it->negate && it->negate==0b111) factor->setChildHandle(i++,scalarNegation); bool monochromeNodes = true; +// TODO: handle the special function IR nodes for (auto itChain=irChain.begin()+1; itChain!=irChain.end(); itChain++) { // only one contributor per chain is allowed @@ -1102,28 +1125,31 @@ auto CFrontendIR::SAdd2IRSession::makeContributors(const CFrontendIR::typed_poin args.logger.log("Couldn't allocate a `CTrueIR::CContributorSum` node",ELL_ERROR); return (headH=errorRetval); } - // append it - if (it!=canonicalSum.begin()) + // append it to previous with a contributor, if it exists + for (auto prev=it; prev!=canonicalSum.begin(); prev--) { - auto itCopy = it; - irPool.deref((--itCopy)->sumTermH)->rest = it->sumTermH; + if (prev==it) // skip first loop + continue; + if (prev->hasContributor) + { + irPool.deref(prev->sumTermH)->rest = it->sumTermH; + break; + } } } else { // TODO: make without a contributor + // Do what the above does, just don't link stuff up assert(false); } } - // now hash in reverse + // now hash in reverse but only contributor containing sums (otherwise they're dependents) for (auto it=canonicalSum.rbegin(); it!=canonicalSum.rend(); it++) - it->sumTermH = tmpIR->hashNCache(it->sumTermH)._const_cast(); - // set result - if (!canonicalSum.empty()) - { - headH = canonicalSum.begin()->sumTermH; - canonicalSum.clear(); - } + if (it->hasContributor) + headH = it->sumTermH = tmpIR->hashNCache(it->sumTermH)._const_cast(); + // headH set by last in loop with contributor, so first with contributor in the sum linked list + canonicalSum.clear(); return headH; } From c942470fc988194b90084dba248b73575d08972c Mon Sep 17 00:00:00 2001 From: keptsecret Date: Fri, 15 May 2026 16:12:41 +0700 Subject: [PATCH 02/16] INode getCapabilities method for getting metadata capabilities, use in rewriteSingleLayer to set metadata --- .../nbl/asset/material_compiler3/CTrueIR.h | 12 ++- src/nbl/asset/material_compiler3/CTrueIR.cpp | 76 +++++++++++++++++++ 2 files changed, 87 insertions(+), 1 deletion(-) diff --git a/include/nbl/asset/material_compiler3/CTrueIR.h b/include/nbl/asset/material_compiler3/CTrueIR.h index afc3a6604e..9a08f031cd 100644 --- a/include/nbl/asset/material_compiler3/CTrueIR.h +++ b/include/nbl/asset/material_compiler3/CTrueIR.h @@ -219,6 +219,8 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! }; virtual EFinalType getFinalType() const = 0; + virtual uint16_t getCapabilities() const = 0; + const auto& getHash() const {return hash;} // only call once the nodes underneath are linked up (because it doesn't call recursively), returning empty hash means error/invalid node @@ -862,6 +864,8 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! public: inline EFinalType getFinalType() const override {return EFinalType::CEmitter;} + uint16_t getCapabilities() const override final; + inline uint8_t getChildCount() const override final { return 0; } inline const std::string_view getTypeName() const override {return TYPE_NAME_STR(CEmitter);} @@ -937,6 +941,8 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! public: inline EFinalType getFinalType() const override {return EFinalType::CDeltaTransmission;} + uint16_t getCapabilities() const override final; + inline uint8_t getChildCount() const override final { return 0; } inline const std::string_view getTypeName() const override {return TYPE_NAME_STR(CDeltaTransmission);} @@ -971,6 +977,8 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! inline EFinalType getFinalType() const override {return EFinalType::COrenNayar;} + uint16_t getCapabilities() const override final; + inline uint8_t getChildCount() const override final { return 0; } inline const std::string_view getTypeName() const override {return TYPE_NAME_STR(COrenNayar);} @@ -998,6 +1006,8 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! public: inline EFinalType getFinalType() const override {return EFinalType::CCookTorrance;} + uint16_t getCapabilities() const override final; + inline uint8_t getChildCount() const override final { return 1; } inline const std::string_view getTypeName() const override {return TYPE_NAME_STR(CCookTorrance);} @@ -1427,7 +1437,7 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! const CTrueIR* const src; CTrueIR* const dst; - const SMaterial::SMetadata* pMetadata; + SMaterial::SMetadata* pMetadata; }; struct SRewriteSession final { diff --git a/src/nbl/asset/material_compiler3/CTrueIR.cpp b/src/nbl/asset/material_compiler3/CTrueIR.cpp index d6f05bd104..b424af5e71 100644 --- a/src/nbl/asset/material_compiler3/CTrueIR.cpp +++ b/src/nbl/asset/material_compiler3/CTrueIR.cpp @@ -292,7 +292,62 @@ bool CTrueIR::SRewriteSession::rewriteSingleLayer(typed_pointer_typegetObjectPool(); + const auto& srcPool = args.src->getObjectPool(); + SMaterial::SMetadata metadata = {}; + + core::vector> stack; + stack.reserve(32); + stack.push_back(oriented); + // 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) + return false; + const auto childCount = node->getChildCount(); + if (auto& copyH = substitutions[entry]; !copyH) + { + for (uint8_t c = 0; c < childCount; c++) + { + const auto childH = node->getChildHandle(c); + if (auto child = srcPool.deref(childH); !child) + continue; + stack.push_back(childH); + } + + copyH = node->copy(args.dst); + if (!copyH) + return false; + } + 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(dstPool, c, found->second); + } + stack.pop_back(); + + if (node->getFinalType() == INode::EFinalType::CSpectralVariable) + { + const auto* factor = dynamic_cast(node); + metadata.usedUVSlots.set(factor->uvSlot(), true); + } + metadata.capabilities |= core::bitflag(node->getCapabilities()); + } + } + // TODO: combine layer meta with `retval.metadata` + args.pMetadata->usedUVSlots = metadata.usedUVSlots; + args.pMetadata->capabilities |= metadata.capabilities; // TODO: deduplicate, collect metadata and insert into current IR @@ -306,6 +361,11 @@ void CTrueIR::ISpectralVariableFactor::printDot(std::ostringstream& sstr, const printDotParameterSet(*pWonky(), getKnotCount(), sstr, selfID, {}); } +uint16_t CTrueIR::CEmitter::getCapabilities() const +{ + return static_cast(SMaterial::SMetadata::ECapabilityBits::NonSpatiallyVaryingEmissive | SMaterial::SMetadata::ECapabilityBits::NotBlackhole); +} + void CTrueIR::CEmitter::printDot(std::ostringstream& sstr, const core::string& selfID) const { if (profile) @@ -321,11 +381,27 @@ void CTrueIR::CEmitter::printDot(std::ostringstream& sstr, const core::string& s } } +uint16_t CTrueIR::CDeltaTransmission::getCapabilities() const +{ + return static_cast(SMaterial::SMetadata::ECapabilityBits::DeltaTransmissive | SMaterial::SMetadata::ECapabilityBits::NotBlackhole); +} + +uint16_t CTrueIR::COrenNayar::getCapabilities() const +{ + return static_cast(SMaterial::SMetadata::ECapabilityBits::OrenNayar | SMaterial::SMetadata::ECapabilityBits::NonDelta | SMaterial::SMetadata::ECapabilityBits::NotBlackhole); +} + void CTrueIR::COrenNayar::printDot(std::ostringstream& sstr, const core::string& selfID) const { ndfParams.printDot(sstr, selfID); } +uint16_t CTrueIR::CCookTorrance::getCapabilities() const +{ + // TODO: could be either beckmann or ggx, also anisotropic? maybe get from ndf params? + return static_cast(SMaterial::SMetadata::ECapabilityBits::GGX | SMaterial::SMetadata::ECapabilityBits::NonDelta | SMaterial::SMetadata::ECapabilityBits::NotBlackhole); +} + void CTrueIR::CCookTorrance::printDot(std::ostringstream& sstr, const core::string& selfID) const { ndfParams.printDot(sstr, selfID); From fa59ef96afbcfe7de7c6d7394e9ab4408161ab9c Mon Sep 17 00:00:00 2001 From: keptsecret Date: Mon, 18 May 2026 16:17:28 +0700 Subject: [PATCH 03/16] getCapabilities should not be abstract method --- include/nbl/asset/material_compiler3/CTrueIR.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/nbl/asset/material_compiler3/CTrueIR.h b/include/nbl/asset/material_compiler3/CTrueIR.h index 9a08f031cd..dff476af9d 100644 --- a/include/nbl/asset/material_compiler3/CTrueIR.h +++ b/include/nbl/asset/material_compiler3/CTrueIR.h @@ -219,7 +219,7 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! }; virtual EFinalType getFinalType() const = 0; - virtual uint16_t getCapabilities() const = 0; + virtual uint16_t getCapabilities() const { return 0; }; const auto& getHash() const {return hash;} From 25c4dbb492cd7d61c06b6ce10070b20e25e76eb1 Mon Sep 17 00:00:00 2001 From: devshgraphicsprogramming Date: Tue, 19 May 2026 16:20:53 +0200 Subject: [PATCH 04/16] compiles, logic is sound, needs testing --- .../asset/material_compiler3/CFrontendIR.h | 73 ++-- .../nbl/asset/material_compiler3/CTrueIR.h | 70 +-- .../core/alloc/SimpleBlockBasedAllocator.h | 7 +- .../asset/material_compiler3/CFrontendIR.cpp | 401 ++++++++++++------ 4 files changed, 360 insertions(+), 191 deletions(-) diff --git a/include/nbl/asset/material_compiler3/CFrontendIR.h b/include/nbl/asset/material_compiler3/CFrontendIR.h index f8427aba5e..2f93d187c0 100644 --- a/include/nbl/asset/material_compiler3/CFrontendIR.h +++ b/include/nbl/asset/material_compiler3/CFrontendIR.h @@ -338,14 +338,20 @@ class CFrontendIR final : public CNodePool NBL_API2 void printDot(std::ostringstream& sstr, const core::string& selfID) const override; - NBL_API2 ir_contributor_handle_t createIRNode(const bool forBTDF, const CFrontendIR* ast, CTrueIR* ir) const; + NBL_API2 ir_contributor_handle_t createIRNode(const bool forBTDF, const CFrontendIR* ast, CTrueIR* ir) const override; + }; + //! Nodes which must obey standard AST DFS traversal to evaluate using function composition and can't be reassociated + class IFunctionNode : public obj_pool_type::INonTrivial, public IExprNode + { + public: + virtual CTrueIR::typed_pointer_type createIRNode(const bool forBTDF, const CFrontendIR* ast, CTrueIR* ir) const = 0; }; //! Special nodes meant to be used as `CMul::rhs`, their behaviour depends on the IContributor in its MUL node relative subgraph. //! If you use a different contributor node type or normal for shading, these nodes get split and duplicated into two in our Final IR. //! Due to the Helmholtz Reciprocity handling outlined in the comments for the entire front-end you can usually count on these nodes //! getting applied once using `VdotH` for Cook-Torrance BRDF, twice using `VdotN` and `LdotN` for Diffuse BRDF, and using their //! complements before multiplication for BTDFs. - class IContributorDependant : public obj_pool_type::INonTrivial, public IExprNode + class IContributorDependant : public IFunctionNode { }; // Beer's Law Node, behaves differently depending on where it is: @@ -358,6 +364,8 @@ class CFrontendIR final : public CNodePool public: inline const std::string_view getTypeName() const override {return TYPE_NAME_STR(CBeer);} inline uint8_t getChildCount() const override {return 2;} + + NBL_API2 CTrueIR::typed_pointer_type createIRNode(const bool forBTDF, const CFrontendIR* ast, CTrueIR* ir) const override; // you can set the members later inline CBeer() = default; @@ -383,9 +391,11 @@ class CFrontendIR final : public CNodePool class CFresnel final : public IContributorDependant { public: + inline const std::string_view getTypeName() const override {return TYPE_NAME_STR(CFresnel);} inline uint8_t getChildCount() const override {return 2;} - inline const std::string_view getTypeName() const override {return TYPE_NAME_STR(CFresnel);} + NBL_API2 CTrueIR::typed_pointer_type createIRNode(const bool forBTDF, const CFrontendIR* ast, CTrueIR* ir) const override; + inline CFresnel() = default; // Already pre-divided Index of Refraction, e.g. exterior/interior since VdotG>0 the ray always arrives from the exterior. @@ -450,7 +460,7 @@ class CFrontendIR final : public CNodePool // ------------------ // // The obvious downside of using this node for transmission is that its impossible to get "milky" glass because a spread of refractions is needed - class CThinInfiniteScatterCorrection final : public obj_pool_type::INonTrivial, public IExprNode + class CThinInfiniteScatterCorrection final : public IFunctionNode { protected: COPY_DEFAULT_IMPL @@ -469,6 +479,8 @@ class CFrontendIR final : public CNodePool inline uint8_t getChildCount() const override final {return 3;} inline const std::string_view getTypeName() const override {return TYPE_NAME_STR(CThinInfiniteScatterCorrection);} + NBL_API2 CTrueIR::typed_pointer_type createIRNode(const bool forBTDF, const CFrontendIR* ast, CTrueIR* ir) const override; + // you can set the children later inline CThinInfiniteScatterCorrection() = default; @@ -801,37 +813,6 @@ class CFrontendIR final : public CNodePool bool btdfSubtree = false; // for going over layers in the AST core::vector layerStack; - // Distribute/hoist the ADD over the MUL. So replace a `MUL ADD A B C` with `ADD MUL A C MUL B C` - struct SFactor - { - struct SContributor - { - CTrueIR::typed_pointer_type handle = {}; - uint32_t monochrome : 1 = true; - uint32_t isContributor : 1 = true; - uint32_t zeroValue : 30 = 0; - }; - // these are the parts that need to be sorted - struct SOrdered - { - CTrueIR::typed_pointer_type handle = {}; - uint32_t monochrome : 1 = true; - // 0 for contributors and leaf factors, contributors can be told apart from leaf factors by having 0s in the whole DWORD here - uint32_t padding : 31 = 0; - }; - - // TODO: do this better, check whole DWORD is 0 - inline bool isContributor() const {return contributor.monochrome && contributor.isContributor && contributor.zeroValue==0;} - - union - { - CTrueIR::typed_pointer_type typeless; - // contributor is always monochrome, etc. - SContributor contributor; - // the fatter thing which actually can be monochrome, etc. - SOrdered factor = {}; - }; - }; // Holds the single `Product_j` of full expression in the form: // f(w_i,w_o) = Sum_i^N Product_j^{N_i} h_{ij}(w_i,w_o) l_i(w_i,w_o) // Everything on the `irChain` multiplies together, everything on the `astStack` before the current top is our relative through a MUL node. @@ -844,14 +825,24 @@ class CFrontendIR final : public CNodePool // Deal with optimizing this later on, not sure if `DoublyLinkedList` is appropriate, maybe I'd need a `DoublyLinkedBeadedCurtain` data structure // also the mulChain needs to be sorted later on, and doubly linked list is PITA to sort core::vector> astStack = {}; // its also a stack - core::vector irChain = {}; - // this is to help us hash in reverse properly - CTrueIR::typed_pointer_type sumTermH = {}; // Expressions for `h_{ij}` can also have ADD/MUL inside and we distribute and canonicalize them at the same time - uint8_t hasContributor : 1 = false; + // Distribute/hoist the ADD over the MUL. So replace a `MUL ADD A B C` with `ADD MUL A C MUL B C` + core::vector> irChain = {}; + // this is to help us hash in reverse properly + union + { + // without the `rest` filled out yet + CTrueIR::typed_pointer_type contribSumH; + // we're targetting a particular argument of this node, and this node shall have binary adds below it + CTrueIR::typed_pointer_type funcH = {}; + }; + uint16_t hasContributor : 1 = false; + // whether this is the last node ever to target this `funcH` + uint16_t finalFuncArgTail : 1 = false; + uint16_t targetArg : CTrueIR::IFunctionNode::MaxFuncArgsLog2 = 0; // extend later when allowing variable bucket count - uint8_t negate : 3 = 0b000; - uint8_t liveSpectralChannels : 3 = 0b111; + uint16_t negate : 3 = 0b000; + uint16_t liveSpectralChannels : 3 = 0b111; }; // We rework the expression Top down because Bottom up would require descent from the top anyway to find ADD within MUL. // The List> allows us to descend the AST dually with multiple traversals at once, so we never actually need to rewrite the AST into something else. diff --git a/include/nbl/asset/material_compiler3/CTrueIR.h b/include/nbl/asset/material_compiler3/CTrueIR.h index afc3a6604e..070e68ba30 100644 --- a/include/nbl/asset/material_compiler3/CTrueIR.h +++ b/include/nbl/asset/material_compiler3/CTrueIR.h @@ -252,7 +252,7 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! auto retval = const_cast(this)->getChildHandle(ix); return retval; } - inline void setChild(const obj_pool_type& pool, const uint8_t ix, _typed_pointer_type newChild) + inline void setChild(const obj_pool_type& pool, const uint8_t ix, _typed_pointer_type newChild) { assert(ix < getChildCount()); setChild_impl(pool, ix, newChild); @@ -323,10 +323,11 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! Mul = 0, Add = 1 }; + constexpr static inline uint8_t MaxChildCountLog2 = 6; struct SState { uint64_t type : 1 = Type::Mul; - uint64_t childCount : 6 = 0; + uint64_t childCount : MaxChildCountLog2 = 0; // which factors get `1-x` for an Add node or `-x` for a Mul node before getting used uint64_t childIxComplementMask : 57 = 0x0u; }; @@ -1024,15 +1025,41 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! inline typed_pointer_type getChildHandle_impl(const uint8_t ix) const override final { return orientedRealEta; } inline void setChild_impl(const obj_pool_type& pool, const uint8_t ix, _typed_pointer_type newChild) override final { orientedRealEta = pool._dynamic_cast(newChild); } }; - //! Basic factor nodes + //! Basic function factor nodes + class IFunctionNode : public obj_pool_type::INonTrivial, public IFactorLeaf + { + public: + constexpr static inline uint8_t MaxFuncArgsLog2 = 2; + + inline uint8_t getChildCount() const override final + { + const auto retval = getChildCount_impl(); + // static assert all below have equal or less children than 0x1u< perpTransmittance = {}; // cannot be null, otherwise its always exp2(0) and term will always be 1 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 @@ -1073,12 +1096,12 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! perpTransmittance = pool._dynamic_cast(newChild); } }; - class CFresnel final : public obj_pool_type::INonTrivial, public IFactorLeaf + class CFresnel final : public IFunctionNode { inline bool computeHash_impl(const obj_pool_type& pool, core::blake3_hasher& hasher) const override { - hasher << reciprocateEtas; - hasher << channels; + computeHash_common(pool,hasher); + hasher << getReciprocateEtas(); if (orientedImagEta) { HASH_OPTIONALS_HASH(orientedRealEta); @@ -1094,22 +1117,19 @@ 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 uint8_t getChildCount_impl() 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";} - inline uint8_t getSpectralBins() const override {return channels;} + inline bool getReciprocateEtas() const {return padding;} + inline void setReciprocateEtas(const bool value) {padding = value;} // cannot be null or a constant of 1 while imaginary is null typed_pointer_type orientedRealEta = {}; // If null, then treated as a 0 (important to optimize those to 0). MUST be null for BTDFs! typed_pointer_type orientedImagEta = {}; - // easier on the codegen - 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 @@ -1128,11 +1148,11 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! orientedRealEta = pool._dynamic_cast(newChild); } }; - class CThinInfiniteScatterCorrection final : public obj_pool_type::INonTrivial, public IFactorLeaf + class CThinInfiniteScatterCorrection final : public IFunctionNode { inline bool computeHash_impl(const obj_pool_type& pool, core::blake3_hasher& hasher) const override { - hasher << channels; + computeHash_common(pool,hasher); HASH_REQUIREDS_HASH(reflectanceTop); HASH_OPTIONALS_HASH(extinction); if (reflectanceBottom) @@ -1145,22 +1165,18 @@ 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 uint8_t getChildCount_impl() 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";} - inline uint8_t getSpectralBins() const override {return channels;} - // cannot be null otherwise no point being there typed_pointer_type reflectanceTop = {}; // optional, if null then treated as E=1.0 typed_pointer_type extinction = {}; // optional, if null then `reflectanceTop` used in its place 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 @@ -1206,7 +1222,7 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! const SBasicNodes& getBasicNodes() const {return m_basicNodes;} // - template requires std::is_base_of_v + template requires (!std::is_const_v && std::is_base_of_v) inline typed_pointer_type hashNCache(const typed_pointer_type origH) { auto* const orig = getObjectPool().deref(origH); diff --git a/include/nbl/core/alloc/SimpleBlockBasedAllocator.h b/include/nbl/core/alloc/SimpleBlockBasedAllocator.h index fb22de4098..d433d138ca 100644 --- a/include/nbl/core/alloc/SimpleBlockBasedAllocator.h +++ b/include/nbl/core/alloc/SimpleBlockBasedAllocator.h @@ -400,8 +400,11 @@ class SimpleBlockBasedAllocator final : protected retval.value = h.value; if (h) { - const auto* const p = deref(h); - retval.value += ptrdiff_t(dynamic_cast(p)) - ptrdiff_t(p); + const auto* const pU = deref(h); + const T* pT = dynamic_cast(pU); + if (!pT) + return {}; + retval.value += ptrdiff_t(pT) - ptrdiff_t(pU); } return retval; } diff --git a/src/nbl/asset/material_compiler3/CFrontendIR.cpp b/src/nbl/asset/material_compiler3/CFrontendIR.cpp index fcf0352474..39f2e0de63 100644 --- a/src/nbl/asset/material_compiler3/CFrontendIR.cpp +++ b/src/nbl/asset/material_compiler3/CFrontendIR.cpp @@ -747,18 +747,16 @@ auto CFrontendIR::SAdd2IRSession::makeContributors(const CFrontendIR::typed_poin assert(canonicalSum.empty()); // Multiplication Chain need to be sorted in a canonical order so its easier to spot them being the same - auto sortMuls = [](const SFactor& lhs, const SFactor& rhs)->bool + auto sortMuls = [&irPool](const CTrueIR::typed_pointer_type& lhs, const CTrueIR::typed_pointer_type& rhs)->bool { - // contributor goes first - if (lhs.isContributor()!=rhs.isContributor()) - return lhs.isContributor(); - // only one contributor allowed per chain! - assert(&lhs==&rhs || !lhs.isContributor() && !rhs.isContributor()); + // we are only sorting non-null nodes + assert(lhs && rhs); // monochrome is cheaper - if (lhs.factor.monochrome!=rhs.factor.monochrome) - return lhs.factor.monochrome; + const bool lhsScalar = irPool.deref(lhs)->isScalar(); + if (lhsScalar!=irPool.deref(rhs)->isScalar()) + return lhsScalar; // then by handle - return lhs.typeless.valueirChain; + bool goBack = false; auto& astStack = it->astStack; - while (!astStack.empty()) + while (!it->astStack.empty()) { auto* pEntry = &astStack.back(); const auto nodeH = *pEntry; @@ -801,21 +801,35 @@ auto CFrontendIR::SAdd2IRSession::makeContributors(const CFrontendIR::typed_poin case ast_expr_type_e::Contributor: { // This must be the only contributor, as a contributor can only be in the leftmost branch of a Mul node's subtree, it also cannot be under any other node than Add or Mul. - assert(!it->hasContributor); + assert(!it->contribSumH); // astStack.pop_back(); // shouldn't invalidate iterator, but underline our vector changes pEntry = nullptr; - // push onto the irChain + // create the contributor node and mark as found const auto contributorH = tmpIR->hashNCache(static_cast(node)->createIRNode(btdfSubtree,srcAST,tmpIR.get())); - if (!contributorH) + if (contributorH) + { + const auto weightedH = irPool.emplace(); + if (weightedH) + { + it->contribSumH = irPool.emplace(); + auto* const sumTerm = irPool.deref(it->contribSumH); + if (sumTerm) + { + irPool.deref(weightedH)->contributor = contributorH; + // note we don't hashNCache the `weightedH` node or any that has it as its child + sumTerm->product = weightedH; + it->hasContributor = true; + } + } + } + if (!it->contribSumH) { args.logger.log("Failed to Create IR Contributor from AST",ELL_ERROR); printSubtree(nodeH); return (headH=errorRetval); } - it->hasContributor = true; - irChain.emplace_back().contributor = {.handle=contributorH}; break; } case ast_expr_type_e::Mul: // pop self but push the two children @@ -890,8 +904,15 @@ auto CFrontendIR::SAdd2IRSession::makeContributors(const CFrontendIR::typed_poin { // first child (second to be pushed) copies our chains and carries on auto afterIt = it; + afterIt = canonicalSum.insert(++afterIt,*it); // but we need to remove the first child from the copy's astStack - canonicalSum.insert(++afterIt,*it)->astStack.pop_back(); + afterIt->astStack.pop_back(); + // make the latter chain take over the function + if (it->finalFuncArgTail) + { + afterIt->finalFuncArgTail = true; + it->finalFuncArgTail = false; + } } } // didn't push anything, need to kill current sum term @@ -902,21 +923,30 @@ auto CFrontendIR::SAdd2IRSession::makeContributors(const CFrontendIR::typed_poin irChain.clear(); astStack.clear(); } + // not that since second child took over our chain and AST stack, we can continue iteration at `it` and current `astStack` break; } - case ast_expr_type_e::Complement: // MUL PREFIX ADD 1.0 CHILD + case ast_expr_type_e::Complement: // MUL PREFIX ADD 1.0 -CHILD { constexpr bool HardFail = true; if (const auto childH=node->getChildHandle(0); astPool.deref(childH)) { - // insert a copy of the current chain before the current with AST cleared, so chain stopped (gets us our MUL PREFIX 1.0 term) - canonicalSum.insert(it,*it)->astStack.clear(); - // start a new chain with a copy of ours but negate it (now `it` points to the copy) + auto afterIt = it; afterIt++; + // insert a copy of the current chain AFTER the current + afterIt = canonicalSum.insert(afterIt,*it); + // with AST cleared, so chain is stopped (gets us our MUL PREFIX 1.0 term) + afterIt->astStack.clear(); + // make the latter chain take over the function + if (it->finalFuncArgTail) + { + afterIt->finalFuncArgTail = true; + it->finalFuncArgTail = false; + } + // negate our current chain (get use our MUL PREFIX -CHILD) it->negate ^= 0b111; // now the expression which was getting complemented needs to be on the AST stack so we don't visit the complement again - it->astStack.back() = childH; - // need to finalize the irChain, so go back to the entry stopped - it--; + *pEntry = childH; + // now we'll continue with the longer and negated product } else // the child is OpUndef, replace complement with 1 node { @@ -981,7 +1011,7 @@ auto CFrontendIR::SAdd2IRSession::makeContributors(const CFrontendIR::typed_poin pEntry = nullptr; break; } - irChain.emplace_back().factor = {.handle=varH,.monochrome=monochrome}; + irChain.emplace_back() = varH; break; } case ast_expr_type_e::Other: // the way this is written, we'd really benefit from a more "intimate" enforcing of the structure of Fresnel, Beer, etc. being identical between AST and IR @@ -991,29 +1021,36 @@ auto CFrontendIR::SAdd2IRSession::makeContributors(const CFrontendIR::typed_poin // shouldn't invalidate iterator, but underline our vector changes pEntry = nullptr; // do not hash yet! the children are invalid! - const auto funcH = static_cast(node)->createIRNode(btdfSubtree,srcAST,tmpIR.get()); - auto* const func = irPool.deref(funcH); + const auto funcH = static_cast(node)->createIRNode(btdfSubtree,srcAST,tmpIR.get()); + auto* const func = irPool.deref(funcH); if (!func) { - args.logger.log("Failed to Function Other IR Contributor from AST",ELL_ERROR); + args.logger.log("Failed to create Other IR Function from AST",ELL_ERROR); printSubtree(nodeH); return (headH=errorRetval); } + // + const uint8_t argCount = node->getChildCount(); // need to start a new `canonicalSum` for every input term of the function // insert back to front so we get good `it` at the end - for (uint8_t c=node->getChildCount(); c;) + for (uint8_t c=argCount; c;) { const auto astChild = node->getChildHandle(--c); - // record in the IR node child, the original AST child - func->setChildUnsafe(c,astChild); // only add sum expressions for non-null children if (!astChild) continue; + // start a new mul chain for the arg + goBack = true; it = canonicalSum.emplace(it); it->astStack.push_back(astChild); + it->funcH = funcH; + it->targetArg = c; } - // what to do about monochrome? - irChain.emplace_back().factor = {.handle=funcH/*,.monochrome*/}; + // actually pushed something, didn't just continue in the loop + if (goBack) + it->finalFuncArgTail = true; + // note that the `irChain` is that of the `it` before we started changing it in the loop above + irChain.emplace_back() = funcH; break; } default: @@ -1021,133 +1058,233 @@ auto CFrontendIR::SAdd2IRSession::makeContributors(const CFrontendIR::typed_poin printSubtree(nodeH); return (headH=errorRetval); } + if (goBack) + break; } - // only once the astChain is empty, if the ir chain is empty skip it, but don't remove it because some Other AST node might need it - if (irChain.empty()) + // we're supposed to go back to something inserted before the sum term we started to process + if (goBack) + continue; + // basic error checking, need to have a target node to add to + if (!it->contribSumH || !it->funcH) { - printSubtree(bxdfRootH); - // can't really print the subtree, because a lot of ADDs and MULs have to collude to produce this - args.logger.log("Empty IR mul chain encountered, skipping...",system::ILogger::ELL_PERFORMANCE); + args.logger.log("This Mul chain has no Contributor Node to partner with or Function Node to be an argument of!",ELL_ERROR); + return (headH=errorRetval); + } + // not printing an extra error message, should have already printed when found we mul to 0 + if (it->liveSpectralChannels==0) + { + it++; continue; } - // should have gotten removed beforehand - assert(it->liveSpectralChannels); // sort the factors in the mulchain std::sort(irChain.begin(),irChain.end(),sortMuls); - // make the combiner - if (it->hasContributor) + // + bool monochromeFactor = true; + // create the factor node - empty node means mul with 1.0 (no mul) + CTrueIR::typed_pointer_type uniqueFactorH = {}; + if (!irChain.empty()) { - // if we have a contributor first node in the sorted chain must be the contributor - assert(irChain.front().isContributor()); - // produce the weighted contributor - const auto weightedContribH = irPool.emplace(); - if (auto* const weightedContrib=irPool.deref(weightedContribH); weightedContrib) + CTrueIR::CFactorCombiner::SState combinerState = { + .type = CTrueIR::CFactorCombiner::Type::Mul, + .childCount = irChain.size() + }; + // one more needed for a negation of any channel + if (it->negate) + ++combinerState.childCount; + const auto factorH = irPool.emplace(combinerState); + if (auto* const factor=irPool.deref(factorH); factor) { - weightedContrib->contributor = irChain.front().contributor.handle; - // now the mul chain - if (irChain.size()>1) + auto i = 0; + // monochrome negation + if (it->negate==0b111) + factor->setChildHandle(i++,scalarNegation); + for (auto itChain=irChain.begin(); itChain!=irChain.end(); itChain++) { - CTrueIR::CFactorCombiner::SState combinerState = { - .type = CTrueIR::CFactorCombiner::Type::Mul, - .childCount = irChain.size() - }; - // if we negate we need to add one more mul factor, otherwise nothing - if (it->negate==0) - --combinerState.childCount; - const auto factorH = irPool.emplace(combinerState); - if (auto* const factor=irPool.deref(factorH); factor) + auto leafH = *itChain; + const auto* const leaf = irPool.deref(leafH); + if (monochromeFactor) { - auto i = 0; - // monochrome negation - if (it->negate && it->negate==0b111) - factor->setChildHandle(i++,scalarNegation); - bool monochromeNodes = true; -// TODO: handle the special function IR nodes - for (auto itChain=irChain.begin()+1; itChain!=irChain.end(); itChain++) + // not monochrome for the first time + if (!leaf->isScalar()) { - // only one contributor per chain is allowed - assert(!itChain->isContributor()); - if (monochromeNodes) + monochromeFactor = false; + // make the non-monochrome negation node, not using premade cause that would tie me up with spectral buckets + if (it->negate && it->negate!=0b111) { - // not monochrome for the first time - if (!itChain->factor.monochrome) + const auto negationH = irPool.emplace(uint8_t(3)); + if (auto* const negation=irPool.deref(negationH); negation) { - monochromeNodes = false; - // make the non-monochrome negation node, not using premade cause that would tie me up with spectral buckets - if (it->negate && it->negate!=0b111) - { - const auto negationH = irPool.emplace(uint8_t(3)); - if (auto* const negation=irPool.deref(negationH); negation) - { - for (uint8_t c=0; c<3; c++) - negation->setParameter(c,{.scale=bool((it->negate>>c)&0x1u) ? (-1.f):1.f}); - } - const auto uniqueH = tmpIR->hashNCache(negationH); - if (!uniqueH) - { - args.logger.log("Couldn't create a unique spectral negation node",ELL_ERROR); - return (headH=errorRetval); - } - factor->setChildHandle(i++,negationH); - } + for (uint8_t c=0; c<3; c++) + negation->setParameter(c,{.scale=bool((it->negate>>c)&0x1u) ? (-1.f):1.f}); } + const auto uniqueH = tmpIR->hashNCache(negationH); + if (!uniqueH) + { + args.logger.log("Couldn't create a unique spectral negation node",ELL_ERROR); + return (headH=errorRetval); + } + factor->setChildHandle(i++,uniqueH); } - else - { - // once you go spectral, you never go back - assert(!itChain->factor.monochrome); - } - factor->setChildHandle(i++,itChain->factor.handle); } } - const auto uniqueH = tmpIR->hashNCache(factorH); - if (!uniqueH) + else { - args.logger.log("Couldn't allocate or hash a `CTrueIR::CFactorCombiner` node",ELL_ERROR); - return (headH=errorRetval); + // once you go spectral, you never go back + assert(!leaf->isScalar()); } - weightedContrib->factor = uniqueH; + // replace with unique while hashing + if (const auto funcH=irPool._dynamic_cast(leafH); funcH) + { + leafH = tmpIR->hashNCache(funcH._const_cast()); + if (leafH) + { + args.logger.log("Couldn't hash a `CTrueIR::IFunction` node",ELL_ERROR); + return (headH=errorRetval); + } + } + factor->setChildHandle(i++,leafH); } } - const auto uniqueWeightedH = tmpIR->hashNCache(weightedContribH); - if (!uniqueWeightedH) + uniqueFactorH = tmpIR->hashNCache(factorH); + if (!uniqueFactorH) { - args.logger.log("Couldn't allocate or hash a `CTrueIR::CWeightedContributor` node",ELL_ERROR); + args.logger.log("Couldn't allocate or hash a `CTrueIR::CFactorCombiner` Mul node",ELL_ERROR); return (headH=errorRetval); } - // allocate new tail and slap the mul chain onto it - it->sumTermH = irPool.emplace(); - // note that we hold off on hashing the tail node, cause its subject to change - if (auto* const tail=irPool.deref(it->sumTermH); tail) - tail->product = uniqueWeightedH; - else + } + // link up the factor node + if (it->hasContributor) + { + auto* const sumTerm = irPool.deref(it->contribSumH); + assert(sumTerm); { - args.logger.log("Couldn't allocate a `CTrueIR::CContributorSum` node",ELL_ERROR); - return (headH=errorRetval); + const auto weightedContribH = sumTerm->product._const_cast(); + // attach the factor + irPool.deref(weightedContribH)->factor = uniqueFactorH; + const auto uniqueWeightedH = tmpIR->hashNCache(weightedContribH); + assert(uniqueWeightedH); + if (!uniqueWeightedH) + { + args.logger.log("Couldn't hash a `CTrueIR::CWeightedContributor` node",ELL_ERROR); + return (headH=errorRetval); + } + // replace the weighted contributor with unique hashed + sumTerm->product = uniqueWeightedH; + // note that we hold off on hashing the `sumTerm`, cause its subject to change (more terms attached to tail) } - // append it to previous with a contributor, if it exists + // now append it to previous with a contributor, if it exists for (auto prev=it; prev!=canonicalSum.begin(); prev--) { - if (prev==it) // skip first loop + if (prev==it) // skip first item, its our current one continue; if (prev->hasContributor) { - irPool.deref(prev->sumTermH)->rest = it->sumTermH; + irPool.deref(prev->contribSumH)->rest = it->contribSumH; break; } } } else { - // TODO: make without a contributor - // Do what the above does, just don't link stuff up - assert(false); + auto* const func = irPool.deref(it->funcH); + assert(func); + // make sure to update that factor is not scalar if we have any non-scalar factor term + if (!monochromeFactor) + func->scalar = false; + // allocate space for 2 add factors, we'll clean up later to have no dangling binop + add_ir_t::SState state = {.type=add_ir_t::Type::Add,.childCount=2}; + // don't try to optimize this, it will make code illegible + { + const auto addTailH = irPool.emplace(state); + if (!addTailH) + { + args.logger.log("Failed to create ADD Node to serve as root for a function arg %d",ELL_ERROR,uint32_t(it->targetArg)); + return (headH=errorRetval); + } + auto* const addTail = irPool.deref(addTailH); + addTail->setChildHandle(0,uniqueFactorH); + // if there's already an add node waiting for us + if (const auto prevH=func->getChildHandle(it->targetArg); prevH) + { + assert(irPool.deref(prevH)->getFinalType()==CTrueIR::INode::EFinalType::CFactorCombiner); + // attach previous node as second sum term + addTail->setChildHandle(1,block_allocator_type::_static_cast(prevH)); + } + // connect the node as the argument to a function + func->setChild(irPool,it->targetArg,addTailH); + } + if (it->finalFuncArgTail) + { + // for logging + const uint32_t arg32 = it->targetArg; + // replace the argument linked list with a single add + const uint8_t argCount = func->getChildCount(); + for (uint8_t c=argCount; c;) + { + // count the add terms (note last node always has a NULL second child, there's exactly as many nodes as there's terms to sum) + state.childCount = 0; + if (const auto firstAddH=func->getChildHandle(it->targetArg); firstAddH) + for (auto* binAdd=irPool.deref(firstAddH); binAdd->getChildHandle(1); binAdd=irPool.deref(binAdd->getChildHandle(1))) + { + assert(binAdd->getChildHandle(0)); + assert(binAdd->getChildCount()==2); + assert(binAdd->getFinalType()==CTrueIR::INode::EFinalType::CFactorCombiner); + assert(static_cast(binAdd)->getState().type==add_ir_t::Type::Add); + if ((state.childCount++)>>CTrueIR::CFactorCombiner::MaxChildCountLog2) + { + args.logger.log("Too many Sum terms in a Function node arg linked list %d",ELL_ERROR,arg32); + return (headH=errorRetval); + } + } + // just use NULL as the child + if (state.childCount) + { + func->setChild(irPool,c,{}); + continue; + } + const auto argH = irPool.emplace(state); + auto* const arg = irPool.deref(argH); + if (!arg) + { + args.logger.log("Failed to create ADD Node to replace the Function argument %d linked list",ELL_ERROR,arg32); + return (headH=errorRetval); + } + // now add all the nodes in + { + uint8_t c = 0; + for (auto* binAdd=irPool.deref(func->getChildHandle(it->targetArg)); binAdd->getChildHandle(1); binAdd=irPool.deref(binAdd->getChildHandle(1))) + arg->setChild(irPool,c++,binAdd->getChildHandle(0)); + } + // and hashNCache + const auto uniqueArgH = tmpIR->hashNCache(argH); + if (!uniqueArgH) + { + args.logger.log("Couldn't hash a `CTrueIR::IFunction` argument %d node",ELL_ERROR,arg32); + return (headH=errorRetval); + } + // connect the node as the argument to a function + func->setChild(irPool,c,uniqueArgH); + } + // don't hashNCache the function yet, we'll do it whenever we use it in an irChain for something else + } } - } - // now hash in reverse but only contributor containing sums (otherwise they're dependents) + // progress onto next term + it++; + } + // now hash in reverse for (auto it=canonicalSum.rbegin(); it!=canonicalSum.rend(); it++) - if (it->hasContributor) - headH = it->sumTermH = tmpIR->hashNCache(it->sumTermH)._const_cast(); + { + // last contributor becomes the new head node, also hashNCache + if (it->hasContributor) + { + const auto uniqueH = tmpIR->hashNCache(it->contribSumH); + // replace reference to previous tail with hashed and cached tail + if (headH) + irPool.deref(headH._const_cast())->rest = uniqueH; + headH = uniqueH; + } + // thankfully args to functions come before the expressions the functions are used in + } // headH set by last in loop with contributor, so first with contributor in the sum linked list canonicalSum.clear(); return headH; @@ -1177,6 +1314,28 @@ auto CFrontendIR::CEmitter::createIRNode(const bool forBTDF, const CFrontendIR* return retval; } +//! record the original AST child handle in the IR node child, cleanup later +auto CFrontendIR::CBeer::createIRNode(const bool forBTDF, const CFrontendIR* ast, CTrueIR* ir) const -> CTrueIR::typed_pointer_type +{ + auto& irPool = ir->getObjectPool(); + auto retval = ir->getObjectPool().emplace(); + return retval; +} +auto CFrontendIR::CFresnel::createIRNode(const bool forBTDF, const CFrontendIR* ast, CTrueIR* ir) const -> CTrueIR::typed_pointer_type +{ + auto& irPool = ir->getObjectPool(); + auto retval = ir->getObjectPool().emplace(); + if (auto* const leaf=irPool.deref(retval); leaf) + leaf->setReciprocateEtas(this->reciprocateEtas); + return retval; +} +auto CFrontendIR::CThinInfiniteScatterCorrection::createIRNode(const bool forBTDF, const CFrontendIR* ast, CTrueIR* ir) const -> CTrueIR::typed_pointer_type +{ + auto& irPool = ir->getObjectPool(); + auto retval = ir->getObjectPool().emplace(); + return retval; +} + auto CFrontendIR::CDeltaTransmission::createIRNode(const bool forBTDF, const CFrontendIR* ast, CTrueIR* ir) const -> ir_contributor_handle_t { assert(forBTDF); From 544ca932c882501b76aaee946a64c69cfbba3428 Mon Sep 17 00:00:00 2001 From: devshgraphicsprogramming Date: Tue, 19 May 2026 18:31:31 +0200 Subject: [PATCH 05/16] fix a lot of issues, move finalize of function node to first use --- .../nbl/asset/material_compiler3/CTrueIR.h | 7 +- .../asset/material_compiler3/CFrontendIR.cpp | 270 ++++++++++-------- 2 files changed, 154 insertions(+), 123 deletions(-) diff --git a/include/nbl/asset/material_compiler3/CTrueIR.h b/include/nbl/asset/material_compiler3/CTrueIR.h index 070e68ba30..5e44ea01fa 100644 --- a/include/nbl/asset/material_compiler3/CTrueIR.h +++ b/include/nbl/asset/material_compiler3/CTrueIR.h @@ -228,13 +228,13 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! // always put the node type into the hash hasher << static_cast(getFinalType()); if (!computeHash_impl(pool,hasher)) - return {}; + return core::blake3_hash_t::EmptyInput(); return hasher.operator core::blake3_hash_t(); } inline bool recomputeHash(const obj_pool_type& pool) { hash = computeHash(pool); - return hash != core::blake3_hash_t::EmptyInput(); + return hash!=core::blake3_hash_t::EmptyInput(); } virtual _typed_pointer_type copy(CTrueIR* ir) const = 0; @@ -341,6 +341,7 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! inline CFactorCombiner(const SState state) { padding = std::bit_cast(state); + std::uninitialized_default_construct_n(child,state.childCount); } // @@ -1035,7 +1036,7 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! { const auto retval = getChildCount_impl(); // static assert all below have equal or less children than 0x1u<=retval); return retval; } diff --git a/src/nbl/asset/material_compiler3/CFrontendIR.cpp b/src/nbl/asset/material_compiler3/CFrontendIR.cpp index 39f2e0de63..987660f0a0 100644 --- a/src/nbl/asset/material_compiler3/CFrontendIR.cpp +++ b/src/nbl/asset/material_compiler3/CFrontendIR.cpp @@ -746,6 +746,7 @@ auto CFrontendIR::SAdd2IRSession::makeContributors(const CFrontendIR::typed_poin // basic checks assert(canonicalSum.empty()); + using add_ir_t = CTrueIR::CFactorCombiner; // Multiplication Chain need to be sorted in a canonical order so its easier to spot them being the same auto sortMuls = [&irPool](const CTrueIR::typed_pointer_type& lhs, const CTrueIR::typed_pointer_type& rhs)->bool { @@ -758,6 +759,82 @@ auto CFrontendIR::SAdd2IRSession::makeContributors(const CFrontendIR::typed_poin // then by handle return lhs.value leafH)->CTrueIR::typed_pointer_type + { + if (const auto funcH=irPool._dynamic_cast(leafH); funcH) + { + // not finalized yet (I know what I'm doing with the const_cast) + if (auto* const func=const_cast(irPool.deref(funcH)); func->getHash() == core::blake3_hash_t::EmptyInput()) + { + // replace the argument linked list with a single add + const uint8_t argCount = func->getChildCount(); + for (uint8_t a=0; agetChildHandle(a); firstAddH) + { + // for logging + const uint32_t arg32 = a; + // count the add terms (note last node always has a NULL second child, there's exactly as many nodes as there's terms to sum) + add_ir_t::SState state = {.type=add_ir_t::Type::Add,.childCount=0}; + for (auto* binAdd=irPool.deref(firstAddH); true; ) + { + assert(binAdd->getChildHandle(0)); + assert(binAdd->getChildCount()==2); + assert(binAdd->getFinalType()==CTrueIR::INode::EFinalType::CFactorCombiner); + assert(static_cast(binAdd)->getState().type==add_ir_t::Type::Add); + if ((state.childCount++)>>CTrueIR::CFactorCombiner::MaxChildCountLog2) + { + args.logger.log("Too many Sum terms in a Function node arg linked list %d",ELL_ERROR,arg32); + return {}; + } + const auto rhsH = binAdd->getChildHandle(1); + if (!rhsH) + break; + binAdd = irPool.deref(rhsH); + } + assert(state.childCount); + // special case skip the ADD node + if (state.childCount==1) + { + func->setChild(irPool,a,irPool.deref(firstAddH)->getChildHandle(0)); + continue; + } + const auto argH = irPool.emplace(state); + auto* const arg = irPool.deref(argH); + if (!arg) + { + args.logger.log("Failed to create ADD Node to replace the Function argument %d linked list",ELL_ERROR,arg32); + return {}; + } + // now add all the nodes in + { + uint8_t c = 0; + for (auto* binAdd=irPool.deref(func->getChildHandle(a)); true; ) + { + arg->setChild(irPool,c++,binAdd->getChildHandle(0)); + const auto rhsH = binAdd->getChildHandle(1); + if (!rhsH) + break; + binAdd = irPool.deref(rhsH); + } + } + // and hashNCache + const auto uniqueArgH = tmpIR->hashNCache(argH); + if (!uniqueArgH) + { + args.logger.log("Couldn't hash a `CTrueIR::IFunction` argument %d node",ELL_ERROR,arg32); + return {}; + } + // connect the node as the argument to a function + func->setChild(irPool,a,uniqueArgH); + } + else // just use NULL as the child + func->setChild(irPool,a,{}); + } + return tmpIR->hashNCache(funcH._const_cast()); + } + return leafH; + }; // error value const auto errorRetval = tmpIR->getBasicNodes().errorBxDF; @@ -779,7 +856,6 @@ auto CFrontendIR::SAdd2IRSession::makeContributors(const CFrontendIR::typed_poin canonicalSum.clear(); } ); - using add_ir_t = CTrueIR::CFactorCombiner; for (auto it=canonicalSum.begin(); it!=canonicalSum.end(); ) { auto& irChain = it->irChain; @@ -1039,6 +1115,8 @@ auto CFrontendIR::SAdd2IRSession::makeContributors(const CFrontendIR::typed_poin // only add sum expressions for non-null children if (!astChild) continue; + // first child pushed will be last child actually + it->finalFuncArgTail = goBack; // start a new mul chain for the arg goBack = true; it = canonicalSum.emplace(it); @@ -1046,9 +1124,6 @@ auto CFrontendIR::SAdd2IRSession::makeContributors(const CFrontendIR::typed_poin it->funcH = funcH; it->targetArg = c; } - // actually pushed something, didn't just continue in the loop - if (goBack) - it->finalFuncArgTail = true; // note that the `irChain` is that of the `it` before we started changing it in the loop above irChain.emplace_back() = funcH; break; @@ -1081,7 +1156,7 @@ auto CFrontendIR::SAdd2IRSession::makeContributors(const CFrontendIR::typed_poin // bool monochromeFactor = true; // create the factor node - empty node means mul with 1.0 (no mul) - CTrueIR::typed_pointer_type uniqueFactorH = {}; + CTrueIR::typed_pointer_type uniqueFactorH = {}; if (!irChain.empty()) { CTrueIR::CFactorCombiner::SState combinerState = { @@ -1091,65 +1166,76 @@ auto CFrontendIR::SAdd2IRSession::makeContributors(const CFrontendIR::typed_poin // one more needed for a negation of any channel if (it->negate) ++combinerState.childCount; - const auto factorH = irPool.emplace(combinerState); - if (auto* const factor=irPool.deref(factorH); factor) + // no point making a mul node + if (combinerState.childCount==1) + { + uniqueFactorH = hashIfFunction(irChain.front()); + if (!uniqueFactorH) + { + args.logger.log("Couldn't hash a `CTrueIR::IFunction` node",ELL_ERROR); + return (headH=errorRetval); + } + } + else { - auto i = 0; - // monochrome negation - if (it->negate==0b111) - factor->setChildHandle(i++,scalarNegation); - for (auto itChain=irChain.begin(); itChain!=irChain.end(); itChain++) + const auto factorH = irPool.emplace(combinerState); + if (auto* const factor=irPool.deref(factorH); factor) { - auto leafH = *itChain; - const auto* const leaf = irPool.deref(leafH); - if (monochromeFactor) + auto i = 0; + // monochrome negation + if (it->negate==0b111) + factor->setChildHandle(i++,scalarNegation); + for (auto itChain=irChain.begin(); itChain!=irChain.end(); itChain++) { - // not monochrome for the first time - if (!leaf->isScalar()) + auto leafH = *itChain; + const auto* const leaf = irPool.deref(leafH); + assert(leaf); + if (monochromeFactor) { - monochromeFactor = false; - // make the non-monochrome negation node, not using premade cause that would tie me up with spectral buckets - if (it->negate && it->negate!=0b111) + // not monochrome for the first time + if (!leaf->isScalar()) { - const auto negationH = irPool.emplace(uint8_t(3)); - if (auto* const negation=irPool.deref(negationH); negation) + monochromeFactor = false; + // make the non-monochrome negation node, not using premade cause that would tie me up with spectral buckets + if (it->negate && it->negate!=0b111) { - for (uint8_t c=0; c<3; c++) - negation->setParameter(c,{.scale=bool((it->negate>>c)&0x1u) ? (-1.f):1.f}); + const auto negationH = irPool.emplace(uint8_t(3)); + if (auto* const negation=irPool.deref(negationH); negation) + { + for (uint8_t c=0; c<3; c++) + negation->setParameter(c,{.scale=bool((it->negate>>c)&0x1u) ? (-1.f):1.f}); + } + const auto uniqueH = tmpIR->hashNCache(negationH); + if (!uniqueH) + { + args.logger.log("Couldn't create a unique spectral negation node",ELL_ERROR); + return (headH=errorRetval); + } + factor->setChildHandle(i++,uniqueH); } - const auto uniqueH = tmpIR->hashNCache(negationH); - if (!uniqueH) - { - args.logger.log("Couldn't create a unique spectral negation node",ELL_ERROR); - return (headH=errorRetval); - } - factor->setChildHandle(i++,uniqueH); } } - } - else - { - // once you go spectral, you never go back - assert(!leaf->isScalar()); - } - // replace with unique while hashing - if (const auto funcH=irPool._dynamic_cast(leafH); funcH) - { - leafH = tmpIR->hashNCache(funcH._const_cast()); - if (leafH) + else + { + // once you go spectral, you never go back + assert(!leaf->isScalar()); + } + // set the child + if (leafH=hashIfFunction(leafH); leafH) + factor->setChildHandle(i++,leafH); + else { args.logger.log("Couldn't hash a `CTrueIR::IFunction` node",ELL_ERROR); return (headH=errorRetval); } } - factor->setChildHandle(i++,leafH); } - } - uniqueFactorH = tmpIR->hashNCache(factorH); - if (!uniqueFactorH) - { - args.logger.log("Couldn't allocate or hash a `CTrueIR::CFactorCombiner` Mul node",ELL_ERROR); - return (headH=errorRetval); + uniqueFactorH = tmpIR->hashNCache(factorH); + if (!uniqueFactorH) + { + args.logger.log("Couldn't allocate or hash a `CTrueIR::CFactorCombiner` Mul node",ELL_ERROR); + return (headH=errorRetval); + } } } // link up the factor node @@ -1188,85 +1274,29 @@ auto CFrontendIR::SAdd2IRSession::makeContributors(const CFrontendIR::typed_poin { auto* const func = irPool.deref(it->funcH); assert(func); + // first time a function gets used, it gets hashedNCache-d, which finalizes it, can't be mutating its state afterwards! + assert(func->getHash()==core::blake3_hash_t::EmptyInput()); // make sure to update that factor is not scalar if we have any non-scalar factor term if (!monochromeFactor) func->scalar = false; // allocate space for 2 add factors, we'll clean up later to have no dangling binop - add_ir_t::SState state = {.type=add_ir_t::Type::Add,.childCount=2}; - // don't try to optimize this, it will make code illegible + const auto addTailH = irPool.emplace(add_ir_t::SState{.type=add_ir_t::Type::Add,.childCount=2}); + if (!addTailH) { - const auto addTailH = irPool.emplace(state); - if (!addTailH) - { - args.logger.log("Failed to create ADD Node to serve as root for a function arg %d",ELL_ERROR,uint32_t(it->targetArg)); - return (headH=errorRetval); - } - auto* const addTail = irPool.deref(addTailH); - addTail->setChildHandle(0,uniqueFactorH); - // if there's already an add node waiting for us - if (const auto prevH=func->getChildHandle(it->targetArg); prevH) - { - assert(irPool.deref(prevH)->getFinalType()==CTrueIR::INode::EFinalType::CFactorCombiner); - // attach previous node as second sum term - addTail->setChildHandle(1,block_allocator_type::_static_cast(prevH)); - } - // connect the node as the argument to a function - func->setChild(irPool,it->targetArg,addTailH); + args.logger.log("Failed to create ADD Node to serve as root for a function arg %d",ELL_ERROR,uint32_t(it->targetArg)); + return (headH=errorRetval); } - if (it->finalFuncArgTail) + auto* const addTail = irPool.deref(addTailH); + addTail->setChildHandle(0,uniqueFactorH); + // if there's already an add node waiting for us + if (const auto prevH=func->getChildHandle(it->targetArg); prevH) { - // for logging - const uint32_t arg32 = it->targetArg; - // replace the argument linked list with a single add - const uint8_t argCount = func->getChildCount(); - for (uint8_t c=argCount; c;) - { - // count the add terms (note last node always has a NULL second child, there's exactly as many nodes as there's terms to sum) - state.childCount = 0; - if (const auto firstAddH=func->getChildHandle(it->targetArg); firstAddH) - for (auto* binAdd=irPool.deref(firstAddH); binAdd->getChildHandle(1); binAdd=irPool.deref(binAdd->getChildHandle(1))) - { - assert(binAdd->getChildHandle(0)); - assert(binAdd->getChildCount()==2); - assert(binAdd->getFinalType()==CTrueIR::INode::EFinalType::CFactorCombiner); - assert(static_cast(binAdd)->getState().type==add_ir_t::Type::Add); - if ((state.childCount++)>>CTrueIR::CFactorCombiner::MaxChildCountLog2) - { - args.logger.log("Too many Sum terms in a Function node arg linked list %d",ELL_ERROR,arg32); - return (headH=errorRetval); - } - } - // just use NULL as the child - if (state.childCount) - { - func->setChild(irPool,c,{}); - continue; - } - const auto argH = irPool.emplace(state); - auto* const arg = irPool.deref(argH); - if (!arg) - { - args.logger.log("Failed to create ADD Node to replace the Function argument %d linked list",ELL_ERROR,arg32); - return (headH=errorRetval); - } - // now add all the nodes in - { - uint8_t c = 0; - for (auto* binAdd=irPool.deref(func->getChildHandle(it->targetArg)); binAdd->getChildHandle(1); binAdd=irPool.deref(binAdd->getChildHandle(1))) - arg->setChild(irPool,c++,binAdd->getChildHandle(0)); - } - // and hashNCache - const auto uniqueArgH = tmpIR->hashNCache(argH); - if (!uniqueArgH) - { - args.logger.log("Couldn't hash a `CTrueIR::IFunction` argument %d node",ELL_ERROR,arg32); - return (headH=errorRetval); - } - // connect the node as the argument to a function - func->setChild(irPool,c,uniqueArgH); - } - // don't hashNCache the function yet, we'll do it whenever we use it in an irChain for something else + assert(irPool.deref(prevH)->getFinalType()==CTrueIR::INode::EFinalType::CFactorCombiner); + // attach previous node as second sum term + addTail->setChildHandle(1,block_allocator_type::_static_cast(prevH)); } + // connect the node as the argument to a function + func->setChild(irPool,it->targetArg,addTailH); } // progress onto next term it++; From c1d2766060168e5c5b3d05689c279bf25ae70906 Mon Sep 17 00:00:00 2001 From: devshgraphicsprogramming Date: Tue, 19 May 2026 19:13:49 +0200 Subject: [PATCH 06/16] less chatty logging, and improve the IR printing --- .../nbl/asset/material_compiler3/CTrueIR.h | 43 ++++++++++++++++--- .../asset/material_compiler3/CFrontendIR.cpp | 24 +++++------ src/nbl/asset/material_compiler3/CTrueIR.cpp | 10 ----- 3 files changed, 48 insertions(+), 29 deletions(-) diff --git a/include/nbl/asset/material_compiler3/CTrueIR.h b/include/nbl/asset/material_compiler3/CTrueIR.h index 5e44ea01fa..6c07c17d30 100644 --- a/include/nbl/asset/material_compiler3/CTrueIR.h +++ b/include/nbl/asset/material_compiler3/CTrueIR.h @@ -266,6 +266,7 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! // virtual inline void printDot(std::ostringstream& sstr, const core::string& selfID) const {} + virtual inline core::string getLabelSuffix() const {return "";} protected: // child managment @@ -355,7 +356,7 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! padding = std::bit_cast(state); } - inline uint8_t getChildCount() const override final { return getState().childCount; } + inline uint8_t getChildCount() const override {return getState().childCount;} // Only sane child count allowed inline void setChildHandle(const uint8_t ix, const typed_pointer_type handle) @@ -364,6 +365,9 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! child[ix] = handle; } + // + inline core::string getLabelSuffix() const override {return getState().type==Type::Mul ? "\\nMUL":"\\nADD";} + protected: inline bool computeHash_impl(const obj_pool_type& pool, core::blake3_hasher& hasher) const override { @@ -827,7 +831,14 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! // inline uint8_t getSpectralBins() const override final {return getKnotCount();} - + + // + inline core::string getLabelSuffix() const override + { + if (getKnotCount()<2) + return ""; + return "\\nSemantics = "+system::to_string(getSemantics()); + } NBL_API2 void printDot(std::ostringstream& sstr, const core::string& selfID) const override final; protected: @@ -958,6 +969,16 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! } public: + // + inline core::string getLabelSuffix() const override + { + return "\\nNDF = "+system::to_string(ndfParams.getDistribution()); + } + inline void printDot(std::ostringstream& sstr, const core::string& selfID) const override + { + ndfParams.printDot(sstr,selfID); + } + SBasicNDFParams ndfParams = {}; }; class COrenNayar final : public obj_pool_type::INonTrivial, public IBxDFWithNDF @@ -979,8 +1000,6 @@ 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 }; @@ -1005,14 +1024,21 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! 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 core::string getLabelSuffix() const override + { + auto retval = IBxDFWithNDF::getLabelSuffix(); + if (orientedRealEta) + retval += "\\nReciprocateEta = "+(isEtaReciprocal() ? "true":"false"); + return retval; + } + inline CCookTorrance() = default; // inline bool isEtaReciprocal() const {return ndfParams.params[2].padding[0];} inline void setEtaReciprocal(const bool value) {ndfParams.params[2].padding[0] = value;} - NBL_API2 void printDot(std::ostringstream& sstr, const core::string& selfID) const override final; - // BTDF ONLY! We need this eta to compute the refractions of `L` when importance sampling and the Jacobian during H to L generation for rough dielectrics // It does not mean we compute the Fresnel weights though! You might ask why we don't do that given that state of the art importance sampling // (at time of writing) is to decide upon reflection vs. refraction after the microfacet normal `H` is already sampled, @@ -1040,6 +1066,9 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! return retval; } + // + inline core::string getLabelSuffix() const override {return "\\nscalar = "+core::string(scalar ? "true":"false");} + // inline uint8_t getSpectralBins() const override {return scalar ? 3:1;} @@ -1481,7 +1510,7 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! retval += " [label=\""; retval += node->getTypeName(); retval += "\\n" + system::to_string(node->getHash()); - // maybe label suffix? + retval += node->getLabelSuffix(); retval += "\"]"; return retval; } diff --git a/src/nbl/asset/material_compiler3/CFrontendIR.cpp b/src/nbl/asset/material_compiler3/CFrontendIR.cpp index 987660f0a0..b03b44c047 100644 --- a/src/nbl/asset/material_compiler3/CFrontendIR.cpp +++ b/src/nbl/asset/material_compiler3/CFrontendIR.cpp @@ -737,8 +737,6 @@ auto CFrontendIR::SAdd2IRSession::makeContributors(const CFrontendIR::typed_poin CTrueIR::typed_pointer_type headH = {}; if (!bxdfRootH) return headH; - // temporary debug for WIP - printSubtree(bxdfRootH); auto& astPool = srcAST->getObjectPool(); auto& irPool = tmpIR->getObjectPool(); @@ -922,7 +920,6 @@ auto CFrontendIR::SAdd2IRSession::makeContributors(const CFrontendIR::typed_poin if constexpr (HardFail) { args.logger.log("Undef node in a MUL",ELL_ERROR); - printSubtree(nodeH); return (headH=errorRetval); } else @@ -959,7 +956,6 @@ auto CFrontendIR::SAdd2IRSession::makeContributors(const CFrontendIR::typed_poin if constexpr (HardFail) { args.logger.log("Undef node in an ADD",ELL_ERROR); - printSubtree(nodeH); return (headH=errorRetval); } else @@ -1030,7 +1026,6 @@ auto CFrontendIR::SAdd2IRSession::makeContributors(const CFrontendIR::typed_poin if constexpr (HardFail) { args.logger.log("Child of COMPLEMENT is null,",ELL_ERROR); - printSubtree(nodeH); return (headH=errorRetval); } else @@ -1075,12 +1070,17 @@ auto CFrontendIR::SAdd2IRSession::makeContributors(const CFrontendIR::typed_poin // combine masks and check if we're dead if ((it->liveSpectralChannels&=liveMask)==0) { - const auto logLevel = system::ILogger::ELL_PERFORMANCE; - // if we REALLY want to we can print all the nodes in the `irChain` so far, but then the irChain needs to track debug data from AST - args.logger.log("Product turns to 0 by multiplying the chain so far with the Spectral Variable:\n",logLevel); - printSubtree(nodeH); - args.logger.log("Forms a 0 constant factor across its ancestors in the mul chain, within the BxDF:\n",logLevel); - printSubtree(bxdfRootH); + // this prints a lot of these messages for 0 parameters of fresnel + constexpr bool ExtraDebug = false; + if constexpr (ExtraDebug) + { + const auto logLevel = system::ILogger::ELL_PERFORMANCE; + // if we REALLY want to we can print all the nodes in the `irChain` so far, but then the irChain needs to track debug data from AST + args.logger.log("Product turns to 0 by multiplying the chain so far with the Spectral Variable:\n",logLevel); + printSubtree(nodeH); + args.logger.log("Forms a 0 constant factor across its ancestors in the mul chain, within the BxDF:\n",logLevel); + printSubtree(bxdfRootH); + } // kill whole term irChain.clear(); astStack.clear(); @@ -1394,7 +1394,7 @@ auto CFrontendIR::CCookTorrance::createIRNode(const bool forBTDF, const CFronten const auto* const srcEta = ast->getObjectPool().deref(orientedRealEta); if (!srcEta) return {}; - etaH = srcEta->createIRNode(ast,ir); + etaH = ir->hashNCache(srcEta->createIRNode(ast,ir)); if (!etaH) return {}; } diff --git a/src/nbl/asset/material_compiler3/CTrueIR.cpp b/src/nbl/asset/material_compiler3/CTrueIR.cpp index d6f05bd104..33c529c971 100644 --- a/src/nbl/asset/material_compiler3/CTrueIR.cpp +++ b/src/nbl/asset/material_compiler3/CTrueIR.cpp @@ -321,15 +321,5 @@ void CTrueIR::CEmitter::printDot(std::ostringstream& sstr, const core::string& s } } -void CTrueIR::COrenNayar::printDot(std::ostringstream& sstr, const core::string& selfID) const -{ - ndfParams.printDot(sstr, selfID); -} - -void CTrueIR::CCookTorrance::printDot(std::ostringstream& sstr, const core::string& selfID) const -{ - ndfParams.printDot(sstr, selfID); -} - template class CTrueIR::CSpectralVariable; } \ No newline at end of file From cdee69be115f29c9dfc0a04c3292292abf05f9af Mon Sep 17 00:00:00 2001 From: devshgraphicsprogramming Date: Tue, 19 May 2026 19:36:05 +0200 Subject: [PATCH 07/16] fix scalar multiplier detection --- include/nbl/asset/material_compiler3/CTrueIR.h | 8 ++------ src/nbl/asset/material_compiler3/CFrontendIR.cpp | 8 +++++++- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/include/nbl/asset/material_compiler3/CTrueIR.h b/include/nbl/asset/material_compiler3/CTrueIR.h index 6c07c17d30..3f429ca04b 100644 --- a/include/nbl/asset/material_compiler3/CTrueIR.h +++ b/include/nbl/asset/material_compiler3/CTrueIR.h @@ -969,11 +969,6 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! } public: - // - inline core::string getLabelSuffix() const override - { - return "\\nNDF = "+system::to_string(ndfParams.getDistribution()); - } inline void printDot(std::ostringstream& sstr, const core::string& selfID) const override { ndfParams.printDot(sstr,selfID); @@ -1029,7 +1024,7 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! { auto retval = IBxDFWithNDF::getLabelSuffix(); if (orientedRealEta) - retval += "\\nReciprocateEta = "+(isEtaReciprocal() ? "true":"false"); + retval += "\\nReciprocateEta = "+core::string(isEtaReciprocal() ? "true":"false"); return retval; } @@ -1628,6 +1623,7 @@ inline void CTrueIR::SParameter::printDot(std::ostringstream& sstr, const core:: inline void CTrueIR::SBasicNDFParams::printDot(std::ostringstream& sstr, const core::string& selfID) const { + // TODO: print the Distribution! constexpr const char* paramSemantics[] = { "dh/du", "dh/dv", diff --git a/src/nbl/asset/material_compiler3/CFrontendIR.cpp b/src/nbl/asset/material_compiler3/CFrontendIR.cpp index b03b44c047..9f55968e15 100644 --- a/src/nbl/asset/material_compiler3/CFrontendIR.cpp +++ b/src/nbl/asset/material_compiler3/CFrontendIR.cpp @@ -737,6 +737,8 @@ auto CFrontendIR::SAdd2IRSession::makeContributors(const CFrontendIR::typed_poin CTrueIR::typed_pointer_type headH = {}; if (!bxdfRootH) return headH; + // temporary debug for WIP + printSubtree(bxdfRootH); auto& astPool = srcAST->getObjectPool(); auto& irPool = tmpIR->getObjectPool(); @@ -1169,7 +1171,9 @@ auto CFrontendIR::SAdd2IRSession::makeContributors(const CFrontendIR::typed_poin // no point making a mul node if (combinerState.childCount==1) { - uniqueFactorH = hashIfFunction(irChain.front()); + const auto leafH = irChain.front(); + monochromeFactor = irPool.deref(leafH)->isScalar(); + uniqueFactorH = hashIfFunction(leafH); if (!uniqueFactorH) { args.logger.log("Couldn't hash a `CTrueIR::IFunction` node",ELL_ERROR); @@ -1403,6 +1407,8 @@ auto CFrontendIR::CCookTorrance::createIRNode(const bool forBTDF, const CFronten { ct->ndfParams = ndParams; // the padding abuse is the same between the classes ct->orientedRealEta = etaH; + // we don't flip depending on `forBTDF` because a BTDF can be hit from underside as long as its not the last BTDF + ct->setEtaReciprocal(this->isEtaReciprocal()); } return retval; } From 7a72d33e056704645319de1198040bbde52493ef Mon Sep 17 00:00:00 2001 From: devshgraphicsprogramming Date: Wed, 20 May 2026 14:52:21 +0200 Subject: [PATCH 08/16] remove abandoned idea dead code, and print better labels for fresnel --- include/nbl/asset/material_compiler3/CFrontendIR.h | 2 -- include/nbl/asset/material_compiler3/CTrueIR.h | 5 +++++ src/nbl/asset/material_compiler3/CFrontendIR.cpp | 14 -------------- 3 files changed, 5 insertions(+), 16 deletions(-) diff --git a/include/nbl/asset/material_compiler3/CFrontendIR.h b/include/nbl/asset/material_compiler3/CFrontendIR.h index 2f93d187c0..1a4de780f8 100644 --- a/include/nbl/asset/material_compiler3/CFrontendIR.h +++ b/include/nbl/asset/material_compiler3/CFrontendIR.h @@ -837,8 +837,6 @@ class CFrontendIR final : public CNodePool CTrueIR::typed_pointer_type funcH = {}; }; uint16_t hasContributor : 1 = false; - // whether this is the last node ever to target this `funcH` - uint16_t finalFuncArgTail : 1 = false; uint16_t targetArg : CTrueIR::IFunctionNode::MaxFuncArgsLog2 = 0; // extend later when allowing variable bucket count uint16_t negate : 3 = 0b000; diff --git a/include/nbl/asset/material_compiler3/CTrueIR.h b/include/nbl/asset/material_compiler3/CTrueIR.h index 3f429ca04b..dfe6fe7944 100644 --- a/include/nbl/asset/material_compiler3/CTrueIR.h +++ b/include/nbl/asset/material_compiler3/CTrueIR.h @@ -1147,6 +1147,11 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! 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";} + + inline core::string getLabelSuffix() const override + { + return IFunctionNode::getLabelSuffix()+"\\nnReciprocateEta = "+(getReciprocateEtas() ? "true" : "false"); + } inline bool getReciprocateEtas() const {return padding;} inline void setReciprocateEtas(const bool value) {padding = value;} diff --git a/src/nbl/asset/material_compiler3/CFrontendIR.cpp b/src/nbl/asset/material_compiler3/CFrontendIR.cpp index 9f55968e15..f786f3a6ab 100644 --- a/src/nbl/asset/material_compiler3/CFrontendIR.cpp +++ b/src/nbl/asset/material_compiler3/CFrontendIR.cpp @@ -981,12 +981,6 @@ auto CFrontendIR::SAdd2IRSession::makeContributors(const CFrontendIR::typed_poin afterIt = canonicalSum.insert(++afterIt,*it); // but we need to remove the first child from the copy's astStack afterIt->astStack.pop_back(); - // make the latter chain take over the function - if (it->finalFuncArgTail) - { - afterIt->finalFuncArgTail = true; - it->finalFuncArgTail = false; - } } } // didn't push anything, need to kill current sum term @@ -1010,12 +1004,6 @@ auto CFrontendIR::SAdd2IRSession::makeContributors(const CFrontendIR::typed_poin afterIt = canonicalSum.insert(afterIt,*it); // with AST cleared, so chain is stopped (gets us our MUL PREFIX 1.0 term) afterIt->astStack.clear(); - // make the latter chain take over the function - if (it->finalFuncArgTail) - { - afterIt->finalFuncArgTail = true; - it->finalFuncArgTail = false; - } // negate our current chain (get use our MUL PREFIX -CHILD) it->negate ^= 0b111; // now the expression which was getting complemented needs to be on the AST stack so we don't visit the complement again @@ -1117,8 +1105,6 @@ auto CFrontendIR::SAdd2IRSession::makeContributors(const CFrontendIR::typed_poin // only add sum expressions for non-null children if (!astChild) continue; - // first child pushed will be last child actually - it->finalFuncArgTail = goBack; // start a new mul chain for the arg goBack = true; it = canonicalSum.emplace(it); From e0d8dbfb195af9c7ed3f3bde218f973150ef58b6 Mon Sep 17 00:00:00 2001 From: devshgraphicsprogramming Date: Wed, 20 May 2026 16:37:51 +0200 Subject: [PATCH 09/16] improve the AST printing a bit --- .../nbl/asset/material_compiler3/CFrontendIR.h | 15 ++++++++++++--- include/nbl/asset/material_compiler3/CTrueIR.h | 6 +++++- src/nbl/asset/material_compiler3/CFrontendIR.cpp | 2 +- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/include/nbl/asset/material_compiler3/CFrontendIR.h b/include/nbl/asset/material_compiler3/CFrontendIR.h index 1a4de780f8..5ba9d75fbc 100644 --- a/include/nbl/asset/material_compiler3/CFrontendIR.h +++ b/include/nbl/asset/material_compiler3/CFrontendIR.h @@ -423,6 +423,10 @@ class CFrontendIR final : public CNodePool } inline std::string_view getChildName_impl(const uint8_t ix) const override {return ix ? "Imaginary":"Real";} + inline core::string getLabelSuffix() const override + { + return "\\nReciprocateEta = "+core::string(reciprocateEtas ? "true" : "false"); + } NBL_API2 void printDot(std::ostringstream& sstr, const core::string& selfID) const override; }; // Compute Inifinite Scatter and extinction between two parallel infinite planes. @@ -574,17 +578,22 @@ class CFrontendIR final : public CNodePool NBL_API2 bool invalid(const SInvalidCheckArgs& args) const override; + // TODO: should this only return true when `orientedRealEta` is present? inline bool reciprocatable() const override {return true;} inline void reciprocate(IExprNode* dst) const override { - (*static_cast(dst) = *this).setEtaReciprocal(!isEtaReciprocal()); + auto* const other = static_cast(dst); + other->setEtaReciprocal(!isEtaReciprocal()); } inline core::string getLabelSuffix() const override { - return ndParams.getDistribution()!=CTrueIR::SBasicNDFParams::EDistribution::GGX ? "\\nNDF = Beckmann":"\\nNDF = GGX"; + core::string retval = ndParams.getDistribution()!=CTrueIR::SBasicNDFParams::EDistribution::GGX ? "\\nNDF = Beckmann":"\\nNDF = GGX"; + if (orientedRealEta) + retval += "\\nReciprocateEta = "+core::string(isEtaReciprocal() ? "true":"false"); + return retval; } - inline std::string_view getChildName_impl(const uint8_t ix) const override {return "Oriented η";} + inline std::string_view getChildName_impl(const uint8_t ix) const override {return "Oriented Eta";} NBL_API2 void printDot(std::ostringstream& sstr, const core::string& selfID) const override; NBL_API2 ir_contributor_handle_t createIRNode(const bool forBTDF, const CFrontendIR* ast, CTrueIR* ir) const; diff --git a/include/nbl/asset/material_compiler3/CTrueIR.h b/include/nbl/asset/material_compiler3/CTrueIR.h index dfe6fe7944..02019c12da 100644 --- a/include/nbl/asset/material_compiler3/CTrueIR.h +++ b/include/nbl/asset/material_compiler3/CTrueIR.h @@ -969,6 +969,10 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! } public: + inline core::string getLabelSuffix() const override + { + return ndfParams.getDistribution()!=CTrueIR::SBasicNDFParams::EDistribution::GGX ? "\\nNDF = Beckmann":"\\nNDF = GGX"; + } inline void printDot(std::ostringstream& sstr, const core::string& selfID) const override { ndfParams.printDot(sstr,selfID); @@ -1065,7 +1069,7 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! inline core::string getLabelSuffix() const override {return "\\nscalar = "+core::string(scalar ? "true":"false");} // - inline uint8_t getSpectralBins() const override {return scalar ? 3:1;} + inline uint8_t getSpectralBins() const override {return scalar ? 1:3;} uint64_t scalar : 1 = true; uint64_t padding : 63 = 0; diff --git a/src/nbl/asset/material_compiler3/CFrontendIR.cpp b/src/nbl/asset/material_compiler3/CFrontendIR.cpp index f786f3a6ab..5d98299033 100644 --- a/src/nbl/asset/material_compiler3/CFrontendIR.cpp +++ b/src/nbl/asset/material_compiler3/CFrontendIR.cpp @@ -765,7 +765,7 @@ auto CFrontendIR::SAdd2IRSession::makeContributors(const CFrontendIR::typed_poin if (const auto funcH=irPool._dynamic_cast(leafH); funcH) { // not finalized yet (I know what I'm doing with the const_cast) - if (auto* const func=const_cast(irPool.deref(funcH)); func->getHash() == core::blake3_hash_t::EmptyInput()) + if (auto* const func=const_cast(irPool.deref(funcH)); func->getHash()==core::blake3_hash_t::EmptyInput()) { // replace the argument linked list with a single add const uint8_t argCount = func->getChildCount(); From fdd61f198b32153a1ec7c4fe76bbc4697a9d6bd8 Mon Sep 17 00:00:00 2001 From: devshgraphicsprogramming Date: Wed, 20 May 2026 18:18:43 +0200 Subject: [PATCH 10/16] fix bugs with AST and IR children enumeration and bad hashing of Cook Torrance node --- .../asset/material_compiler3/CFrontendIR.h | 7 +++-- .../nbl/asset/material_compiler3/CTrueIR.h | 30 ++++++++++++++----- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/include/nbl/asset/material_compiler3/CFrontendIR.h b/include/nbl/asset/material_compiler3/CFrontendIR.h index 5ba9d75fbc..dc0d981055 100644 --- a/include/nbl/asset/material_compiler3/CFrontendIR.h +++ b/include/nbl/asset/material_compiler3/CFrontendIR.h @@ -54,8 +54,9 @@ namespace nbl::asset::material_compiler3 // both the Top and Bottom BRDF treat the Eta as being the speed of light in the medium above over the speed of light in the medium below. // This means that for modelling air-vs-glass you use the same Eta for the Top BRDF, the middle BTDF and Bottom BRDF. // We don't track the IoRs per layer because that would deprive us of the option to model each layer interface as a mixture of materials (metalness workflow). +// NOTE: We cannot check consistency of refractive indices between BRDFs and BTDFs ! // -// The backend can expand the Top BRDF, Middle BTDF, Bottom BRDF into 4 separate instruction streams for Front-Back BRDF and BTDF. This is because we can +// The backends can expand the Top BRDF, Middle BTDF, Bottom BRDF into 4 separate instruction streams for Front-Back BRDF and BTDF. This is because we can // throw away the first or last BRDF+BTDF in the stack, as well as use different pre-computed Etas if we know the sign of `cos(theta_i)` as we interact with each layer. // Whether the backend actually generates a separate instruction stream depends on the impact of Instruction Cache misses due to not sharing streams for layers. // @@ -377,10 +378,10 @@ class CFrontendIR final : public CNodePool protected: COPY_DEFAULT_IMPL - inline typed_pointer_type getChildHandle_impl(const uint8_t ix) const override {return ix ? perpTransmittance:thickness;} + inline typed_pointer_type getChildHandle_impl(const uint8_t ix) const override {return ix ? thickness:perpTransmittance;} inline void setChild_impl(const uint8_t ix, _typed_pointer_type newChild) override { - *(ix ? &perpTransmittance:&thickness) = block_allocator_type::_static_cast(newChild); + *(ix ? &thickness:&perpTransmittance) = block_allocator_type::_static_cast(newChild); } inline std::string_view getChildName_impl(const uint8_t ix) const override {return ix ? "Thickness":"Perpendicular\\nTransmittance";} diff --git a/include/nbl/asset/material_compiler3/CTrueIR.h b/include/nbl/asset/material_compiler3/CTrueIR.h index 02019c12da..aed65ad2f7 100644 --- a/include/nbl/asset/material_compiler3/CTrueIR.h +++ b/include/nbl/asset/material_compiler3/CTrueIR.h @@ -587,6 +587,7 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! break; default: btdf = pool._dynamic_cast(newChild); + break; } } }; @@ -1010,8 +1011,12 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! return false; IBxDFWithNDF::computeHash_impl(pool,hasher); hasher << static_cast(ndfParams.getDistribution()); - hasher << isEtaReciprocal(); - HASH_OPTIONALS_HASH(orientedRealEta); + if (orientedRealEta) + { + // only hash the reciprocal or not option if there's an Eta to be used + hasher << isEtaReciprocal(); + HASH_REQUIREDS_HASH(orientedRealEta); + } return true; } @@ -1225,12 +1230,21 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! } inline void setChild_impl(const obj_pool_type& pool, const uint8_t ix, _typed_pointer_type newChild) override final { - if (ix > 1) - reflectanceBottom = pool._dynamic_cast(newChild); - if (ix) - extinction = pool._dynamic_cast(newChild); - else - reflectanceTop = pool._dynamic_cast(newChild); + switch (ix) + { + case 0: + reflectanceBottom = pool._dynamic_cast(newChild); + break; + case 1: + extinction = pool._dynamic_cast(newChild); + break; + case 2: + reflectanceTop = pool._dynamic_cast(newChild); + break; + default: + assert(false); + break; + } } }; #undef COPY_DEFAULT_IMPL From 4995594eeea5bdc7126fb483c6342298853e3741 Mon Sep 17 00:00:00 2001 From: devshgraphicsprogramming Date: Wed, 20 May 2026 18:32:08 +0200 Subject: [PATCH 11/16] stop the child returning confusion once and for all --- .../nbl/asset/material_compiler3/CTrueIR.h | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/include/nbl/asset/material_compiler3/CTrueIR.h b/include/nbl/asset/material_compiler3/CTrueIR.h index aed65ad2f7..eaafdbcc96 100644 --- a/include/nbl/asset/material_compiler3/CTrueIR.h +++ b/include/nbl/asset/material_compiler3/CTrueIR.h @@ -1222,27 +1222,28 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! inline typed_pointer_type getChildHandle_impl(const uint8_t ix) const override final { - if (ix > 1) - return reflectanceBottom; - if (ix) - return extinction; - return reflectanceTop; + switch (ix) + { + default: + return reflectanceTop; + case 1: + return extinction; + case 2: + return reflectanceBottom; + } } inline void setChild_impl(const obj_pool_type& pool, const uint8_t ix, _typed_pointer_type newChild) override final { switch (ix) { - case 0: - reflectanceBottom = pool._dynamic_cast(newChild); + default: + reflectanceTop = pool._dynamic_cast(newChild); break; case 1: extinction = pool._dynamic_cast(newChild); break; case 2: - reflectanceTop = pool._dynamic_cast(newChild); - break; - default: - assert(false); + reflectanceBottom = pool._dynamic_cast(newChild); break; } } From c73dab676bed524d45fd8ecd8fd74aecf0e8e246 Mon Sep 17 00:00:00 2001 From: devshgraphicsprogramming Date: Wed, 20 May 2026 18:51:16 +0200 Subject: [PATCH 12/16] `CThinInfiniteScatterCorrection` needs to be reciprocatable Also change how reciprocation works in the AST --- .../nbl/asset/material_compiler3/CFrontendIR.h | 15 +++++++-------- src/nbl/asset/material_compiler3/CFrontendIR.cpp | 5 +++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/include/nbl/asset/material_compiler3/CFrontendIR.h b/include/nbl/asset/material_compiler3/CFrontendIR.h index dc0d981055..af2d959fc5 100644 --- a/include/nbl/asset/material_compiler3/CFrontendIR.h +++ b/include/nbl/asset/material_compiler3/CFrontendIR.h @@ -197,7 +197,7 @@ class CFrontendIR final : public CNodePool virtual bool inline reciprocatable() const {return false;} // unless you override it, you're not supposed to call it - virtual void reciprocate(IExprNode* dst) const {assert(reciprocatable() && dst);} + virtual void reciprocate() {assert(reciprocatable());} virtual inline core::string getLabelSuffix() const {return "";} virtual inline std::string_view getChildName_impl(const uint8_t ix) const {return "";} @@ -418,9 +418,9 @@ class CFrontendIR final : public CNodePool NBL_API2 bool invalid(const SInvalidCheckArgs& args) const override; inline bool reciprocatable() const override {return true;} - inline void reciprocate(IExprNode* dst) const override + inline void reciprocate() override { - (*static_cast(dst) = *this).reciprocateEtas = ~reciprocateEtas; + reciprocateEtas = ~reciprocateEtas; } inline std::string_view getChildName_impl(const uint8_t ix) const override {return ix ? "Imaginary":"Real";} @@ -477,6 +477,9 @@ class CFrontendIR final : public CNodePool } NBL_API2 bool invalid(const SInvalidCheckArgs& args) const override; + + inline bool reciprocatable() const override {return true;} + inline void reciprocate() override {std::swap(reflectanceTop,reflectanceBottom);} inline std::string_view getChildName_impl(const uint8_t ix) const override {return ix ? (ix>1 ? "reflectanceBottom":"extinction"):"reflectanceTop";} @@ -581,11 +584,7 @@ class CFrontendIR final : public CNodePool // TODO: should this only return true when `orientedRealEta` is present? inline bool reciprocatable() const override {return true;} - inline void reciprocate(IExprNode* dst) const override - { - auto* const other = static_cast(dst); - other->setEtaReciprocal(!isEtaReciprocal()); - } + inline void reciprocate() override {setEtaReciprocal(!isEtaReciprocal());} inline core::string getLabelSuffix() const override { diff --git a/src/nbl/asset/material_compiler3/CFrontendIR.cpp b/src/nbl/asset/material_compiler3/CFrontendIR.cpp index 5d98299033..42f8b99d71 100644 --- a/src/nbl/asset/material_compiler3/CFrontendIR.cpp +++ b/src/nbl/asset/material_compiler3/CFrontendIR.cpp @@ -228,8 +228,6 @@ auto CFrontendIR::reciprocate(const typed_pointer_type orig, co auto* const copy = dstPool.deref(copyH); if (!copy) return {}; - if (needToReciprocate) - node->reciprocate(copy); // reciprocate might take full copies, and copy pointers across, so do all modifications after if (pSourceIR!=this) copy->debugInfo = copyDebugInfo(node->debugInfo,pSourceIR); @@ -242,6 +240,9 @@ auto CFrontendIR::reciprocate(const typed_pointer_type orig, co if (auto found=substitutions.find(childH); found!=substitutions.end()) copy->setChild(c,found->second); } + // reciprocate only after all data is complete + if (needToReciprocate) + copy->reciprocate(); substitutions.insert({entry,copyH}); } stack.pop_back(); From 3eb0887453dd842eb04b0185487a32507978b434 Mon Sep 17 00:00:00 2001 From: devshgraphicsprogramming Date: Wed, 20 May 2026 18:52:29 +0200 Subject: [PATCH 13/16] Fix up how thindielectric gets done in the AST --- examples_tests | 2 +- src/nbl/ext/MitsubaLoader/CMitsubaLoader.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples_tests b/examples_tests index d81ba2e901..1c1d756f83 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit d81ba2e9010d59edc1a320baaf76ecc17fc76160 +Subproject commit 1c1d756f83729ee0977ace387a0c96f07eea0ba7 diff --git a/src/nbl/ext/MitsubaLoader/CMitsubaLoader.cpp b/src/nbl/ext/MitsubaLoader/CMitsubaLoader.cpp index 405525d9e4..b80fc067a6 100644 --- a/src/nbl/ext/MitsubaLoader/CMitsubaLoader.cpp +++ b/src/nbl/ext/MitsubaLoader/CMitsubaLoader.cpp @@ -929,7 +929,7 @@ auto SContext::genMaterial(const CElementBSDF* bsdf, system::ISystem* debugFileW { auto* thinInfiniteScatter = frontPool.deref(thinInfiniteScatterH); thinInfiniteScatter->reflectanceTop = fresnelH; - thinInfiniteScatter->reflectanceBottom = fresnelH; + thinInfiniteScatter->reflectanceBottom = frontIR->reciprocate(fresnelH)._const_cast(); // TODO: extinction } mul->lhs = deltaTransmission._const_cast(); @@ -1117,7 +1117,7 @@ auto SContext::genMaterial(const CElementBSDF* bsdf, system::ISystem* debugFileW case CElementBSDF::NORMALMAP: { // we basically ignore and skip because derivative map already applied - newMaterialH = getChildFromCache(_bsdf->mask.bsdf[0]); + newMaterialH = getChildFromCache(_bsdf->normalmap.bsdf[0]); break; } case CElementBSDF::MIXTURE_BSDF: From d1f2fff359cdeed44602aef23412c51b1e21aa18 Mon Sep 17 00:00:00 2001 From: devshgraphicsprogramming Date: Thu, 21 May 2026 00:47:56 +0200 Subject: [PATCH 14/16] make debug print significantly less chatty, spot a bug in typed_pointer_type::operator<=> pertaining to static casts and autoconversions, leave as TODO --- examples_tests | 2 +- include/nbl/asset/material_compiler3/CFrontendIR.h | 2 +- include/nbl/asset/material_compiler3/CTrueIR.h | 14 +++++--------- include/nbl/core/alloc/SimpleBlockBasedAllocator.h | 4 ++-- src/nbl/asset/material_compiler3/CFrontendIR.cpp | 6 ++---- src/nbl/asset/material_compiler3/CTrueIR.cpp | 6 +++++- 6 files changed, 16 insertions(+), 18 deletions(-) diff --git a/examples_tests b/examples_tests index 1c1d756f83..33dcc0501c 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit 1c1d756f83729ee0977ace387a0c96f07eea0ba7 +Subproject commit 33dcc0501c5b436d297e571e7347e307ea9a460a diff --git a/include/nbl/asset/material_compiler3/CFrontendIR.h b/include/nbl/asset/material_compiler3/CFrontendIR.h index af2d959fc5..85f9fb23b2 100644 --- a/include/nbl/asset/material_compiler3/CFrontendIR.h +++ b/include/nbl/asset/material_compiler3/CFrontendIR.h @@ -800,7 +800,7 @@ class CFrontendIR final : public CNodePool { irPrinter.reset(ir); irPrinter.layerStack.push_back(layerH); - args.logger.log("IR Layer Dot3 : \n%s\n",system::ILogger::ELL_DEBUG,irPrinter().c_str()); + args.logger.log("IR Layer Dot3 : \n%s\n",system::ILogger::ELL_DEBUG,irPrinter(true).c_str()); irPrinter.visitedNodes.clear(); } diff --git a/include/nbl/asset/material_compiler3/CTrueIR.h b/include/nbl/asset/material_compiler3/CTrueIR.h index eaafdbcc96..d9a65e8719 100644 --- a/include/nbl/asset/material_compiler3/CTrueIR.h +++ b/include/nbl/asset/material_compiler3/CTrueIR.h @@ -970,10 +970,6 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! } public: - inline core::string getLabelSuffix() const override - { - return ndfParams.getDistribution()!=CTrueIR::SBasicNDFParams::EDistribution::GGX ? "\\nNDF = Beckmann":"\\nNDF = GGX"; - } inline void printDot(std::ostringstream& sstr, const core::string& selfID) const override { ndfParams.printDot(sstr,selfID); @@ -1031,7 +1027,7 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! // inline core::string getLabelSuffix() const override { - auto retval = IBxDFWithNDF::getLabelSuffix(); + core::string retval = ndfParams.getDistribution()!=SBasicNDFParams::EDistribution::GGX ? "\\nNDF = Beckmann":"\\nNDF = GGX"; if (orientedRealEta) retval += "\\nReciprocateEta = "+core::string(isEtaReciprocal() ? "true":"false"); return retval; @@ -1159,7 +1155,7 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! inline core::string getLabelSuffix() const override { - return IFunctionNode::getLabelSuffix()+"\\nnReciprocateEta = "+(getReciprocateEtas() ? "true" : "false"); + return IFunctionNode::getLabelSuffix()+"\\nReciprocateEta = "+(getReciprocateEtas() ? "true" : "false"); } inline bool getReciprocateEtas() const {return padding;} @@ -1441,11 +1437,11 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! m_ir = ir; } - NBL_API2 void operator()(std::ostringstream& output); - inline core::string operator()() + NBL_API2 void operator()(std::ostringstream& output, const bool singleLayer=false); + inline core::string operator()(const bool singleLayer=false) { std::ostringstream tmp; - operator()(tmp); + operator()(tmp,singleLayer); return tmp.str(); } diff --git a/include/nbl/core/alloc/SimpleBlockBasedAllocator.h b/include/nbl/core/alloc/SimpleBlockBasedAllocator.h index d433d138ca..ae9ffb62ae 100644 --- a/include/nbl/core/alloc/SimpleBlockBasedAllocator.h +++ b/include/nbl/core/alloc/SimpleBlockBasedAllocator.h @@ -53,8 +53,8 @@ struct TypedHandle final : std::conditional_t,typename _Handl using base_t = std::conditional_t; public: - inline auto operator<=>(const _Handle& other) const {return base_t::operator<=>(other);} - inline auto operator<=>(const typename _Handle::const_type& other) const {return base_t::operator<=>(other);} + // don't allow comparison with just any type pointer (might get bitten by static cast shifting around + inline auto operator<=>(const TypedHandle& other) const {return base_t::operator<=>(other);} // implicit const_cast inline operator TypedHandle() const {return {{.value=base_t::value}};} diff --git a/src/nbl/asset/material_compiler3/CFrontendIR.cpp b/src/nbl/asset/material_compiler3/CFrontendIR.cpp index 42f8b99d71..c4d63c3617 100644 --- a/src/nbl/asset/material_compiler3/CFrontendIR.cpp +++ b/src/nbl/asset/material_compiler3/CFrontendIR.cpp @@ -692,8 +692,8 @@ auto CFrontendIR::SAdd2IRSession::makeOrientedMaterial(const CFrontendIR::typed_ retval.root = tmpIR->hashNCache(layerH); // Now optimize everything inserting it into the proper IR { - // temporary debug print - constexpr bool DebugBeforeAndAfterOpt = true; + // extra debug print + constexpr bool DebugBeforeAndAfterOpt = false; if constexpr(DebugBeforeAndAfterOpt) { args.logger.log("Before optimization:",ELL_DEBUG); @@ -738,8 +738,6 @@ auto CFrontendIR::SAdd2IRSession::makeContributors(const CFrontendIR::typed_poin CTrueIR::typed_pointer_type headH = {}; if (!bxdfRootH) return headH; - // temporary debug for WIP - printSubtree(bxdfRootH); auto& astPool = srcAST->getObjectPool(); auto& irPool = tmpIR->getObjectPool(); diff --git a/src/nbl/asset/material_compiler3/CTrueIR.cpp b/src/nbl/asset/material_compiler3/CTrueIR.cpp index 33c529c971..cfcf1f3a1b 100644 --- a/src/nbl/asset/material_compiler3/CTrueIR.cpp +++ b/src/nbl/asset/material_compiler3/CTrueIR.cpp @@ -182,7 +182,7 @@ uint32_t CTrueIR::deepCopy(typed_pointer_type* out, const std::span " << m_ir->getNodeID(childHandle) << "[label=\"" << node->getChildName(childIx) << "\"]"; const auto [unused,inserted] = visitedNodes.insert(childHandle); + // TODO: ban comparison of base class pointer against derived without downcasting first + if (singleLayer && node->getFinalType()==INode::EFinalType::CCorellatedTransmission) + if (decltype(childHandle) coatedAsINode=static_cast(node)->coated; coatedAsINode==childHandle) + continue; if (inserted) nodeStack.push_back(childHandle); } From 6cff904ff529b4f09ce2cfe4998b3027f884e292 Mon Sep 17 00:00:00 2001 From: devshgraphicsprogramming Date: Thu, 21 May 2026 01:19:38 +0200 Subject: [PATCH 15/16] fix ADd node ASt to IR transcription --- src/nbl/asset/material_compiler3/CFrontendIR.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nbl/asset/material_compiler3/CFrontendIR.cpp b/src/nbl/asset/material_compiler3/CFrontendIR.cpp index c4d63c3617..9ea0a59876 100644 --- a/src/nbl/asset/material_compiler3/CFrontendIR.cpp +++ b/src/nbl/asset/material_compiler3/CFrontendIR.cpp @@ -978,8 +978,8 @@ auto CFrontendIR::SAdd2IRSession::makeContributors(const CFrontendIR::typed_poin // first child (second to be pushed) copies our chains and carries on auto afterIt = it; afterIt = canonicalSum.insert(++afterIt,*it); - // but we need to remove the first child from the copy's astStack - afterIt->astStack.pop_back(); + // need to visit a different child + afterIt->astStack.back() = childH; } } // didn't push anything, need to kill current sum term From 07536ad010a87a8cd8930a43ef40e8c4e49f315e Mon Sep 17 00:00:00 2001 From: keptsecret Date: Thu, 21 May 2026 14:46:32 +0700 Subject: [PATCH 16/16] re-add missing function defs after merge --- include/nbl/asset/material_compiler3/CTrueIR.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/include/nbl/asset/material_compiler3/CTrueIR.h b/include/nbl/asset/material_compiler3/CTrueIR.h index 61c79d7322..ce71a419f7 100644 --- a/include/nbl/asset/material_compiler3/CTrueIR.h +++ b/include/nbl/asset/material_compiler3/CTrueIR.h @@ -1004,6 +1004,8 @@ 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 }; @@ -1045,6 +1047,8 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! inline CCookTorrance() = default; + NBL_API2 void printDot(std::ostringstream& sstr, const core::string& selfID) const override final; + // inline bool isEtaReciprocal() const {return ndfParams.params[2].padding[0];} inline void setEtaReciprocal(const bool value) {ndfParams.params[2].padding[0] = value;}