Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions devices/rtx/apps/tests/api/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ if (VISRTX_ENABLE_MDL_SUPPORT)
target_compile_definitions(
TestPbrSpecularDefault PRIVATE VISRTX_TEST_MDL_WRAPPER)
make_test(TestEmissiveMdlLight)
make_test(TestEmissionDescriptorPolicy)
make_test(TestMdlPipelineRebuild)
make_test(TestMdlMaterialRegistryRelease)
make_test(TestEmissiveMaterialParity)
Expand Down
177 changes: 177 additions & 0 deletions devices/rtx/apps/tests/api/TestEmissionDescriptorPolicy.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
/*
* Copyright (c) 2019-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: BSD-3-Clause
*/

// Device-level checks of the emission descriptor registration policy (ADR
// 0007): a raw `mdl` surface becomes a next-event-sampled Geometry Light iff
// its folded descriptor is non-null AND faithfully NEE-evaluable — diffuse EDF,
// radiant- exitance mode, provably non-negative, no geometric-state dependence.
// Each case is asserted via the world's `numLightInstances` property (a
// zero-radiance or forward-only emitter is pixel-identical to no light, so the
// count is the only observable seam). Emission excluded here still renders via
// the forward path, unbiased — this test asserts registration, not radiance.

// anari_cpp
#define ANARI_EXTENSION_UTILITY_IMPL
#include <anari/anari_cpp/ext/std.h>
#include <anari/anari_cpp.hpp>
// VisRTX
#include <anari/ext/visrtx/makeVisRTXDevice.h>
// std
#include <array>
#include <cstdint>
#include <cstdio>

using vec3 = std::array<float, 3>;

static void statusFunc(const void *,
ANARIDevice,
ANARIObject,
ANARIDataType,
ANARIStatusSeverity severity,
ANARIStatusCode,
const char *message)
{
if (severity <= ANARI_SEVERITY_WARNING)
fprintf(stderr, "[anari] %s\n", message);
}

// A control: constant diffuse radiant-exitance emission — registers.
static const char *POLICY_DIFFUSE = R"mdl(mdl 1.6;
import ::df::*;
import ::math::*;
export material diffuse_emit() = material(
surface: material_surface(
emission: material_emission(
emission: df::diffuse_edf(),
intensity: color(8.0) * math::PI)));
)mdl";

// Non-diffuse EDF: not in faithfulSet — described, not registered.
static const char *POLICY_SPOT = R"mdl(mdl 1.6;
import ::df::*;
import ::math::*;
export material spot_emit() = material(
surface: material_surface(
emission: material_emission(
emission: df::spot_edf(exponent: 1.0),
intensity: color(8.0) * math::PI)));
)mdl";

// Provably-negative emission: sign gate excludes it (its all-negative
// next-event contribution would be dropped by the shadow epsilon gate while the
// forward deposit is MIS-downweighted — bias). Forward-only, unbiased.
static const char *POLICY_NEGATIVE = R"mdl(mdl 1.6;
import ::df::*;
import ::math::*;
export material negative_emit() = material(
surface: material_surface(
emission: material_emission(
emission: df::diffuse_edf(),
intensity: color(-8.0) * math::PI)));
)mdl";

// Intensity reads state::normal — a geometric-state quantity the synthetic hit
// fabricates. Faithful EDF kind, but unfaithful integrand: not registered.
static const char *POLICY_STATE = R"mdl(mdl 1.6;
import ::df::*;
import ::math::*;
import ::state::*;
export material state_emit() = material(
surface: material_surface(
emission: material_emission(
emission: df::diffuse_edf(),
intensity: color(math::length(state::normal())) * math::PI)));
)mdl";

// Power intensity mode: not faithfully handled — described, not registered.
static const char *POLICY_POWER = R"mdl(mdl 1.6;
import ::df::*;
import ::math::*;
export material power_emit() = material(
surface: material_surface(
emission: material_emission(
emission: df::diffuse_edf(),
intensity: color(8.0) * math::PI,
mode: intensity_power)));
)mdl";

static bool checkLightCount(
ANARIDevice device, const char *source, const char *name, uint32_t expected)
{
const std::array<vec3, 4> pos = {vec3{-0.5f, 1.5f, -0.5f},
vec3{0.5f, 1.5f, -0.5f},
vec3{0.5f, 1.5f, 0.5f},
vec3{-0.5f, 1.5f, 0.5f}};
const std::array<std::array<unsigned, 3>, 2> idx = {
std::array<unsigned, 3>{0, 1, 2}, std::array<unsigned, 3>{0, 2, 3}};

auto geom = anari::newObject<anari::Geometry>(device, "triangle");
anari::setParameterArray1D(device, geom, "vertex.position", pos.data(), 4);
anari::setParameterArray1D(device, geom, "primitive.index", idx.data(), 2);
anari::commitParameters(device, geom);

auto mat = anari::newObject<anari::Material>(device, "mdl");
anari::setParameter(device, mat, "sourceType", "code");
anari::setParameter(device, mat, "source", source);
anari::setParameter(device, mat, "materialName", name);
anari::commitParameters(device, mat);

auto surface = anari::newObject<anari::Surface>(device);
anari::setAndReleaseParameter(device, surface, "geometry", geom);
anari::setAndReleaseParameter(device, surface, "material", mat);
anari::commitParameters(device, surface);

auto world = anari::newObject<anari::World>(device);
anari::setParameterArray1D(device, world, "surface", &surface, 1);
anari::release(device, surface);
anari::commitParameters(device, world);

uint32_t count = ~0u;
const bool found =
anari::getProperty(device, world, "numLightInstances", count, ANARI_WAIT);
anari::release(device, world);

printf("numLightInstances(%s)=%u (expected %u)\n", name, count, expected);
if (!found) {
fprintf(stderr, "FAIL: world has no numLightInstances property\n");
return false;
}
if (count != expected) {
fprintf(stderr,
"FAIL: %s expected %u light(s), got %u\n",
name,
expected,
count);
return false;
}
return true;
}

int main()
{
auto device = makeVisRTXDevice(statusFunc);
if (!device) {
fprintf(stderr, "FAIL: could not create VisRTX device\n");
return 1;
}

bool ok = true;
// Control: a faithful diffuse emitter registers.
ok = checkLightCount(device, POLICY_DIFFUSE, "diffuse_emit", 1) && ok;
// Each faithfulness gate excludes its case (described, forward-only).
ok = checkLightCount(device, POLICY_SPOT, "spot_emit", 0) && ok;
ok = checkLightCount(device, POLICY_NEGATIVE, "negative_emit", 0) && ok;
ok = checkLightCount(device, POLICY_STATE, "state_emit", 0) && ok;
ok = checkLightCount(device, POLICY_POWER, "power_emit", 0) && ok;

anari::release(device, device);

if (!ok) {
fprintf(stderr, "TestEmissionDescriptorPolicy FAILED\n");
return 1;
}
printf("TestEmissionDescriptorPolicy passed\n");
return 0;
}
10 changes: 6 additions & 4 deletions devices/rtx/apps/tests/api/TestEmissiveMdlLight.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -585,10 +585,12 @@ int main()

ok = checkLightCount(device, MDL_EMISSIVE, "emissive", 1) && ok;
ok = checkLightCount(device, MDL_DARK, "dark", 0) && ok;
// Textured intensity with an UNBOUND texture still classifies as a light —
// the documented conservative over-inclusion (unbiased; it merely wastes a
// pick slot while rendering black).
ok = checkLightCount(device, MDL_TEXTURED, "emissive_tex", 1) && ok;
// Textured intensity with an UNBOUND texture is NOT a light: the MDL lookup
// is invalid and folds to 0 (ADR 0007 A5), so the surface emits nothing. This
// supersedes ADR-0006's conservative over-inclusion (which registered a
// zero-radiance light). A BOUND emissive texture still registers — see the
// textured-uniform/checker/away radiance parity above.
ok = checkLightCount(device, MDL_TEXTURED, "emissive_tex", 0) && ok;
ok = checkLightCount(device, MDL_NONFINITE, "nonfinite", 0) && ok;
ok = checkWrapperLightCount(device, 5.f, 1) && ok;
ok = checkWrapperLightCount(device, 0.f, 0) && ok;
Expand Down
17 changes: 17 additions & 0 deletions devices/rtx/apps/tests/unit/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,20 @@ target_compile_definitions(${PROJECT_NAME} PRIVATE GLM_ENABLE_EXPERIMENTAL)
target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_17)

add_test(NAME rtx::LightPickPower COMMAND ${PROJECT_NAME})

# classifyEmission lives in libmdl, a standalone static lib with no CUDA/OptiX/GPU
# (the MDL SDK is dlopen'd at runtime), so the classifier is testable on the host.
# Gated on MDL support, since libmdl only exists then.
if (VISRTX_ENABLE_MDL_SUPPORT)
project(rtx_test_MdlEmissionClassifier LANGUAGES CXX)

add_executable(${PROJECT_NAME} test_MdlEmissionClassifier.cpp)
# libmdl's Core.h includes <fmt/core.h> but links fmt PRIVATE, so a consumer
# that only links libmdl must pull fmt itself.
target_link_libraries(${PROJECT_NAME} PRIVATE libmdl fmt::fmt)
target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_17)

add_test(NAME rtx::MdlEmissionClassifier COMMAND ${PROJECT_NAME})
# The test SKIPs (not fails) when the MDL SDK shared library is not loadable.
set_tests_properties(rtx::MdlEmissionClassifier PROPERTIES SKIP_RETURN_CODE 77)
endif()
Loading
Loading