diff --git a/3rdparty/dxc/dxc b/3rdparty/dxc/dxc index c765af7b86..a532a63697 160000 --- a/3rdparty/dxc/dxc +++ b/3rdparty/dxc/dxc @@ -1 +1 @@ -Subproject commit c765af7b863c5af1e0e4d2d61dd7ae0f1f37f546 +Subproject commit a532a63697946c09871f4932e4e0ce6e89a843de diff --git a/include/nbl/asset/material_compiler3/CReferenceUnidirectionalPathTracing.h b/include/nbl/asset/material_compiler3/CReferenceUnidirectionalPathTracing.h new file mode 100644 index 0000000000..fda4436916 --- /dev/null +++ b/include/nbl/asset/material_compiler3/CReferenceUnidirectionalPathTracing.h @@ -0,0 +1,46 @@ +// Copyright (C) 2018-2026 - DevSH Graphics Programming Sp. z O.O. +// This file is part of the "Nabla Engine". +// For conditions of distribution and use, see copyright notice in nabla.h +#ifndef _NBL_ASSET_MATERIAL_COMPILER_V3_C_REFERENCE_UNIDIRECTIONAL_PATH_TRACING_H_INCLUDED_ +#define _NBL_ASSET_MATERIAL_COMPILER_V3_C_REFERENCE_UNIDIRECTIONAL_PATH_TRACING_H_INCLUDED_ + +#include "nbl/asset/material_compiler3/IBackend.h" + +namespace nbl::asset::material_compiler3 +{ + +class CReferenceUnidirectionalPathTracing final : public IBackend +{ +public: + class CResult final : public IBackend::IResult + { + public: + std::string fragmentShaderSource_declarations; + std::string fragmentShaderSource; + }; + + core::smart_refctd_ptr compile(const CTrueIR* ir, const std::span materials); + +private: + std::string getHashAs4UintsString(const CTrueIR::INode* node, const CTrueIR* ir, const std::string& separator = ",") const; + + void getAlbedoHLSLCode(std::ostringstream& sstr, const CTrueIR::INode* node, const CTrueIR* ir); + + void getNormalHLSLCode(std::ostringstream& sstr, const CTrueIR::INode* node, const CTrueIR* ir); + + void getAOVThroughputHLSLCode(std::ostringstream& sstr, const CTrueIR::INode* node, const CTrueIR* ir); + + void getTransparencyHLSLCode(std::ostringstream& sstr, const CTrueIR::INode* node, const CTrueIR* ir); + + void getGenerateHLSLCode(std::ostringstream& sstr, const CTrueIR::INode* node, const CTrueIR* ir); + + void getQuotientWeightHLSLCode(std::ostringstream& sstr, const CTrueIR::INode* node, const CTrueIR* ir); + + void getEvalWeightHLSLCode(std::ostringstream& sstr, const CTrueIR::INode* node, const CTrueIR* ir); + + void getEmissionHLSLCode(std::ostringstream& sstr, const CTrueIR::INode* node, const CTrueIR* ir); +}; + +} + +#endif diff --git a/include/nbl/asset/material_compiler3/IBackend.h b/include/nbl/asset/material_compiler3/IBackend.h new file mode 100644 index 0000000000..c0ce755354 --- /dev/null +++ b/include/nbl/asset/material_compiler3/IBackend.h @@ -0,0 +1,25 @@ +// Copyright (C) 2018-2026 - DevSH Graphics Programming Sp. z O.O. +// This file is part of the "Nabla Engine". +// For conditions of distribution and use, see copyright notice in nabla.h +#ifndef _NBL_ASSET_MATERIAL_COMPILER_V3_I_BACKEND_H_INCLUDED_ +#define _NBL_ASSET_MATERIAL_COMPILER_V3_I_BACKEND_H_INCLUDED_ + +#include "nbl/asset/material_compiler3/CTrueIR.h" + +namespace nbl::asset::material_compiler3 +{ + +class IBackend : public core::IReferenceCounted +{ +public: + class IResult : public core::IReferenceCounted + { + + }; + + core::smart_refctd_ptr compile(const CTrueIR*, const std::span); +}; + +} + +#endif diff --git a/src/nbl/CMakeLists.txt b/src/nbl/CMakeLists.txt index 502d0c70c8..feb1e664fb 100644 --- a/src/nbl/CMakeLists.txt +++ b/src/nbl/CMakeLists.txt @@ -170,6 +170,7 @@ set(NBL_ASSET_SOURCES # Materials asset/material_compiler3/CFrontendIR.cpp asset/material_compiler3/CTrueIR.cpp + asset/material_compiler3/CReferenceUnidirectionalPathTracing.cpp # Shaders asset/utils/ISPIRVOptimizer.cpp diff --git a/src/nbl/asset/material_compiler3/CReferenceUnidirectionalPathTracing.cpp b/src/nbl/asset/material_compiler3/CReferenceUnidirectionalPathTracing.cpp new file mode 100644 index 0000000000..487abfcacf --- /dev/null +++ b/src/nbl/asset/material_compiler3/CReferenceUnidirectionalPathTracing.cpp @@ -0,0 +1,463 @@ +// Copyright (C) 2022-2026 - DevSH Graphics Programming Sp. z O.O. +// This file is part of the "Nabla Engine". +// For conditions of distribution and use, see copyright notice in nabla.h +#define _NBL_ASSET_MATERIAL_COMPILER3_C_REFERENCE_UNIDIRECTIONAL_PATH_TRACING_CPP_ +#include "nbl/asset/material_compiler3/CReferenceUnidirectionalPathTracing.h" + +namespace nbl::asset::material_compiler3 +{ + + core::smart_refctd_ptr CReferenceUnidirectionalPathTracing::compile(const CTrueIR* ir, const std::span materialHandles) +{ + auto res = core::make_smart_refctd_ptr(); + + // TODO: handle textures somehow + // TODO: templated structs for types in certain functions, e.g. cache (generate + quotient) + // TODO: where do all the type aliases come from? e.g. sample_t, vector3_t, etc. + std::ostringstream code; + + // define templates of node functions + util structs + code << "template\nstruct gen_cache;\n"; // cache struct + + code << "template\nspectral_t albedo();\n"; // TODO: might add interaction arg + code << "template\nvector3_t normal(NBL_CONST_REF_ARG(aniso_interaction_t) inter);\n"; + code << "template\nvector3_t aov_throughput();\n"; // TODO: confirm return value is vec3, what args needed? uv? + code << "template\nvector3_t transparency();\n"; // TODO: return value is vec3? what args needed? + + // TODO: check how many rand numbers, might need to declare template functions by node type as well (because rand numbers vary by generate, etc.) + // if the above is the case, will have to check whether the node-specific template function declaration is included yet, then add before function definition + code << "template\n" + << "sample_t generate(NBL_CONST_REF_ARG(aniso_interaction_t) inter, NBL_REF_ARG(rand_t) xi, NBL_REF_ARG(rand_t) xi_extra, " + << "NBL_REF_ARG(gen_cache) cache);\n"; + + code << "template\n" + << "quotient_weight_t quotientAndWeight(NBL_CONST_REF_ARG(sample_t) _sample, NBL_CONST_REF_ARG(aniso_interaction_t) inter, " + << "NBL_REF_ARG(gen_cache) cache);\n"; + + code << "template\n" + << "eval_weight_t evalAndWeight(NBL_CONST_REF_ARG(sample_t) _sample, NBL_CONST_REF_ARG(aniso_interaction_t) inter);\n"; + + code << "template\nspectral_t emission();\n"; // TODO: what args needed? + + // loop through layers in IR and construct materials map? maybe just node map is fine + core::vector> nodeStack; + core::unordered_set> visitedNodes; + auto compileBSDFRootNode = [&](CTrueIR::typed_pointer_type rootHandle) -> void { + nodeStack.clear(); + visitedNodes.clear(); + + nodeStack.push_back(rootHandle); + const auto& pool = ir->getObjectPool(); + + while (!nodeStack.empty()) + { + const auto handle = nodeStack.back(); + nodeStack.pop_back(); + const auto* node = pool.deref(handle); + if (!node) + continue; + + getAlbedoHLSLCode(code, node, ir); + getNormalHLSLCode(code, node, ir); + getAOVThroughputHLSLCode(code, node, ir); + getTransparencyHLSLCode(code, node, ir); + getGenerateHLSLCode(code, node, ir); + getEvalWeightHLSLCode(code, node, ir); + getQuotientWeightHLSLCode(code, node, ir); + getEmissionHLSLCode(code, node, ir); + + // TODO: might need to do children first or forward declare function signatures + const auto childCount = node->getChildCount(); + if (childCount) + { + for (auto childIx = 0; childIx < childCount; childIx++) + { + const auto childHandle = node->getChildHandle(childIx); + if (const auto child = pool.deref(childHandle); child) + { + const auto [unused, inserted] = visitedNodes.insert(childHandle); + if (inserted) + nodeStack.push_back(childHandle); + } + } + } + } + }; + + const auto& materials = ir->getMaterials(); + for (uint32_t i = 0; i < materialHandles.size(); i++) + { + if (materialHandles[i].value == CTrueIR::SMaterialHandle::Invalid) + continue; + + const auto& mat = materials[materialHandles[i].value]; + if (auto node = ir->getObjectPool().deref(mat.front.root); node) + compileBSDFRootNode(mat.front.root); + if (auto node = ir->getObjectPool().deref(mat.back.root); node) + compileBSDFRootNode(mat.back.root); + } + + // each layer/node writes as string its own code? or just hash + // 8 functions each node: albedo, normal, aov_throughput, transparency, generate, quotientAndWeight, evalAndWeight, emission + + res->fragmentShaderSource = code.str(); + + return res; +} + +std::string CReferenceUnidirectionalPathTracing::getHashAs4UintsString(const CTrueIR::INode* node, const CTrueIR* ir, const std::string& separator) const +{ + // break up hash into 8 pieces of uint32_t + const auto hash = node->computeHash(ir->getObjectPool()); + uint32_t hashPieces[8]; + for (uint8_t i = 0; i < 8; i++) + for (uint8_t j = 0; j < 4; j++) + hlsl::glsl::bitfieldInsert(hashPieces[i], static_cast(hash.data[4 * i + 0]), j * 8, 8); + std::stringstream hashString; + for (uint8_t i = 0; i < 8; i++) + { + hashString << hashPieces[i]; + if (i < 7) + hashString << separator; + } + return hashString.str(); +} + +void CReferenceUnidirectionalPathTracing::getAlbedoHLSLCode(std::ostringstream& sstr, const CTrueIR::INode* node, const CTrueIR* ir) +{ + switch (node->getFinalType()) + { + case CTrueIR::INode::EFinalType::COrientedLayer: + { + const auto* layer = dynamic_cast(node); + if (!layer) + break; + + const auto hashString = getHashAs4UintsString(node, ir); + sstr << "template<>\nspectral_t albedo<" << hashString << ">()\n{\n"; // TODO: what args needed? uv? + + if (auto childBrdf = ir->getObjectPool().deref(layer->brdfTop); childBrdf) + { + const auto childBrdfHash = getHashAs4UintsString(childBrdf, ir); + sstr << "spectral_t brdf = albedo<" << childBrdfHash << ">();\n"; + } + if (auto childBtdf = ir->getObjectPool().deref(layer->firstTransmission); childBtdf) + { + const auto childBtdfHash = getHashAs4UintsString(childBtdf, ir); + sstr << "spectral_t btdf = albedo<" << childBtdfHash << ">();\n"; + } + + sstr << "spectral_t retval = brdf + btdf;\n"; + sstr << "return retval;\n}\n"; + break; + } + case CTrueIR::INode::EFinalType::CContributorSum: + { + const auto* sum = dynamic_cast(node); + if (!sum) + break; + + const auto hashString = getHashAs4UintsString(node, ir); + sstr << "template<>\nspectral_t albedo<" << hashString << ">()\n{\n"; // TODO: what args needed? uv? + + if (auto childProduct = ir->getObjectPool().deref(sum->product); childProduct) + { + const auto childProductHash = getHashAs4UintsString(childProduct, ir); + sstr << "spectral_t product = albedo<" << childProductHash << ">();\n"; + } + if (auto childRest = ir->getObjectPool().deref(sum->rest); childRest) + { + const auto childRestHash = getHashAs4UintsString(childRest, ir); + sstr << "spectral_t rest = albedo<" << childRestHash << ">();\n"; + } + + sstr << "spectral_t retval = product + rest;\n"; + sstr << "return retval;\n}\n"; + break; + } + case CTrueIR::INode::EFinalType::CFactorCombiner: + { + const auto* combiner = dynamic_cast(node); + if (!combiner) + break; + + const auto hashString = getHashAs4UintsString(node, ir); + sstr << "template<>\nspectral_t albedo<" << hashString << ">()\n{\n"; // TODO: what args needed? uv? + + const auto childCount = combiner->getChildCount(); + + for (uint8_t i = 0; i < childCount; i++) + { + if (auto child = ir->getObjectPool().deref(combiner->getChildHandle(i)); child) + { + const auto childHash = getHashAs4UintsString(child, ir); + sstr << "spectral_t child" << static_cast(i) << " = albedo<" << childHash << ">();\n"; + } + } + + sstr << "spectral_t retval = "; + for (uint8_t i = 0; i < childCount; i++) // TODO: check for invalid children? + sstr << "child" << static_cast(i) << (i < childCount - 1 ? " + " : ""); + sstr << ";\n"; + sstr << "return retval;\n}\n"; + break; + } + case CTrueIR::INode::EFinalType::CWeightedContributor: + { + const auto* contrib = dynamic_cast(node); + if (!contrib) + break; + + const auto hashString = getHashAs4UintsString(node, ir); + sstr << "template<>\nspectral_t albedo<" << hashString << ">()\n{\n"; // TODO: what args needed? uv? + + if (auto childContrib = ir->getObjectPool().deref(contrib->contributor); childContrib) + { + const auto childContribHash = getHashAs4UintsString(childContrib, ir); + sstr << "spectral_t contributor = albedo<" << childContribHash << ">();\n"; + } + if (auto childFactor = ir->getObjectPool().deref(contrib->factor); childFactor) + { + const auto childFactorHash = getHashAs4UintsString(childFactor, ir); + sstr << "spectral_t factor = albedo<" << childFactorHash << ">();\n"; + } + + sstr << "spectral_t retval = contributor + factor;\n"; + sstr << "return retval;\n}\n"; + break; + } + case CTrueIR::INode::EFinalType::CCorellatedTransmission: + { + const auto* transmission = dynamic_cast(node); + if (!transmission) + break; + + const auto hashString = getHashAs4UintsString(node, ir); + sstr << "template<>\nspectral_t albedo<" << hashString << ">()\n{\n"; // TODO: what args needed? uv? + + if (auto child = ir->getObjectPool().deref(transmission->btdf); child) + { + const auto childHash = getHashAs4UintsString(child, ir); + sstr << "spectral_t btdf = albedo<" << childHash << ">();\n"; + } + if (auto child = ir->getObjectPool().deref(transmission->brdfBottom); child) + { + const auto childHash = getHashAs4UintsString(child, ir); + sstr << "spectral_t brdf = albedo<" << childHash << ">();\n"; + } + if (auto child = ir->getObjectPool().deref(transmission->coated); child) + { + const auto childHash = getHashAs4UintsString(child, ir); + sstr << "spectral_t coated = albedo<" << childHash << ">();\n"; + } + if (auto child = ir->getObjectPool().deref(transmission->next); child) + { + const auto childHash = getHashAs4UintsString(child, ir); + sstr << "spectral_t next = albedo<" << childHash << ">();\n"; + } + + sstr << "spectral_t retval = btdf + brdf + coated + next;\n"; + sstr << "return retval;\n}\n"; + break; + } + case CTrueIR::INode::EFinalType::CSpectralVariable: + { + const auto* spectral = dynamic_cast(node); + if (!spectral) + break; + + auto bins = spectral->getSpectralBins(); + const auto hashString = getHashAs4UintsString(node, ir); + sstr << "template<>\nspectral_t albedo<" << hashString << ">()\n{\n"; // TODO: what args needed? uv? + if (bins > 1) + { + sstr << "return spectral_t("; + for (uint8_t i = 0; i < bins; i++) + sstr << spectral->getParameter(i).scale << (i < bins - 1 ? "," : ""); + sstr << ");\n}\n"; + } + else + sstr << "return hlsl::promote(" << spectral->getParameter(0).scale << ");\n}\n"; + break; + } + case CTrueIR::INode::EFinalType::CEmitter: + [[fallthrough]] + case CTrueIR::INode::EFinalType::COrenNayar: + [[fallthrough]] + case CTrueIR::INode::EFinalType::CCookTorrance: + [[fallthrough]] + case CTrueIR::INode::EFinalType::CBeer: + [[fallthrough]] + case CTrueIR::INode::EFinalType::CFresnel: + [[fallthrough]] + case CTrueIR::INode::EFinalType::CThinInfiniteScatterCorrection: + { + const auto hashString = getHashAs4UintsString(node, ir); + sstr << "template<>\nspectral_t albedo<" << hashString << ">()\n{\n"; // TODO: what args needed? uv? + sstr << "return hlsl::promote(0.0);\n}\n"; + break; + } + default: + { + const auto hashString = getHashAs4UintsString(node, ir); + sstr << "template<>\nspectral_t albedo<" << hashString << ">()\n{\nreturn hlsl::promote(0);\n}\n"; // TODO: what args needed? uv? + } + } +} + +void CReferenceUnidirectionalPathTracing::getNormalHLSLCode(std::ostringstream& sstr, const CTrueIR::INode* node, const CTrueIR* ir) +{ + switch (node->getFinalType()) + { + case CTrueIR::INode::EFinalType::COrientedLayer: + [[fallthrough]] + case CTrueIR::INode::EFinalType::CContributorSum: + [[fallthrough]] + case CTrueIR::INode::EFinalType::CFactorCombiner: + [[fallthrough]] + case CTrueIR::INode::EFinalType::CWeightedContributor: + [[fallthrough]] + case CTrueIR::INode::EFinalType::CCorellatedTransmission: + [[fallthrough]] + case CTrueIR::INode::EFinalType::CSpectralVariable: + [[fallthrough]] + case CTrueIR::INode::EFinalType::CEmitter: + [[fallthrough]] + case CTrueIR::INode::EFinalType::COrenNayar: + [[fallthrough]] + case CTrueIR::INode::EFinalType::CCookTorrance: + [[fallthrough]] + case CTrueIR::INode::EFinalType::CBeer: + [[fallthrough]] + case CTrueIR::INode::EFinalType::CFresnel: + [[fallthrough]] + case CTrueIR::INode::EFinalType::CThinInfiniteScatterCorrection: + [[fallthrough]] + default: + { + const auto hashString = getHashAs4UintsString(node, ir); + sstr << R"===( +template<> +vector3_t normal<)===" << hashString << R"===(>(NBL_CONST_REF_ARG(aniso_interaction_t) inter) +{ + return inter.getN(); +} +)==="; // return shading normal by default + } + } +} + +void CReferenceUnidirectionalPathTracing::getGenerateHLSLCode(std::ostringstream& sstr, const CTrueIR::INode* node, const CTrueIR* ir) +{ + switch (node->getFinalType()) + { + case CTrueIR::INode::EFinalType::COrientedLayer: + { + const auto* layer = dynamic_cast(node); + if (!layer) + break; + + const auto hashString = getHashAs4UintsString(node, ir); + sstr << "template<>\nsample_t generate<" << hashString; + sstr << ">(NBL_CONST_REF_ARG(aniso_interaction_t) inter, NBL_REF_ARG(rand_t) xi, NBL_REF_ARG(rand_t) xi_extra, NBL_REF_ARG(gen_cache<" // TODO: class type specific cache + << hashString << ">) cache)\n{\n"; + + if (auto childBrdf = ir->getObjectPool().deref(layer->brdfTop); childBrdf) + { + const auto childBrdfHash = getHashAs4UintsString(childBrdf, ir); + sstr << "gen_cache<" << childBrdfHash << "> brdf_cache;\n"; + sstr << "sample_t brdf = generate<" << childBrdfHash << ">(inter, xi, brdf_cache);\n"; + // TODO: what to do with child caches? + } + if (auto childBtdf = ir->getObjectPool().deref(layer->firstTransmission); childBtdf) + { + const auto childBtdfHash = getHashAs4UintsString(childBtdf, ir); + sstr << "gen_cache<" << childBtdfHash << "> btdf_cache;\n"; + sstr << "sample_t btdf = generate<" << childBtdfHash << ">(inter, xi_extra, btdf_cache);\n"; + // TODO: what to do with child caches? + } + + // TODO: do resampled importance sampling HLSL code + + sstr << "return retval;\n}\n"; + break; + } + case CTrueIR::INode::EFinalType::CContributorSum: + { + const auto* sum = dynamic_cast(node); + if (!sum) + break; + + const auto hashString = getHashAs4UintsString(node, ir); + sstr << "template<>\nsample_t generate<" << hashString; + sstr << ">(NBL_CONST_REF_ARG(aniso_interaction_t) inter, NBL_REF_ARG(rand_t) xi, NBL_REF_ARG(gen_cache<" + << hashString << ">) cache)\n{\n"; + + sstr << "uint16_t chosenLobe = 0;\npdf_t choiceRcpPdf = 1.f;\n"; + + if (auto childProduct = ir->getObjectPool().deref(sum->product); childProduct) + { + const auto childProductHash = getHashAs4UintsString(childProduct, ir); + sstr << "gen_cache<" << childProductHash << "> product_cache;\n"; + sstr << "sample_t product = generate<" << childProductHash << ">(inter, xi, product_cache);\n"; + // TODO: what to do with child caches? + } + if (auto childRest = ir->getObjectPool().deref(sum->rest); childRest) + { + const auto childRestHash = getHashAs4UintsString(childRest, ir); + sstr << "gen_cache<" << childRestHash << "> rest_cache;\n"; + sstr << "sample_t rest = generate<" << childRestHash << ">(inter, xi, rest_cache);\n"; + // TODO: what to do with child caches? + } + + // TODO: weight sample + + sstr << "return retval;\n}\n"; + break; + } + case CTrueIR::INode::EFinalType::CWeightedContributor: + { + // TODO + break; + } + case CTrueIR::INode::EFinalType::CCorellatedTransmission: + { + // TODO + break; + } + case CTrueIR::INode::EFinalType::COrenNayar: + { + const auto* oren_nayar = dynamic_cast(node); + if (!oren_nayar) + break; + + const auto hashString = getHashAs4UintsString(node, ir); + sstr << "template<>\nsample_t generate<" << hashString; + sstr << ">(NBL_CONST_REF_ARG(aniso_interaction_t) inter, NBL_REF_ARG(rand_t) xi, NBL_REF_ARG(gen_cache<" + << hashString << ">) cache)\n{\n"; + + auto roughness = oren_nayar->ndfParams.getRougness(); + sstr << "using oren_nayar_t = bxdf::reflection::SOrenNayar;\n"; + sstr << "using creation_t = typename oren_nayar_t::creation_type;\n"; + sstr << "creation_t params;\nparams.A = " << roughness.data()[0].scale << ";\n"; + sstr << "oren_nayar_t bxdf = diffuse_op_type::create(params);\n"; + + sstr << "typename oren_nayar_t::anisocache_type bxdf_cache;\n"; + sstr << "sample_t _sample = bxdf.generate(inter, xi, bxdf_cache);\n"; + // TODO: what to do with child caches? + + sstr << "return _sample;\n}\n"; + break; + break; + } + case CTrueIR::INode::EFinalType::CCookTorrance: + { + // TODO + break; + } + default: + break; + } +} + +}