// RefineSampleLocations.fx
// Refines sample locations using inscattering difference or z difference
#include "BasicStructures.fxh"
#include "AtmosphereShadersCommon.fxh"
// In fact we only need RG16U texture to store
// interpolation source indices. However, NVidia GLES does
// not supported imge load/store operations on this format,
// so we have to resort to RGBA32U.
RWTexture2D g_rwtex2DInterpolationSource;
Texture2D g_tex2DCoordinates;
Texture2D g_tex2DEpipolarCamSpaceZ;
Texture2D g_tex2DScatteredColor;
Texture2D g_tex2DAverageLuminance;
cbuffer cbPostProcessingAttribs
{
EpipolarLightScatteringAttribs g_PPAttribs;
};
#include "ToneMapping.fxh"
#ifndef INITIAL_SAMPLE_STEP
# define INITIAL_SAMPLE_STEP 128
#endif
#ifndef THREAD_GROUP_SIZE
# define THREAD_GROUP_SIZE max(INITIAL_SAMPLE_STEP, 32)
#endif
#ifndef REFINEMENT_CRITERION
# define REFINEMENT_CRITERION REFINEMENT_CRITERION_INSCTR_DIFF
#endif
// In my first implementation I used group shared memory to store camera space z
// values. This was a very low-performing method
// After that I tried using arrays of bool flags instead, but this did not help very much
// since memory bandwidth was almost the same (on GPU each bool consumes 4 bytes)
// Finally, I came up with packing 32 flags into single uint value.
// This not only enables using 32x times less memory, but also enables very efficient
// test if depth break is present in the section
#define NUM_PACKED_FLAGS (THREAD_GROUP_SIZE/32)
groupshared uint g_uiPackedCamSpaceDiffFlags[ NUM_PACKED_FLAGS ];
#if REFINEMENT_CRITERION == REFINEMENT_CRITERION_INSCTR_DIFF
groupshared float3 g_f3Inscattering[THREAD_GROUP_SIZE+1];
#endif
[numthreads(THREAD_GROUP_SIZE, 1, 1)]
void RefineSampleLocationsCS(uint3 Gid : SV_GroupID,
uint3 GTid : SV_GroupThreadID)
{
// Each thread group processes one slice
uint uiSliceInd = Gid.y;
// Compute global index of the first sample in the thread group
// Each group processes THREAD_GROUP_SIZE samples in the slice
uint uiGroupStartGlobalInd = Gid.x * uint(THREAD_GROUP_SIZE);
uint uiSampleInd = GTid.x; // Sample index in the group
// Compute global index of this sample which is required to fetch the sample's coordinates
uint uiGlobalSampleInd = uiGroupStartGlobalInd + uiSampleInd;
// Load location of the current sample using global sample index
float2 f2SampleLocationPS = g_tex2DCoordinates.Load( int3(uiGlobalSampleInd, uiSliceInd, 0) );
bool bIsValidThread = all( Less( abs(f2SampleLocationPS), (1.0 + 1e-4)*float2(1.0,1.0) ) );
// Initialize flags with zeroes
if( GTid.x < uint(NUM_PACKED_FLAGS) )
g_uiPackedCamSpaceDiffFlags[GTid.x] = 0u;
GroupMemoryBarrierWithGroupSync();
// Let each thread in the group compute its own flag
// Note that if the sample is located behind the screen, its flag will be set to zero
// Besides, since g_tex2DEpipolarCamSpaceZ is cleared with invalid coordinates, the difference
// flag between valid and invalid locations will also be zero. Thus the sample next to invalid will always
// be marked as ray marching sample
[branch]
if( bIsValidThread )
{
#if REFINEMENT_CRITERION == REFINEMENT_CRITERION_DEPTH_DIFF
// Load camera space Z for this sample and for its right neighbour (remember to use global sample index)
float fCamSpaceZ = g_tex2DEpipolarCamSpaceZ.Load( int3(uiGlobalSampleInd, uiSliceInd, 0) );
float fRightNeighbCamSpaceZ = g_tex2DEpipolarCamSpaceZ.Load( int3( int(uiGlobalSampleInd)+1, uiSliceInd, 0) );
float fMaxZ = max(fCamSpaceZ, fRightNeighbCamSpaceZ);
fMaxZ = max(fMaxZ, 1.0);
// Compare the difference with the threshold
bool bFlag = abs(fCamSpaceZ - fRightNeighbCamSpaceZ)/fMaxZ < 0.2*g_PPAttribs.fRefinementThreshold;
#elif REFINEMENT_CRITERION == REFINEMENT_CRITERION_INSCTR_DIFF
// Load inscattering for this sample and for its right neighbour
float3 f3Insctr0 = g_tex2DScatteredColor.Load( int3(uiGlobalSampleInd, uiSliceInd, 0) );
float3 f3Insctr1 = g_tex2DScatteredColor.Load( int3( int(uiGlobalSampleInd)+1, uiSliceInd, 0) );
float3 f3MaxInsctr = max(f3Insctr0, f3Insctr1);
// Compute minimum inscattering threshold based on the average scene luminance
float fAverageLum = GetAverageSceneLuminance(g_tex2DAverageLuminance);
// Inscattering threshold should be proportional to the average scene luminance and
// inversely proportional to the middle gray level (the higher middle gray, the brighter the scene,
// thus the less the threshold)
// It should also account for the fact that rgb channels contribute differently
// to the perceived brightness. For r channel the threshold should be smallest,
// for b channel - the largest
float3 f3MinInsctrThreshold = (0.02 * fAverageLum / RGB_TO_LUMINANCE.xyz) / g_PPAttribs.ToneMapping.fMiddleGray;
f3MaxInsctr = max(f3MaxInsctr, f3MinInsctrThreshold);
// Compare the difference with the threshold. If the neighbour sample is invalid, its inscattering
// is large negative value and the difference is guaranteed to be larger than the threshold
bool bFlag = all( Less(abs(f3Insctr0 - f3Insctr1)/f3MaxInsctr, g_PPAttribs.fRefinementThreshold*float3(1.0,1.0,1.0) ) );
#endif
// Set appropriate flag using INTERLOCKED Or:
uint uiBit = bFlag ? (1u << (uiSampleInd % 32u)) : 0u;
InterlockedOr( g_uiPackedCamSpaceDiffFlags[int(uiSampleInd)/32], uiBit );
}
// Synchronize threads in the group
GroupMemoryBarrierWithGroupSync();
// Skip invalid threads. This can be done only after the synchronization
if( !bIsValidThread )
return;
// uiInitialSampleStep
// uiSampleInd |<--------->|
// | | |
// X * * * X * * * X * * * X X - locations of initial samples
// | |
// | uiInitialSample1Ind
// uiInitialSample0Ind
//
// Find two closest initial ray marching samples
uint uiInitialSampleStep = uint(INITIAL_SAMPLE_STEP);
uint uiInitialSample0Ind = (uiSampleInd / uiInitialSampleStep) * uiInitialSampleStep;
// Use denser sampling near the epipole to account for high variation
// Note that sampling near the epipole is very cheap since only a few steps
// are required to perform ray marching
uint uiInitialSample0GlobalInd = uiInitialSample0Ind + uiGroupStartGlobalInd;
float2 f2InitialSample0Coords = g_tex2DCoordinates.Load( int3(uiInitialSample0GlobalInd, uiSliceInd, 0) );
if( float(uiInitialSample0GlobalInd)/float(g_PPAttribs.uiMaxSamplesInSlice) < 0.05 &&
length(f2InitialSample0Coords - g_PPAttribs.f4LightScreenPos.xy) < 0.1 )
{
uiInitialSampleStep = max( uint(INITIAL_SAMPLE_STEP) / g_PPAttribs.uiEpipoleSamplingDensityFactor, 1u );
uiInitialSample0Ind = (uiSampleInd / uiInitialSampleStep) * uiInitialSampleStep;
}
uint uiInitialSample1Ind = uiInitialSample0Ind + uiInitialSampleStep;
// Remember that the last sample in each epipolar slice must be ray marching one
uint uiInterpolationTexWidth, uiInterpolationTexHeight;
g_rwtex2DInterpolationSource.GetDimensions(uiInterpolationTexWidth, uiInterpolationTexHeight);
if( Gid.x == uiInterpolationTexWidth/uint(THREAD_GROUP_SIZE) - 1u )
uiInitialSample1Ind = min(uiInitialSample1Ind, uint(THREAD_GROUP_SIZE-1) );
uint uiLeftSrcSampleInd = uiSampleInd;
uint uiRightSrcSampleInd = uiSampleInd;
// Do nothing if sample is one of initial samples. In this case the sample will be
// interpolated from itself
if( uiSampleInd > uiInitialSample0Ind && uiSampleInd < uiInitialSample1Ind )
{
// Load group shared memory to the thread local memory
uint uiPackedCamSpaceDiffFlags[ NUM_PACKED_FLAGS ];
for(int i=0; i < NUM_PACKED_FLAGS; ++i)
uiPackedCamSpaceDiffFlags[i] = g_uiPackedCamSpaceDiffFlags[i];
// Check if there are no depth breaks in the whole section
// In such case all the flags are set
bool bNoDepthBreaks = true;
#if INITIAL_SAMPLE_STEP < 32
{
// Check if all uiInitialSampleStep flags starting from
// position uiInitialSample0Ind are set:
int iFlagPackOrder = int(uiInitialSample0Ind / 32u);
int iFlagOrderInPack = int(uiInitialSample0Ind % 32u);
uint uiFlagPack = uiPackedCamSpaceDiffFlags[iFlagPackOrder];
uint uiAllFlagsMask = ((1u<> uint(iFlagOrderInPack)) & uiAllFlagsMask) != uiAllFlagsMask )
bNoDepthBreaks = false;
}
#else
{
for(int i=0; i < NUM_PACKED_FLAGS; ++i)
if( uiPackedCamSpaceDiffFlags[i] != 0xFFFFFFFFU )
// If at least one flag is not set, there is a depth break on this section
bNoDepthBreaks = false;
}
#endif
if( bNoDepthBreaks )
{
// If there are no depth breaks, we can skip all calculations
// and use initial sample locations as interpolation sources:
uiLeftSrcSampleInd = uiInitialSample0Ind;
uiRightSrcSampleInd = uiInitialSample1Ind;
}
else
{
// Find left interpolation source
{
// Note that i-th flag reflects the difference between i-th and (i+1)-th samples:
// Flag[i] = abs(fCamSpaceZ[i] - fCamSpaceZ[i+1]) < g_PPAttribs.fRefinementThreshold;
// We need to find first depth break starting from iFirstDepthBreakToTheLeftInd sample
// and going to the left up to uiInitialSample0Ind
int iFirstDepthBreakToTheLeftInd = int(uiSampleInd)-1;
// iFirstDepthBreakToTheLeftInd
// |
// V
// 0 1 2 3 30 31 32 33 .... i-1 i i+1 .... 63 64
// | | 1 1 1 1 |
// uiPackedCamSpaceDiffFlags[0] uiPackedCamSpaceDiffFlags[1]
//
// iFlagOrderInPack == i % 32
int iFlagPackOrder = int( uint(iFirstDepthBreakToTheLeftInd) / 32u );
int iFlagOrderInPack = int( uint(iFirstDepthBreakToTheLeftInd) % 32u );
uint uiFlagPack = uiPackedCamSpaceDiffFlags[iFlagPackOrder];
// To test if there is a depth break in the current flag pack,
// we must check all flags starting from the iFlagOrderInPack
// downward to 0 position. We must skip all flags from iFlagOrderInPack+1 to 31
if( iFlagOrderInPack < 31 )
{
// Set all higher flags to 1, so that they will be skipped
// Note that if iFlagOrderInPack == 31, there are no flags to skip
// Note also that (U << 32) != 0 as it can be expected. (U << 32) == U instead
uiFlagPack |= ( uint(0x0FFFFFFFFU) << uint(iFlagOrderInPack+1) );
}
// Find first zero flag starting from iFlagOrderInPack position. Since all
// higher bits are set, they will be effectivelly skipped
int iFirstUnsetFlagPos = firstbithigh( uint(~uiFlagPack) );
// firstbithigh(0) == +INT_MAX
if( !(0 <= iFirstUnsetFlagPos && iFirstUnsetFlagPos < 32) )
// There are no set flags => proceed to the next uint flag pack
iFirstUnsetFlagPos = -1;
iFirstDepthBreakToTheLeftInd -= iFlagOrderInPack - iFirstUnsetFlagPos;
#if INITIAL_SAMPLE_STEP > 32
// Check the remaining full flag packs
iFlagPackOrder--;
while( iFlagPackOrder >= 0 && iFirstUnsetFlagPos == -1 )
{
uiFlagPack = uiPackedCamSpaceDiffFlags[iFlagPackOrder];
iFirstUnsetFlagPos = firstbithigh( uint(~uiFlagPack) );
if( !(0 <= iFirstUnsetFlagPos && iFirstUnsetFlagPos < 32) )
iFirstUnsetFlagPos = -1;
iFirstDepthBreakToTheLeftInd -= 31 - iFirstUnsetFlagPos;
iFlagPackOrder--;
}
#endif
// Ray marching sample is located next to the identified depth break:
uiLeftSrcSampleInd = max( uint(iFirstDepthBreakToTheLeftInd + 1), uiInitialSample0Ind );
}
// Find right interpolation source using symmetric method
{
// We need to find first depth break starting from iRightSrcSampleInd and
// going to the right up to the uiInitialSample1Ind
uiRightSrcSampleInd = uiSampleInd;
int iFlagPackOrder = int(uiRightSrcSampleInd / 32u );
int iFlagOrderInPack = int(uiRightSrcSampleInd % 32u);
uint uiFlagPack = uiPackedCamSpaceDiffFlags[iFlagPackOrder];
// We need to find first unset flag in the current flag pack
// starting from iFlagOrderInPack position and up to the 31st bit
// Set all lower order bits to 1 so that they are skipped during
// the test:
if( iFlagOrderInPack > 0 )
uiFlagPack |= uint( (1 << iFlagOrderInPack)-1 );
// Find first zero flag:
int iFirstUnsetFlagPos = firstbitlow( uint(~uiFlagPack) );
if( !(0 <= iFirstUnsetFlagPos && iFirstUnsetFlagPos < 32) )
iFirstUnsetFlagPos = 32;
uiRightSrcSampleInd += uint(iFirstUnsetFlagPos - iFlagOrderInPack);
#if INITIAL_SAMPLE_STEP > 32
// Check the remaining full flag packs
iFlagPackOrder++;
while( iFlagPackOrder < int(NUM_PACKED_FLAGS) && iFirstUnsetFlagPos == 32 )
{
uiFlagPack = uiPackedCamSpaceDiffFlags[iFlagPackOrder];
iFirstUnsetFlagPos = firstbitlow( uint(~uiFlagPack) );
if( !(0 <= iFirstUnsetFlagPos && iFirstUnsetFlagPos < 32) )
iFirstUnsetFlagPos = 32;
uiRightSrcSampleInd += uint(iFirstUnsetFlagPos);
iFlagPackOrder++;
}
#endif
uiRightSrcSampleInd = min(uiRightSrcSampleInd, uiInitialSample1Ind);
}
}
// If at least one interpolation source is the same as the sample itself, the
// sample is ray marching sample and is interpolated from itself:
if(uiLeftSrcSampleInd == uiSampleInd || uiRightSrcSampleInd == uiSampleInd )
uiLeftSrcSampleInd = uiRightSrcSampleInd = uiSampleInd;
}
g_rwtex2DInterpolationSource[ int2(uiGlobalSampleInd, uiSliceInd) ] = uint2(uiGroupStartGlobalInd + uiLeftSrcSampleInd, uiGroupStartGlobalInd + uiRightSrcSampleInd);
}