Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
ebf25f4
Created an new example to test the compilation and concepts of samplers
karimsayedre Feb 18, 2026
18fe5eb
changed example number to 37
karimsayedre Feb 25, 2026
d1fa476
Tests using `ITester`, Benchmarking using the same testing shaders (s…
karimsayedre Mar 5, 2026
d8a6e9b
Merge branch 'master' into sampler-concepts
karimsayedre Mar 5, 2026
6740883
Merge branch 'master' into sampler-concepts
karimsayedre Mar 6, 2026
4a45531
fixes after merge
karimsayedre Mar 7, 2026
7931826
Samplers now conform to concepts, benchmarks for all samplers, more c…
karimsayedre Mar 13, 2026
d9f4907
added alias table and cumulative probablility testing and benchmarks
karimsayedre Mar 16, 2026
69fe759
Merge branch 'master' into sampler-concepts
karimsayedre Mar 17, 2026
8f3c1c9
minor formatting changes
karimsayedre Mar 17, 2026
24baa87
addressed comments in concepts.hlsl, making sure 37 and 66 work
karimsayedre Mar 18, 2026
54acb0c
renamed InvertableSampler to BackwardTracableSampler
karimsayedre Mar 19, 2026
f94133f
Merge branch 'master' into sampler-concepts-upd
karimsayedre Mar 23, 2026
60e0a35
formatting to 3 spaces, added pipeline info report
karimsayedre Mar 23, 2026
b33f66a
addressed more comments, made benchmarks more fair
karimsayedre Mar 26, 2026
f76b427
Merge branch 'master' into sampler-concepts
karimsayedre Mar 28, 2026
ff397d0
changed forwardPdf to take the codomain as an arg
karimsayedre Mar 28, 2026
cfefa02
test code reduction in main.cpp
karimsayedre Mar 28, 2026
d93a0f8
Merge branch 'master' into sampler-concepts
karimsayedre Mar 30, 2026
eb10cf2
forwardPdf should take domain not codomain, also corrected some comments
karimsayedre Mar 30, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion 31_HLSLPathTracer/app_resources/hlsl/material_system.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ struct MaterialSystem
case MaterialType::DIFFUSE:
{
quotient_pdf_type ret = _cache.diffuseBxDF.quotient_and_pdf(_sample, interaction.isotropic);
ret.quotient *= bxdfs[matID.id].albedo;
ret._quotient *= bxdfs[matID.id].albedo;
return ret;
}
case MaterialType::CONDUCTOR:
Expand Down
25 changes: 13 additions & 12 deletions 31_HLSLPathTracer/app_resources/hlsl/next_event_estimator.hlsl
Original file line number Diff line number Diff line change
Expand Up @@ -125,22 +125,22 @@ struct ShapeSampling<T, PST_TRIANGLE, PPM_SOLID_ANGLE>
{
const vector3_type tri_vertices[3] = {tri.vertex0, tri.vertex1, tri.vertex2};
shapes::SphericalTriangle<scalar_type> st = shapes::SphericalTriangle<scalar_type>::create(tri_vertices, ray.origin);
const scalar_type rcpProb = st.solidAngle();
const scalar_type rcpProb = st.solid_angle;
// if `rcpProb` is NAN then the triangle's solid angle was close to 0.0
return rcpProb > numeric_limits<scalar_type>::min ? (1.0 / rcpProb) : numeric_limits<scalar_type>::max;
}

template<class Aniso>
vector3_type generate_and_pdf(NBL_REF_ARG(scalar_type) pdf, NBL_REF_ARG(scalar_type) newRayMaxT, NBL_CONST_REF_ARG(vector3_type) origin, NBL_CONST_REF_ARG(Aniso) interaction, NBL_CONST_REF_ARG(vector3_type) xi)
{
scalar_type rcpPdf;
const vector3_type tri_vertices[3] = {tri.vertex0, tri.vertex1, tri.vertex2};
shapes::SphericalTriangle<scalar_type> st = shapes::SphericalTriangle<scalar_type>::create(tri_vertices, origin);
sampling::SphericalTriangle<scalar_type> sst = sampling::SphericalTriangle<scalar_type>::create(st);

const vector3_type L = sst.generate(rcpPdf, xi.xy);
typename sampling::SphericalTriangle<scalar_type>::cache_type cache;
const vector3_type L = sst.generate(xi.xy, cache);

pdf = rcpPdf > numeric_limits<scalar_type>::min ? (1.0 / rcpPdf) : numeric_limits<scalar_type>::max;
pdf = sst.forwardPdf(xi.xy, cache);

const vector3_type N = tri.getNormalTimesArea();
newRayMaxT = hlsl::dot<vector3_type>(N, tri.vertex0 - origin) / hlsl::dot<vector3_type>(N, L);
Expand Down Expand Up @@ -169,23 +169,23 @@ struct ShapeSampling<T, PST_TRIANGLE, PPM_APPROX_PROJECTED_SOLID_ANGLE>
const vector3_type L = ray.direction;
const vector3_type tri_vertices[3] = {tri.vertex0, tri.vertex1, tri.vertex2};
shapes::SphericalTriangle<scalar_type> st = shapes::SphericalTriangle<scalar_type>::create(tri_vertices, ray.origin);
sampling::ProjectedSphericalTriangle<scalar_type> pst = sampling::ProjectedSphericalTriangle<scalar_type>::create(st);
const scalar_type pdf = pst.backwardPdf(ray.normalAtOrigin, ray.wasBSDFAtOrigin, L);
sampling::ProjectedSphericalTriangle<scalar_type> pst = sampling::ProjectedSphericalTriangle<scalar_type>::create(st, ray.normalAtOrigin, ray.wasBSDFAtOrigin);
const scalar_type pdf = pst.backwardPdf(L);
// if `pdf` is NAN then the triangle's projected solid angle was close to 0.0, if its close to INF then the triangle was very small
return pdf < numeric_limits<scalar_type>::max ? pdf : numeric_limits<scalar_type>::max;
}

template<class Aniso>
vector3_type generate_and_pdf(NBL_REF_ARG(scalar_type) pdf, NBL_REF_ARG(scalar_type) newRayMaxT, NBL_CONST_REF_ARG(vector3_type) origin, NBL_CONST_REF_ARG(Aniso) interaction, NBL_CONST_REF_ARG(vector3_type) xi)
{
scalar_type rcpPdf;
const vector3_type tri_vertices[3] = {tri.vertex0, tri.vertex1, tri.vertex2};
shapes::SphericalTriangle<scalar_type> st = shapes::SphericalTriangle<scalar_type>::create(tri_vertices, origin);
sampling::ProjectedSphericalTriangle<scalar_type> pst = sampling::ProjectedSphericalTriangle<scalar_type>::create(st);
sampling::ProjectedSphericalTriangle<scalar_type> pst = sampling::ProjectedSphericalTriangle<scalar_type>::create(st, interaction.getN(), interaction.isMaterialBSDF());

const vector3_type L = pst.generate(rcpPdf, interaction.getN(), interaction.isMaterialBSDF(), xi.xy);
typename sampling::ProjectedSphericalTriangle<scalar_type>::cache_type pstCache;
const vector3_type L = pst.generate(xi.xy, pstCache);

pdf = rcpPdf > numeric_limits<scalar_type>::min ? (1.0 / rcpPdf) : numeric_limits<scalar_type>::max;
pdf = pst.forwardPdf(xi.xy, pstCache);

const vector3_type N = tri.getNormalTimesArea();
newRayMaxT = hlsl::dot<vector3_type>(N, tri.vertex0 - origin) / hlsl::dot<vector3_type>(N, L);
Expand Down Expand Up @@ -283,8 +283,9 @@ struct ShapeSampling<T, PST_RECTANGLE, PPM_SOLID_ANGLE>
vector3_type L = hlsl::promote<vector3_type>(0.0);
scalar_type solidAngle = sphR0.solidAngle(origin);

sampling::SphericalRectangle<scalar_type> ssph = sampling::SphericalRectangle<scalar_type>::create(sphR0);
vector<T, 2> sphUv = ssph.generate(origin, xi.xy, solidAngle);
sampling::SphericalRectangle<scalar_type> ssph = sampling::SphericalRectangle<scalar_type>::create(sphR0, origin);
typename sampling::SphericalRectangle<scalar_type>::cache_type cache;
vector<T, 2> sphUv = ssph.generate(xi.xy, cache);
if (solidAngle > numeric_limits<scalar_type>::min)
{
vector3_type sph_sample = sphUv.x * rect.edge0 + sphUv.y * rect.edge1 + rect.offset;
Expand Down
71 changes: 71 additions & 0 deletions 37_HLSLSamplingTests/CAliasTableGPUTester.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#ifndef _NBL_EXAMPLES_TESTS_37_SAMPLING_C_ALIAS_TABLE_GPU_TESTER_INCLUDED_
#define _NBL_EXAMPLES_TESTS_37_SAMPLING_C_ALIAS_TABLE_GPU_TESTER_INCLUDED_

#include "nbl/examples/examples.hpp"
#include "app_resources/common/alias_table.hlsl"
#include "nbl/examples/Tester/ITester.h"

class CAliasTableGPUTester final : public ITester<AliasTableInputValues, AliasTableTestResults, AliasTableTestExecutor>
{
using base_t = ITester<AliasTableInputValues, AliasTableTestResults, AliasTableTestExecutor>;

public:
CAliasTableGPUTester(const uint32_t testBatchCount, const uint32_t workgroupSize) : base_t(testBatchCount, workgroupSize) {}

private:
AliasTableInputValues generateInputTestValues() override
{
std::uniform_real_distribution<float> uDist(0.0f, 1.0f);

AliasTableInputValues input;
input.u = uDist(getRandomEngine());
return input;
}

AliasTableTestResults determineExpectedResults(const AliasTableInputValues& input) override
{
AliasTableTestResults expected;
AliasTableTestExecutor executor;
executor(input, expected);
return expected;
}

bool verifyTestResults(const AliasTableTestResults& expected, const AliasTableTestResults& actual, const size_t iteration, const uint32_t seed, TestType testType) override
{
bool pass = true;

if (expected.generatedIndex != actual.generatedIndex)
{
pass = false;
printTestFail("AliasTable::generatedIndex", float(expected.generatedIndex), float(actual.generatedIndex), iteration, seed, testType, 0.0, 0.0);
}

pass &= verifyTestValue("AliasTable::forwardPdf", expected.forwardPdf, actual.forwardPdf, iteration, seed, testType, 1e-5, 1e-6);
pass &= verifyTestValue("AliasTable::backwardPdf", expected.backwardPdf, actual.backwardPdf, iteration, seed, testType, 1e-5, 1e-6);
pass &= verifyTestValue("AliasTable::forwardWeight", expected.forwardWeight, actual.forwardWeight, iteration, seed, testType, 1e-5, 1e-6);
pass &= verifyTestValue("AliasTable::backwardWeight", expected.backwardWeight, actual.backwardWeight, iteration, seed, testType, 1e-5, 1e-6);

if (!(actual.forwardPdf > 0.0f) || !std::isfinite(actual.forwardPdf))
{
pass = false;
printTestFail("AliasTable::forwardPdf (positive & finite)", 1.0f, actual.forwardPdf, iteration, seed, testType, 0.0, 0.0);
}

if (!(actual.backwardPdf > 0.0f) || !std::isfinite(actual.backwardPdf))
{
pass = false;
printTestFail("AliasTable::backwardPdf (positive & finite)", 1.0f, actual.backwardPdf, iteration, seed, testType, 0.0, 0.0);
}

// forwardPdf and backwardPdf(generatedIndex) should be identical
pass &= verifyTestValue("AliasTable::pdf consistency", actual.forwardPdf, actual.backwardPdf, iteration, seed, testType, 1e-7, 1e-7);

// forwardWeight == forwardPdf and backwardWeight == backwardPdf (structural invariant)
pass &= verifyTestValue("AliasTable::forwardWeight == forwardPdf", actual.forwardPdf, actual.forwardWeight, iteration, seed, testType, 1e-7, 1e-7);
pass &= verifyTestValue("AliasTable::backwardWeight == backwardPdf", actual.backwardPdf, actual.backwardWeight, iteration, seed, testType, 1e-7, 1e-7);

return pass;
}
};

#endif
59 changes: 59 additions & 0 deletions 37_HLSLSamplingTests/CBilinearTester.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#ifndef _NBL_EXAMPLES_TESTS_37_SAMPLING_C_BILINEAR_TESTER_INCLUDED_
#define _NBL_EXAMPLES_TESTS_37_SAMPLING_C_BILINEAR_TESTER_INCLUDED_

#include "nbl/examples/examples.hpp"
#include "app_resources/common/bilinear.hlsl"
#include "nbl/examples/Tester/ITester.h"

class CBilinearTester final : public ITester<BilinearInputValues, BilinearTestResults, BilinearTestExecutor>
{
using base_t = ITester<BilinearInputValues, BilinearTestResults, BilinearTestExecutor>;

public:
CBilinearTester(const uint32_t testBatchCount, const uint32_t workgroupSize) : base_t(testBatchCount, workgroupSize) {}

private:
BilinearInputValues generateInputTestValues() override
{
std::uniform_real_distribution<float> coeffDist(0.1f, 5.0f);
std::uniform_real_distribution<float> uDist(0.0f, 1.0f);

BilinearInputValues input;
input.bilinearCoeffs = nbl::hlsl::float32_t4(
coeffDist(getRandomEngine()), coeffDist(getRandomEngine()),
coeffDist(getRandomEngine()), coeffDist(getRandomEngine()));
input.u = nbl::hlsl::float32_t2(uDist(getRandomEngine()), uDist(getRandomEngine()));
return input;
}

BilinearTestResults determineExpectedResults(const BilinearInputValues& input) override
{
BilinearTestResults expected;
BilinearTestExecutor executor;
executor(input, expected);
return expected;
}

bool verifyTestResults(const BilinearTestResults& expected, const BilinearTestResults& actual, const size_t iteration, const uint32_t seed, TestType testType) override
{
bool pass = true;
pass &= verifyTestValue("Bilinear::generate", expected.generated, actual.generated, iteration, seed, testType, 1e-2, 1e-3);
pass &= verifyTestValue("Bilinear::pdf", expected.backwardPdf, actual.backwardPdf, iteration, seed, testType, 1e-5, 5e-3);
pass &= verifyTestValue("Bilinear::forwardPdf", expected.forwardPdf, actual.forwardPdf, iteration, seed, testType, 1e-5, 5e-3);

if (!(actual.forwardPdf > 0.0f) || !std::isfinite(actual.forwardPdf))
{
pass = false;
printTestFail("Bilinear::forwardPdf (positive & finite)", 1.0f, actual.forwardPdf, iteration, seed, testType, 0.0, 0.0);
}
if (!(actual.backwardPdf > 0.0f) || !std::isfinite(actual.backwardPdf))
{
pass = false;
printTestFail("Bilinear::backwardPdf (positive & finite)", 1.0f, actual.backwardPdf, iteration, seed, testType, 0.0, 0.0);
}

return pass;
}
};

#endif
67 changes: 67 additions & 0 deletions 37_HLSLSamplingTests/CBoxMullerTransformTester.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#ifndef _NBL_EXAMPLES_TESTS_37_SAMPLING_C_BOX_MULLER_TRANSFORM_TESTER_INCLUDED_
#define _NBL_EXAMPLES_TESTS_37_SAMPLING_C_BOX_MULLER_TRANSFORM_TESTER_INCLUDED_

#include "nbl/examples/examples.hpp"
#include "app_resources/common/box_muller_transform.hlsl"
#include "nbl/examples/Tester/ITester.h"

class CBoxMullerTransformTester final : public ITester<BoxMullerTransformInputValues, BoxMullerTransformTestResults, BoxMullerTransformTestExecutor>
{
using base_t = ITester<BoxMullerTransformInputValues, BoxMullerTransformTestResults, BoxMullerTransformTestExecutor>;

public:
CBoxMullerTransformTester(const uint32_t testBatchCount, const uint32_t workgroupSize) : base_t(testBatchCount, workgroupSize) {}

private:
BoxMullerTransformInputValues generateInputTestValues() override
{
std::uniform_real_distribution<float> stddevDist(0.1f, 5.0f);
// Avoid u.x near 0 to prevent log(0) = -inf
std::uniform_real_distribution<float> uDist(1e-4f, 1.0f - 1e-4f);

BoxMullerTransformInputValues input;
input.stddev = stddevDist(getRandomEngine());
input.u = nbl::hlsl::float32_t2(uDist(getRandomEngine()), uDist(getRandomEngine()));
return input;
}

BoxMullerTransformTestResults determineExpectedResults(const BoxMullerTransformInputValues& input) override
{
BoxMullerTransformTestResults expected;
BoxMullerTransformTestExecutor executor;
executor(input, expected);
return expected;
}

bool verifyTestResults(const BoxMullerTransformTestResults& expected, const BoxMullerTransformTestResults& actual,
const size_t iteration, const uint32_t seed, TestType testType) override
{
bool pass = true;
pass &= verifyTestValue("BoxMullerTransform::generate", expected.generated, actual.generated, iteration, seed, testType, 1e-5, 2e-3); // tolerated
pass &= verifyTestValue("BoxMullerTransform::cache.pdf", expected.cachedPdf, actual.cachedPdf, iteration, seed, testType, 1e-5, 1e-3);
pass &= verifyTestValue("BoxMullerTransform::forwardPdf", expected.forwardPdf, actual.forwardPdf, iteration, seed, testType, 1e-5, 1e-3);
pass &= verifyTestValue("BoxMullerTransform::backwardPdf", expected.backwardPdf, actual.backwardPdf, iteration, seed, testType, 1e-5, 1e-3);
pass &= verifyTestValue("BoxMullerTransform::separateBackwardPdf", expected.separateBackwardPdf, actual.separateBackwardPdf, iteration, seed, testType, 1e-5, 1e-3);

// Joint PDF == product of marginal PDFs (independent random variables)
pass &= verifyTestValue("BoxMullerTransform::jointPdf == pdf product", actual.backwardPdf, actual.separateBackwardPdf.x * actual.separateBackwardPdf.y, iteration, seed, testType, 1e-5, 1e-5);

// forwardPdf must return the same value stored in cache.pdf by generate
pass &= verifyTestValue("BoxMullerTransform::forwardPdf == cache.pdf", actual.forwardPdf, actual.cachedPdf, iteration, seed, testType, 1e-5, 1e-5);

if (!(actual.forwardPdf > 0.0f) || !std::isfinite(actual.forwardPdf))
{
pass = false;
printTestFail("BoxMullerTransform::forwardPdf (positive & finite)", 1.0f, actual.forwardPdf, iteration, seed, testType, 0.0, 0.0);
}
if (!(actual.backwardPdf > 0.0f) || !std::isfinite(actual.backwardPdf))
{
pass = false;
printTestFail("BoxMullerTransform::backwardPdf (positive & finite)", 1.0f, actual.backwardPdf, iteration, seed, testType, 0.0, 0.0);
}

return pass;
}
};

#endif
59 changes: 59 additions & 0 deletions 37_HLSLSamplingTests/CConcentricMappingTester.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#ifndef _NBL_EXAMPLES_TESTS_37_SAMPLING_C_CONCENTRIC_MAPPING_TESTER_INCLUDED_
#define _NBL_EXAMPLES_TESTS_37_SAMPLING_C_CONCENTRIC_MAPPING_TESTER_INCLUDED_

#include "nbl/examples/examples.hpp"
#include "app_resources/common/concentric_mapping.hlsl"
#include "nbl/examples/Tester/ITester.h"

class CConcentricMappingTester final : public ITester<ConcentricMappingInputValues, ConcentricMappingTestResults, ConcentricMappingTestExecutor>
{
using base_t = ITester<ConcentricMappingInputValues, ConcentricMappingTestResults, ConcentricMappingTestExecutor>;

public:
CConcentricMappingTester(const uint32_t testBatchCount, const uint32_t workgroupSize) : base_t(testBatchCount, workgroupSize) {}

private:
ConcentricMappingInputValues generateInputTestValues() override
{
std::uniform_real_distribution<float> dist(0.0f, 1.0f);

ConcentricMappingInputValues input;
input.u = nbl::hlsl::float32_t2(dist(getRandomEngine()), dist(getRandomEngine()));
return input;
}

ConcentricMappingTestResults determineExpectedResults(const ConcentricMappingInputValues& input) override
{
ConcentricMappingTestResults expected;
ConcentricMappingTestExecutor executor;
executor(input, expected);
return expected;
}

bool verifyTestResults(const ConcentricMappingTestResults& expected, const ConcentricMappingTestResults& actual,
const size_t iteration, const uint32_t seed, TestType testType) override
{
bool pass = true;
pass &= verifyTestValue("ConcentricMapping::concentricMapping", expected.mapped, actual.mapped, iteration, seed, testType, 1e-5, 1e-5);
pass &= verifyTestValue("ConcentricMapping::invertConcentricMapping", expected.inverted, actual.inverted, iteration, seed, testType, 1e-5, 1e-5);
pass &= verifyTestValue("ConcentringMapping::roundtripError (absolute)", 0.0f, actual.roundtripError, iteration, seed, testType, 1e-5, 1e-5);
pass &= verifyTestValue("ConcentringMapping::forwardPdf", expected.forwardPdf, actual.forwardPdf, iteration, seed, testType, 1e-5, 1e-5);
pass &= verifyTestValue("ConcentringMapping::backwardPdf", expected.backwardPdf, actual.backwardPdf, iteration, seed, testType, 1e-5, 1e-5);
pass &= verifyTestValue("ConcentringMapping::jacobianProduct", 1.0f, actual.jacobianProduct, iteration, seed, testType, 1e-5, 1e-5);

if (!(actual.forwardPdf > 0.0f) || !std::isfinite(actual.forwardPdf))
{
pass = false;
printTestFail("ConcentricMapping::forwardPdf (positive & finite)", 1.0f, actual.forwardPdf, iteration, seed, testType, 0.0, 0.0);
}
if (!(actual.backwardPdf > 0.0f) || !std::isfinite(actual.backwardPdf))
{
pass = false;
printTestFail("ConcentricMapping::backwardPdf (positive & finite)", 1.0f, actual.backwardPdf, iteration, seed, testType, 0.0, 0.0);
}

return pass;
}
};

#endif
Loading