From 3d37742a5723e79f72681be90f3b00ed0196681a Mon Sep 17 00:00:00 2001 From: Daqi Lin Date: Tue, 1 Sep 2026 16:13:05 +0800 Subject: [PATCH 1/3] add light identity resolve to fix sparse reuse problem --- .../include/rtxdi/ResamplingFunctions.slangh | 106 +++++++++++++----- rtxdi-sdk/include/rtxdi/Reservoir.slangh | 6 +- .../shaders/ResamplingCompileTest.comp.slang | 21 ++++ 3 files changed, 101 insertions(+), 32 deletions(-) diff --git a/rtxdi-sdk/include/rtxdi/ResamplingFunctions.slangh b/rtxdi-sdk/include/rtxdi/ResamplingFunctions.slangh index 4014a3c..1ae16a6 100644 --- a/rtxdi-sdk/include/rtxdi/ResamplingFunctions.slangh +++ b/rtxdi-sdk/include/rtxdi/ResamplingFunctions.slangh @@ -57,6 +57,7 @@ bool RTXDI_StreamSample( reservoir.portalIdx = uint8_t(portalIndex); reservoir.uv = uv; reservoir.targetPdf = targetPdf; + reservoir.lightIdentity = RAB_UseLightIdentityResolution() ? RAB_GetLightIdentity(uint16_t(lightIndex)) : uint2(0); } return selectSample; @@ -87,6 +88,7 @@ bool RTXDI_CombineReservoirs( reservoir.portalIdx = newReservoir.portalIdx; reservoir.uv = newReservoir.uv; reservoir.targetPdf = targetPdf; + reservoir.lightIdentity = newReservoir.lightIdentity; } return selectSample; @@ -330,6 +332,7 @@ RTXDI_Reservoir RTXDI_SampleSingleLight( state.uv = lightUV; state.targetPdf = targetPdf; state.M = 1; + state.lightIdentity = (targetPdf > 0 && RAB_UseLightIdentityResolution()) ? RAB_GetLightIdentity(lightIdx) : uint2(0); if (testVisibility) { @@ -485,6 +488,8 @@ void RTXDI_TemporalResampling( // calculate targetPdf for the old sample bool selectedPreviousSample = false; uint previousM = 0; + bool useStoredTemporalTargetPdf = false; + float storedTemporalTargetPdf = 0.0f; if (prevPixelID.x >= 0) { RTXDI_Reservoir prevReservoir = RAB_LoadReservoir(prevPixelID, tparams.sourceBufferIndex); @@ -493,17 +498,26 @@ void RTXDI_TemporalResampling( // Save the light index in the previous frame context const uint16_t originalPrevLightIdx = prevReservoir.lightIdx; - - uint16_t mappedLightID = RAB_TranslateLightIndex(prevReservoir.lightIdx, /* currentToPrevious = */ false); - if (mappedLightID == RTXDI_INVALID_LIGHT_INDEX) + + // Map the previous reservoir's stored light index to the current frame. + bool exactIndexMatch = true; + bool prevReservoirValid; + if (RAB_UseLightIdentityResolution()) { - // Kill the reservoir - prevReservoir.weightSum = 0; - prevReservoir.lightIdx = RTXDI_INVALID_LIGHT_INDEX; + prevReservoirValid = RAB_ResolveReservoirLightIndex(prevReservoir, exactIndexMatch); } else { + const uint16_t mappedLightID = RAB_TranslateLightIndex(prevReservoir.lightIdx, /* currentToPrevious = */ false); prevReservoir.lightIdx = mappedLightID; + prevReservoirValid = (mappedLightID != RTXDI_INVALID_LIGHT_INDEX); + } + + if (!prevReservoirValid) + { + // The light this reservoir referenced no longer exists: kill the reservoir. + prevReservoir.weightSum = 0; + prevReservoir.lightIdx = RTXDI_INVALID_LIGHT_INDEX; } float weightAtCurrent = 0; @@ -527,7 +541,21 @@ void RTXDI_TemporalResampling( selectedPreviousSample = RTXDI_CombineReservoirs(state, prevReservoir, RAB_GetNextRandom(rng), weightAtCurrent); if (selectedPreviousSample) { - selectedLightPrevIdx = originalPrevLightIdx; + if (exactIndexMatch) + { + // 1-frame-old reservoir: its stored index is a valid previous-frame index, so the temporal + // MIS target is recomputed against the previous light buffer at the temporal surface below. + selectedLightPrevIdx = originalPrevLightIdx; + } + else + { + // Multi-frame-stale reservoir recovered via identity: its stored index can't index the + // previous-frame light buffer. Reuse the reservoir's stored targetPdf as the temporal MIS + // target + selectedLightPrevIdx = RTXDI_INVALID_LIGHT_INDEX; + useStoredTemporalTargetPdf = true; + storedTemporalTargetPdf = prevReservoir.targetPdf; + } } } @@ -538,44 +566,54 @@ void RTXDI_TemporalResampling( float piSum = state.targetPdf * initialM; if(RTXDI_IsValidReservoir(state) && - selectedLightPrevIdx != RTXDI_INVALID_LIGHT_INDEX && + (selectedLightPrevIdx != RTXDI_INVALID_LIGHT_INDEX || useStoredTemporalTargetPdf) && previousM > 0) { - float temporalP = 0; - RAB_Surface temporalSurface = RAB_GetSurfaceForTemporalBiasCorrection(surface, prevPixelID, prevNormal, tparams.virtualMotionVector); + float temporalP; - RAB_Surface virtualTemporalSurface = temporalSurface; -#if !RTXDI_NO_PORTALS - if (state.portalIdx != RTXDI_INVALID_PORTAL_INDEX) + if (useStoredTemporalTargetPdf) { - RAB_TransformSurface(virtualTemporalSurface, RAB_GetPortalTransform(state.portalIdx)); + // the previous-frame light buffer can't be indexed with its stored index, + // so use the reservoir's stored target as the selected sample's target in the temporal domain. + temporalP = storedTemporalTargetPdf; } + else + { + RAB_Surface temporalSurface = RAB_GetSurfaceForTemporalBiasCorrection(surface, prevPixelID, prevNormal, tparams.virtualMotionVector); + + RAB_Surface virtualTemporalSurface = temporalSurface; +#if !RTXDI_NO_PORTALS + if (state.portalIdx != RTXDI_INVALID_PORTAL_INDEX) + { + RAB_TransformSurface(virtualTemporalSurface, RAB_GetPortalTransform(state.portalIdx)); + } #endif - RAB_LightSample selectedSampleAtTemporalSurface = RAB_GetLightSample(selectedLightPrevIdx, - state.uv, virtualTemporalSurface, /* usePreviousLights = */ true); + RAB_LightSample selectedSampleAtTemporalSurface = RAB_GetLightSample(selectedLightPrevIdx, + state.uv, virtualTemporalSurface, /* usePreviousLights = */ true); - temporalP = RAB_GetLightSampleTargetPdfForSurface(selectedSampleAtTemporalSurface, virtualTemporalSurface); + temporalP = RAB_GetLightSampleTargetPdfForSurface(selectedSampleAtTemporalSurface, virtualTemporalSurface); - // If we have selected the temporal sample and discard is enabled, we know that the sample was visible - // on the previous frame because otherwise it would've been discarded and we couldn't select it. - // In practice, temporal samples are selected way more often than new samples, which means this - // condition culls the vast majority of visibility rays here. - const bool selectedSampleKnownVisible = selectedPreviousSample && tparams.discardInvisibleSamples; + // If we have selected the temporal sample and discard is enabled, we know that the sample was visible + // on the previous frame because otherwise it would've been discarded and we couldn't select it. + // In practice, temporal samples are selected way more often than new samples, which means this + // condition culls the vast majority of visibility rays here. + const bool selectedSampleKnownVisible = selectedPreviousSample && tparams.discardInvisibleSamples; - if (temporalP > 0 && tparams.biasCorrectionMode >= RTXDI_BIAS_CORRECTION_RAY_TRACED && !selectedSampleKnownVisible) - { - float3 attenuation; + if (temporalP > 0 && tparams.biasCorrectionMode >= RTXDI_BIAS_CORRECTION_RAY_TRACED && !selectedSampleKnownVisible) + { + float3 attenuation; #if RTXDI_NO_PORTALS - bool sampleVisible = RAB_TraceLightSampleVisibility(temporalSurface, selectedSampleAtTemporalSurface, - RTXDI_INVALID_PORTAL_INDEX, false, false, true, attenuation); + bool sampleVisible = RAB_TraceLightSampleVisibility(temporalSurface, selectedSampleAtTemporalSurface, + RTXDI_INVALID_PORTAL_INDEX, false, false, true, attenuation); #else - bool sampleVisible = RAB_TraceLightSampleVisibility(temporalSurface, selectedSampleAtTemporalSurface, - state.portalIdx, false, false, true, attenuation); + bool sampleVisible = RAB_TraceLightSampleVisibility(temporalSurface, selectedSampleAtTemporalSurface, + state.portalIdx, false, false, true, attenuation); #endif - if (!sampleVisible) - temporalP = 0; + if (!sampleVisible) + temporalP = 0; + } } pi = selectedPreviousSample ? temporalP : pi; @@ -684,6 +722,12 @@ RTXDI_Reservoir RTXDI_SpatialResampling( RTXDI_Reservoir neighborReservoir = RAB_LoadReservoir(neighborID, sparams.sourceBufferIndex); + if (RAB_UseLightIdentityResolution()) + { + bool neighborExactIndexMatch; + RAB_ResolveReservoirLightIndex(neighborReservoir, neighborExactIndexMatch); + } + float targetPdf = 0; if (RTXDI_IsValidReservoir(neighborReservoir)) { diff --git a/rtxdi-sdk/include/rtxdi/Reservoir.slangh b/rtxdi-sdk/include/rtxdi/Reservoir.slangh index 2d3ff3b..a752a06 100644 --- a/rtxdi-sdk/include/rtxdi/Reservoir.slangh +++ b/rtxdi-sdk/include/rtxdi/Reservoir.slangh @@ -30,6 +30,7 @@ struct RTXDI_Reservoir float weightSum; float targetPdf; uint16_t M; + uint2 lightIdentity; }; RTXDI_PackedReservoir RTXDI_PackReservoir(RTXDI_Reservoir reservoir) @@ -38,7 +39,8 @@ RTXDI_PackedReservoir RTXDI_PackReservoir(RTXDI_Reservoir reservoir) packedReservoir.data0 = asuint(float4(reservoir.uv, reservoir.weightSum, reservoir.targetPdf)); packedReservoir.data1.x = ((uint(reservoir.lightIdx) << 16) | (reservoir.M & 0xffff)); packedReservoir.data1.y = uint(reservoir.portalIdx); - packedReservoir.data1.zw = 0; + packedReservoir.data1.z = reservoir.lightIdentity.x; + packedReservoir.data1.w = reservoir.lightIdentity.y; return packedReservoir; } @@ -51,6 +53,7 @@ RTXDI_Reservoir RTXDI_EmptyReservoir() s.weightSum = 0.0f; s.M = uint16_t(0); s.portalIdx = RTXDI_INVALID_PORTAL_INDEX; + s.lightIdentity = uint2(0); return s; } @@ -64,6 +67,7 @@ RTXDI_Reservoir RTXDI_UnpackReservoir(RTXDI_PackedReservoir packedReservoir) reservoir.M = uint16_t(packedReservoir.data1.x & 0xffff); reservoir.portalIdx = uint8_t(packedReservoir.data1.y); + reservoir.lightIdentity = uint2(packedReservoir.data1.z, packedReservoir.data1.w); return reservoir; } diff --git a/rtxdi-sdk/shaders/ResamplingCompileTest.comp.slang b/rtxdi-sdk/shaders/ResamplingCompileTest.comp.slang index d9f517b..faf24f8 100644 --- a/rtxdi-sdk/shaders/ResamplingCompileTest.comp.slang +++ b/rtxdi-sdk/shaders/ResamplingCompileTest.comp.slang @@ -230,6 +230,27 @@ int RAB_TranslateLightIndex(uint lightIndex, bool currentToPrevious) return -1; } +bool RAB_UseLightIdentityResolution() +{ + return false; +} + +uint2 RAB_GetLightIdentity(uint currentLightIdx) +{ + return uint2(0); +} + +uint16_t RAB_ResolveLightIndexFromIdentity(uint2 identity) +{ + return RTXDI_INVALID_LIGHT_INDEX; +} + +bool RAB_ResolveReservoirLightIndex(inout RTXDI_Reservoir reservoir, out bool exactIndexMatch) +{ + exactIndexMatch = false; + return RTXDI_IsValidReservoir(reservoir); +} + bool RAB_ShouldDiscardEnlargedPixel(inout RAB_RandomSamplerState rng, RAB_Surface surface, float3 virtualMotionVector) { return false; From eaebfab08a0cba85791f534fda5fee65be40217a Mon Sep 17 00:00:00 2001 From: Daqi Lin Date: Fri, 4 Sep 2026 18:19:50 +0800 Subject: [PATCH 2/3] shrink light identity size from 64 bit to 32 bit --- rtxdi-sdk/include/rtxdi/ResamplingFunctions.slangh | 4 ++-- rtxdi-sdk/include/rtxdi/Reservoir.slangh | 10 +++++----- rtxdi-sdk/shaders/ResamplingCompileTest.comp.slang | 6 +++--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/rtxdi-sdk/include/rtxdi/ResamplingFunctions.slangh b/rtxdi-sdk/include/rtxdi/ResamplingFunctions.slangh index 1ae16a6..ba374b4 100644 --- a/rtxdi-sdk/include/rtxdi/ResamplingFunctions.slangh +++ b/rtxdi-sdk/include/rtxdi/ResamplingFunctions.slangh @@ -57,7 +57,7 @@ bool RTXDI_StreamSample( reservoir.portalIdx = uint8_t(portalIndex); reservoir.uv = uv; reservoir.targetPdf = targetPdf; - reservoir.lightIdentity = RAB_UseLightIdentityResolution() ? RAB_GetLightIdentity(uint16_t(lightIndex)) : uint2(0); + reservoir.lightIdentity = RAB_UseLightIdentityResolution() ? RAB_GetLightIdentity(uint16_t(lightIndex)) : 0u; } return selectSample; @@ -332,7 +332,7 @@ RTXDI_Reservoir RTXDI_SampleSingleLight( state.uv = lightUV; state.targetPdf = targetPdf; state.M = 1; - state.lightIdentity = (targetPdf > 0 && RAB_UseLightIdentityResolution()) ? RAB_GetLightIdentity(lightIdx) : uint2(0); + state.lightIdentity = (targetPdf > 0 && RAB_UseLightIdentityResolution()) ? RAB_GetLightIdentity(lightIdx) : 0u; if (testVisibility) { diff --git a/rtxdi-sdk/include/rtxdi/Reservoir.slangh b/rtxdi-sdk/include/rtxdi/Reservoir.slangh index a752a06..e2ccd58 100644 --- a/rtxdi-sdk/include/rtxdi/Reservoir.slangh +++ b/rtxdi-sdk/include/rtxdi/Reservoir.slangh @@ -30,7 +30,7 @@ struct RTXDI_Reservoir float weightSum; float targetPdf; uint16_t M; - uint2 lightIdentity; + uint lightIdentity; }; RTXDI_PackedReservoir RTXDI_PackReservoir(RTXDI_Reservoir reservoir) @@ -39,8 +39,8 @@ RTXDI_PackedReservoir RTXDI_PackReservoir(RTXDI_Reservoir reservoir) packedReservoir.data0 = asuint(float4(reservoir.uv, reservoir.weightSum, reservoir.targetPdf)); packedReservoir.data1.x = ((uint(reservoir.lightIdx) << 16) | (reservoir.M & 0xffff)); packedReservoir.data1.y = uint(reservoir.portalIdx); - packedReservoir.data1.z = reservoir.lightIdentity.x; - packedReservoir.data1.w = reservoir.lightIdentity.y; + packedReservoir.data1.z = reservoir.lightIdentity; + packedReservoir.data1.w = 0; return packedReservoir; } @@ -53,7 +53,7 @@ RTXDI_Reservoir RTXDI_EmptyReservoir() s.weightSum = 0.0f; s.M = uint16_t(0); s.portalIdx = RTXDI_INVALID_PORTAL_INDEX; - s.lightIdentity = uint2(0); + s.lightIdentity = 0; return s; } @@ -67,7 +67,7 @@ RTXDI_Reservoir RTXDI_UnpackReservoir(RTXDI_PackedReservoir packedReservoir) reservoir.M = uint16_t(packedReservoir.data1.x & 0xffff); reservoir.portalIdx = uint8_t(packedReservoir.data1.y); - reservoir.lightIdentity = uint2(packedReservoir.data1.z, packedReservoir.data1.w); + reservoir.lightIdentity = packedReservoir.data1.z; return reservoir; } diff --git a/rtxdi-sdk/shaders/ResamplingCompileTest.comp.slang b/rtxdi-sdk/shaders/ResamplingCompileTest.comp.slang index faf24f8..a2a0aa0 100644 --- a/rtxdi-sdk/shaders/ResamplingCompileTest.comp.slang +++ b/rtxdi-sdk/shaders/ResamplingCompileTest.comp.slang @@ -235,12 +235,12 @@ bool RAB_UseLightIdentityResolution() return false; } -uint2 RAB_GetLightIdentity(uint currentLightIdx) +uint RAB_GetLightIdentity(uint currentLightIdx) { - return uint2(0); + return 0; } -uint16_t RAB_ResolveLightIndexFromIdentity(uint2 identity) +uint16_t RAB_ResolveLightIndexFromIdentity(uint identity) { return RTXDI_INVALID_LIGHT_INDEX; } From 836162053290fa75f0004b2c6d0ef571dacb0717 Mon Sep 17 00:00:00 2001 From: daqil Date: Thu, 10 Sep 2026 13:20:35 +0800 Subject: [PATCH 3/3] update the temporal MIS fallback behavior for sparse mode --- .../include/rtxdi/ResamplingFunctions.slangh | 16 +++++----------- .../shaders/ResamplingCompileTest.comp.slang | 3 +-- 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/rtxdi-sdk/include/rtxdi/ResamplingFunctions.slangh b/rtxdi-sdk/include/rtxdi/ResamplingFunctions.slangh index ba374b4..351843d 100644 --- a/rtxdi-sdk/include/rtxdi/ResamplingFunctions.slangh +++ b/rtxdi-sdk/include/rtxdi/ResamplingFunctions.slangh @@ -500,11 +500,10 @@ void RTXDI_TemporalResampling( const uint16_t originalPrevLightIdx = prevReservoir.lightIdx; // Map the previous reservoir's stored light index to the current frame. - bool exactIndexMatch = true; bool prevReservoirValid; if (RAB_UseLightIdentityResolution()) { - prevReservoirValid = RAB_ResolveReservoirLightIndex(prevReservoir, exactIndexMatch); + prevReservoirValid = RAB_ResolveReservoirLightIndex(prevReservoir); } else { @@ -541,18 +540,14 @@ void RTXDI_TemporalResampling( selectedPreviousSample = RTXDI_CombineReservoirs(state, prevReservoir, RAB_GetNextRandom(rng), weightAtCurrent); if (selectedPreviousSample) { - if (exactIndexMatch) + if (!RAB_UseLightIdentityResolution()) { - // 1-frame-old reservoir: its stored index is a valid previous-frame index, so the temporal - // MIS target is recomputed against the previous light buffer at the temporal surface below. selectedLightPrevIdx = originalPrevLightIdx; } else { - // Multi-frame-stale reservoir recovered via identity: its stored index can't index the - // previous-frame light buffer. Reuse the reservoir's stored targetPdf as the temporal MIS - // target - selectedLightPrevIdx = RTXDI_INVALID_LIGHT_INDEX; + // Reuse the reservoir's stored targetPdf as the temporal MIS target + // This is more stable when reservoirs are not updated every frame useStoredTemporalTargetPdf = true; storedTemporalTargetPdf = prevReservoir.targetPdf; } @@ -724,8 +719,7 @@ RTXDI_Reservoir RTXDI_SpatialResampling( if (RAB_UseLightIdentityResolution()) { - bool neighborExactIndexMatch; - RAB_ResolveReservoirLightIndex(neighborReservoir, neighborExactIndexMatch); + RAB_ResolveReservoirLightIndex(neighborReservoir); } float targetPdf = 0; diff --git a/rtxdi-sdk/shaders/ResamplingCompileTest.comp.slang b/rtxdi-sdk/shaders/ResamplingCompileTest.comp.slang index a2a0aa0..843d1b9 100644 --- a/rtxdi-sdk/shaders/ResamplingCompileTest.comp.slang +++ b/rtxdi-sdk/shaders/ResamplingCompileTest.comp.slang @@ -245,9 +245,8 @@ uint16_t RAB_ResolveLightIndexFromIdentity(uint identity) return RTXDI_INVALID_LIGHT_INDEX; } -bool RAB_ResolveReservoirLightIndex(inout RTXDI_Reservoir reservoir, out bool exactIndexMatch) +bool RAB_ResolveReservoirLightIndex(inout RTXDI_Reservoir reservoir) { - exactIndexMatch = false; return RTXDI_IsValidReservoir(reservoir); }