Files
UnrealEngine/Engine/Shaders/Private/Lumen/LumenSceneDirectLightingCulling.usf
2025-05-18 13:04:45 +08:00

619 lines
20 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
#include "../Common.ush"
#include "../IntersectionUtils.ush"
#define SUPPORT_CONTACT_SHADOWS 0
#include "../DeferredLightingCommon.ush"
#include "LumenCardCommon.ush"
#include "LumenCardTile.ush"
#include "LumenSceneDirectLighting.ush"
#include "SurfaceCache/LumenSurfaceCache.ush"
#ifndef THREADGROUP_SIZE
#define THREADGROUP_SIZE 1
#endif
#ifndef MAX_LIGHT_SAMPLES
#define MAX_LIGHT_SAMPLES 1
#endif
StructuredBuffer<uint> CardPageIndexAllocator;
StructuredBuffer<uint> CardPageIndexData;
RWStructuredBuffer<uint> RWCardTileAllocator;
RWStructuredBuffer<uint> RWCardTiles;
#ifdef SpliceCardPagesIntoTilesCS
groupshared uint SharedTileAllocator;
groupshared uint SharedTiles[THREADGROUP_SIZE * THREADGROUP_SIZE];
groupshared uint SharedGlobalTileOffset;
/**
* Splice card pages into N card tiles
*/
[numthreads(THREADGROUP_SIZE, THREADGROUP_SIZE, 1)]
void SpliceCardPagesIntoTilesCS(
uint3 GroupId : SV_GroupID,
uint3 DispatchThreadId : SV_DispatchThreadID,
uint3 GroupThreadId : SV_GroupThreadID)
{
uint LinearThreadIndex = GroupThreadId.x + GroupThreadId.y * THREADGROUP_SIZE;
if (all(GroupThreadId == 0))
{
SharedTileAllocator = 0;
SharedGlobalTileOffset = 0;
SharedTiles[0] = 0;
}
GroupMemoryBarrierWithGroupSync();
// One thread per tile
uint LinearLightTileOffset = (GroupId.x % 4);
uint IndexInIndexBuffer = GroupId.x / 4;
// Improve tile coherency
uint2 TileOffset = ZOrder2D(LinearThreadIndex, log2(8));
uint2 TileCoord;
TileCoord.x = (LinearLightTileOffset % 2) * 8 + TileOffset.x;
TileCoord.y = (LinearLightTileOffset / 2) * 8 + TileOffset.y;
if (IndexInIndexBuffer < CardPageIndexAllocator[0])
{
uint CardPageIndex = CardPageIndexData[IndexInIndexBuffer];
FLumenCardPageData CardPage = GetLumenCardPageData(CardPageIndex);
if (CardPage.CardIndex >= 0)
{
FLumenCardData Card = GetLumenCardData(CardPage.CardIndex);
const uint2 SizeInTiles = CardPage.SizeInTexels / CARD_TILE_SIZE;
if (all(TileCoord < SizeInTiles))
{
FCardTileData CardTile;
CardTile.CardPageIndex = CardPageIndex;
CardTile.TileCoord = TileCoord;
uint CardTileIndex = 0;
InterlockedAdd(SharedTileAllocator, 1, CardTileIndex);
SharedTiles[CardTileIndex] = PackCardTileData(CardTile);
}
}
}
GroupMemoryBarrierWithGroupSync();
if (all(GroupThreadId == 0) && SharedTileAllocator > 0)
{
InterlockedAdd(RWCardTileAllocator[0], SharedTileAllocator, SharedGlobalTileOffset);
}
GroupMemoryBarrierWithGroupSync();
if (LinearThreadIndex < SharedTileAllocator)
{
RWCardTiles[SharedGlobalTileOffset + LinearThreadIndex] = SharedTiles[LinearThreadIndex];
}
}
#endif
StructuredBuffer<uint> CardTileAllocator;
StructuredBuffer<uint> CardTiles;
RWBuffer<uint> RWDispatchCardTilesIndirectArgs;
#ifdef InitializeCardTileIndirectArgsCS
[numthreads(THREADGROUP_SIZE, 1, 1)]
void InitializeCardTileIndirectArgsCS(uint3 DispatchThreadId : SV_DispatchThreadID)
{
if (DispatchThreadId.x == 0)
{
uint NumCardTiles = CardTileAllocator[0];
// One thread per card tile
WriteDispatchIndirectArgs(RWDispatchCardTilesIndirectArgs, 0, DivideAndRoundUp64(NumCardTiles), 1, 1);
// One thread group per card tile
WriteDispatchIndirectArgs(RWDispatchCardTilesIndirectArgs, 1, NumCardTiles, 1, 1);
}
}
#endif
#ifdef CalculateCardTileDepthRangesCS
RWStructuredBuffer<uint> RWCardTileDepthRanges;
groupshared uint SharedMinTileDepth;
groupshared uint SharedMaxTileDepth;
[numthreads(THREADGROUP_SIZE, THREADGROUP_SIZE, 1)]
void CalculateCardTileDepthRangesCS(
uint GroupId : SV_GroupID,
uint2 GroupThreadId : SV_GroupThreadID)
{
uint CardTileIndex = GroupId;
if (GroupThreadId.x == 0 && GroupThreadId.y == 0)
{
SharedMinTileDepth = 0xFFFFFFFF;
SharedMaxTileDepth = 0;
}
GroupMemoryBarrierWithGroupSync();
if (CardTileIndex < CardTileAllocator[0])
{
FCardTileData CardTile = UnpackCardTileData(CardTiles[CardTileIndex]);
FLumenCardPageData CardPage = GetLumenCardPageData(CardTile.CardPageIndex);
if (CardPage.CardIndex >= 0)
{
uint2 TexelInCardPageCoord = CardTile.TileCoord * CARD_TILE_SIZE + GroupThreadId.xy;
float2 AtlasUV = CardPage.PhysicalAtlasUVRect.xy + CardPage.PhysicalAtlasUVTexelScale * (TexelInCardPageCoord + 0.5f);
float Depth = Texture2DSampleLevel(LumenCardScene.DepthAtlas, GlobalPointClampedSampler, AtlasUV, 0).x;
if (IsSurfaceCacheDepthValid(Depth))
{
InterlockedMax(SharedMaxTileDepth, asuint(Depth));
InterlockedMin(SharedMinTileDepth, asuint(Depth));
}
}
}
GroupMemoryBarrierWithGroupSync();
if (GroupThreadId.x == 0 && GroupThreadId.y == 0)
{
float MinTileDepth = SharedMinTileDepth == 0xFFFFFFFF ? 0.0f : asfloat(SharedMinTileDepth);
float MaxTileDepth = SharedMaxTileDepth == 0 ? 1.0f : asfloat(SharedMaxTileDepth);
RWCardTileDepthRanges[CardTileIndex] = f32tof16(MinTileDepth) | (f32tof16(MaxTileDepth) << 16);
}
}
#endif
uint CullToCardTileDepthRange;
StructuredBuffer<uint> CardTileDepthRanges;
float2 GetCardTileDepthRange(uint CardTileIndex)
{
if (CullToCardTileDepthRange)
{
uint Packed = CardTileDepthRanges[CardTileIndex];
return float2(f16tof32(Packed & 0xFFFF), f16tof32(Packed >> 16));
}
return float2(0.0f, 1.0f);
}
bool DoesLightInfluenceSphereAffectCardPageUVRange(float4 InfluenceSphere, FLumenCardPageData CardPage, FLumenCardData Card, float2 UVMin, float2 UVMax, float2 CardTileDepthRange)
{
float3 CardPageLocalCenter;
float3 CardPageLocalExtent;
GetCardLocalBBox(CardPage, Card, UVMin, UVMax, CardTileDepthRange, CardPageLocalCenter, CardPageLocalExtent);
float3 LightInfluenceSphereLocalCenter = mul(InfluenceSphere.xyz - Card.Origin, Card.WorldToLocalRotation);
const float BoxDistanceSq = ComputeSquaredDistanceFromBoxToPoint(CardPageLocalCenter, CardPageLocalExtent, LightInfluenceSphereLocalCenter);
return BoxDistanceSq < InfluenceSphere.w * InfluenceSphere.w;
}
// This function doesn't test light influence spheres
bool DoesLightAffectCardPageUVRange(FLumenLight LumenLight, FLumenCardPageData CardPage, FLumenCardData Card, float2 UVMin, float2 UVMax, float2 CardTileDepthRange, inout float3 OutCardPageWorldCenter)
{
// Lighting channels test
if (!(Card.LightingChannelMask & LumenLight.LightingChannelMask))
{
return false;
}
float3 CardPageLocalCenter;
float3 CardPageLocalExtent;
GetCardLocalBBox(CardPage, Card, UVMin, UVMax, CardTileDepthRange, CardPageLocalCenter, CardPageLocalExtent);
float3 CardPageWorldCenter = mul(Card.WorldToLocalRotation, CardPageLocalCenter) + Card.Origin;
float3 CardPageWorldExtent = mul(abs(Card.WorldToLocalRotation), CardPageLocalExtent);
float CardPageWorldBoundingSphere = length(CardPageLocalExtent);
OutCardPageWorldCenter = CardPageWorldCenter;
const uint LightType = LumenLight.Type;
const float3 LightPosition = LumenLight.ProxyPosition;
const float3 LightDirection = LumenLight.ProxyDirection;
const float LightRadius = LumenLight.ProxyRadius;
if (LightType == LIGHT_TYPE_DIRECTIONAL)
{
return true;
}
else if (LightType == LIGHT_TYPE_POINT)
{
// Point light
return true;
}
else if (LightType == LIGHT_TYPE_SPOT)
{
float CosConeAngle = LumenLight.CosConeAngle;
float SinConeAngle = LumenLight.SinConeAngle;
float ConeAxisDistance = dot(CardPageWorldCenter - LightPosition, LightDirection);
float2 ConeAxisDistanceMinMax = float2(ConeAxisDistance + CardPageWorldBoundingSphere, ConeAxisDistance - CardPageWorldBoundingSphere);
// Spot light
return SphereIntersectCone(float4(CardPageWorldCenter, CardPageWorldBoundingSphere), LightPosition, LightDirection, CosConeAngle, SinConeAngle)
&& ConeAxisDistanceMinMax.x > 0 && ConeAxisDistanceMinMax.y < LightRadius;
}
else if (LightType == LIGHT_TYPE_RECT)
{
// Rect light
float4 BackPlane = float4(LightDirection, dot(LightPosition, LightDirection));
float DistanceFromBoxCenterToPlane = dot(BackPlane.xyz, CardPageWorldCenter) - BackPlane.w;
float MaxExtent = dot(CardPageWorldExtent, abs(BackPlane.xyz));
bool bInFrontOfPlane = DistanceFromBoxCenterToPlane + MaxExtent > 0.0f;
return bInFrontOfPlane;
}
// Error: Unknown light type
return false;
}
RWStructuredBuffer<uint> RWLightTileAllocator;
RWStructuredBuffer<uint2> RWLightTiles;
RWStructuredBuffer<uint> RWLightTileAllocatorPerLight;
RWStructuredBuffer<uint> RWLightTileAllocatorForPerCardTileDispatch;
RWStructuredBuffer<uint> RWLightTileOffsetNumPerCardTile;
uint MaxLightsPerTile;
uint NumLights;
uint NumViews;
int bUseLightTilesPerLightType;
struct FLightSample
{
float Weight;
uint LightIndex;
uint LightType; // 0:standalone, 1:point, 2:spot, 3:rect
bool bHasShadowMask;
};
struct FLightSampleAccumulator
{
uint PackedSamples[MAX_LIGHT_SAMPLES];
};
FLightSampleAccumulator InitLightSampleAccumulator()
{
FLightSampleAccumulator LightSampleAccumulator = (FLightSampleAccumulator)0;
for (uint PackedSampleIndex = 0; PackedSampleIndex < MAX_LIGHT_SAMPLES; ++PackedSampleIndex)
{
LightSampleAccumulator.PackedSamples[PackedSampleIndex] = 0;
}
return LightSampleAccumulator;
}
uint PackLightSample(FLightSample LightSample)
{
uint PackedLightSample = LightSample.LightIndex & 0x3FFF;
PackedLightSample |= (LightSample.LightType << 14u) & 0xC000;
PackedLightSample |= LightSample.bHasShadowMask ? 0x10000 : 0;
PackedLightSample |= (asuint(LightSample.Weight) << 1u) & 0xFFFE0000;
return PackedLightSample;
}
FLightSample UnpackLightSample(uint PackedLightSample)
{
FLightSample LightSample = (FLightSample)0;
LightSample.LightIndex = PackedLightSample & 0x3FFF;
LightSample.LightType = BitFieldExtractU32(PackedLightSample, 2, 14);
LightSample.bHasShadowMask = PackedLightSample & 0x10000 ? true : false;
LightSample.Weight = 0.0f;
return LightSample;
}
void AddLightSample(inout FLightSampleAccumulator LightSampleAccumulator, FLightSample LightSample)
{
uint PackedLightSample = PackLightSample(LightSample);
// Try inserting the new light and keep things in descending order
for (uint PackedSampleIndex = 0; PackedSampleIndex < MAX_LIGHT_SAMPLES; ++PackedSampleIndex)
{
uint Temp = LightSampleAccumulator.PackedSamples[PackedSampleIndex];
if (PackedLightSample > Temp)
{
LightSampleAccumulator.PackedSamples[PackedSampleIndex] = PackedLightSample;
PackedLightSample = Temp;
#if MAX_LIGHT_SAMPLES <= 16 // workaround for a shader compiler bug
if (PackedLightSample == 0)
{
break;
}
#endif
}
}
}
float GetLightWeight(FLumenLight LumenLight, float3 TranslatedWorldPosition)
{
FDeferredLightData LightData = LumenLight.DeferredLightData;
float Weight = Luminance(LightData.Color);
if (LightData.bRadialLight)
{
float3 L = LightData.Direction;
float3 ToLight = L;
Weight *= GetLocalLightAttenuation(TranslatedWorldPosition, LightData, ToLight, L);
float Attenuation = 1.0f;
if (LightData.bRectLight)
{
FRect Rect = GetRect(ToLight, LightData);
Attenuation = IntegrateLight(Rect);
}
else
{
FCapsuleLight Capsule = GetCapsule(ToLight, LightData);
Capsule.DistBiasSqr = 0;
Attenuation = IntegrateLight(Capsule, LightData.bInverseSquared);
}
Weight *= Attenuation;
}
return Weight;
}
#ifdef BuildLightTilesCS
/**
* Pick N most important lights per tile in page selected to update to update this frame, and output a list of light tiles
*/
[numthreads(THREADGROUP_SIZE, 1, 1)]
void BuildLightTilesCS(
uint3 GroupId : SV_GroupID,
uint3 DispatchThreadId : SV_DispatchThreadID,
uint3 GroupThreadId : SV_GroupThreadID)
{
// One thread per tile
uint CardTileIndex = DispatchThreadId.x;
FLightSampleAccumulator LightSampleAccumulator = InitLightSampleAccumulator();
if (CardTileIndex < CardTileAllocator[0])
{
FCardTileData CardTile = UnpackCardTileData(CardTiles[CardTileIndex]);
FLumenCardPageData CardPage = GetLumenCardPageData(CardTile.CardPageIndex);
uint PackedOffsetNum = 0;
if (CardPage.CardIndex >= 0)
{
FLumenCardData Card = GetLumenCardData(CardPage.CardIndex);
const uint2 SizeInTiles = CardPage.SizeInTexels / CARD_TILE_SIZE;
float2 UVMin = float2(CardTile.TileCoord) / SizeInTiles;
float2 UVMax = float2(CardTile.TileCoord + 1) / SizeInTiles;
float SwapY = UVMin.y;
UVMin.y = 1.0f - UVMax.y;
UVMax.y = 1.0f - SwapY;
float2 CardTileDepthRange = GetCardTileDepthRange(CardTileIndex);
uint ViewIndex = GetCardViewIndex(CardPage, Card, UVMin, UVMax, CardTileDepthRange, NumViews, true);
// Loop over lights to select N most important lights
for (uint LightIndex = 0; LightIndex < NumLights; ++LightIndex)
{
if (DoesLightInfluenceSphereAffectCardPageUVRange(LoadLumenLightInfluenceSphere(LightIndex), CardPage, Card, UVMin, UVMax, CardTileDepthRange))
{
const FDFVector3 PreViewTranslation = GetPreViewTranslation(ViewIndex);
FLumenLight LumenLight = LoadLumenLight(LightIndex, DFHackToFloat(PreViewTranslation), ViewExposure[ViewIndex]);
float3 CardPageWorldCenter = 0.0f; // LWC_TODO:
bool bLightAffectsCard = DoesLightAffectCardPageUVRange(LumenLight, CardPage, Card, UVMin, UVMax, CardTileDepthRange, CardPageWorldCenter);
if (bLightAffectsCard)
{
// Center of a tile for estimating attenuation
float3 TranslatedWorldPosition = CardPageWorldCenter + DFHackToFloat(PrimaryView.PreViewTranslation);
FLightSample LightSample;
LightSample.Weight = GetLightWeight(LumenLight, TranslatedWorldPosition);
LightSample.LightIndex = LightIndex;
LightSample.LightType = LumenLight.bIsStandaloneLight ? 0 : LumenLight.Type;
LightSample.bHasShadowMask = LumenLight.bHasShadowMask;
AddLightSample(LightSampleAccumulator, LightSample);
}
}
}
uint NumPackedLightSamples = 0;
for (uint PackedSampleIndex = 0; PackedSampleIndex < MAX_LIGHT_SAMPLES; ++PackedSampleIndex)
{
if (LightSampleAccumulator.PackedSamples[PackedSampleIndex] > 0)
{
++NumPackedLightSamples;
}
}
uint LightTileOffset = 0;
if (NumPackedLightSamples > 0)
{
InterlockedAdd(RWLightTileAllocator[0], NumPackedLightSamples, LightTileOffset);
}
for (uint LightSampleIndex = 0; LightSampleIndex < NumPackedLightSamples; ++LightSampleIndex)
{
FLightSample LightSample = UnpackLightSample(LightSampleAccumulator.PackedSamples[LightSampleIndex]);
// Write light tile to global light tile array
FLightTileForCompactionPass LightTile;
LightTile.LightIndex = LightSample.LightIndex;
LightTile.ViewIndex = ViewIndex;
LightTile.bHasShadowMask = LightSample.bHasShadowMask;
LightTile.CardTileIndex = CardTileIndex;
LightTile.CulledLightIndex = LightSampleIndex;
LightTile.LightType = LightSample.LightType;
RWLightTiles[LightTileOffset + LightSampleIndex] = PackLightTileForCompactionPass(LightTile);
uint SlotIndex = bUseLightTilesPerLightType != 0 && LightSample.LightType > 0 ?
LightSample.LightType - 1 :
NUM_BATCHABLE_LIGHT_TYPES + LightSample.LightIndex;
InterlockedAdd(RWLightTileAllocatorPerLight[SlotIndex * NumViews + ViewIndex], 1);
}
if (NumPackedLightSamples > 0)
{
uint CardLightTilesOffset;
InterlockedAdd(RWLightTileAllocatorForPerCardTileDispatch[0], NumPackedLightSamples, CardLightTilesOffset);
PackedOffsetNum = (NumPackedLightSamples << 24) | CardLightTilesOffset;
}
}
RWLightTileOffsetNumPerCardTile[CardTileIndex] = PackedOffsetNum;
}
}
#endif
StructuredBuffer<uint> LightTileAllocatorPerLight;
RWStructuredBuffer<uint> RWLightTileOffsetsPerLight;
#ifdef ComputeLightTileOffsetsPerLightCS
StructuredBuffer<int> StandaloneLightIndices;
uint NumStandaloneLights;
/**
* Prefix sum for card tile array compaction
*/
[numthreads(THREADGROUP_SIZE, 1, 1)]
void ComputeLightTileOffsetsPerLightCS(uint3 DispatchThreadId : SV_DispatchThreadID)
{
if (DispatchThreadId.x == 0)
{
uint TileOffset = 0;
for (uint ViewIndex = 0; ViewIndex < NumViews; ViewIndex++)
{
#if USE_STANDALONE_LIGHT_INDICES
for (uint BufferIndex = 0; BufferIndex < NumStandaloneLights; ++BufferIndex)
{
uint LightIndex = uint(StandaloneLightIndices[BufferIndex]);
#else
for (uint LightIndex = 0; LightIndex < NumLights; ++LightIndex)
{
#endif
uint Index = NUM_BATCHABLE_LIGHT_TYPES + LightIndex;
RWLightTileOffsetsPerLight[Index * NumViews + ViewIndex] = TileOffset;
TileOffset += LightTileAllocatorPerLight[Index * NumViews + ViewIndex];
}
}
for (uint ViewIndex = 0; ViewIndex < NumViews; ViewIndex++)
{
for (uint LightTypeIndex = 0; LightTypeIndex < NUM_BATCHABLE_LIGHT_TYPES; ++LightTypeIndex)
{
RWLightTileOffsetsPerLight[LightTypeIndex * NumViews + ViewIndex] = TileOffset;
TileOffset += LightTileAllocatorPerLight[LightTypeIndex * NumViews + ViewIndex];
}
}
}
}
#endif
RWStructuredBuffer<uint2> RWCompactedLightTiles;
RWStructuredBuffer<uint2> RWLightTilesPerCardTile;
RWStructuredBuffer<uint> RWCompactedLightTileAllocatorPerLight;
StructuredBuffer<uint> LightTileAllocator;
StructuredBuffer<uint2> LightTiles;
StructuredBuffer<uint> LightTileOffsetsPerLight;
StructuredBuffer<uint> LightTileOffsetNumPerCardTile;
#ifdef CompactLightTilesCS
/**
* Compact card tile array
*/
[numthreads(THREADGROUP_SIZE, 1, 1)]
void CompactLightTilesCS(uint3 DispatchThreadId : SV_DispatchThreadID)
{
const uint LightTileIndex = DispatchThreadId.x;
if (LightTileIndex < LightTileAllocator[0])
{
FLightTileForCompactionPass LightTile = UnpackLightTileForCompactionPass(LightTiles[LightTileIndex]);
uint SlotIndex = bUseLightTilesPerLightType != 0 && LightTile.LightType > 0 ? LightTile.LightType - 1 : NUM_BATCHABLE_LIGHT_TYPES + LightTile.LightIndex;
uint CompactedLightTileIndex = 0;
InterlockedAdd(RWCompactedLightTileAllocatorPerLight[SlotIndex * NumViews + LightTile.ViewIndex], 1, CompactedLightTileIndex);
CompactedLightTileIndex += LightTileOffsetsPerLight[SlotIndex * NumViews + LightTile.ViewIndex];
uint CardTileIndex = LightTile.CardTileIndex;
FCardTileData CardTile = UnpackCardTileData(CardTiles[CardTileIndex]);
FLightTileForShadowMaskPass TileForLight;
TileForLight.LightIndex = LightTile.LightIndex;
TileForLight.ViewIndex = LightTile.ViewIndex;
TileForLight.CardPageIndex = CardTile.CardPageIndex;
TileForLight.TileCoord = CardTile.TileCoord;
uint2 PackedLightTile = PackLightTileForShadowMaskPass(TileForLight);
RWCompactedLightTiles[CompactedLightTileIndex] = PackedLightTile;
uint PackedOffsetNum = LightTileOffsetNumPerCardTile[CardTileIndex];
uint LightTileOffset = (PackedOffsetNum & 0x00ffffff) + LightTile.CulledLightIndex;
FLightTileForLightPass TileForCardTile;
TileForCardTile.LightIndex = LightTile.LightIndex;
TileForCardTile.ViewIndex = LightTile.ViewIndex;
TileForCardTile.ShadowMaskIndex = LightTile.bHasShadowMask ? CompactedLightTileIndex : 0xffffffff;
PackedLightTile = PackLightTileForLightPass(TileForCardTile);
RWLightTilesPerCardTile[LightTileOffset] = PackedLightTile;
}
}
#endif
RWBuffer<uint> RWDispatchLightTilesIndirectArgs;
RWBuffer<uint> RWDrawTilesPerLightIndirectArgs;
RWBuffer<uint> RWDispatchTilesPerLightIndirectArgs;
uint VertexCountPerInstanceIndirect;
uint PerLightDispatchFactor;
#ifdef InitializeLightTileIndirectArgsCS
[numthreads(THREADGROUP_SIZE, 1, 1)]
void InitializeLightTileIndirectArgsCS(uint3 DispatchThreadId : SV_DispatchThreadID)
{
uint PerViewLightIndex = DispatchThreadId.x;
// Global card tile array
if (PerViewLightIndex == 0)
{
uint NumLightTiles = LightTileAllocator[0];
// NumTilesDiv1
WriteDispatchIndirectArgs(RWDispatchLightTilesIndirectArgs, 0, NumLightTiles, 1, 1);
// NumTilesDiv64
WriteDispatchIndirectArgs(RWDispatchLightTilesIndirectArgs, 1, DivideAndRoundUp64(NumLightTiles), 1, 1);
}
// Per light card tile array
if (PerViewLightIndex < NumLights * NumViews)
{
uint NumLightTilesPerLight = LightTileAllocatorPerLight[PerViewLightIndex];
// FRHIDispatchIndirectParameters
WriteDispatchIndirectArgs(RWDispatchTilesPerLightIndirectArgs, PerViewLightIndex, PerLightDispatchFactor * NumLightTilesPerLight, 1, 1);
// FRHIDrawIndirectParameters
RWDrawTilesPerLightIndirectArgs[4 * PerViewLightIndex + 0] = VertexCountPerInstanceIndirect;
RWDrawTilesPerLightIndirectArgs[4 * PerViewLightIndex + 1] = NumLightTilesPerLight;
RWDrawTilesPerLightIndirectArgs[4 * PerViewLightIndex + 2] = 0;
RWDrawTilesPerLightIndirectArgs[4 * PerViewLightIndex + 3] = 0;
}
}
#endif