Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
559b9be
draft
devshgraphicsprogramming May 15, 2026
c942470
INode getCapabilities method for getting metadata capabilities, use i…
keptsecret May 15, 2026
fa59ef9
getCapabilities should not be abstract method
keptsecret May 18, 2026
25c4dbb
compiles, logic is sound, needs testing
devshgraphicsprogramming May 19, 2026
544ca93
fix a lot of issues, move finalize of function node to first use
devshgraphicsprogramming May 19, 2026
c1d2766
less chatty logging, and improve the IR printing
devshgraphicsprogramming May 19, 2026
cdee69b
fix scalar multiplier detection
devshgraphicsprogramming May 19, 2026
7a72d33
remove abandoned idea dead code, and print better labels for fresnel
devshgraphicsprogramming May 20, 2026
e0d8dbf
improve the AST printing a bit
devshgraphicsprogramming May 20, 2026
fdd61f1
fix bugs with AST and IR children enumeration and bad hashing of Cook…
devshgraphicsprogramming May 20, 2026
4995594
stop the child returning confusion once and for all
devshgraphicsprogramming May 20, 2026
c73dab6
`CThinInfiniteScatterCorrection` needs to be reciprocatable
devshgraphicsprogramming May 20, 2026
3eb0887
Fix up how thindielectric gets done in the AST
devshgraphicsprogramming May 20, 2026
d1f2fff
make debug print significantly less chatty, spot a bug in typed_point…
devshgraphicsprogramming May 20, 2026
6cff904
fix ADd node ASt to IR transcription
devshgraphicsprogramming May 20, 2026
cf5fb39
merge latest material compiler
keptsecret May 21, 2026
07536ad
re-add missing function defs after merge
keptsecret May 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples_tests
106 changes: 52 additions & 54 deletions include/nbl/asset/material_compiler3/CFrontendIR.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
//
Expand Down Expand Up @@ -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 "";}
Expand Down Expand Up @@ -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<CTrueIR::IFunctionNode> 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:
Expand All @@ -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<CTrueIR::IFunctionNode> createIRNode(const bool forBTDF, const CFrontendIR* ast, CTrueIR* ir) const override;

// you can set the members later
inline CBeer() = default;
Expand All @@ -369,10 +378,10 @@ class CFrontendIR final : public CNodePool
protected:
COPY_DEFAULT_IMPL

inline typed_pointer_type<IExprNode> getChildHandle_impl(const uint8_t ix) const override {return ix ? perpTransmittance:thickness;}
inline typed_pointer_type<IExprNode> getChildHandle_impl(const uint8_t ix) const override {return ix ? thickness:perpTransmittance;}
inline void setChild_impl(const uint8_t ix, _typed_pointer_type<IExprNode> newChild) override
{
*(ix ? &perpTransmittance:&thickness) = block_allocator_type::_static_cast<CSpectralVariableExpr>(newChild);
*(ix ? &thickness:&perpTransmittance) = block_allocator_type::_static_cast<CSpectralVariableExpr>(newChild);
}

inline std::string_view getChildName_impl(const uint8_t ix) const override {return ix ? "Thickness":"Perpendicular\\nTransmittance";}
Expand All @@ -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<CTrueIR::IFunctionNode> 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.
Expand All @@ -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<CFresnel*>(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.
Expand Down Expand Up @@ -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
Expand All @@ -462,13 +477,18 @@ 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";}

public:
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<CTrueIR::IFunctionNode> createIRNode(const bool forBTDF, const CFrontendIR* ast, CTrueIR* ir) const override;

// you can set the children later
inline CThinInfiniteScatterCorrection() = default;

Expand Down Expand Up @@ -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<CCookTorrance*>(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;
Expand Down Expand Up @@ -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();
}

Expand All @@ -801,37 +822,6 @@ class CFrontendIR final : public CNodePool
bool btdfSubtree = false;
// for going over layers in the AST
core::vector<const CLayer*> 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<const CTrueIR::IContributor> 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<const CTrueIR::IFactorLeaf> 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<const CTrueIR::INode> 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.
Expand All @@ -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<typed_pointer_type<const IExprNode>> astStack = {}; // its also a stack
core::vector<SFactor> irChain = {};
// this is to help us hash in reverse properly
CTrueIR::typed_pointer_type<CTrueIR::CContributorSum> 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<CTrueIR::typed_pointer_type<const CTrueIR::IFactorLeaf>> irChain = {};
// this is to help us hash in reverse properly
union
{
// without the `rest` filled out yet
CTrueIR::typed_pointer_type<CTrueIR::CContributorSum> contribSumH;
// we're targetting a particular argument of this node, and this node shall have binary adds below it
CTrueIR::typed_pointer_type<CTrueIR::IFunctionNode> 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<Stack<>> allows us to descend the AST dually with multiple traversals at once, so we never actually need to rewrite the AST into something else.
Expand Down
Loading
Loading