diff --git a/examples_tests b/examples_tests index d81ba2e901..33dcc0501c 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit d81ba2e9010d59edc1a320baaf76ecc17fc76160 +Subproject commit 33dcc0501c5b436d297e571e7347e307ea9a460a diff --git a/include/nbl/asset/material_compiler3/CFrontendIR.h b/include/nbl/asset/material_compiler3/CFrontendIR.h index f8427aba5e..85f9fb23b2 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. // @@ -196,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 "";} @@ -338,14 +339,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 +365,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; @@ -369,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";} @@ -383,9 +392,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. @@ -407,12 +418,16 @@ 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";} + 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. @@ -450,7 +465,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 @@ -462,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";} @@ -469,6 +487,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; @@ -562,17 +582,18 @@ 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()); - } + inline void reciprocate() override {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; @@ -779,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(); } @@ -801,37 +822,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 +834,22 @@ 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; + 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..ce71a419f7 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 { return 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 @@ -228,13 +230,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; @@ -252,7 +254,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); @@ -266,6 +268,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 @@ -323,10 +326,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; }; @@ -340,6 +344,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); } // @@ -353,7 +358,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) @@ -362,6 +367,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 { @@ -581,6 +589,7 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! break; default: btdf = pool._dynamic_cast(newChild); + break; } } }; @@ -825,7 +834,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: @@ -862,6 +878,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 +955,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);} @@ -956,6 +976,11 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! } public: + 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 @@ -971,6 +996,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);} @@ -990,27 +1017,42 @@ 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; } 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);} inline std::string_view getChildName_impl(const uint8_t ix) const override final { return "orientedRealEta"; } + // + inline core::string getLabelSuffix() const override + { + core::string retval = ndfParams.getDistribution()!=SBasicNDFParams::EDistribution::GGX ? "\\nNDF = Beckmann":"\\nNDF = GGX"; + if (orientedRealEta) + retval += "\\nReciprocateEta = "+core::string(isEtaReciprocal() ? "true":"false"); + return retval; + } + 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;} - 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, @@ -1024,15 +1066,44 @@ 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<=retval); + return retval; + } + + // + inline core::string getLabelSuffix() const override {return "\\nscalar = "+core::string(scalar ? "true":"false");} + + // + inline uint8_t getSpectralBins() const override {return scalar ? 1:3;} + + uint64_t scalar : 1 = true; + uint64_t padding : 63 = 0; + + protected: + virtual uint8_t getChildCount_impl() const = 0; + inline void computeHash_common(const obj_pool_type& pool, core::blake3_hasher& hasher) const + { + hasher << scalar; + } + }; // Effective transparency = exp2(log2(perpTransmittance)*thickness/dot(refract(V,X,eta),X)) = exp2(log2(perpTransmittance)*thickness*inversesqrt(1.f+(LdotX-1)*rcpEta)) // Eta and `LdotX` is taken from the contributor BxDF node. With refractions from Dielectrics, we get just `1/LdotX`, for Delta Transmission we get `1/VdotN` since its the same. // Note: its allowed to apply Beer directly on BRDF as well as BTDF to simulate foggy extinction on the top layer - class CBeer final : public obj_pool_type::INonTrivial, public IFactorLeaf + class CBeer 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(perpTransmittance); HASH_REQUIREDS_HASH(thickness); return true; @@ -1041,20 +1112,16 @@ class CTrueIR : public CNodePool // TODO: turn into an asset! public: inline EFinalType getFinalType() const override {return EFinalType::CBeer;} - inline uint8_t getChildCount() const override final { return 2; } + inline uint8_t getChildCount_impl() const override final { return 2; } inline const std::string_view getTypeName() const override {return TYPE_NAME_STR(CBeer);} inline std::string_view getChildName_impl(const uint8_t ix) const override final {return ix ? "Thickness":"Perpendicular\\nTransmittance";} - inline uint8_t getSpectralBins() const override {return channels;} - // cannot be null, otherwise no point being there as term will multiply to 0 typed_pointer_type 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 +1140,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 +1161,24 @@ 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 core::string getLabelSuffix() const override + { + return IFunctionNode::getLabelSuffix()+"\\nReciprocateEta = "+(getReciprocateEtas() ? "true" : "false"); + } - 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 +1197,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,42 +1214,48 @@ 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 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 { - if (ix > 1) - reflectanceBottom = pool._dynamic_cast(newChild); - if (ix) - extinction = pool._dynamic_cast(newChild); - else - reflectanceTop = pool._dynamic_cast(newChild); + switch (ix) + { + default: + reflectanceTop = pool._dynamic_cast(newChild); + break; + case 1: + extinction = pool._dynamic_cast(newChild); + break; + case 2: + reflectanceBottom = pool._dynamic_cast(newChild); + break; + } } }; #undef COPY_DEFAULT_IMPL @@ -1206,7 +1281,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); @@ -1376,11 +1451,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(); } @@ -1427,7 +1502,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 { @@ -1464,7 +1539,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; } @@ -1582,6 +1657,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/include/nbl/core/alloc/SimpleBlockBasedAllocator.h b/include/nbl/core/alloc/SimpleBlockBasedAllocator.h index fb22de4098..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}};} @@ -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 5af5f25e01..9ea0a59876 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(); @@ -691,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); @@ -737,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(); @@ -746,19 +745,94 @@ 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 = [](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.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 @@ -781,11 +855,12 @@ auto CFrontendIR::SAdd2IRSession::makeContributors(const CFrontendIR::typed_poin canonicalSum.clear(); } ); - for (auto it=canonicalSum.begin(); it!=canonicalSum.end(); it++) + for (auto it=canonicalSum.begin(); it!=canonicalSum.end(); ) { auto& irChain = it->irChain; + bool goBack = false; auto& astStack = it->astStack; - while (!astStack.empty()) + while (!it->astStack.empty()) { auto* pEntry = &astStack.back(); const auto nodeH = *pEntry; @@ -801,21 +876,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 @@ -832,7 +921,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 @@ -869,7 +957,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 @@ -890,8 +977,9 @@ auto CFrontendIR::SAdd2IRSession::makeContributors(const CFrontendIR::typed_poin { // first child (second to be pushed) copies our chains and carries on auto afterIt = it; - // but we need to remove the first child from the copy's astStack - canonicalSum.insert(++afterIt,*it)->astStack.pop_back(); + afterIt = canonicalSum.insert(++afterIt,*it); + // need to visit a different child + afterIt->astStack.back() = childH; } } // didn't push anything, need to kill current sum term @@ -902,21 +990,24 @@ 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(); + // 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 { @@ -924,7 +1015,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 @@ -969,29 +1059,60 @@ 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(); pEntry = nullptr; break; } - irChain.emplace_back().factor = {.handle=varH,.monochrome=monochrome}; + irChain.emplace_back() = varH; 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 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=argCount; c;) + { + const auto astChild = node->getChildHandle(--c); + // 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; + } + // note that the `irChain` is that of the `it` before we started changing it in the loop above + irChain.emplace_back() = funcH; break; } default: @@ -999,131 +1120,192 @@ auto CFrontendIR::SAdd2IRSession::makeContributors(const CFrontendIR::typed_poin printSubtree(nodeH); return (headH=errorRetval); } + if (goBack) + break; + } + // 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) + { + 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); } - // 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()) + // not printing an extra error message, should have already printed when found we mul to 0 + if (it->liveSpectralChannels==0) { - 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); + 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; + // no point making a mul node + if (combinerState.childCount==1) + { + 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); + return (headH=errorRetval); + } + } + else { - weightedContrib->contributor = irChain.front().contributor.handle; - // now the mul chain - if (irChain.size()>1) + const auto factorH = irPool.emplace(combinerState); + if (auto* const factor=irPool.deref(factorH); factor) { - 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 i = 0; + // monochrome negation + if (it->negate==0b111) + factor->setChildHandle(i++,scalarNegation); + for (auto itChain=irChain.begin(); itChain!=irChain.end(); itChain++) { - auto i = 0; - // monochrome negation - if (it->negate && it->negate==0b111) - factor->setChildHandle(i++,scalarNegation); - bool monochromeNodes = true; - for (auto itChain=irChain.begin()+1; itChain!=irChain.end(); itChain++) + auto leafH = *itChain; + const auto* const leaf = irPool.deref(leafH); + assert(leaf); + if (monochromeFactor) { - // only one contributor per chain is allowed - assert(!itChain->isContributor()); - if (monochromeNodes) + // not monochrome for the first time + if (!leaf->isScalar()) { - // not monochrome for the first time - if (!itChain->factor.monochrome) + 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) { - 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) { - 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); + } + 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); } } - const auto uniqueH = tmpIR->hashNCache(factorH); - if (!uniqueH) - { - args.logger.log("Couldn't allocate or hash a `CTrueIR::CFactorCombiner` node",ELL_ERROR); - return (headH=errorRetval); - } - weightedContrib->factor = uniqueH; + } + uniqueFactorH = tmpIR->hashNCache(factorH); + if (!uniqueFactorH) + { + args.logger.log("Couldn't allocate or hash a `CTrueIR::CFactorCombiner` Mul node",ELL_ERROR); + return (headH=errorRetval); } } - const auto uniqueWeightedH = tmpIR->hashNCache(weightedContribH); - if (!uniqueWeightedH) - { - args.logger.log("Couldn't allocate or hash a `CTrueIR::CWeightedContributor` 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 - if (it!=canonicalSum.begin()) + // now 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 item, its our current one + continue; + if (prev->hasContributor) + { + irPool.deref(prev->contribSumH)->rest = it->contribSumH; + break; + } } } else { - // TODO: make without a contributor - assert(false); + 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 + const auto addTailH = irPool.emplace(add_ir_t::SState{.type=add_ir_t::Type::Add,.childCount=2}); + 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); } - } + // progress onto next term + it++; + } // now hash in reverse 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(); + // 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; } @@ -1151,6 +1333,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); @@ -1179,7 +1383,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 {}; } @@ -1188,6 +1392,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; } diff --git a/src/nbl/asset/material_compiler3/CTrueIR.cpp b/src/nbl/asset/material_compiler3/CTrueIR.cpp index d6f05bd104..c7238d5033 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); } @@ -292,7 +296,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 +365,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 +385,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); 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: