diff --git a/examples_tests b/examples_tests index aa2d4bda3e..24baa877d2 160000 --- a/examples_tests +++ b/examples_tests @@ -1 +1 @@ -Subproject commit aa2d4bda3ee0a0e0590aaaba32942fd45c6ddf01 +Subproject commit 24baa877d25cf8eaf0461ba0bce371a4bad57537 diff --git a/include/nbl/builtin/hlsl/bxdf/base/lambertian.hlsl b/include/nbl/builtin/hlsl/bxdf/base/lambertian.hlsl index a107921026..3f7b85875a 100644 --- a/include/nbl/builtin/hlsl/bxdf/base/lambertian.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/base/lambertian.hlsl @@ -37,16 +37,18 @@ struct SLambertianBase template > enable_if_t generate(NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction, const vector2_type u) NBL_CONST_MEMBER_FUNC { + typename sampling::ProjectedHemisphere::cache_type cache; ray_dir_info_type L; - L.setDirection(sampling::ProjectedHemisphere::generate(u)); + L.setDirection(sampling::ProjectedHemisphere::generate(u, cache)); return sample_type::createFromTangentSpace(L, interaction.getFromTangentSpace()); } template > enable_if_t generate(NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction, const vector3_type u) NBL_CONST_MEMBER_FUNC { + typename sampling::ProjectedSphere::cache_type cache; vector3_type _u = u; ray_dir_info_type L; - L.setDirection(sampling::ProjectedSphere::generate(_u)); + L.setDirection(sampling::ProjectedSphere::generate(_u, cache)); return sample_type::createFromTangentSpace(L, interaction.getFromTangentSpace()); } template > @@ -76,10 +78,10 @@ struct SLambertianBase { sampling::quotient_and_pdf qp; NBL_IF_CONSTEXPR (IsBSDF) - qp = sampling::ProjectedSphere::template quotient_and_pdf(_sample.getNdotL(_clamp)); + qp = sampling::ProjectedSphere::template quotientAndPdf(_sample.getNdotL(_clamp)); else - qp = sampling::ProjectedHemisphere::template quotient_and_pdf(_sample.getNdotL(_clamp)); - return quotient_pdf_type::create(qp.quotient[0], qp.pdf); + qp = sampling::ProjectedHemisphere::template quotientAndPdf(_sample.getNdotL(_clamp)); + return quotient_pdf_type::create(qp.quotient()[0], qp.pdf()); } quotient_pdf_type quotient_and_pdf(NBL_CONST_REF_ARG(sample_type) _sample, NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction) NBL_CONST_MEMBER_FUNC { diff --git a/include/nbl/builtin/hlsl/bxdf/base/oren_nayar.hlsl b/include/nbl/builtin/hlsl/bxdf/base/oren_nayar.hlsl index d104842608..ab06e8d43a 100644 --- a/include/nbl/builtin/hlsl/bxdf/base/oren_nayar.hlsl +++ b/include/nbl/builtin/hlsl/bxdf/base/oren_nayar.hlsl @@ -72,16 +72,18 @@ struct SOrenNayarBase template > enable_if_t generate(NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction, const vector2_type u) NBL_CONST_MEMBER_FUNC { + typename sampling::ProjectedHemisphere::cache_type cache; ray_dir_info_type L; - L.setDirection(sampling::ProjectedHemisphere::generate(u)); + L.setDirection(sampling::ProjectedHemisphere::generate(u, cache)); return sample_type::createFromTangentSpace(L, interaction.getFromTangentSpace()); } template > enable_if_t generate(NBL_CONST_REF_ARG(anisotropic_interaction_type) interaction, const vector3_type u) NBL_CONST_MEMBER_FUNC { + typename sampling::ProjectedSphere::cache_type cache; vector3_type _u = u; ray_dir_info_type L; - L.setDirection(sampling::ProjectedSphere::generate(_u)); + L.setDirection(sampling::ProjectedSphere::generate(_u, cache)); return sample_type::createFromTangentSpace(L, interaction.getFromTangentSpace()); } template > diff --git a/include/nbl/builtin/hlsl/bxdf/concepts.hlsl b/include/nbl/builtin/hlsl/bxdf/concepts.hlsl new file mode 100644 index 0000000000..f28fe96755 --- /dev/null +++ b/include/nbl/builtin/hlsl/bxdf/concepts.hlsl @@ -0,0 +1,81 @@ +#ifndef _NBL_BUILTIN_HLSL_BXDF_CONCEPTS_INCLUDED_ +#define _NBL_BUILTIN_HLSL_BXDF_CONCEPTS_INCLUDED_ + +#include + +namespace nbl +{ +namespace hlsl +{ +namespace bxdf +{ +namespace concepts +{ + +// ============================================================================ +// SampleWithPDF +// +// Checks that a sample type bundles a value with its PDF. +// +// Required methods: +// value() - the sampled value +// pdf() - the probability density +// +// Satisfied by: codomain_and_pdf, domain_and_pdf, quotient_and_pdf +// ============================================================================ + +// clang-format off +#define NBL_CONCEPT_NAME SampleWithPDF +#define NBL_CONCEPT_TPLT_PRM_KINDS (typename) +#define NBL_CONCEPT_TPLT_PRM_NAMES (T) +#define NBL_CONCEPT_PARAM_0 (s, T) +NBL_CONCEPT_BEGIN(1) +#define s NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 +NBL_CONCEPT_END( + ((NBL_CONCEPT_REQ_EXPR)(s.pdf())) + ((NBL_CONCEPT_REQ_EXPR)(s.value()))); +#undef s +#include +// clang-format on + +// ============================================================================ +// SampleWithRcpPDF +// +// Checks that a sample type bundles a value with its reciprocal PDF. +// +// Required methods: +// value() - the sampled value +// rcpPdf() - the reciprocal probability density +// +// Satisfied by: codomain_and_rcpPdf, domain_and_rcpPdf +// ============================================================================ + +// clang-format off +#define NBL_CONCEPT_NAME SampleWithRcpPDF +#define NBL_CONCEPT_TPLT_PRM_KINDS (typename) +#define NBL_CONCEPT_TPLT_PRM_NAMES (T) +#define NBL_CONCEPT_PARAM_0 (s, T) +NBL_CONCEPT_BEGIN(1) +#define s NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 +NBL_CONCEPT_END( + ((NBL_CONCEPT_REQ_EXPR)(s.rcpPdf())) + ((NBL_CONCEPT_REQ_EXPR)(s.value()))); +#undef s +#include +// clang-format on + +// ============================================================================ +// SampleWithDensity +// +// A sample type that bundles a value with either its PDF or reciprocal PDF. +// This is the disjunction of SampleWithPDF and SampleWithRcpPDF. +// ============================================================================ +template +NBL_BOOL_CONCEPT SampleWithDensity = SampleWithPDF || SampleWithRcpPDF; + +} // namespace concepts +} // namespace bxdf +} // namespace hlsl +} // namespace nbl + +#endif // _NBL_BUILTIN_HLSL_BXDF_CONCEPTS_INCLUDED_ diff --git a/include/nbl/builtin/hlsl/math/functions.hlsl b/include/nbl/builtin/hlsl/math/functions.hlsl index f7db44b9fb..7930bb73aa 100644 --- a/include/nbl/builtin/hlsl/math/functions.hlsl +++ b/include/nbl/builtin/hlsl/math/functions.hlsl @@ -136,7 +136,7 @@ struct conditionalAbsOrMax_helper; const T condAbs = bit_cast(bit_cast(x) & (cond ? (numeric_limits::max >> 1) : numeric_limits::max)); - return max(condAbs, limit); + return nbl::hlsl::max(condAbs, limit); } }; @@ -156,7 +156,7 @@ struct conditionalAbsOrMax_helper(condAbsAsUint); - return max(condAbs, limit); + return nbl::hlsl::max(condAbs, limit); } }; } diff --git a/include/nbl/builtin/hlsl/path_tracing/gaussian_filter.hlsl b/include/nbl/builtin/hlsl/path_tracing/gaussian_filter.hlsl index 6e27749405..9667275f4e 100644 --- a/include/nbl/builtin/hlsl/path_tracing/gaussian_filter.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/gaussian_filter.hlsl @@ -31,7 +31,8 @@ struct GaussianFilter vector2_type remappedRand = randVec; remappedRand.x *= 1.0 - truncation; remappedRand.x += truncation; - return boxMuller(remappedRand); + typename nbl::hlsl::sampling::BoxMullerTransform::cache_type cache; + return boxMuller.generate(remappedRand, cache); } scalar_type truncation; diff --git a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl index 43e4cb124e..98a81738cb 100644 --- a/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl +++ b/include/nbl/builtin/hlsl/path_tracing/unidirectional.hlsl @@ -106,15 +106,15 @@ struct Unidirectional // So we need to weigh the Delta lobes as if the MIS weight is always 1, but other areas regularly. // Meaning that eval's pdf should equal quotient's pdf , this way even the diffuse contributions coming from within a specular lobe get a MIS weight near 0 for NEE. // This stops a discrepancy in MIS weights and NEE mistakenly trying to add non-delta lobe contributions with a MIS weight > 0 and creating energy from thin air. - if (neeContrib.pdf > scalar_type(0.0)) + if (neeContrib.pdf() > scalar_type(0.0)) { // TODO: we'll need an `eval_and_mis_weight` and `quotient_and_mis_weight` const scalar_type bsdf_pdf = materialSystem.pdf(matID, nee_sample, interaction); - neeContrib.quotient *= materialSystem.eval(matID, nee_sample, interaction) * rcpChoiceProb; - if (neeContrib.pdf < bit_cast(numeric_limits::infinity)) + neeContrib._quotient *= materialSystem.eval(matID, nee_sample, interaction) * rcpChoiceProb; + if (neeContrib.pdf() < bit_cast(numeric_limits::infinity)) { - const scalar_type otherGenOverLightAndChoice = bsdf_pdf * rcpChoiceProb / neeContrib.pdf; - neeContrib.quotient /= 1.f + otherGenOverLightAndChoice * otherGenOverLightAndChoice; // balance heuristic + const scalar_type otherGenOverLightAndChoice = bsdf_pdf * rcpChoiceProb / neeContrib.pdf(); + neeContrib._quotient /= 1.f + otherGenOverLightAndChoice * otherGenOverLightAndChoice; // balance heuristic } const vector3_type origin = intersectP; @@ -124,8 +124,8 @@ struct Unidirectional nee_ray.template setInteraction(interaction); nee_ray.setT(t); tolerance_method_type::template adjust(nee_ray, intersectData.getGeometricNormal(), depth); - if (getLuma(neeContrib.quotient) > lumaContributionThreshold) - ray.addPayloadContribution(neeContrib.quotient * intersector_type::traceShadowRay(scene, nee_ray, ret.getLightObjectID())); + if (getLuma(neeContrib.quotient()) > lumaContributionThreshold) + ray.addPayloadContribution(neeContrib.quotient() * intersector_type::traceShadowRay(scene, nee_ray, ret.getLightObjectID())); } } @@ -142,8 +142,8 @@ struct Unidirectional // the value of the bsdf divided by the probability of the sample being generated quotient_pdf_type bsdf_quotient_pdf = materialSystem.quotient_and_pdf(matID, bsdf_sample, interaction, _cache); - throughput *= bsdf_quotient_pdf.quotient; - bxdfPdf = bsdf_quotient_pdf.pdf; + throughput *= bsdf_quotient_pdf.quotient(); + bxdfPdf = bsdf_quotient_pdf.pdf(); bxdfSample = bsdf_sample.getL().getDirection(); } diff --git a/include/nbl/builtin/hlsl/sampling/alias_table.hlsl b/include/nbl/builtin/hlsl/sampling/alias_table.hlsl new file mode 100644 index 0000000000..1c97f9ff08 --- /dev/null +++ b/include/nbl/builtin/hlsl/sampling/alias_table.hlsl @@ -0,0 +1,114 @@ +// Copyright (C) 2018-2025 - 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_BUILTIN_HLSL_SAMPLING_ALIAS_TABLE_INCLUDED_ +#define _NBL_BUILTIN_HLSL_SAMPLING_ALIAS_TABLE_INCLUDED_ + +#include +#include + +namespace nbl +{ +namespace hlsl +{ +namespace sampling +{ + +// Alias Method (Vose/Walker) discrete sampler. +// +// Samples a discrete index in [0, N) with probability proportional to +// precomputed weights in O(1) time per sample, using a prebuilt alias table. +// +// Template parameters are ReadOnly accessors, each with: +// value_type get(uint32_t i) const; +// +// - ProbabilityAccessor: returns scalar_type threshold in [0, 1] for bin i +// - AliasIndexAccessor: returns uint32_t redirect index for bin i +// - PdfAccessor: returns scalar_type weight[i] / totalWeight +// +// Satisfies TractableSampler (not InvertibleSampler: the mapping is discrete). +// The cache stores the sampled index so forwardPdf can look up the PDF. +template +struct AliasTable +{ + using scalar_type = T; + + using domain_type = scalar_type; + using codomain_type = uint32_t; + using density_type = scalar_type; + using weight_type = density_type; + + struct cache_type + { + codomain_type sampledIndex; + }; + + static AliasTable create(NBL_CONST_REF_ARG(ProbabilityAccessor) _probAccessor, NBL_CONST_REF_ARG(AliasIndexAccessor) _aliasAccessor, NBL_CONST_REF_ARG(PdfAccessor) _pdfAccessor, uint32_t _size) + { + AliasTable retval; + retval.probAccessor = _probAccessor; + retval.aliasAccessor = _aliasAccessor; + retval.pdfAccessor = _pdfAccessor; + // Precompute tableSize as float minus 1 ULP so that u=1.0 maps to bin N-1 + const scalar_type exact = scalar_type(_size); + retval.tableSizeMinusUlp = nbl::hlsl::bit_cast(nbl::hlsl::bit_cast(exact) - 1u); + return retval; + } + + // BasicSampler interface + codomain_type generate(const domain_type u) + { + const scalar_type scaled = u * tableSizeMinusUlp; + const uint32_t bin = uint32_t(scaled); + const scalar_type remainder = scaled - scalar_type(bin); + + // Use if-statement to avoid select: aliasIndex is a dependent read + codomain_type result; + if (remainder < probAccessor.get(bin)) + result = bin; + else + result = aliasAccessor.get(bin); + + return result; + } + + // TractableSampler interface + codomain_type generate(const domain_type u, NBL_REF_ARG(cache_type) cache) + { + const codomain_type result = generate(u); + cache.sampledIndex = result; + return result; + } + + density_type forwardPdf(NBL_CONST_REF_ARG(cache_type) cache) + { + return pdfAccessor.get(cache.sampledIndex); + } + + weight_type forwardWeight(NBL_CONST_REF_ARG(cache_type) cache) + { + return forwardPdf(cache); + } + + density_type backwardPdf(const codomain_type v) + { + return pdfAccessor.get(v); + } + + weight_type backwardWeight(const codomain_type v) + { + return backwardPdf(v); + } + + ProbabilityAccessor probAccessor; + AliasIndexAccessor aliasAccessor; + PdfAccessor pdfAccessor; + scalar_type tableSizeMinusUlp; +}; + +} // namespace sampling +} // namespace hlsl +} // namespace nbl + +#endif diff --git a/include/nbl/builtin/hlsl/sampling/basic.hlsl b/include/nbl/builtin/hlsl/sampling/basic.hlsl index 9c575a22ce..c405275e55 100644 --- a/include/nbl/builtin/hlsl/sampling/basic.hlsl +++ b/include/nbl/builtin/hlsl/sampling/basic.hlsl @@ -18,29 +18,29 @@ namespace sampling template) struct PartitionRandVariable { - using floating_point_type = T; - using uint_type = unsigned_integer_of_size_t; + using floating_point_type = T; + using uint_type = unsigned_integer_of_size_t; - bool operator()(NBL_REF_ARG(floating_point_type) xi, NBL_REF_ARG(floating_point_type) rcpChoiceProb) - { - const floating_point_type NextULPAfterUnity = bit_cast(bit_cast(floating_point_type(1.0)) + uint_type(1u)); - const bool pickRight = xi >= leftProb * NextULPAfterUnity; + bool operator()(NBL_REF_ARG(floating_point_type) xi, NBL_REF_ARG(floating_point_type) rcpChoiceProb) + { + const floating_point_type NextULPAfterUnity = bit_cast(bit_cast(floating_point_type(1.0)) + uint_type(1u)); + const bool pickRight = xi >= leftProb * NextULPAfterUnity; - // This is all 100% correct taking into account the above NextULPAfterUnity - xi -= pickRight ? leftProb : floating_point_type(0.0); + // This is all 100% correct taking into account the above NextULPAfterUnity + xi -= pickRight ? leftProb : floating_point_type(0.0); - rcpChoiceProb = floating_point_type(1.0) / (pickRight ? (floating_point_type(1.0) - leftProb) : leftProb); - xi *= rcpChoiceProb; + rcpChoiceProb = floating_point_type(1.0) / (pickRight ? (floating_point_type(1.0) - leftProb) : leftProb); + xi *= rcpChoiceProb; - return pickRight; - } + return pickRight; + } - floating_point_type leftProb; + floating_point_type leftProb; }; -} -} -} +} // namespace sampling +} // namespace hlsl +} // namespace nbl #endif diff --git a/include/nbl/builtin/hlsl/sampling/bilinear.hlsl b/include/nbl/builtin/hlsl/sampling/bilinear.hlsl index e1ed03c5e9..b676269714 100644 --- a/include/nbl/builtin/hlsl/sampling/bilinear.hlsl +++ b/include/nbl/builtin/hlsl/sampling/bilinear.hlsl @@ -24,6 +24,17 @@ struct Bilinear using vector3_type = vector; using vector4_type = vector; + // BijectiveSampler concept types + using domain_type = vector2_type; + using codomain_type = vector2_type; + using density_type = scalar_type; + using weight_type = density_type; + + struct cache_type + { + density_type pdf; + }; + static Bilinear create(const vector4_type bilinearCoeffs) { Bilinear retval; @@ -35,22 +46,51 @@ struct Bilinear return retval; } - vector2_type generate(const vector2_type _u) + codomain_type generate(const domain_type u, NBL_REF_ARG(cache_type) cache) { - vector2_type u; - u.y = lineary.generate(_u.y); + typename Linear::cache_type linearCache; + + vector2_type p; + p.y = lineary.generate(u.y, linearCache); - const vector2_type ySliceEndPoints = vector2_type(bilinearCoeffs[0] + u.y * bilinearCoeffDiffs[0], bilinearCoeffs[1] + u.y * bilinearCoeffDiffs[1]); + const vector2_type ySliceEndPoints = vector2_type(bilinearCoeffs[0] + p.y * bilinearCoeffDiffs[0], bilinearCoeffs[1] + p.y * bilinearCoeffDiffs[1]); + Linear linearx = Linear::create(ySliceEndPoints); + p.x = linearx.generate(u.x, linearCache); + + cache.pdf = backwardPdf(p); + return p; + } + + domain_type generateInverse(const codomain_type p) + { + vector2_type u; + const vector2_type ySliceEndPoints = vector2_type(bilinearCoeffs[0] + p.y * bilinearCoeffDiffs[0], bilinearCoeffs[1] + p.y * bilinearCoeffDiffs[1]); Linear linearx = Linear::create(ySliceEndPoints); - u.x = linearx.generate(_u.x); + u.x = linearx.generateInverse(p.x); + u.y = lineary.generateInverse(p.y); return u; } - scalar_type backwardPdf(const vector2_type u) + density_type forwardPdf(const cache_type cache) + { + return cache.pdf; + } + + weight_type forwardWeight(const cache_type cache) + { + return forwardPdf(cache); + } + + density_type backwardPdf(const codomain_type p) + { + const vector2_type ySliceEndPoints = vector2_type(bilinearCoeffs[0] + p.y * bilinearCoeffDiffs[0], bilinearCoeffs[1] + p.y * bilinearCoeffDiffs[1]); + return nbl::hlsl::mix(ySliceEndPoints[0], ySliceEndPoints[1], p.x) * fourOverTwiceAreasUnderXCurveSum; + } + + weight_type backwardWeight(const codomain_type p) { - const vector2_type ySliceEndPoints = vector2_type(bilinearCoeffs[0] + u.y * bilinearCoeffDiffs[0], bilinearCoeffs[1] + u.y * bilinearCoeffDiffs[1]); - return nbl::hlsl::mix(ySliceEndPoints[0], ySliceEndPoints[1], u.x) * fourOverTwiceAreasUnderXCurveSum; + return backwardPdf(p); } // unit square: x0y0 x1y0 @@ -61,8 +101,8 @@ struct Bilinear Linear lineary; }; -} -} -} +} // namespace sampling +} // namespace hlsl +} // namespace nbl #endif diff --git a/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl b/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl index cdd87ee4dc..1ee96a3f75 100644 --- a/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl +++ b/include/nbl/builtin/hlsl/sampling/box_muller_transform.hlsl @@ -7,6 +7,7 @@ #include "nbl/builtin/hlsl/math/functions.hlsl" #include "nbl/builtin/hlsl/numbers.hlsl" +#include "nbl/builtin/hlsl/sampling/value_and_pdf.hlsl" namespace nbl { @@ -19,26 +20,70 @@ template) struct BoxMullerTransform { using scalar_type = T; - using vector2_type = vector; + using vector2_type = vector; - vector2_type operator()(const vector2_type xi) + // InvertibleSampler concept types + using domain_type = vector2_type; + using codomain_type = vector2_type; + using density_type = scalar_type; + using weight_type = density_type; + + struct cache_type + { + density_type pdf; + }; + + codomain_type generate(const domain_type u, NBL_REF_ARG(cache_type) cache) { scalar_type sinPhi, cosPhi; - math::sincos(2.0 * numbers::pi * xi.y - numbers::pi, sinPhi, cosPhi); - return vector2_type(cosPhi, sinPhi) * nbl::hlsl::sqrt(-2.0 * nbl::hlsl::log(xi.x)) * stddev; + math::sincos(scalar_type(2.0) * numbers::pi * u.y - numbers::pi, sinPhi, cosPhi); + const codomain_type outPos = vector2_type(cosPhi, sinPhi) * nbl::hlsl::sqrt(scalar_type(-2.0) * nbl::hlsl::log(u.x)) * stddev; + cache.pdf = backwardPdf(outPos); + return outPos; + } + + density_type forwardPdf(const cache_type cache) + { + return cache.pdf; + } + + vector2_type separateForwardPdf(const cache_type cache, const codomain_type outPos) + { + return separateBackwardPdf(outPos); } - vector2_type backwardPdf(const vector2_type outPos) + weight_type forwardWeight(const cache_type cache) { + return forwardPdf(cache); + } + + density_type backwardPdf(const codomain_type outPos) + { + const vector2_type marginals = separateBackwardPdf(outPos); + return marginals.x * marginals.y; + } + + vector2_type separateBackwardPdf(const codomain_type outPos) + { + const scalar_type stddev2 = stddev * stddev; + const scalar_type normalization = scalar_type(1.0) / (stddev * nbl::hlsl::sqrt(scalar_type(2.0) * numbers::pi)); const vector2_type outPos2 = outPos * outPos; - return vector2_type(nbl::hlsl::exp(scalar_type(-0.5) * (outPos2.x + outPos2.y)), numbers::pi * scalar_type(0.5) * hlsl::atan2(outPos.y, outPos.x)); + return vector2_type( + normalization * nbl::hlsl::exp(scalar_type(-0.5) * outPos2.x / stddev2), + normalization * nbl::hlsl::exp(scalar_type(-0.5) * outPos2.y / stddev2) + ); + } + + weight_type backwardWeight(const codomain_type outPos) + { + return backwardPdf(outPos); } T stddev; }; -} -} -} +} // namespace sampling +} // namespace hlsl +} // namespace nbl #endif diff --git a/include/nbl/builtin/hlsl/sampling/concentric_mapping.hlsl b/include/nbl/builtin/hlsl/sampling/concentric_mapping.hlsl index 841fc9ff2d..b94c4fc561 100644 --- a/include/nbl/builtin/hlsl/sampling/concentric_mapping.hlsl +++ b/include/nbl/builtin/hlsl/sampling/concentric_mapping.hlsl @@ -17,34 +17,117 @@ namespace sampling { template -vector concentricMapping(const vector _u) +struct ConcentricMapping { - //map [0;1]^2 to [-1;1]^2 - vector u = 2.0f * _u - hlsl::promote >(1.0); - - vector p; - if (hlsl::all >(glsl::equal(u, hlsl::promote >(0.0)))) - p = hlsl::promote >(0.0); - else - { - T r; - T theta; - if (abs(u.x) > abs(u.y)) { - r = u.x; - theta = 0.25 * numbers::pi * (u.y / u.x); - } else { - r = u.y; - theta = 0.5 * numbers::pi - 0.25 * numbers::pi * (u.x / u.y); - } - - p = r * vector(cos(theta), sin(theta)); - } - - return p; -} - -} -} -} + using scalar_type = T; + using vector2_type = vector; + using vector3_type = vector; + using vector4_type = vector; + + // BijectiveSampler concept types + using domain_type = vector2_type; + using codomain_type = vector2_type; + using density_type = scalar_type; + using weight_type = density_type; + + struct cache_type + { + density_type pdf; + // TODO: should we cache `r`? + }; + + static codomain_type generate(const domain_type _u, NBL_REF_ARG(cache_type) cache) + { + cache.pdf = numbers::inv_pi; + //map [0;1]^2 to [-1;1]^2 + domain_type u = 2.0f * _u - hlsl::promote >(1.0); + + vector p; + if (hlsl::all >(glsl::equal(u, hlsl::promote >(0.0)))) + p = hlsl::promote >(0.0); + else + { + T r; + T theta; + if (hlsl::abs(u.x) > hlsl::abs(u.y)) + { + r = u.x; + theta = 0.25 * numbers::pi * (u.y / u.x); + } + else + { + r = u.y; + theta = 0.5 * numbers::pi - 0.25 * numbers::pi * (u.x / u.y); + } + + p = r * vector(hlsl::cos(theta), hlsl::sin(theta)); + } + + return p; + } + + // Overload for BasicSampler + static codomain_type generate(domain_type _u) + { + cache_type dummy; + return generate(_u, dummy); + } + + static domain_type generateInverse(const codomain_type p) + { + T theta = hlsl::atan2(p.y, p.x); // -pi -> pi + T r = hlsl::sqrt(p.x * p.x + p.y * p.y); + const T PiOver4 = T(0.25) * numbers::pi; + + vector u; + // TODO: should reduce branching somehow? + if (hlsl::abs(theta) < PiOver4 || hlsl::abs(theta) > 3 * PiOver4) + { + r = ieee754::copySign(r, p.x); + u.x = r; + if (p.x < 0) + { + if (p.y < 0) + { + u.y = (numbers::pi + theta) * r / PiOver4; + } + else + { + u.y = (theta - numbers::pi)*r / PiOver4; + } + } + else + { + u.y = (theta * r) / PiOver4; + } + } + else + { + r = ieee754::copySign(r, p.y); + u.y = r; + if (p.y < 0) + { + u.x = -(T(0.5) * numbers::pi + theta) * r / PiOver4; + } + else + { + u.x = (T(0.5) * numbers::pi - theta) * r / PiOver4; + } + } + + return (u + hlsl::promote >(1.0)) * T(0.5); + } + + // The PDF of Shirley mapping is constant (1/PI on the unit disk) + static density_type forwardPdf(cache_type cache) { return numbers::inv_pi; } + static density_type backwardPdf(codomain_type v) { return numbers::inv_pi; } + + static weight_type forwardWeight(cache_type cache) { return forwardPdf(cache); } + static weight_type backwardWeight(codomain_type v) { return backwardPdf(v); } +}; + +} // namespace sampling +} // namespace hlsl +} // namespace nbl #endif diff --git a/include/nbl/builtin/hlsl/sampling/concepts.hlsl b/include/nbl/builtin/hlsl/sampling/concepts.hlsl new file mode 100644 index 0000000000..4bd129f732 --- /dev/null +++ b/include/nbl/builtin/hlsl/sampling/concepts.hlsl @@ -0,0 +1,230 @@ +#ifndef _NBL_BUILTIN_HLSL_SAMPLING_CONCEPTS_INCLUDED_ +#define _NBL_BUILTIN_HLSL_SAMPLING_CONCEPTS_INCLUDED_ + +#include + +namespace nbl +{ +namespace hlsl +{ +namespace sampling +{ +namespace concepts +{ + +// ============================================================================ +// BasicSampler +// +// The simplest sampler: maps domain -> codomain. +// +// Required types: +// domain_type - the input space (e.g. float for 1D, float2 for 2D) +// codomain_type - the output space (e.g. float3 for directions) +// +// Required methods: +// codomain_type generate(domain_type u) +// ============================================================================ + +// clang-format off +#define NBL_CONCEPT_NAME BasicSampler +#define NBL_CONCEPT_TPLT_PRM_KINDS (typename) +#define NBL_CONCEPT_TPLT_PRM_NAMES (T) +#define NBL_CONCEPT_PARAM_0 (_sampler, T) +#define NBL_CONCEPT_PARAM_1 (u, typename T::domain_type) +NBL_CONCEPT_BEGIN(2) +#define _sampler NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 +#define u NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 +NBL_CONCEPT_END( + ((NBL_CONCEPT_REQ_TYPE)(T::domain_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::codomain_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE) ((_sampler.generate(u)), ::nbl::hlsl::is_same_v, typename T::codomain_type))); +#undef u +#undef _sampler +#include +// clang-format on + +// ============================================================================ +// TractableSampler +// +// A sampler whose density can be computed analytically in the forward +// (sampling) direction. generate returns a codomain_type value and writes +// intermediates to a cache_type out-param for later pdf evaluation. +// +// The cache_type out-param stores the input domain_type and/or values +// derived from it during generate, for reuse by forwardPdf without +// redundant recomputation. If there is no common computation between +// generate and backwardPdf, the cache simply stores the codomain value. +// +// For constant-pdf samplers, forwardPdf(cache) == __pdf() (cache ignored). +// For complex samplers (e.g. Cook-Torrance), cache carries intermediate +// values derived from the domain input (e.g. DG1/Fresnel) and forwardPdf +// computes the pdf from those stored intermediates. +// +// Required types: +// domain_type, codomain_type, density_type, cache_type +// +// Required methods: +// codomain_type generate(domain_type u, out cache_type cache) +// density_type forwardPdf(cache_type cache) +// ============================================================================ + +// clang-format off +#define NBL_CONCEPT_NAME TractableSampler +#define NBL_CONCEPT_TPLT_PRM_KINDS (typename) +#define NBL_CONCEPT_TPLT_PRM_NAMES (T) +#define NBL_CONCEPT_PARAM_0 (_sampler, T) +#define NBL_CONCEPT_PARAM_1 (u, typename T::domain_type) +#define NBL_CONCEPT_PARAM_2 (cache, typename T::cache_type) +NBL_CONCEPT_BEGIN(3) +#define _sampler NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 +#define u NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 +#define cache NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_2 +NBL_CONCEPT_END( + ((NBL_CONCEPT_REQ_TYPE)(T::domain_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::codomain_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::density_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::cache_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE) ((_sampler.generate(u, cache)), ::nbl::hlsl::is_same_v, typename T::codomain_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE) ((_sampler.forwardPdf(cache)), ::nbl::hlsl::is_same_v, typename T::density_type))); +#undef cache +#undef u +#undef _sampler +#include +// clang-format on + +// ============================================================================ +// ResamplableSampler +// +// A sampler with forward and backward importance weights, enabling use in +// Multiple Importance Sampling (MIS) and Resampled Importance Sampling (RIS). +// +// Note: resampling does not require tractability - the weights need not be +// normalized probability densities, so this concept is satisfied by +// intractable samplers as well. +// +// Required types: +// domain_type - the input space +// codomain_type - the output space +// cache_type - stores intermediates from generate for forward weight reuse +// weight_type - the type of the importance weight +// +// Required methods: +// codomain_type generate(domain_type u, out cache_type cache) +// weight_type forwardWeight(cache_type cache) - forward weight for MIS +// weight_type backwardWeight(codomain_type v) - backward weight for RIS +// ============================================================================ + +// clang-format off +#define NBL_CONCEPT_NAME ResamplableSampler +#define NBL_CONCEPT_TPLT_PRM_KINDS (typename) +#define NBL_CONCEPT_TPLT_PRM_NAMES (T) +#define NBL_CONCEPT_PARAM_0 (_sampler, T) +#define NBL_CONCEPT_PARAM_1 (u, typename T::domain_type) +#define NBL_CONCEPT_PARAM_2 (v, typename T::codomain_type) +#define NBL_CONCEPT_PARAM_3 (cache, typename T::cache_type) +NBL_CONCEPT_BEGIN(4) +#define _sampler NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 +#define u NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 +#define v NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_2 +#define cache NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_3 +NBL_CONCEPT_END( + ((NBL_CONCEPT_REQ_TYPE)(T::domain_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::codomain_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::cache_type)) + ((NBL_CONCEPT_REQ_TYPE)(T::weight_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((_sampler.generate(u, cache)), ::nbl::hlsl::is_same_v, typename T::codomain_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((_sampler.forwardWeight(cache)), ::nbl::hlsl::is_same_v, typename T::weight_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((_sampler.backwardWeight(v)), ::nbl::hlsl::is_same_v, typename T::weight_type))); +#undef cache +#undef v +#undef u +#undef _sampler +#include +// clang-format on + +// ============================================================================ +// InvertibleSampler +// +// Extends TractableSampler with the ability to evaluate the PDF given +// a codomain value (i.e. without knowing the original domain input). +// The mapping need not be injective — multiple domain elements may map +// to the same codomain output, and backwardPdf accounts for this +// correctly (e.g. by summing contributions). +// +// Also exposes forward and backward importance weights for use in MIS +// and RIS. +// +// Required types (in addition to TractableSampler): +// weight_type - the type of the importance weight +// +// Required methods (in addition to TractableSampler): +// density_type backwardPdf(codomain_type v) - evaluate pdf at codomain value v +// weight_type forwardWeight(cache_type cache) - weight for MIS, reuses generate cache +// weight_type backwardWeight(codomain_type v) - weight for RIS, evaluated at v +// ============================================================================ + +// clang-format off +#define NBL_CONCEPT_NAME InvertibleSampler +#define NBL_CONCEPT_TPLT_PRM_KINDS (typename) +#define NBL_CONCEPT_TPLT_PRM_NAMES (T) +#define NBL_CONCEPT_PARAM_0 (_sampler, T) +#define NBL_CONCEPT_PARAM_1 (u, typename T::domain_type) +#define NBL_CONCEPT_PARAM_2 (v, typename T::codomain_type) +#define NBL_CONCEPT_PARAM_3 (cache, typename T::cache_type) +NBL_CONCEPT_BEGIN(4) +#define _sampler NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 +#define u NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 +#define v NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_2 +#define cache NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_3 +NBL_CONCEPT_END( + ((NBL_CONCEPT_REQ_TYPE_ALIAS_CONCEPT)(TractableSampler, T)) + ((NBL_CONCEPT_REQ_TYPE)(T::weight_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((_sampler.backwardPdf(v)), ::nbl::hlsl::is_same_v, typename T::density_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((_sampler.forwardWeight(cache)), ::nbl::hlsl::is_same_v, typename T::weight_type)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((_sampler.backwardWeight(v)), ::nbl::hlsl::is_same_v, typename T::weight_type))); +#undef cache +#undef v +#undef u +#undef _sampler +#include +// clang-format on + +// ============================================================================ +// BijectiveSampler +// +// The mapping domain <-> codomain is bijective (1:1), so it can be +// inverted. Extends InvertibleSampler with generateInverse. +// +// Because the mapping is bijective, the absolute value of the determinant +// of the Jacobian matrix of the inverse equals the reciprocal of the +// absolute value of the determinant of the Jacobian matrix of the forward +// mapping (the Jacobian is an NxM matrix, not a scalar): +// backwardPdf(v) == 1.0 / forwardPdf(cache) (where v == generate(u, cache)) +// +// Required methods (in addition to InvertibleSampler): +// domain_type generateInverse(codomain_type v) +// ============================================================================ + +// clang-format off +#define NBL_CONCEPT_NAME BijectiveSampler +#define NBL_CONCEPT_TPLT_PRM_KINDS (typename) +#define NBL_CONCEPT_TPLT_PRM_NAMES (T) +#define NBL_CONCEPT_PARAM_0 (_sampler, T) +#define NBL_CONCEPT_PARAM_1 (v, typename T::codomain_type) +NBL_CONCEPT_BEGIN(2) +#define _sampler NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_0 +#define v NBL_CONCEPT_PARAM_T NBL_CONCEPT_PARAM_1 +NBL_CONCEPT_END( + ((NBL_CONCEPT_REQ_TYPE_ALIAS_CONCEPT)(InvertibleSampler, T)) + ((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((_sampler.generateInverse(v)), ::nbl::hlsl::is_same_v, typename T::domain_type))); +#undef v +#undef _sampler +#include +// clang-format on + +} // namespace concepts +} // namespace sampling +} // namespace hlsl +} // namespace nbl + +#endif // _NBL_BUILTIN_HLSL_SAMPLING_CONCEPTS_INCLUDED_ diff --git a/include/nbl/builtin/hlsl/sampling/cos_weighted_spheres.hlsl b/include/nbl/builtin/hlsl/sampling/cos_weighted_spheres.hlsl index ddbb961300..06fd7552d3 100644 --- a/include/nbl/builtin/hlsl/sampling/cos_weighted_spheres.hlsl +++ b/include/nbl/builtin/hlsl/sampling/cos_weighted_spheres.hlsl @@ -19,72 +19,182 @@ namespace sampling template) struct ProjectedHemisphere { - using vector_t2 = vector; - using vector_t3 = vector; - - static vector_t3 generate(const vector_t2 _sample) - { - vector_t2 p = concentricMapping(_sample * T(0.99999) + T(0.000005)); - T z = hlsl::sqrt(hlsl::max(T(0.0), T(1.0) - p.x * p.x - p.y * p.y)); - return vector_t3(p.x, p.y, z); - } - - static T pdf(const T L_z) - { - return L_z * numbers::inv_pi; - } - - template > - static sampling::quotient_and_pdf quotient_and_pdf(const T L) - { - return sampling::quotient_and_pdf::create(hlsl::promote(1.0), pdf(L)); - } - - template > - static sampling::quotient_and_pdf quotient_and_pdf(const vector_t3 L) - { - return sampling::quotient_and_pdf::create(hlsl::promote(1.0), pdf(L.z)); - } + using vector_t2 = vector; + using vector_t3 = vector; + + // BijectiveSampler concept types + using scalar_type = T; + using domain_type = vector_t2; + using codomain_type = vector_t3; + using density_type = T; + using weight_type = density_type; + + struct cache_type + { + density_type pdf; + }; + + static codomain_type __generate(const domain_type _sample) + { + vector_t2 p = ConcentricMapping::generate(_sample * T(0.99999) + T(0.000005)); + T z = hlsl::sqrt(hlsl::max(T(0.0), T(1.0) - p.x * p.x - p.y * p.y)); + return vector_t3(p.x, p.y, z); + } + + static codomain_type generate(const domain_type _sample, NBL_REF_ARG(cache_type) cache) + { + const codomain_type L = __generate(_sample); + cache.pdf = __pdf(L.z); + return L; + } + + static domain_type __generateInverse(const codomain_type L) + { + return ConcentricMapping::generateInverse(L.xy); + } + + static domain_type generateInverse(const codomain_type L) + { + return __generateInverse(L); + } + + static T __pdf(const T L_z) + { + return L_z * numbers::inv_pi; + } + + static scalar_type pdf(const T L_z) + { + return __pdf(L_z); + } + + static density_type forwardPdf(const cache_type cache) + { + return cache.pdf; + } + + static weight_type forwardWeight(const cache_type cache) + { + return forwardPdf(cache); + } + + static density_type backwardPdf(const codomain_type L) + { + return __pdf(L.z); + } + + static weight_type backwardWeight(const codomain_type L) + { + return backwardPdf(L); + } + + template > + static quotient_and_pdf quotientAndPdf(const T L) + { + return quotient_and_pdf::create(hlsl::promote(1.0), __pdf(L)); + } + + template > + static quotient_and_pdf quotientAndPdf(const vector_t3 L) + { + return quotient_and_pdf::create(hlsl::promote(1.0), __pdf(L.z)); + } }; template) struct ProjectedSphere { - using vector_t2 = vector; - using vector_t3 = vector; - using hemisphere_t = ProjectedHemisphere; - - static vector_t3 generate(NBL_REF_ARG(vector_t3) _sample) - { - vector_t3 retval = hemisphere_t::generate(_sample.xy); - const bool chooseLower = _sample.z > T(0.5); - retval.z = chooseLower ? (-retval.z) : retval.z; - if (chooseLower) - _sample.z -= T(0.5); - _sample.z *= T(2.0); - return retval; - } - - static T pdf(T L_z) - { - return T(0.5) * hemisphere_t::pdf(L_z); - } - - template > - static sampling::quotient_and_pdf quotient_and_pdf(T L) - { - return sampling::quotient_and_pdf::create(hlsl::promote(1.0), pdf(L)); - } - - template > - static sampling::quotient_and_pdf quotient_and_pdf(const vector_t3 L) - { - return sampling::quotient_and_pdf::create(hlsl::promote(1.0), pdf(L.z)); - } + using vector_t2 = vector; + using vector_t3 = vector; + using hemisphere_t = ProjectedHemisphere; + + // BijectiveSampler concept types + using scalar_type = T; + using domain_type = vector_t3; + using codomain_type = vector_t3; + using density_type = T; + using weight_type = density_type; + + struct cache_type + { + density_type pdf; + }; + + static codomain_type __generate(NBL_REF_ARG(domain_type) _sample) + { + vector_t3 retval = hemisphere_t::__generate(_sample.xy); + const bool chooseLower = _sample.z > T(0.5); + retval.z = chooseLower ? (-retval.z) : retval.z; + if (chooseLower) + _sample.z -= T(0.5); + _sample.z *= T(2.0); + return retval; + } + + static codomain_type generate(NBL_REF_ARG(domain_type) _sample, NBL_REF_ARG(cache_type) cache) + { + const codomain_type L = __generate(_sample); + cache.pdf = __pdf(L.z); + return L; + } + + static domain_type __generateInverse(const codomain_type L) + { + // NOTE: incomplete information to recover exact z component; we only know which hemisphere L came from, + // so we return a canonical value (0.0 for upper, 1.0 for lower) that round-trips correctly through __generate + return vector_t3(hemisphere_t::__generateInverse(L), hlsl::mix(T(1.0), T(0.0), L.z > T(0.0))); + } + + static domain_type generateInverse(const codomain_type L) + { + return __generateInverse(L); + } + + static T __pdf(T L_z) + { + return T(0.5) * hemisphere_t::__pdf(hlsl::abs(L_z)); + } + + static scalar_type pdf(T L_z) + { + return __pdf(L_z); + } + + static density_type forwardPdf(const cache_type cache) + { + return cache.pdf; + } + + static weight_type forwardWeight(const cache_type cache) + { + return forwardPdf(cache); + } + + static density_type backwardPdf(const codomain_type L) + { + return __pdf(L.z); + } + + static weight_type backwardWeight(const codomain_type L) + { + return backwardPdf(L); + } + + template > + static quotient_and_pdf quotientAndPdf(T L) + { + return quotient_and_pdf::create(hlsl::promote(1.0), __pdf(L)); + } + + template > + static quotient_and_pdf quotientAndPdf(const vector_t3 L) + { + return quotient_and_pdf::create(hlsl::promote(1.0), __pdf(L.z)); + } }; -} -} -} +} // namespace sampling +} // namespace hlsl +} // namespace nbl #endif diff --git a/include/nbl/builtin/hlsl/sampling/cumulative_probability.hlsl b/include/nbl/builtin/hlsl/sampling/cumulative_probability.hlsl new file mode 100644 index 0000000000..a8cb01b7a1 --- /dev/null +++ b/include/nbl/builtin/hlsl/sampling/cumulative_probability.hlsl @@ -0,0 +1,104 @@ +// Copyright (C) 2018-2025 - 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_BUILTIN_HLSL_SAMPLING_CUMULATIVE_PROBABILITY_INCLUDED_ +#define _NBL_BUILTIN_HLSL_SAMPLING_CUMULATIVE_PROBABILITY_INCLUDED_ + +#include +#include + +namespace nbl +{ +namespace hlsl +{ +namespace sampling +{ + +// Discrete sampler using cumulative probability lookup via upper_bound. +// +// Samples a discrete index in [0, N) with probability proportional to +// precomputed weights in O(log N) time per sample. +// +// The cumulative probability array stores N-1 entries (the last bucket +// is always 1.0 and need not be stored). Entry i holds the sum of +// probabilities for indices [0, i]. +// +// Template parameters are ReadOnly accessors: +// - CumulativeProbabilityAccessor: returns scalar_type cumProb for index i, +// must have `value_type` typedef and `operator[](uint32_t)` for upper_bound +// - PdfAccessor: returns scalar_type weight[i] / totalWeight +// +// Satisfies TractableSampler and ResamplableSampler (not InvertibleSampler: +// the mapping is discrete). +template +struct CumulativeProbabilitySampler +{ + using scalar_type = T; + + using domain_type = scalar_type; + using codomain_type = uint32_t; + using density_type = scalar_type; + using weight_type = density_type; + + struct cache_type + { + codomain_type sampledIndex; + }; + + static CumulativeProbabilitySampler create(NBL_CONST_REF_ARG(CumulativeProbabilityAccessor) _cumProbAccessor, NBL_CONST_REF_ARG(PdfAccessor) _pdfAccessor, uint32_t _size) + { + CumulativeProbabilitySampler retval; + retval.cumProbAccessor = _cumProbAccessor; + retval.pdfAccessor = _pdfAccessor; + retval.size = _size; + return retval; + } + + // BasicSampler interface + codomain_type generate(const domain_type u) + { + // upper_bound on N-1 stored entries; if u >= all stored values, returns N-1 (the last bucket) + const uint32_t storedCount = size - 1u; + // upper_bound returns first index where cumProb > u + return hlsl::upper_bound(cumProbAccessor, 0u, storedCount, u); + } + + // TractableSampler interface + codomain_type generate(const domain_type u, NBL_REF_ARG(cache_type) cache) + { + const codomain_type result = generate(u); + cache.sampledIndex = result; + return result; + } + + density_type forwardPdf(NBL_CONST_REF_ARG(cache_type) cache) + { + return pdfAccessor.get(cache.sampledIndex); + } + + weight_type forwardWeight(NBL_CONST_REF_ARG(cache_type) cache) + { + return forwardPdf(cache); + } + + density_type backwardPdf(const codomain_type v) + { + return pdfAccessor.get(v); + } + + weight_type backwardWeight(const codomain_type v) + { + return backwardPdf(v); + } + + CumulativeProbabilityAccessor cumProbAccessor; + PdfAccessor pdfAccessor; + uint32_t size; +}; + +} // namespace sampling +} // namespace hlsl +} // namespace nbl + +#endif diff --git a/include/nbl/builtin/hlsl/sampling/linear.hlsl b/include/nbl/builtin/hlsl/sampling/linear.hlsl index 289ea75485..2d3097f490 100644 --- a/include/nbl/builtin/hlsl/sampling/linear.hlsl +++ b/include/nbl/builtin/hlsl/sampling/linear.hlsl @@ -21,30 +21,79 @@ struct Linear using scalar_type = T; using vector2_type = vector; + // BijectiveSampler concept types + using domain_type = scalar_type; + using codomain_type = scalar_type; + using density_type = scalar_type; + using weight_type = density_type; + + struct cache_type + { + density_type pdf; + }; + static Linear create(const vector2_type linearCoeffs) // start and end importance values (start, end), assumed to be at x=0 and x=1 { Linear retval; retval.linearCoeffStart = linearCoeffs[0]; - retval.rcpDiff = 1.0 / (linearCoeffs[0] - linearCoeffs[1]); + retval.linearCoeffDiff = linearCoeffs[1] - linearCoeffs[0]; + retval.rcpCoeffSum = scalar_type(1.0) / (linearCoeffs[0] + linearCoeffs[1]); + retval.rcpDiff = -scalar_type(1.0) / retval.linearCoeffDiff; vector2_type squaredCoeffs = linearCoeffs * linearCoeffs; retval.squaredCoeffStart = squaredCoeffs[0]; retval.squaredCoeffDiff = squaredCoeffs[1] - squaredCoeffs[0]; return retval; } - scalar_type generate(const scalar_type u) + density_type __pdf(const codomain_type x) + { + if (x < scalar_type(0.0) || x > scalar_type(1.0)) + return scalar_type(0.0); + return scalar_type(2.0) * (linearCoeffStart + x * linearCoeffDiff) * rcpCoeffSum; + } + + codomain_type generate(const domain_type u, NBL_REF_ARG(cache_type) cache) + { + const codomain_type x = hlsl::mix(u, (linearCoeffStart - sqrt(squaredCoeffStart + u * squaredCoeffDiff)) * rcpDiff, abs(rcpDiff) < hlsl::numeric_limits::max); + cache.pdf = __pdf(x); + return x; + } + + domain_type generateInverse(const codomain_type x) + { + return x * (scalar_type(2.0) * linearCoeffStart + linearCoeffDiff * x) * rcpCoeffSum; + } + + density_type forwardPdf(const cache_type cache) + { + return cache.pdf; + } + + weight_type forwardWeight(const cache_type cache) + { + return forwardPdf(cache); + } + + density_type backwardPdf(const codomain_type x) + { + return __pdf(x); + } + + weight_type backwardWeight(const codomain_type x) { - return hlsl::mix(u, (linearCoeffStart - hlsl::sqrt(squaredCoeffStart + u * squaredCoeffDiff)) * rcpDiff, hlsl::abs(rcpDiff) < numeric_limits::max); + return backwardPdf(x); } scalar_type linearCoeffStart; + scalar_type linearCoeffDiff; + scalar_type rcpCoeffSum; scalar_type rcpDiff; scalar_type squaredCoeffStart; scalar_type squaredCoeffDiff; }; -} -} -} +} // namespace sampling +} // namespace hlsl +} // namespace nbl #endif diff --git a/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl b/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl index 116dbd3cd9..2866d3e618 100644 --- a/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/projected_spherical_triangle.hlsl @@ -26,61 +26,72 @@ struct ProjectedSphericalTriangle using vector3_type = vector; using vector4_type = vector; - static ProjectedSphericalTriangle create(NBL_CONST_REF_ARG(shapes::SphericalTriangle) tri) + // InvertibleSampler concept types + using domain_type = vector2_type; + using codomain_type = vector3_type; + using density_type = scalar_type; + using weight_type = density_type; + + struct cache_type { - ProjectedSphericalTriangle retval; - retval.sphtri = sampling::SphericalTriangle::create(tri); - return retval; - } + density_type pdf; + }; - vector4_type computeBilinearPatch(const vector3_type receiverNormal, bool isBSDF) + // NOTE: produces a degenerate (all-zero) bilinear patch when the receiver normal faces away + // from all three triangle vertices, resulting in NaN PDFs (0 * inf). Callers must ensure + // at least one vertex has positive projection onto the receiver normal. + Bilinear computeBilinearPatch() { const scalar_type minimumProjSolidAngle = 0.0; - matrix m = matrix(sphtri.tri.vertices[0], sphtri.tri.vertices[1], sphtri.tri.vertices[2]); - const vector3_type bxdfPdfAtVertex = math::conditionalAbsOrMax(isBSDF, hlsl::mul(m, receiverNormal), hlsl::promote(minimumProjSolidAngle)); + matrix m = matrix(sphtri.tri_vertices[0], sphtri.tri_vertices[1], sphtri.tri_vertices[2]); + const vector3_type bxdfPdfAtVertex = math::conditionalAbsOrMax(receiverWasBSDF, hlsl::mul(m, receiverNormal), hlsl::promote(minimumProjSolidAngle)); - return bxdfPdfAtVertex.yyxz; + return Bilinear::create(bxdfPdfAtVertex.yyxz); } - vector3_type generate(NBL_REF_ARG(scalar_type) rcpPdf, scalar_type solidAngle, scalar_type cos_c, scalar_type csc_b, const vector3_type receiverNormal, bool isBSDF, const vector2_type _u) + codomain_type generate(const domain_type u, NBL_REF_ARG(cache_type) cache) { - vector2_type u; - // pre-warp according to proj solid angle approximation - vector4_type patch = computeBilinearPatch(receiverNormal, isBSDF); - Bilinear bilinear = Bilinear::create(patch); - u = bilinear.generate(_u); - - // now warp the points onto a spherical triangle - const vector3_type L = sphtri.generate(cos_c, csc_b, u); - rcpPdf = solidAngle / bilinear.backwardPdf(u); - + Bilinear bilinear = computeBilinearPatch(); + typename Bilinear::cache_type bilinearCache; + const vector2_type warped = bilinear.generate(u, bilinearCache); + typename SphericalTriangle::cache_type sphtriCache; + const vector3_type L = sphtri.generate(warped, sphtriCache); + // combined weight: sphtri pdf (1/solidAngle) * bilinear pdf at u + cache.pdf = sphtri.forwardPdf(sphtriCache) * bilinear.forwardPdf(bilinearCache); return L; } - vector3_type generate(NBL_REF_ARG(scalar_type) rcpPdf, const vector3_type receiverNormal, bool isBSDF, const vector2_type u) + density_type forwardPdf(const cache_type cache) { - const scalar_type cos_c = sphtri.tri.cos_sides[2]; - const scalar_type csc_b = sphtri.tri.csc_sides[1]; - const scalar_type solidAngle = sphtri.tri.solidAngle(); - return generate(rcpPdf, solidAngle, cos_c, csc_b, receiverNormal, isBSDF, u); + return cache.pdf; } - scalar_type backwardPdf(const vector3_type receiverNormal, bool receiverWasBSDF, const vector3_type L) + weight_type forwardWeight(const cache_type cache) { - scalar_type pdf; - const vector2_type u = sphtri.generateInverse(pdf, L); + return forwardPdf(cache); + } - vector4_type patch = computeBilinearPatch(receiverNormal, receiverWasBSDF); - Bilinear bilinear = Bilinear::create(patch); + density_type backwardPdf(const vector3_type L) + { + const density_type pdf = sphtri.backwardPdf(L); + const vector2_type u = sphtri.generateInverse(L); + Bilinear bilinear = computeBilinearPatch(); return pdf * bilinear.backwardPdf(u); } + weight_type backwardWeight(const vector3_type L) + { + return backwardPdf(L); + } + sampling::SphericalTriangle sphtri; + vector3_type receiverNormal; + bool receiverWasBSDF; }; -} -} -} +} // namespace sampling +} // namespace hlsl +} // namespace nbl #endif diff --git a/include/nbl/builtin/hlsl/sampling/quotient_and_pdf.hlsl b/include/nbl/builtin/hlsl/sampling/quotient_and_pdf.hlsl index 26a62ea617..056f5a91c7 100644 --- a/include/nbl/builtin/hlsl/sampling/quotient_and_pdf.hlsl +++ b/include/nbl/builtin/hlsl/sampling/quotient_and_pdf.hlsl @@ -25,26 +25,29 @@ struct quotient_and_pdf static this_t create(const Q _quotient, const P _pdf) { this_t retval; - retval.quotient = _quotient; - retval.pdf = _pdf; + retval._quotient = _quotient; + retval._pdf = _pdf; return retval; } static this_t create(const scalar_q _quotient, const P _pdf) { this_t retval; - retval.quotient = hlsl::promote(_quotient); - retval.pdf = _pdf; + retval._quotient = hlsl::promote(_quotient); + retval._pdf = _pdf; return retval; } + Q quotient() { return _quotient; } + P pdf() { return _pdf; } + Q value() { - return quotient*pdf; + return _quotient * _pdf; } - Q quotient; - P pdf; + Q _quotient; + P _pdf; }; } diff --git a/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl b/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl index 4c3f02e5f2..afa805150b 100644 --- a/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/spherical_rectangle.hlsl @@ -8,7 +8,7 @@ #include #include #include -#include +#include namespace nbl { @@ -25,44 +25,60 @@ struct SphericalRectangle using vector3_type = vector; using vector4_type = vector; - static SphericalRectangle create(NBL_CONST_REF_ARG(shapes::SphericalRectangle) rect) + // InvertibleSampler concept types + using domain_type = vector2_type; + using codomain_type = vector2_type; + using density_type = scalar_type; + using weight_type = density_type; + + struct cache_type { - SphericalRectangle retval; - retval.rect = rect; - return retval; - } + density_type pdf; + }; - vector2_type generate(const vector3_type observer, const vector2_type uv, NBL_REF_ARG(scalar_type) S) + NBL_CONSTEXPR_STATIC_INLINE scalar_type ClampEps = 1e-5; + + static SphericalRectangle create(NBL_CONST_REF_ARG(shapes::SphericalRectangle) rect, const vector3_type observer) { - vector3_type r0 = hlsl::mul(rect.basis, rect.origin - observer); - const vector4_type denorm_n_z = vector4_type(-r0.y, r0.x + rect.extents.x, r0.y + rect.extents.y, -r0.x); - const vector4_type n_z = denorm_n_z / hlsl::sqrt(hlsl::promote(r0.z * r0.z) + denorm_n_z * denorm_n_z); - const vector4_type cosGamma = vector4_type( + SphericalRectangle retval; + + retval.r0 = hlsl::mul(rect.basis, rect.origin - observer); + const vector4_type denorm_n_z = vector4_type(-retval.r0.y, retval.r0.x + rect.extents.x, retval.r0.y + rect.extents.y, -retval.r0.x); + const vector4_type n_z = denorm_n_z / hlsl::sqrt(hlsl::promote(retval.r0.z * retval.r0.z) + denorm_n_z * denorm_n_z); + retval.cosGamma = vector4_type( -n_z[0] * n_z[1], -n_z[1] * n_z[2], -n_z[2] * n_z[3], -n_z[3] * n_z[0] ); - math::sincos_accumulator angle_adder = math::sincos_accumulator::create(cosGamma[0]); - angle_adder.addCosine(cosGamma[1]); + math::sincos_accumulator angle_adder = math::sincos_accumulator::create(retval.cosGamma[0]); + angle_adder.addCosine(retval.cosGamma[1]); scalar_type p = angle_adder.getSumofArccos(); - angle_adder = math::sincos_accumulator::create(cosGamma[2]); - angle_adder.addCosine(cosGamma[3]); + angle_adder = math::sincos_accumulator::create(retval.cosGamma[2]); + angle_adder.addCosine(retval.cosGamma[3]); scalar_type q = angle_adder.getSumofArccos(); const scalar_type k = scalar_type(2.0) * numbers::pi - q; - const scalar_type b0 = n_z[0]; - const scalar_type b1 = n_z[2]; - S = p + q - scalar_type(2.0) * numbers::pi; - - const scalar_type CLAMP_EPS = 1e-5; + retval.solidAngle = p + q - scalar_type(2.0) * numbers::pi; // flip z axis if r0.z > 0 - r0.z = -hlsl::abs(r0.z); - vector3_type r1 = r0 + vector3_type(rect.extents.x, rect.extents.y, 0); + retval.r0 = hlsl::promote(-hlsl::abs(retval.r0.z)); + retval.r1 = retval.r0 + vector3_type(rect.extents.x, rect.extents.y, 0); + + retval.b0 = n_z[0]; + retval.b1 = n_z[2]; + return retval; + } + + codomain_type generate(const domain_type u, NBL_REF_ARG(cache_type) cache) + { + math::sincos_accumulator angle_adder = math::sincos_accumulator::create(cosGamma[2]); + angle_adder.addCosine(cosGamma[3]); + scalar_type q = angle_adder.getSumofArccos(); + const scalar_type k = scalar_type(2.0) * numbers::pi - q; - const scalar_type au = uv.x * S + k; + const scalar_type au = u.x * solidAngle + k; const scalar_type fu = (hlsl::cos(au) * b0 - b1) / hlsl::sin(au); const scalar_type cu_2 = hlsl::max(fu * fu + b0 * b0, 1.f); // forces `cu` to be in [-1,1] const scalar_type cu = ieee754::flipSignIfRHSNegative(scalar_type(1.0) / hlsl::sqrt(cu_2), fu); @@ -74,18 +90,45 @@ struct SphericalRectangle const scalar_type h0 = r0.y / hlsl::sqrt(d_2 + r0.y * r0.y); const scalar_type h1 = r1.y / hlsl::sqrt(d_2 + r1.y * r1.y); - const scalar_type hv = h0 + uv.y * (h1 - h0); + const scalar_type hv = h0 + u.y * (h1 - h0); const scalar_type hv2 = hv * hv; - const scalar_type yv = hlsl::mix(r1.y, (hv * d) / hlsl::sqrt(scalar_type(1.0) - hv2), hv2 < scalar_type(1.0) - CLAMP_EPS); + const scalar_type yv = hlsl::mix(r1.y, (hv * d) / hlsl::sqrt(scalar_type(1.0) - hv2), hv2 < scalar_type(1.0) - ClampEps); + + cache.pdf = scalar_type(1.0) / solidAngle; + + return vector2_type((xu - r0.x), (yv - r0.y)); + } + + density_type forwardPdf(const cache_type cache) + { + return cache.pdf; + } - return vector2_type((xu - r0.x) / rect.extents.x, (yv - r0.y) / rect.extents.y); + weight_type forwardWeight(const cache_type cache) + { + return forwardPdf(cache); + } + + density_type backwardPdf(const codomain_type L) + { + return scalar_type(1.0) / solidAngle; + } + + weight_type backwardWeight(const codomain_type L) + { + return backwardPdf(L); } - shapes::SphericalRectangle rect; + scalar_type solidAngle; + vector4_type cosGamma; + scalar_type b0; + scalar_type b1; + vector3_type r0; + vector3_type r1; }; -} -} -} +} // namespace sampling +} // namespace hlsl +} // namespace nbl #endif diff --git a/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl b/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl index 2b2dc0ccb6..0e922882bb 100644 --- a/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl +++ b/include/nbl/builtin/hlsl/sampling/spherical_triangle.hlsl @@ -21,106 +21,141 @@ namespace sampling template struct SphericalTriangle { - using scalar_type = T; - using vector2_type = vector; - using vector3_type = vector; - - static SphericalTriangle create(NBL_CONST_REF_ARG(shapes::SphericalTriangle) tri) - { - SphericalTriangle retval; - retval.tri = tri; - vector3_type cos_vertices, sin_vertices; - retval.solidAngle = tri.solidAngle(cos_vertices, sin_vertices); - retval.cosA = cos_vertices[0]; - retval.sinA = sin_vertices[0]; - return retval; - } - - vector3_type generate(scalar_type cos_c, scalar_type csc_b, const vector2_type u) - { - scalar_type negSinSubSolidAngle,negCosSubSolidAngle; - math::sincos(solidAngle * u.x - numbers::pi, negSinSubSolidAngle, negCosSubSolidAngle); - - const scalar_type p = negCosSubSolidAngle * sinA - negSinSubSolidAngle * cosA; - const scalar_type q = -negSinSubSolidAngle * sinA - negCosSubSolidAngle * cosA; - - // TODO: we could optimize everything up and including to the first slerp, because precision here is just godawful - scalar_type u_ = q - cosA; - scalar_type v_ = p + sinA * cos_c; - - // the slerps could probably be optimized by sidestepping `normalize` calls and accumulating scaling factors - vector3_type C_s = tri.vertices[0]; - if (csc_b < numeric_limits::max) - { - const scalar_type cosAngleAlongAC = ((v_ * q - u_ * p) * cosA - v_) / ((v_ * p + u_ * q) * sinA); - if (nbl::hlsl::abs(cosAngleAlongAC) < 1.f) - C_s += math::quaternion::slerp_delta(tri.vertices[0], tri.vertices[2] * csc_b, cosAngleAlongAC); - } - - vector3_type retval = tri.vertices[1]; - const scalar_type cosBC_s = nbl::hlsl::dot(C_s, tri.vertices[1]); - const scalar_type csc_b_s = 1.0 / nbl::hlsl::sqrt(1.0 - cosBC_s * cosBC_s); - if (csc_b_s < numeric_limits::max) - { - const scalar_type cosAngleAlongBC_s = nbl::hlsl::clamp(1.0 + cosBC_s * u.y - u.y, -1.f, 1.f); - if (nbl::hlsl::abs(cosAngleAlongBC_s) < 1.f) - retval += math::quaternion::slerp_delta(tri.vertices[1], C_s * csc_b_s, cosAngleAlongBC_s); - } - return retval; - } - - vector3_type generate(NBL_REF_ARG(scalar_type) rcpPdf, const vector2_type u) - { - const scalar_type cos_c = tri.cos_sides[2]; - const scalar_type csc_b = tri.csc_sides[1]; - - rcpPdf = solidAngle; - - return generate(cos_c, csc_b, u); - } - - vector2_type generateInverse(NBL_REF_ARG(scalar_type) pdf, scalar_type cos_c, scalar_type csc_c, const vector3_type L) - { - pdf = 1.0 / solidAngle; - - const scalar_type cosAngleAlongBC_s = nbl::hlsl::dot(L, tri.vertices[1]); - const scalar_type csc_a_ = nbl::hlsl::rsqrt(1.0 - cosAngleAlongBC_s * cosAngleAlongBC_s); - const scalar_type cos_b_ = nbl::hlsl::dot(L, tri.vertices[0]); - - const scalar_type cosB_ = (cos_b_ - cosAngleAlongBC_s * cos_c) * csc_a_ * csc_c; - const scalar_type sinB_ = nbl::hlsl::sqrt(1.0 - cosB_ * cosB_); - - const scalar_type cosC_ = sinA * sinB_* cos_c - cosA * cosB_; - const scalar_type sinC_ = nbl::hlsl::sqrt(1.0 - cosC_ * cosC_); - - math::sincos_accumulator angle_adder = math::sincos_accumulator::create(cosA, sinA); - angle_adder.addAngle(cosB_, sinB_); - angle_adder.addAngle(cosC_, sinC_); - const scalar_type subTriSolidAngleRatio = (angle_adder.getSumofArccos() - numbers::pi) * pdf; - const scalar_type u = subTriSolidAngleRatio > numeric_limits::min ? subTriSolidAngleRatio : 0.0; - - const scalar_type cosBC_s = (cosA + cosB_ * cosC_) / (sinB_ * sinC_); - const scalar_type v = (1.0 - cosAngleAlongBC_s) / (1.0 - (cosBC_s < bit_cast(0x3f7fffff) ? cosBC_s : cos_c)); - - return vector2_type(u,v); - } - - vector2_type generateInverse(NBL_REF_ARG(scalar_type) pdf, const vector3_type L) - { - const scalar_type cos_c = tri.cos_sides[2]; - const scalar_type csc_c = tri.csc_sides[2]; - - return generateInverse(pdf, cos_c, csc_c, L); - } - - shapes::SphericalTriangle tri; - scalar_type solidAngle; - scalar_type cosA; - scalar_type sinA; + using scalar_type = T; + using vector2_type = vector; + using vector3_type = vector; + using uint_type = unsigned_integer_of_size_t; + + // BijectiveSampler concept types + using domain_type = vector2_type; + using codomain_type = vector3_type; + using density_type = scalar_type; + using weight_type = density_type; + + struct cache_type + { + density_type pdf; + }; + + static SphericalTriangle create(NBL_CONST_REF_ARG(shapes::SphericalTriangle) tri) + { + SphericalTriangle retval; + vector3_type cos_vertices, sin_vertices; + shapes::SphericalTriangle tri_mut = tri; + retval.solidAngle = tri_mut.solidAngle(cos_vertices, sin_vertices); + retval.cosA = cos_vertices[0]; + retval.sinA = sin_vertices[0]; + retval.tri_vertices[0] = tri.vertices[0]; + retval.tri_vertices[1] = tri.vertices[1]; + retval.tri_vertices[2] = tri.vertices[2]; + retval.triCosC = tri.cos_sides[2]; + retval.triCscB = tri.csc_sides[1]; + retval.triCscC = tri.csc_sides[2]; + return retval; + } + + codomain_type generate(const domain_type u, NBL_REF_ARG(cache_type) cache) + { + scalar_type negSinSubSolidAngle, negCosSubSolidAngle; + math::sincos(solidAngle * u.x - numbers::pi, negSinSubSolidAngle, negCosSubSolidAngle); + + const scalar_type p = negCosSubSolidAngle * sinA - negSinSubSolidAngle * cosA; + const scalar_type q = -negSinSubSolidAngle * sinA - negCosSubSolidAngle * cosA; + + // TODO: we could optimize everything up and including to the first slerp, because precision here is just godawful + scalar_type u_ = q - cosA; + scalar_type v_ = p + sinA * triCosC; + + // the slerps could probably be optimized by sidestepping `normalize` calls and accumulating scaling factors + vector3_type C_s = tri_vertices[0]; + if (triCscB < numeric_limits::max) + { + const scalar_type cosAngleAlongAC = ((v_ * q - u_ * p) * cosA - v_) / ((v_ * p + u_ * q) * sinA); + if (nbl::hlsl::abs(cosAngleAlongAC) < 1.f) + C_s += math::quaternion::slerp_delta(tri_vertices[0], tri_vertices[2] * triCscB, cosAngleAlongAC); + } + + vector3_type retval = tri_vertices[1]; + const scalar_type cosBC_s = nbl::hlsl::dot(C_s, tri_vertices[1]); + const scalar_type csc_b_s = 1.0 / nbl::hlsl::sqrt(1.0 - cosBC_s * cosBC_s); + if (csc_b_s < numeric_limits::max) + { + const scalar_type cosAngleAlongBC_s = nbl::hlsl::clamp(scalar_type(1.0) + cosBC_s * u.y - u.y, scalar_type(-1.0), scalar_type(1.0)); + if (nbl::hlsl::abs(cosAngleAlongBC_s) < scalar_type(1.0)) + retval += math::quaternion::slerp_delta(tri_vertices[1], C_s * csc_b_s, cosAngleAlongBC_s); + } + + cache.pdf = scalar_type(1.0) / solidAngle; + + return retval; + } + + domain_type _generateInverse(const codomain_type L) + { + const scalar_type cosAngleAlongBC_s = nbl::hlsl::dot(L, tri_vertices[1]); + const scalar_type csc_a_ = 1.0 / nbl::hlsl::sqrt(1.0 - cosAngleAlongBC_s * cosAngleAlongBC_s); + const scalar_type cos_b_ = nbl::hlsl::dot(L, tri_vertices[0]); + + const scalar_type cosB_ = (cos_b_ - cosAngleAlongBC_s * triCosC) * csc_a_ * triCscC; + const scalar_type sinB_ = nbl::hlsl::sqrt(1.0 - cosB_ * cosB_); + + const scalar_type cosC_ = sinA * sinB_ * triCosC - cosA * cosB_; + const scalar_type sinC_ = nbl::hlsl::sqrt(1.0 - cosC_ * cosC_); + + math::sincos_accumulator angle_adder = math::sincos_accumulator::create(cosA, sinA); + angle_adder.addAngle(cosB_, sinB_); + angle_adder.addAngle(cosC_, sinC_); + const scalar_type subTriSolidAngleRatio = (angle_adder.getSumofArccos() - numbers::pi) * (scalar_type(1.0) / solidAngle); + const scalar_type u = subTriSolidAngleRatio > numeric_limits::min ? subTriSolidAngleRatio : 0.0; + + const scalar_type sinBC_s_product = sinB_ * sinC_; + + // 1 ULP below 1.0, ensures (1.0 - cosBC_s) is strictly positive in float + const scalar_type one_below_one = bit_cast(bit_cast(scalar_type(1)) - uint_type(1)); + const scalar_type cosBC_s = sinBC_s_product > numeric_limits::min ? (cosA + cosB_ * cosC_) / sinBC_s_product : triCosC; + const scalar_type v_denom = scalar_type(1.0) - (cosBC_s < one_below_one ? cosBC_s : triCosC); + const scalar_type v = (scalar_type(1.0) - cosAngleAlongBC_s) / v_denom; + + return vector2_type(u, v); + } + + domain_type generateInverse(const codomain_type L) + { + return _generateInverse(L); + } + + density_type forwardPdf(const cache_type cache) + { + return cache.pdf; + } + + weight_type forwardWeight(const cache_type cache) + { + return forwardPdf(cache); + } + + density_type backwardPdf(const codomain_type L) + { + return scalar_type(1.0) / solidAngle; + } + + weight_type backwardWeight(const codomain_type L) + { + return backwardPdf(L); + } + + scalar_type solidAngle; + scalar_type cosA; + scalar_type sinA; + + vector3_type tri_vertices[3]; + scalar_type triCosC; + scalar_type triCscB; + scalar_type triCscC; }; -} -} -} +} // namespace sampling +} // namespace hlsl +} // namespace nbl #endif diff --git a/include/nbl/builtin/hlsl/sampling/uniform_spheres.hlsl b/include/nbl/builtin/hlsl/sampling/uniform_spheres.hlsl index 5fc3bc7a0b..06f9baca29 100644 --- a/include/nbl/builtin/hlsl/sampling/uniform_spheres.hlsl +++ b/include/nbl/builtin/hlsl/sampling/uniform_spheres.hlsl @@ -20,57 +20,161 @@ namespace sampling template) struct UniformHemisphere { - using vector_t2 = vector; - using vector_t3 = vector; - - static vector_t3 generate(const vector_t2 _sample) - { - T z = _sample.x; - T r = hlsl::sqrt(hlsl::max(T(0.0), T(1.0) - z * z)); - T phi = T(2.0) * numbers::pi * _sample.y; - return vector_t3(r * hlsl::cos(phi), r * hlsl::sin(phi), z); - } - - static T pdf() - { - return T(1.0) / (T(2.0) * numbers::pi); - } - - template > - static quotient_and_pdf quotient_and_pdf() - { - return quotient_and_pdf::create(hlsl::promote(1.0), pdf()); - } + using vector_t2 = vector; + using vector_t3 = vector; + + // BijectiveSampler concept types + using scalar_type = T; + using domain_type = vector_t2; + using codomain_type = vector_t3; + using density_type = T; + using weight_type = density_type; + + struct cache_type {}; + + static codomain_type __generate(const domain_type _sample) + { + T z = _sample.x; + T r = hlsl::sqrt(hlsl::max(T(0.0), T(1.0) - z * z)); + T phi = T(2.0) * numbers::pi * _sample.y; + return vector_t3(r * hlsl::cos(phi), r * hlsl::sin(phi), z); + } + + static codomain_type generate(const domain_type _sample) + { + return __generate(_sample); + } + + static codomain_type generate(const domain_type _sample, NBL_REF_ARG(cache_type) cache) + { + return __generate(_sample); + } + + static domain_type __generateInverse(const codomain_type _sample) + { + T phi = hlsl::atan2(_sample.y, _sample.x); + const T twopi = T(2.0) * numbers::pi; + phi += hlsl::mix(T(0.0), twopi, phi < T(0.0)); + return vector_t2(_sample.z, phi / twopi); + } + + static domain_type generateInverse(const codomain_type _sample) + { + return __generateInverse(_sample); + } + + static scalar_type __pdf() + { + return T(1.0) / (T(2.0) * numbers::pi); + } + + static density_type forwardPdf(const cache_type cache) + { + return __pdf(); + } + + static weight_type forwardWeight(const cache_type cache) + { + return __pdf(); + } + + static density_type backwardPdf(const vector_t3 _sample) + { + return __pdf(); + } + + static weight_type backwardWeight(const codomain_type sample) + { + return backwardPdf(sample); + } + + template > + static quotient_and_pdf quotientAndPdf() + { + return quotient_and_pdf::create(hlsl::promote(1.0), __pdf()); + } }; template) struct UniformSphere { - using vector_t2 = vector; - using vector_t3 = vector; - - static vector_t3 generate(const vector_t2 _sample) - { - T z = T(1.0) - T(2.0) * _sample.x; - T r = hlsl::sqrt(hlsl::max(T(0.0), T(1.0) - z * z)); - T phi = T(2.0) * numbers::pi * _sample.y; - return vector_t3(r * hlsl::cos(phi), r * hlsl::sin(phi), z); - } - - static T pdf() - { - return T(1.0) / (T(4.0) * numbers::pi); - } - - template > - static quotient_and_pdf quotient_and_pdf() - { - return quotient_and_pdf::create(hlsl::promote(1.0), pdf()); - } + using vector_t2 = vector; + using vector_t3 = vector; + + // BijectiveSampler concept types + using scalar_type = T; + using domain_type = vector_t2; + using codomain_type = vector_t3; + using density_type = T; + using weight_type = density_type; + + struct cache_type {}; + + static codomain_type __generate(const domain_type _sample) + { + T z = T(1.0) - T(2.0) * _sample.x; + T r = hlsl::sqrt(hlsl::max(T(0.0), T(1.0) - z * z)); + T phi = T(2.0) * numbers::pi * _sample.y; + return vector_t3(r * hlsl::cos(phi), r * hlsl::sin(phi), z); + } + + static codomain_type generate(const domain_type _sample) + { + return __generate(_sample); + } + + static codomain_type generate(const domain_type _sample, NBL_REF_ARG(cache_type) cache) + { + return __generate(_sample); + } + + static domain_type __generateInverse(const codomain_type _sample) + { + T phi = hlsl::atan2(_sample.y, _sample.x); + const T twopi = T(2.0) * numbers::pi; + phi += hlsl::mix(T(0.0), twopi, phi < T(0.0)); + return vector_t2((T(1.0) - _sample.z) * T(0.5), phi / twopi); + } + + static domain_type generateInverse(const codomain_type _sample) + { + return __generateInverse(_sample); + } + + static T __pdf() + { + return T(1.0) / (T(4.0) * numbers::pi); + } + + static density_type forwardPdf(const cache_type cache) + { + return __pdf(); + } + + static weight_type forwardWeight(const cache_type cache) + { + return __pdf(); + } + + static density_type backwardPdf(const vector_t3 _sample) + { + return __pdf(); + } + + static weight_type backwardWeight(const codomain_type sample) + { + return backwardPdf(sample); + } + + template > + static quotient_and_pdf quotientAndPdf() + { + return quotient_and_pdf::create(hlsl::promote(1.0), __pdf()); + } }; -} +} // namespace sampling -} -} +} // namespace hlsl +} // namespace nbl #endif diff --git a/include/nbl/builtin/hlsl/sampling/value_and_pdf.hlsl b/include/nbl/builtin/hlsl/sampling/value_and_pdf.hlsl new file mode 100644 index 0000000000..a037c0e3d8 --- /dev/null +++ b/include/nbl/builtin/hlsl/sampling/value_and_pdf.hlsl @@ -0,0 +1,75 @@ +// Copyright (C) 2018-2025 - 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_BUILTIN_HLSL_SAMPLING_VALUE_AND_PDF_INCLUDED_ +#define _NBL_BUILTIN_HLSL_SAMPLING_VALUE_AND_PDF_INCLUDED_ + +namespace nbl +{ +namespace hlsl +{ +namespace sampling +{ + +template +struct value_and_rcpPdf +{ + using this_t = value_and_rcpPdf; + + static this_t create(const V _value, const P _rcpPdf) + { + this_t retval; + retval._value = _value; + retval._rcpPdf = _rcpPdf; + return retval; + } + + V value() { return _value; } + P rcpPdf() { return _rcpPdf; } + + V _value; + P _rcpPdf; +}; + +template +struct value_and_pdf +{ + using this_t = value_and_pdf; + + static this_t create(const V _value, const P _pdf) + { + this_t retval; + retval._value = _value; + retval._pdf = _pdf; + return retval; + } + + V value() { return _value; } + P pdf() { return _pdf; } + + V _value; + P _pdf; +}; + +// Returned by TractableSampler::generate, codomain sample bundled with its rcpPdf +template +using codomain_and_rcpPdf = value_and_rcpPdf; + +// Returned by TractableSampler::generate, codomain sample bundled with its pdf +template +using codomain_and_pdf = value_and_pdf; + +// Returned by BijectiveSampler::invertGenerate, domain value bundled with its rcpPdf +template +using domain_and_rcpPdf = value_and_rcpPdf; + +// Returned by BijectiveSampler::invertGenerate, domain value bundled with its pdf +template +using domain_and_pdf = value_and_pdf; + +} // namespace sampling +} // namespace hlsl +} // namespace nbl + +#endif diff --git a/include/nbl/builtin/hlsl/shapes/spherical_rectangle.hlsl b/include/nbl/builtin/hlsl/shapes/spherical_rectangle.hlsl index 3890d1a2db..9743049a60 100644 --- a/include/nbl/builtin/hlsl/shapes/spherical_rectangle.hlsl +++ b/include/nbl/builtin/hlsl/shapes/spherical_rectangle.hlsl @@ -84,22 +84,18 @@ struct SphericalRectangle using vector3_type = vector; using matrix3x3_type = matrix; - static SphericalRectangle create(const vector3_type rectangleOrigin, const vector3_type right, const vector3_type up) + static SphericalRectangle create(NBL_CONST_REF_ARG(CompressedSphericalRectangle) compressed) { SphericalRectangle retval; - retval.origin = rectangleOrigin; - retval.extents = vector2_type(hlsl::length(right), hlsl::length(up)); - retval.basis[0] = right / retval.extents[0]; - retval.basis[1] = up / retval.extents[1]; + retval.origin = compressed.origin; + retval.extents = vector2_type(hlsl::length(compressed.right), hlsl::length(compressed.up)); + retval.basis[0] = compressed.right / retval.extents[0]; + retval.basis[1] = compressed.up / retval.extents[1]; + assert(hlsl::dot(retval.basis[0], retval.basis[1]) > scalar_type(0.0)); retval.basis[2] = hlsl::normalize(hlsl::cross(retval.basis[0], retval.basis[1])); return retval; } - static SphericalRectangle create(NBL_CONST_REF_ARG(CompressedSphericalRectangle) compressed) - { - return create(compressed.origin, compressed.right, compressed.up); - } - scalar_type solidAngle(const vector3_type observer) { const vector3_type r0 = hlsl::mul(basis, origin - observer); diff --git a/include/nbl/builtin/hlsl/shapes/spherical_triangle.hlsl b/include/nbl/builtin/hlsl/shapes/spherical_triangle.hlsl index 028d3e3653..118f022640 100644 --- a/include/nbl/builtin/hlsl/shapes/spherical_triangle.hlsl +++ b/include/nbl/builtin/hlsl/shapes/spherical_triangle.hlsl @@ -43,13 +43,19 @@ struct SphericalTriangle return hlsl::any >(csc_sides >= hlsl::promote(numeric_limits::max)); } + vector3_type __getCosVertices() + { + // using Spherical Law of Cosines (TODO: do we need to clamp anymore? since the pyramid angles method introduction?) + return hlsl::clamp((cos_sides - cos_sides.yzx * cos_sides.zxy) * csc_sides.yzx * csc_sides.zxy, hlsl::promote(-1.0), hlsl::promote(1.0)); + } + scalar_type solidAngle(NBL_REF_ARG(vector3_type) cos_vertices, NBL_REF_ARG(vector3_type) sin_vertices) { if (pyramidAngles()) return 0.f; // Both vertices and angles at the vertices are denoted by the same upper case letters A, B, and C. The angles A, B, C of the triangle are equal to the angles between the planes that intersect the surface of the sphere or, equivalently, the angles between the tangent vectors of the great circle arcs where they meet at the vertices. Angles are in radians. The angles of proper spherical triangles are (by convention) less than PI - cos_vertices = hlsl::clamp((cos_sides - cos_sides.yzx * cos_sides.zxy) * csc_sides.yzx * csc_sides.zxy, hlsl::promote(-1.0), hlsl::promote(1.0)); // using Spherical Law of Cosines (TODO: do we need to clamp anymore? since the pyramid angles method introduction?) + cos_vertices = __getCosVertices(); sin_vertices = hlsl::sqrt(hlsl::promote(1.0) - cos_vertices * cos_vertices); math::sincos_accumulator angle_adder = math::sincos_accumulator::create(cos_vertices[0], sin_vertices[0]); @@ -69,7 +75,7 @@ struct SphericalTriangle if (pyramidAngles()) return 0.f; - cos_vertices = hlsl::clamp((cos_sides - cos_sides.yzx * cos_sides.zxy) * csc_sides.yzx * csc_sides.zxy, hlsl::promote(-1.0), hlsl::promote(1.0)); + cos_vertices = __getCosVertices(); matrix awayFromEdgePlane; awayFromEdgePlane[0] = hlsl::cross(vertices[1], vertices[2]) * csc_sides[0]; diff --git a/include/nbl/builtin/hlsl/testing/approx_compare.hlsl b/include/nbl/builtin/hlsl/testing/approx_compare.hlsl new file mode 100644 index 0000000000..945e9a48cb --- /dev/null +++ b/include/nbl/builtin/hlsl/testing/approx_compare.hlsl @@ -0,0 +1,82 @@ +#ifndef _NBL_BUILTIN_HLSL_TESTING_APPROX_COMPARE_INCLUDED_ +#define _NBL_BUILTIN_HLSL_TESTING_APPROX_COMPARE_INCLUDED_ + +#include + +namespace nbl +{ +namespace hlsl +{ +namespace testing +{ +namespace impl +{ + +template +struct AbsoluteAndRelativeApproxCompareHelper; + +template +NBL_PARTIAL_REQ_TOP(concepts::FloatingPointLikeScalar) +struct AbsoluteAndRelativeApproxCompareHelper) > +{ + static bool __call(NBL_CONST_REF_ARG(FloatingPoint) lhs, NBL_CONST_REF_ARG(FloatingPoint) rhs, const float64_t maxAbsoluteDifference, const float64_t maxRelativeDifference) + { + // Absolute check first: catches small-magnitude values where relative comparison breaks down + if (hlsl::abs(float64_t(lhs) - float64_t(rhs)) <= maxAbsoluteDifference) + return true; + + // Fall back to relative comparison for larger values + return RelativeApproxCompareHelper::__call(lhs, rhs, maxRelativeDifference); + } +}; + +template +NBL_PARTIAL_REQ_TOP(concepts::FloatingPointLikeVectorial) +struct AbsoluteAndRelativeApproxCompareHelper) > +{ + static bool __call(NBL_CONST_REF_ARG(FloatingPointVector) lhs, NBL_CONST_REF_ARG(FloatingPointVector) rhs, const float64_t maxAbsoluteDifference, const float64_t maxRelativeDifference) + { + using traits = nbl::hlsl::vector_traits; + for (uint32_t i = 0; i < traits::Dimension; ++i) + { + if (!AbsoluteAndRelativeApproxCompareHelper::__call(lhs[i], rhs[i], maxAbsoluteDifference, maxRelativeDifference)) + return false; + } + + return true; + } +}; + +template +NBL_PARTIAL_REQ_TOP(concepts::Matricial && concepts::FloatingPointLikeScalar::scalar_type>) +struct AbsoluteAndRelativeApproxCompareHelper && concepts::FloatingPointLikeScalar::scalar_type>) > +{ + static bool __call(NBL_CONST_REF_ARG(FloatingPointMatrix) lhs, NBL_CONST_REF_ARG(FloatingPointMatrix) rhs, const float64_t maxAbsoluteDifference, const float64_t maxRelativeDifference) + { + using traits = nbl::hlsl::matrix_traits; + for (uint32_t i = 0; i < traits::RowCount; ++i) + { + if (!AbsoluteAndRelativeApproxCompareHelper::__call(lhs[i], rhs[i], maxAbsoluteDifference, maxRelativeDifference)) + return false; + } + + return true; + } +}; + +} + +// Composite comparator that builds on top of relativeApproxCompare. +// Checks absolute difference first (handles small-magnitude values where +// relative comparison breaks down), then falls back to relative comparison. +template +bool approxCompare(NBL_CONST_REF_ARG(T) lhs, NBL_CONST_REF_ARG(T) rhs, const float64_t maxAbsoluteDifference, const float64_t maxRelativeDifference) +{ + return impl::AbsoluteAndRelativeApproxCompareHelper::__call(lhs, rhs, maxAbsoluteDifference, maxRelativeDifference); +} + +} +} +} + +#endif diff --git a/include/nbl/core/sampling/alias_table_builder.h b/include/nbl/core/sampling/alias_table_builder.h new file mode 100644 index 0000000000..a0a10f25d8 --- /dev/null +++ b/include/nbl/core/sampling/alias_table_builder.h @@ -0,0 +1,92 @@ +// Copyright (C) 2018-2025 - 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_CORE_SAMPLING_ALIAS_TABLE_BUILDER_H_INCLUDED_ +#define _NBL_CORE_SAMPLING_ALIAS_TABLE_BUILDER_H_INCLUDED_ + +#include + +namespace nbl +{ +namespace core +{ +namespace sampling +{ + +// Builds the alias table from an array of non-negative weights. +// All output arrays must be pre-allocated to N entries. +// +// Parameters: +// weights - input weights (non-negative, at least one must be > 0) +// N - number of entries +// outProbability - [out] alias table probability threshold per bin, in [0, 1] +// outAlias - [out] alias redirect index per bin +// outPdf - [out] normalized PDF per entry: weight[i] / sum(weights) +// workspace - scratch buffer of N uint32_t entries +template +struct AliasTableBuilder +{ + static void build(const T* weights, uint32_t N, T* outProbability, uint32_t* outAlias, T* outPdf, uint32_t* workspace) + { + T totalWeight = T(0); + for (uint32_t i = 0; i < N; i++) + totalWeight += weights[i]; + + const T rcpTotalWeight = T(1) / totalWeight; + + // Compute PDFs, scaled probabilities, and partition into small/large in one pass + uint32_t smallEnd = 0; + uint32_t largeBegin = N; + for (uint32_t i = 0; i < N; i++) + { + outPdf[i] = weights[i] * rcpTotalWeight; + outProbability[i] = outPdf[i] * T(N); + + if (outProbability[i] < T(1)) + workspace[smallEnd++] = i; + else + workspace[--largeBegin] = i; + } + + // Pair small and large entries + while (smallEnd > 0 && largeBegin < N) + { + const uint32_t s = workspace[--smallEnd]; + const uint32_t l = workspace[largeBegin]; + + outAlias[s] = l; + // outProbability[s] already holds the correct probability for bin s + + outProbability[l] -= (T(1) - outProbability[s]); + + if (outProbability[l] < T(1)) + { + // l became small: pop from large, push to small + largeBegin++; + workspace[smallEnd++] = l; + } + // else l stays in large (don't pop, reuse next iteration) + } + + // Remaining entries (floating point rounding artifacts) + while (smallEnd > 0) + { + const uint32_t s = workspace[--smallEnd]; + outProbability[s] = T(1); + outAlias[s] = s; + } + while (largeBegin < N) + { + const uint32_t l = workspace[largeBegin++]; + outProbability[l] = T(1); + outAlias[l] = l; + } + } +}; + +} // namespace sampling +} // namespace core +} // namespace nbl + +#endif diff --git a/include/nbl/core/sampling/cumulative_probability_builder.h b/include/nbl/core/sampling/cumulative_probability_builder.h new file mode 100644 index 0000000000..fe2191bb7a --- /dev/null +++ b/include/nbl/core/sampling/cumulative_probability_builder.h @@ -0,0 +1,53 @@ +// Copyright (C) 2018-2025 - 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_CORE_SAMPLING_CUMULATIVE_PROBABILITY_BUILDER_H_INCLUDED_ +#define _NBL_CORE_SAMPLING_CUMULATIVE_PROBABILITY_BUILDER_H_INCLUDED_ + +#include + +namespace nbl +{ +namespace core +{ +namespace sampling +{ + +// Builds the CDF and PDF arrays from an array of non-negative weights. +// +// Parameters: +// weights - input weights (non-negative, at least one must be > 0) +// N - number of entries +// outCumProb - [out] cumulative probability array, N-1 entries +// (last bucket implicitly 1.0) +// outPdf - [out] normalized PDF per entry: weight[i] / sum(weights), N entries +template +struct CumulativeProbabilityBuilder +{ + static void build(const T* weights, uint32_t N, T* outCumProb, T* outPdf) + { + T totalWeight = T(0); + for (uint32_t i = 0; i < N; i++) + totalWeight += weights[i]; + + const T rcpTotalWeight = T(1) / totalWeight; + + for (uint32_t i = 0; i < N; i++) + outPdf[i] = weights[i] * rcpTotalWeight; + + // N-1 stored entries (last bucket is implicitly 1.0) + T cumulative = T(0); + for (uint32_t i = 0; i < N - 1; i++) + { + cumulative += outPdf[i]; + outCumProb[i] = cumulative; + } + } +}; + +} // namespace sampling +} // namespace core +} // namespace nbl + +#endif diff --git a/src/nbl/builtin/CMakeLists.txt b/src/nbl/builtin/CMakeLists.txt index f27514c2c7..839c5e3778 100644 --- a/src/nbl/builtin/CMakeLists.txt +++ b/src/nbl/builtin/CMakeLists.txt @@ -280,7 +280,11 @@ LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/sampling/projected_spherical_ LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/sampling/spherical_rectangle.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/sampling/cos_weighted_spheres.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/sampling/quotient_and_pdf.hlsl") +LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/sampling/value_and_pdf.hlsl") +LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/sampling/concepts.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/sampling/uniform_spheres.hlsl") +LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/sampling/alias_table.hlsl") +LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/sampling/cumulative_probability.hlsl") # LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/ndarray_addressing.hlsl") # @@ -392,6 +396,7 @@ LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/rwmc/ResolveParameters.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/morton.hlsl") #testing LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/testing/relative_approx_compare.hlsl") +LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/testing/approx_compare.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/testing/orientation_compare.hlsl") LIST_BUILTIN_RESOURCE(NBL_RESOURCES_TO_EMBED "hlsl/testing/vector_length_compare.hlsl")