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
16 changes: 11 additions & 5 deletions devices/rtx/device/gpu/gpu_objects.h
Original file line number Diff line number Diff line change
Expand Up @@ -833,12 +833,18 @@ struct WorldGPUData

// Power-proportional Light Pick (built in World::buildInstanceLightGPUData).
// Normalized cumulative Pick Power over lightInstances (length
// numLightInstances, last entry == 1); pick a slot with inverseSampleCDF. The
// slot's discrete pick probability is lightPickCdf[i]-lightPickCdf[i-1].
// Double so a dim light's mass (below float epsilon of the total) is
// preserved as a nonzero interval — else it is unselectable while the
// hit-side pNee is still positive, biasing the deposit's MIS weight.
// numLightInstances, last entry == 1); pick a slot with inverseSampleCDF.
// Double so a dim light's mass (below float epsilon of the total) survives the
// adjacent-difference of two ≈1.0 CDF values — else it is unselectable while
// the hit-side pNee is still positive, biasing the deposit's MIS weight. (The
// 24-bit pick sample floors selection at ~2^-24 regardless; the double CDF only
// widens which dim slots the differencing can resolve, not which are reachable.)
const double *lightPickCdf;
// Per-slot discrete pick probability power_i/total (length numLightInstances),
// precomputed host-side. Read this directly instead of differencing adjacent
// lightPickCdf entries: a standalone float represents a selected dim light's
// mass accurately, where the adjacent-difference of two ≈1.0 values would not.
const float *lightPickDelta;
float totalLightPower; // sum of un-normalized instance Pick Powers
float hdriPower; // subset sum over HDRI instances (env-MIS pick probability)
float sceneRadius; // bounding-sphere radius, for the ambient term's power
Expand Down
5 changes: 3 additions & 2 deletions devices/rtx/device/renderer/Interactive_ptx.cu
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,9 @@ struct InteractiveShadingPolicy
idx = glm::min(size_t(detail::inverseSampleCDF(
world.lightPickCdf, int(numLights), u)),
numLights - 1);
const float lo = idx > 0 ? world.lightPickCdf[idx - 1] : 0.0f;
pPick = world.lightPickCdf[idx] - lo;
// Precomputed per-slot mass (power_i/total). Read raw: this pick pool
// has no ambient stratum, unlike Quality's instancePickProbability.
pPick = world.lightPickDelta[idx];
} else {
idx = glm::min(size_t(u * float(numLights)), numLights - 1);
pPick = 1.0f / float(numLights);
Expand Down
18 changes: 8 additions & 10 deletions devices/rtx/device/renderer/Quality_ptx.cu
Original file line number Diff line number Diff line change
Expand Up @@ -169,19 +169,17 @@ VISRTX_DEVICE size_t pickLightInstance(const WorldGPUData &world, float u)
}

// Discrete probability that pickLightInstance selected `idx`, folded with the
// ambient stratum so P(pick) sums to 1 across every pick candidate. The CDF is
// normalized by totalLightPower, so its per-slot delta is
// power_i/totalLightPower.
// ambient stratum so P(pick) sums to 1 across every pick candidate. lightPickDelta
// holds power_i normalized by the double cumulative total, so folding in
// totalLightPower/totalPower reweights it onto the ambient-inclusive partition.
VISRTX_DEVICE float instancePickProbability(
const WorldGPUData &world, size_t idx, float totalPower)
{
// Double delta: the CDF preserves masses below float epsilon, so the delta of
// adjacent entries must be differenced in double or a dim light's interval
// collapses to zero here even though it is selectable.
const double lo = idx > 0 ? world.lightPickCdf[idx - 1] : 0.0;
const double conditional = world.lightPickCdf[idx] - lo;
return float(
conditional * double(world.totalLightPower) / double(totalPower));
// Read the precomputed per-slot mass (power_i/total): a dim light's interval,
// stored directly as float, survives here — differencing adjacent ≈1.0 CDF
// entries at float would collapse it even though the double CDF made it
// selectable.
return world.lightPickDelta[idx] * world.totalLightPower / totalPower;
}

// Aggregate probability that the Light Pick lands on the HDRI environment. Both
Expand Down
13 changes: 12 additions & 1 deletion devices/rtx/device/world/World.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ WorldGPUData World::gpuData() const
retval.numHdriLightInstances = m_instanceHdriLightGPUData.size();

retval.lightPickCdf = m_lightPickCdf.dataDevice();
retval.lightPickDelta = m_lightPickDelta.dataDevice();
retval.totalLightPower = m_totalLightPower;
retval.hdriPower = m_hdriPower;
retval.sceneRadius = m_sceneRadius;
Expand Down Expand Up @@ -592,10 +593,11 @@ void World::buildInstanceLightGPUData()
geometryLights,
totalHdriLights);

// Allocate both arrays
// Allocate the instance-data, HDRI, and pick arrays.
m_instanceLightGPUData.resize(totalLights);
m_instanceHdriLightGPUData.resize(totalHdriLights);
m_lightPickCdf.resize(totalLights);
m_lightPickDelta.resize(totalLights);

// Bounding-sphere radius over the committed scene, sizing the infinite
// lights' Pick Power. Fall back to unit radius so an empty scene still
Expand Down Expand Up @@ -626,6 +628,7 @@ void World::buildInstanceLightGPUData()
// Filled with each instance's raw Pick Power, then normalized into the
// cumulative CDF in place once the total is known.
auto *pickCdf = m_lightPickCdf.dataHost();
auto *pickDelta = m_lightPickDelta.dataHost();
m_totalLightPower = 0.0f;
m_hdriPower = 0.0f;

Expand Down Expand Up @@ -700,13 +703,20 @@ void World::buildInstanceLightGPUData()
// stays positive — bias. The double total makes the last entry exactly 1.0
// and preserves those masses. A zero total (every light dark) leaves the CDF
// unused: uniform pick.
// All-dark: pickDelta is zeroed here and pickCdf stays at its zero raw powers
// (never renormalized), so the uniform-pick fallback reads neither.
std::fill(pickDelta, pickDelta + totalLights, 0.0f);
if (m_totalLightPower > 0.0f) {
double total = 0.0;
for (size_t i = 0; i < totalLights; ++i)
total += pickCdf[i];
const double invTotal = total > 0.0 ? 1.0 / total : 0.0;
double cumulative = 0.0;
for (size_t i = 0; i < totalLights; ++i) {
// delta from the raw power (still in pickCdf[i]) before it is overwritten
// by the cumulative value; storing the mass directly as float avoids the
// adjacent-difference of two ≈1.0 doubles the kernel used to do.
pickDelta[i] = float(pickCdf[i] * invTotal);
cumulative += pickCdf[i];
pickCdf[i] = cumulative * invTotal;
}
Expand All @@ -715,6 +725,7 @@ void World::buildInstanceLightGPUData()
m_instanceLightGPUData.upload();
m_instanceHdriLightGPUData.upload();
m_lightPickCdf.upload();
m_lightPickDelta.upload();
}

} // namespace visrtx
Expand Down
2 changes: 2 additions & 0 deletions devices/rtx/device/world/World.h
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ struct World : public Object

// Power-proportional Light Pick, rebuilt with the light instances.
HostDeviceArray<double> m_lightPickCdf;
// Per-slot discrete pick probability (power_i/total), parallel to m_lightPickCdf.
HostDeviceArray<float> m_lightPickDelta;
float m_totalLightPower{0.f};
float m_hdriPower{0.f};
float m_sceneRadius{0.f};
Expand Down
Loading