// 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 CardPageIndexAllocator; StructuredBuffer CardPageIndexData; RWStructuredBuffer RWCardTileAllocator; RWStructuredBuffer 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 CardTileAllocator; StructuredBuffer CardTiles; RWBuffer 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 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 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 RWLightTileAllocator; RWStructuredBuffer RWLightTiles; RWStructuredBuffer RWLightTileAllocatorPerLight; RWStructuredBuffer RWLightTileAllocatorForPerCardTileDispatch; RWStructuredBuffer 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 LightTileAllocatorPerLight; RWStructuredBuffer RWLightTileOffsetsPerLight; #ifdef ComputeLightTileOffsetsPerLightCS StructuredBuffer 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 RWCompactedLightTiles; RWStructuredBuffer RWLightTilesPerCardTile; RWStructuredBuffer RWCompactedLightTileAllocatorPerLight; StructuredBuffer LightTileAllocator; StructuredBuffer LightTiles; StructuredBuffer LightTileOffsetsPerLight; StructuredBuffer 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 RWDispatchLightTilesIndirectArgs; RWBuffer RWDrawTilesPerLightIndirectArgs; RWBuffer 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