Files
UnrealEngineUWP/Engine/Shaders/Private/VirtualShadowMaps/VirtualShadowMapPageMarking.usf
wouter dek 8f310241b2 Use DoubleFloat instead of tile+offset in various systems on GPU to improve numerical precision.
#jira UE-171685
#rb Charles.deRousiers, tim.doerries

[CL 30763421 by wouter dek in ue5-main branch]
2024-01-21 21:02:35 -05:00

576 lines
23 KiB
Plaintext

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
VirtualShadowMapPageMarking.usf:
=============================================================================*/
#include "/Engine/Shared/SingleLayerWaterDefinitions.h"
#include "../HairStrands/HairStrandsVisibilityCommonStruct.ush"
#include "../Common.ush"
#include "../WaveOpUtil.ush"
#include "../LightGridCommon.ush"
#include "../SceneTexturesCommon.ush"
#include "../DeferredShadingCommon.ush"
#include "../HairStrands/HairStrandsVisibilityCommon.ush"
#include "../HairStrands/HairStrandsTileCommon.ush"
#include "../Substrate/Substrate.ush"
#include "VirtualShadowMapProjectionDirectional.ush"
#include "VirtualShadowMapProjectionSpot.ush"
#include "VirtualShadowMapProjectionCommon.ush"
#include "VirtualShadowMapStats.ush"
#include "VirtualShadowMapLightGrid.ush"
// Type of input data consume by the page allocation (i.e., data read from the source buffer: Gbuffer, HairStrands data, ...)
#define INPUT_TYPE_GBUFFER 0
#define INPUT_TYPE_HAIRSTRANDS 1
#define INPUT_TYPE_GBUFFER_AND_WATER_DEPTH 2
// Flags generated by per-pixel pass to determine which pages are required to provide shadow for the visible geometry
RWStructuredBuffer<uint> OutPageRequestFlags;
StructuredBuffer<int> DirectionalLightIds;
float PageDilationBorderSizeDirectional;
float PageDilationBorderSizeLocal;
uint InputType;
uint bCullBackfacingPixels;
uint NumDirectionalLightSmInds;
uint MipModeLocal;
int GetBiasedClipmapLevel(FVirtualShadowMapProjectionShaderData BaseProjectionData, float3 TranslatedWorldPosition)
{
float BiasedLevel = CalcAbsoluteClipmapLevel(BaseProjectionData, TranslatedWorldPosition) + BaseProjectionData.ResolutionLodBias + VirtualShadowMap.GlobalResolutionLodBias;
return int(floor(BiasedLevel));
}
uint GetMipLevelLocal(int VirtualShadowMapId, float3 TranslatedWorldPosition, float SceneDepth)
{
FVirtualShadowMapProjectionShaderData ProjectionData = GetVirtualShadowMapProjectionData(VirtualShadowMapId);
// If local lights are near the primary view the combined offset should be small
float3 ViewToShadowTranslation = DFFastLocalSubtractDemote(ProjectionData.PreViewTranslation, PrimaryView.PreViewTranslation);
float3 ShadowTranslatedWorldPosition = TranslatedWorldPosition + ViewToShadowTranslation;
float Footprint = VirtualShadowMapCalcPixelFootprintLocal(ProjectionData, ShadowTranslatedWorldPosition, SceneDepth);
float MipLevelFloat = log2(Footprint) + ProjectionData.ResolutionLodBias + VirtualShadowMap.GlobalResolutionLodBias;
uint MipLevel = uint(max(floor(MipLevelFloat), 0.0f));
MipLevel = min(MipLevel, (VSM_MAX_MIP_LEVELS - 1U));
if (MipModeLocal == 1)
{
// Even mips
MipLevel &= ~(1U);
}
else if (MipModeLocal == 2)
{
// Odd mips
MipLevel |= 1U;
}
return MipLevel;
}
void MarkPageAddress(uint PageOffset, uint Flags)
{
checkStructuredBufferAccessSlow(OutPageRequestFlags, PageOffset);
OutPageRequestFlags[PageOffset] = Flags;
}
void MarkPage(uint VirtualShadowMapId, uint MipLevel, float3 TranslatedWorldPosition, bool bUsePageDilation, float2 PageDilationOffset)
{
FVirtualShadowMapProjectionShaderData ProjectionData = GetVirtualShadowMapProjectionData(VirtualShadowMapId);
// MarkPage (or mark pixel pages) should never run for a distant light.
checkSlow(!IsSinglePageVirtualShadowMap(VirtualShadowMapId));
float3 ViewToShadowTranslation = DFFastLocalSubtractDemote(ProjectionData.PreViewTranslation, PrimaryView.PreViewTranslation);
float3 ShadowTranslatedWorldPosition = TranslatedWorldPosition + ViewToShadowTranslation;
float4 ShadowUVz = mul(float4(ShadowTranslatedWorldPosition, 1.0f), ProjectionData.TranslatedWorldToShadowUVMatrix);
ShadowUVz.xyz /= ShadowUVz.w;
// Check overlap vs the shadow map space
// NOTE: XY test not really needed anymore with the precise cone test in the caller, but we'll leave it for the moment
bool bInClip = ShadowUVz.w > 0.0f &&
all(and(ShadowUVz.xyz <= ShadowUVz.w,
ShadowUVz.xyz >= float3(-ShadowUVz.ww, 0.0f)));
if (!bInClip)
{
return;
}
// Normal pages marked through pixel processing are not "coarse" and should include "detail geometry" - i.e., all geometry
uint Flags = VSM_ALLOCATED_FLAG | VSM_DETAIL_GEOMETRY_FLAG;
uint MaxPageAddress = CalcLevelDimsPages(MipLevel) - 1U;
float2 PageAddressFloat = ShadowUVz.xy * CalcLevelDimsPages(MipLevel);
uint2 PageAddress = clamp(uint2(PageAddressFloat), 0U, MaxPageAddress);
uint PageOffset = CalcPageOffset(VirtualShadowMapId, MipLevel, PageAddress);
MarkPageAddress(PageOffset, Flags);
// PageDilationBorderSize == 0 implies PageDilationOffset.xy == 0
if (bUsePageDilation)
{
uint2 PageAddress2 = clamp(uint2(PageAddressFloat + PageDilationOffset), 0U, MaxPageAddress);
uint PageOffset2 = CalcPageOffset(VirtualShadowMapId, MipLevel, PageAddress2);
if (PageOffset2 != PageOffset)
{
MarkPageAddress(PageOffset2, Flags);
}
uint2 PageAddress3 = clamp(uint2(PageAddressFloat - PageDilationOffset), 0U, MaxPageAddress);
uint PageOffset3 = CalcPageOffset(VirtualShadowMapId, MipLevel, PageAddress3);
if (PageOffset3 != PageOffset)
{
MarkPageAddress(PageOffset3, Flags);
}
}
}
void MarkPageClipmap(
FVirtualShadowMapProjectionShaderData ProjectionData,
bool bUsePageDilation,
float2 PageDilationOffset,
float3 TranslatedWorldPosition)
{
const int ClipmapLevel = GetBiasedClipmapLevel(ProjectionData, TranslatedWorldPosition);
int ClipmapIndex = max(0, ClipmapLevel - ProjectionData.ClipmapLevel);
if (ClipmapIndex < ProjectionData.ClipmapLevelCountRemaining)
{
MarkPage(ProjectionData.VirtualShadowMapId + ClipmapIndex, 0, TranslatedWorldPosition, bUsePageDilation, PageDilationOffset);
}
}
//
RWStructuredBuffer<uint> OutPrunedLightGridData;
RWStructuredBuffer<uint> OutPrunedNumCulledLightsGrid;
[numthreads(VSM_DEFAULT_CS_GROUP_X, 1, 1)]
void PruneLightGridCS(uint GridLinearIndex : SV_DispatchThreadID)
{
uint EyeIndex = 0;
const FLightGridData GridData = GetLightGridData(EyeIndex);
if (GridLinearIndex >= GridData.CulledGridSize.x * GridData.CulledGridSize.y * GridData.CulledGridSize.z)
{
return;
}
const FCulledLightsGridData CulledLightGridData = GetCulledLightsGrid(GridLinearIndex, EyeIndex);
uint NumRetainedLights = 0U;
// First pass add the non-distant lights with VSMs
LOOP
for (uint Index = 0; Index < CulledLightGridData.NumLights; ++Index)
{
int VirtualShadowMapId = GetLocalLightData(CulledLightGridData.DataStartIndex + Index, EyeIndex).VirtualShadowMapId;
if (VirtualShadowMapId != INDEX_NONE && !IsSinglePageVirtualShadowMap(VirtualShadowMapId))
{
// Copy light grid data index
checkStructuredBufferAccessSlow(OutPrunedLightGridData, CulledLightGridData.DataStartIndex + NumRetainedLights);
OutPrunedLightGridData[CulledLightGridData.DataStartIndex + NumRetainedLights++] = GetCulledLightDataGrid(CulledLightGridData.DataStartIndex + Index);
}
}
// Second pass add the distant lights with VSMs after
// NOTE: We could add these to the end in the first pass and then re-pack them instead, but this works
// well enough for moderate light counts.
LOOP
for (uint Index = 0; Index < CulledLightGridData.NumLights; ++Index)
{
int VirtualShadowMapId = GetLocalLightData(CulledLightGridData.DataStartIndex + Index, EyeIndex).VirtualShadowMapId;
// Discard any light without a VSM
if (VirtualShadowMapId != INDEX_NONE && IsSinglePageVirtualShadowMap(VirtualShadowMapId))
{
checkStructuredBufferAccessSlow(OutPrunedLightGridData, CulledLightGridData.DataStartIndex + NumRetainedLights);
OutPrunedLightGridData[CulledLightGridData.DataStartIndex + NumRetainedLights++] = GetCulledLightDataGrid(CulledLightGridData.DataStartIndex + Index);
}
}
checkStructuredBufferAccessSlow(OutPrunedNumCulledLightsGrid, GridLinearIndex);
OutPrunedNumCulledLightsGrid[GridLinearIndex] = NumRetainedLights;
}
uint2 PixelStride;
#if PERMUTATION_WATER_DEPTH
Texture2D<float> SingleLayerWaterDepthTexture;
StructuredBuffer< uint > SingleLayerWaterTileMask;
int2 SingleLayerWaterTileViewRes;
#endif
#if PERMUTATION_TRANSLUCENCY_DEPTH
uint FrontLayerMode;
float4 FrontLayerHistoryUVMinMax;
float4 FrontLayerHistoryScreenPositionScaleBias;
float4 FrontLayerHistoryBufferSizeAndInvSize;
Texture2D<float> FrontLayerTranslucencyDepthTexture;
Texture2D<float4> FrontLayerTranslucencyNormalTexture;
#endif
struct FPositionData
{
float SceneDepth;
float4 SvPosition;
float3 TranslatedWorldPosition;
float DeviceZ;
};
FPositionData InitPositionData(uint2 PixelPos, float DeviceZ, bool bIsValid)
{
FPositionData Out = (FPositionData)0;
if (bIsValid)
{
Out.DeviceZ = DeviceZ;
Out.SceneDepth = ConvertFromDeviceZ(Out.DeviceZ);
Out.SvPosition = float4(float2(PixelPos) + 0.5f, Out.DeviceZ, 1.0f);
Out.TranslatedWorldPosition = SvPositionToTranslatedWorld(Out.SvPosition);
}
return Out;
}
// Note: we use the tile size defined by the water as the group-size - this is needed because the tile mask testing code relies on the size being the same to scalarize efficiently.
[numthreads(SLW_TILE_SIZE_XY, SLW_TILE_SIZE_XY, 1)]
void GeneratePageFlagsFromPixels(
uint3 InGroupId : SV_GroupID,
uint GroupIndex : SV_GroupIndex,
uint3 GroupThreadId : SV_GroupThreadID,
uint3 DispatchThreadId : SV_DispatchThreadID)
{
#if PERMUTATION_INPUT_TYPE == INPUT_TYPE_HAIRSTRANDS
uint2 GroupId = InGroupId.xy;
if (HairStrands.bHairTileValid>0)
{
const uint TileCount = HairStrands.HairTileCount[HAIRTILE_HAIR_ALL];
const uint LinearIndex = InGroupId.x + InGroupId.y * HairStrands.HairTileCountXY.x;
if (LinearIndex >= TileCount)
{
return;
}
GroupId = HairStrands.HairTileData[LinearIndex];
}
#else // PERMUTATION_INPUT_TYPE == INPUT_TYPE_GBUFFER || PERMUTATION_INPUT_TYPE == INPUT_TYPE_GBUFFER_AND_WATER_DEPTH
const uint2 GroupId = InGroupId.xy;
#endif
const uint2 PixelLocalPos = DispatchThreadId.xy * PixelStride;
const uint2 PixelPos = uint2(View.ViewRectMin.xy) + PixelLocalPos;
if (any(PixelPos >= uint2(View.ViewRectMin.xy + View.ViewSizeAndInvSize.xy)))
{
return;
}
half3 WorldNormal = half3(0, 0, 0);
bool bBackfaceCull = bCullBackfacingPixels != 0;
float DeviceZ = 0;
float DeviceZWater = 0;
float DeviceZTranslucency = 0;
bool bIsValid = true;
bool bIsWaterDepthValid = false;
bool bIsTranslucencyDepthValid= false;
#if PERMUTATION_INPUT_TYPE == INPUT_TYPE_HAIRSTRANDS
{
DeviceZ = HairStrands.HairOnlyDepthTexture.Load(uint3(PixelPos, 0)).x;
bIsValid = DeviceZ > 0;
bBackfaceCull = false;
}
#else // PERMUTATION_INPUT_TYPE == INPUT_TYPE_GBUFFER
{
DeviceZ = LookupDeviceZ(PixelPos);
#if SUBSTRATE_ENABLED
const FSubstrateTopLayerData TopLayerData = SubstrateUnpackTopLayerData(Substrate.TopLayerTexture.Load(uint3(PixelPos, 0)));
FSubstrateAddressing SubstrateAddressing = GetSubstratePixelDataByteOffset(PixelPos, uint2(View.BufferSizeAndInvSize.xy), Substrate.MaxBytesPerPixel);
FSubstratePixelHeader SubstratePixelHeader = UnpackSubstrateHeaderIn(Substrate.MaterialTextureArray, SubstrateAddressing, Substrate.TopLayerTexture);
WorldNormal = TopLayerData.WorldNormal;
bIsValid = SubstratePixelHeader.IsSubstrateMaterial();
bBackfaceCull = bBackfaceCull && !SubstratePixelHeader.HasSubsurface();
#else // !SUBSTRATE_ENABLED
FGBufferData GBuffer = GetGBufferDataUint(PixelPos, true);
WorldNormal = GBuffer.WorldNormal;
// Excluding unlit to avoid including processing sky dome
bIsValid = GBuffer.ShadingModelID != SHADINGMODELID_UNLIT;
bBackfaceCull = bBackfaceCull && !IsSubsurfaceModel(GBuffer.ShadingModelID);
#endif // SUBSTRATE_ENABLED
#if PERMUTATION_WATER_DEPTH
// Assume valid if there is no mask
bIsWaterDepthValid = SingleLayerWaterTileViewRes.x <= 0;
// Skip the mask pass if the rect is zero
if (!bIsWaterDepthValid)
{
// uint2 WaterTileIndex = DispatchThreadId.xy * PixelStride / SLW_TILE_SIZE_XY; <=>
// uint2 WaterTileIndex = (groupId*SLW_TILE_SIZE_XY + threadgroupIndex) * PixelStride / SLW_TILE_SIZE_XY; <=>
// uint2 WaterTileIndex = groupId * PixelStride + threadgroupIndex * PixelStride / SLW_TILE_SIZE_XY; <=>
// uint2 WaterTileMin = groupId * PixelStride + {0,0} * PixelStride / SLW_TILE_SIZE_XY; <=> groupId * PixelStride
// uint2 WaterTileMax = groupId * PixelStride + (SLW_TILE_SIZE_XY-1) * PixelStride / SLW_TILE_SIZE_XY; <=>
// uint2 WaterTileMax = groupId * PixelStride + (SLW_TILE_SIZE_XY * PixelStride - PixelStride) / SLW_TILE_SIZE_XY; <=>
// uint2 WaterTileMax = groupId * PixelStride + PixelStride - PixelStride / SLW_TILE_SIZE_XY; <~>
// uint2 WaterTileMax = groupId * PixelStride + PixelStride; <~>
// Bit-scalarized version, must test rect
uint2 WaterTileMin = GroupId * PixelStride;
uint2 WaterTileMax = WaterTileMin + PixelStride;
for (uint WaterTileY = WaterTileMin.y; WaterTileY < WaterTileMax.y && !bIsWaterDepthValid; ++WaterTileY)
{
for (uint WaterTileX = WaterTileMin.x; WaterTileX < WaterTileMax.x; ++WaterTileX)
{
uint MaskLinearIndex = uint(SingleLayerWaterTileViewRes.x) * WaterTileY + WaterTileX;
if ((SingleLayerWaterTileMask[MaskLinearIndex / 32U] & (1U << (MaskLinearIndex % 32U))) != 0U)
{
bIsWaterDepthValid = true;
break;
}
}
}
}
if (bIsWaterDepthValid)
{
DeviceZWater = SingleLayerWaterDepthTexture.Load(uint3(PixelPos, 0)).x;
bIsWaterDepthValid = DeviceZWater != DeviceZ;
}
#endif // PERMUTATION_WATER_DEPTH
#if PERMUTATION_TRANSLUCENCY_DEPTH
// Same frame front layer depth
if (FrontLayerMode == 0)
{
DeviceZTranslucency = FrontLayerTranslucencyDepthTexture.Load(uint3(PixelPos, 0)).x;
const float4 EncodedData = FrontLayerTranslucencyNormalTexture.Load(uint3(PixelPos, 0));
bIsTranslucencyDepthValid = EncodedData.w > 0 && DeviceZTranslucency > 0;
}
// Previous frame Front layer depth reprojection
else
{
const float2 ScreenPosition = SvPositionToScreenPosition(float4(PixelPos, 0, 1)).xy;
float3 HistoryScreenPosition = float3(ScreenPosition, DeviceZ);
const float4 ThisClip = float4(HistoryScreenPosition, 1);
//float4 PrevClip = mul(ThisClip, View.ClipToPrevClip); //<=== doesn't contain AA offsets
const float4 PrevClip = mul(ThisClip, View.ClipToPrevClipWithAA);
const float3 PrevScreen = PrevClip.xyz / PrevClip.w;
const float3 Velocity = HistoryScreenPosition - PrevScreen;
// TODO handle dynamic object by using velocity buffer?
HistoryScreenPosition -= Velocity;
const float2 HistoryUV = HistoryScreenPosition.xy * FrontLayerHistoryScreenPositionScaleBias.xy + FrontLayerHistoryScreenPositionScaleBias.wz;
bool bHistoryValid = true;
FLATTEN
if (any(HistoryUV > FrontLayerHistoryUVMinMax.zw) || any(HistoryUV < FrontLayerHistoryUVMinMax.xy))
{
bHistoryValid = false;
}
if (bHistoryValid)
{
const float2 HistoryPixelPos = HistoryUV * FrontLayerHistoryBufferSizeAndInvSize.xy;
DeviceZTranslucency = FrontLayerTranslucencyDepthTexture.Load(uint3(HistoryPixelPos, 0)).x;
const float4 EncodedData = FrontLayerTranslucencyNormalTexture.Load(uint3(PixelPos, 0));
bIsTranslucencyDepthValid = EncodedData.w > 0 && DeviceZTranslucency > 0;
}
}
#endif
}
#endif // PERMUTATION_INPUT_TYPE == INPUT_TYPE_GBUFFER
if (!bIsValid && !bIsWaterDepthValid && !bIsTranslucencyDepthValid)
{
return;
}
const FPositionData Primary = InitPositionData(PixelPos, DeviceZ, true);
#if PERMUTATION_WATER_DEPTH
const FPositionData Water = InitPositionData(PixelPos, DeviceZWater, bIsWaterDepthValid);
#endif
#if PERMUTATION_TRANSLUCENCY_DEPTH
const FPositionData Translucency = InitPositionData(PixelPos, DeviceZTranslucency, bIsTranslucencyDepthValid);
#endif
// Dither pattern for page dilation
// We don't need to to check all 8 adjacent pages; as long as there's at least a single pixel near the edge
// the adjacent one will get mapped. In practice only checking one diagonal seems to work fine and have minimal
// overhead.
const float2 PageDilationDither = float2(
(GroupIndex & 1) ? 1.0f : -1.0f,
(GroupIndex & 2) ? 1.0f : -1.0f);
// Directional lights
{
const bool bUsePageDilation = PageDilationBorderSizeDirectional > 0.0f;
const float2 PageDilationOffset = PageDilationBorderSizeDirectional * PageDilationDither;
for (uint Index = 0; Index < NumDirectionalLightSmInds; ++Index)
{
int VirtualShadowMapId = DirectionalLightIds[Index];
FVirtualShadowMapProjectionShaderData ProjectionData = GetVirtualShadowMapProjectionData(VirtualShadowMapId);
bool bBackfaceCulledGBuffer = false;
#if PERMUTATION_INPUT_TYPE != INPUT_TYPE_HAIRSTRANDS
// Backface test if requested
if (bBackfaceCull && IsBackfaceToDirectionalLight(WorldNormal, ProjectionData.LightDirection, ProjectionData.LightSourceRadius))
{
bBackfaceCulledGBuffer = true;
}
#endif
if (!bBackfaceCulledGBuffer)
{
MarkPageClipmap(ProjectionData, bUsePageDilation, PageDilationOffset, Primary.TranslatedWorldPosition);
}
#if PERMUTATION_WATER_DEPTH
if (bIsWaterDepthValid)
{
MarkPageClipmap(ProjectionData, bUsePageDilation, PageDilationOffset, Water.TranslatedWorldPosition);
}
#endif // PERMUTATION_INPUT_TYPE == INPUT_TYPE_GBUFFER_AND_WATER_DEPTH
#if PERMUTATION_TRANSLUCENCY_DEPTH
if (bIsTranslucencyDepthValid)
{
MarkPageClipmap(ProjectionData, bUsePageDilation, PageDilationOffset, Translucency.TranslatedWorldPosition);
}
#endif
}
}
// Local lights
{
const bool bUsePageDilation = PageDilationBorderSizeLocal > 0.0f;
const float2 PageDilationOffset = PageDilationBorderSizeLocal * PageDilationDither;
uint2 LocalPosition = PixelPos - uint2(View.ViewRectMin.xy);
const FCulledLightsGridData LightGridData = VirtualShadowMapGetLightsGrid(LocalPosition, Primary.SceneDepth);
LOOP
for (uint Index = 0; Index < LightGridData.NumLights; ++Index)
{
const FLocalLightData LightData = VirtualShadowMapGetLocalLightData(LightGridData, Index);
// If we hit a distant light, we're done as we sorted those to the end. See PruneLightGridCS.
if (IsSinglePageVirtualShadowMap(LightData.VirtualShadowMapId))
{
break;
}
// Relative to PrimaryView
const float3 LightTranslatedWorldPosition = LightData.LightPositionAndInvRadius.xyz;
float LengthSquared = length2(LightTranslatedWorldPosition - Primary.TranslatedWorldPosition);
if (LengthSquared > Square(1.0f / LightData.LightPositionAndInvRadius.w))
{
continue;
}
float3 ToLight = normalize(LightTranslatedWorldPosition - Primary.TranslatedWorldPosition);
// TODO: Can optimize these tests by fusing them together
// Also do precise cone test, since froxels are pretty coarse at times.
if (dot(ToLight, LightData.LightDirectionAndShadowMask.xyz) < GetLightSpotAngles(LightData).x)
{
continue;
}
const uint LightTypeAndPackedShadowMapChannelMask = asuint(LightData.LightDirectionAndShadowMask.w);
const uint LightType = UnpackLightType(LightTypeAndPackedShadowMapChannelMask);
const bool bIsRectLight = LightType == LIGHT_TYPE_RECT;
if (bIsRectLight)
{
const bool bIsBehindRectLight = dot(ToLight, LightData.LightDirectionAndShadowMask.xyz) < 0;
if (bIsBehindRectLight)
{
continue;
}
}
// TODO: Precise radius test necessary?
#if PERMUTATION_INPUT_TYPE != INPUT_TYPE_HAIRSTRANDS
// Backface test if requested
if (bBackfaceCull && IsBackfaceToLocalLight(ToLight, WorldNormal, GetLightSourceRadius(LightData)))
{
continue;
}
#endif
int VirtualShadowMapId = LightData.VirtualShadowMapId;
bool bSpotLight = GetLightSpotAngles(LightData).x > -2.0f;
if( !bSpotLight )
{
VirtualShadowMapId += VirtualShadowMapGetCubeFace(-ToLight);
}
uint MipLevel = GetMipLevelLocal(VirtualShadowMapId, Primary.TranslatedWorldPosition, Primary.SceneDepth);
MarkPage(VirtualShadowMapId, MipLevel, Primary.TranslatedWorldPosition, bUsePageDilation, PageDilationOffset);
}
}
}
uint bMarkCoarsePagesLocal;
int ClipmapFirstLevel;
uint ClipmapIndexMask;
uint bIncludeNonNaniteGeometry;
[numthreads(VSM_DEFAULT_CS_GROUP_X, 1, 1)]
void MarkCoarsePages(uint DispatchThreadId : SV_DispatchThreadID)
{
// Remap thread ID [0..NumShadowMaps) to full / single page ranges.
uint VirtualShadowMapId = DispatchThreadId;
if (VirtualShadowMapId < VirtualShadowMap.NumFullShadowMaps)
{
VirtualShadowMapId += VSM_MAX_SINGLE_PAGE_SHADOW_MAPS;
}
else
{
VirtualShadowMapId -= VirtualShadowMap.NumFullShadowMaps;
if (VirtualShadowMapId >= VirtualShadowMap.NumSinglePageShadowMaps)
{
return;
}
}
FVirtualShadowMapProjectionShaderData ProjectionData = GetVirtualShadowMapProjectionData(VirtualShadowMapId);
// NOTE: Coarse pages are very large and tend to get invalidated a lot due to anything in the scene moving
// Rendering non-nanite geometry into these pages can be very expensive and thus isn't always desirable.
uint Flags = VSM_ALLOCATED_FLAG;
if (ProjectionData.bUnreferenced)
{
// Don't mark any pages for lights not referenced this render
}
else if (ProjectionData.LightType == LIGHT_TYPE_DIRECTIONAL)
{
// Idea here is the clipmaps already cover supersets of lower levels
// Thus to get coarser pages we can just mark the center page(s) offset by a level/LOD bias
// The limit on how far dense data goes out from the camera then becomes the world space size of the marked page(s) on the coarses clipmap
// We could of course mark a broader set of pages in the coarses clipmap level, but the effective radius
// even from just marking a single one is usually already large enough for the systems that need this
// data (volumetric fog, translucent light volume).
uint ClipmapIndex = uint(ProjectionData.ClipmapLevel - ClipmapFirstLevel);
if (((ClipmapIndexMask >> ClipmapIndex) & 1) != 0)
{
// TODO: Optimize this... can be boiled down to be just in terms of the snap offsets
float3 OriginTranslatedWorld = ProjectionData.ClipmapWorldOriginOffset;
float4 ShadowUVz = mul(float4(OriginTranslatedWorld, 1.0f), ProjectionData.TranslatedWorldToShadowUVMatrix);
float2 VirtualTexelAddressFloat = ShadowUVz.xy * float(CalcLevelDimsTexels(0));
float2 PageAddressFloat = VirtualTexelAddressFloat * float(1.0f / VSM_PAGE_SIZE);
// NOTE: Page addresses round down/truncate normally, so grab the surrounding 4
int4 PageAddressLowHigh = int4(floor(PageAddressFloat - 0.5f), ceil(PageAddressFloat - 0.5f));
MarkPageAddress(CalcPageOffset(VirtualShadowMapId, 0, PageAddressLowHigh.xy), Flags);
MarkPageAddress(CalcPageOffset(VirtualShadowMapId, 0, PageAddressLowHigh.xw), Flags);
MarkPageAddress(CalcPageOffset(VirtualShadowMapId, 0, PageAddressLowHigh.zy), Flags);
MarkPageAddress(CalcPageOffset(VirtualShadowMapId, 0, PageAddressLowHigh.zw), Flags);
}
}
// Note: always mark last mip for "distant" light
else if (bMarkCoarsePagesLocal != 0U || IsSinglePageVirtualShadowMap(VirtualShadowMapId))
{
// Mark last mip
uint PageOffset = CalcPageOffset(VirtualShadowMapId, VSM_MAX_MIP_LEVELS - 1, uint2(0, 0));
MarkPageAddress(PageOffset, Flags);
}
}