Files
UnrealEngine/Engine/Source/Runtime/Renderer/Private/Lumen/LumenMeshCards.cpp
2025-05-18 13:04:45 +08:00

1379 lines
50 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "LumenMeshCards.h"
#include "RendererPrivate.h"
#include "MeshCardRepresentation.h"
#include "ComponentRecreateRenderStateContext.h"
#include "LumenHeightfields.h"
#include "MeshCardBuild.h"
#include "InstanceDataSceneProxy.h"
TAutoConsoleVariable<float> CVarLumenMeshCardsMinSize(
TEXT("r.LumenScene.SurfaceCache.MeshCardsMinSize"),
10.0f,
TEXT("Minimum mesh cards world space size to be included in Lumen Scene."),
FConsoleVariableDelegate::CreateLambda([](IConsoleVariable* InVariable)
{
FGlobalComponentRecreateRenderStateContext Context;
}),
ECVF_Scalability | ECVF_RenderThreadSafe
);
int32 GLumenMeshCardsMergeComponents = 1;
FAutoConsoleVariableRef CVarLumenMeshCardsMergeComponents(
TEXT("r.LumenScene.SurfaceCache.MeshCardsMergeComponents"),
GLumenMeshCardsMergeComponents,
TEXT("Whether to merge all components with the same RayTracingGroupId into a single MeshCards."),
FConsoleVariableDelegate::CreateLambda([](IConsoleVariable* InVariable)
{
FGlobalComponentRecreateRenderStateContext Context;
}),
ECVF_Scalability | ECVF_RenderThreadSafe
);
int32 GLumenMeshCardsMergeInstances = 0;
FAutoConsoleVariableRef CVarLumenMeshCardsMergeInstances(
TEXT("r.LumenScene.SurfaceCache.MeshCardsMergeInstances"),
GLumenMeshCardsMergeInstances,
TEXT("Whether to merge all instances of a Instanced Static Mesh Component into a single MeshCards."),
FConsoleVariableDelegate::CreateLambda([](IConsoleVariable* InVariable)
{
FGlobalComponentRecreateRenderStateContext Context;
}),
ECVF_Scalability | ECVF_RenderThreadSafe
);
float GLumenMeshCardsMergedCardMinSurfaceArea = 0.05f;
FAutoConsoleVariableRef CVarLumenMeshCardsMergedCardMinSurfaceArea(
TEXT("r.LumenScene.SurfaceCache.MeshCardsMergedCardMinSurfaceArea"),
GLumenMeshCardsMergedCardMinSurfaceArea,
TEXT("Minimum area to spawn a merged card."),
FConsoleVariableDelegate::CreateLambda([](IConsoleVariable* InVariable)
{
FGlobalComponentRecreateRenderStateContext Context;
}),
ECVF_Scalability | ECVF_RenderThreadSafe
);
float GLumenMeshCardsMergeInstancesMaxSurfaceAreaRatio = 1.7f;
FAutoConsoleVariableRef CVarLumenMeshCardsMergeInstancesMaxSurfaceAreaRatio(
TEXT("r.LumenScene.SurfaceCache.MeshCardsMergeInstancesMaxSurfaceAreaRatio"),
GLumenMeshCardsMergeInstancesMaxSurfaceAreaRatio,
TEXT("Only merge if the (combined box surface area) / (summed instance box surface area) < MaxSurfaceAreaRatio"),
FConsoleVariableDelegate::CreateLambda([](IConsoleVariable* InVariable)
{
FGlobalComponentRecreateRenderStateContext Context;
}),
ECVF_Scalability | ECVF_RenderThreadSafe
);
float GLumenMeshCardsMergedResolutionScale = .3f;
FAutoConsoleVariableRef CVarLumenMeshCardsMergedResolutionScale(
TEXT("r.LumenScene.SurfaceCache.MeshCardsMergedResolutionScale"),
GLumenMeshCardsMergedResolutionScale,
TEXT("Scale on the resolution calculation for a merged MeshCards. This compensates for the merged box getting a higher resolution assigned due to being closer to the viewer."),
FConsoleVariableDelegate::CreateLambda([](IConsoleVariable* InVariable)
{
FGlobalComponentRecreateRenderStateContext Context;
}),
ECVF_Scalability | ECVF_RenderThreadSafe
);
float GLumenMeshCardsMergedMaxWorldSize = 10000.0f;
FAutoConsoleVariableRef CVarLumenMeshCardsMergedMaxWorldSize(
TEXT("r.LumenScene.SurfaceCache.MeshCardsMergedMaxWorldSize"),
GLumenMeshCardsMergedMaxWorldSize,
TEXT("Only merged bounds less than this size on any axis are considered, since Lumen Scene streaming relies on object granularity."),
FConsoleVariableDelegate::CreateLambda([](IConsoleVariable* InVariable)
{
FGlobalComponentRecreateRenderStateContext Context;
}),
ECVF_Scalability | ECVF_RenderThreadSafe
);
int32 GLumenMeshCardsCullFaces = 1;
FAutoConsoleVariableRef CVarLumenMeshCardsCullFaces(
TEXT("r.LumenScene.SurfaceCache.MeshCardsCullFaces"),
GLumenMeshCardsCullFaces,
TEXT(""),
FConsoleVariableDelegate::CreateLambda([](IConsoleVariable* InVariable)
{
FGlobalComponentRecreateRenderStateContext Context;
}),
ECVF_Scalability | ECVF_RenderThreadSafe
);
int32 GLumenMeshCardsDebugSingleCard = -1;
FAutoConsoleVariableRef CVarLumenMeshCardsDebugSingleCard(
TEXT("r.LumenScene.SurfaceCache.MeshCardsDebugSingleCard"),
GLumenMeshCardsDebugSingleCard,
TEXT("Spawn only a specified card on mesh. Useful for debugging."),
FConsoleVariableDelegate::CreateLambda([](IConsoleVariable* InVariable)
{
FGlobalComponentRecreateRenderStateContext Context;
}),
ECVF_RenderThreadSafe
);
static TAutoConsoleVariable<float> CVarLumenSurfaceCacheHeightfieldCaptureMargin(
TEXT("r.Lumen.SurfaceCache.HeightfieldCaptureMargin"),
100.0f,
TEXT("Amount to expand heightfield component bbox for card capture purposes."),
ECVF_RenderThreadSafe
);
float LumenMeshCards::GetCardMinSurfaceArea(bool bEmissiveLightSource)
{
const float MeshCardsMinSize = CVarLumenMeshCardsMinSize.GetValueOnRenderThread();
return MeshCardsMinSize * MeshCardsMinSize * (bEmissiveLightSource ? 0.2f : 1.0f);
}
class FLumenCardGPUData
{
public:
// Must match usf
enum { DataStrideInFloat4s = 10 };
enum { DataStrideInBytes = DataStrideInFloat4s * sizeof(FVector4f) };
static void PackSurfaceMipMap(const FLumenCard& Card, int32 ResLevel, uint32& PackedSizeInPages, uint32& PackedPageTableOffset)
{
PackedSizeInPages = 0;
PackedPageTableOffset = 0;
if (Card.IsAllocated())
{
const FLumenSurfaceMipMap& MipMap = Card.GetMipMap(ResLevel);
if (MipMap.IsAllocated())
{
PackedSizeInPages = MipMap.SizeInPagesX | (MipMap.SizeInPagesY << 16);
PackedPageTableOffset = MipMap.PageTableSpanOffset;
}
}
}
static void FillData(const FLumenCard& RESTRICT Card, const FLumenPrimitiveGroup* InPrimitiveGroup, FVector4f* RESTRICT OutData)
{
// Note: layout must match GetLumenCardData in usf
const FDFVector3 WorldPosition(Card.WorldOBB.Origin);
OutData[0] = WorldPosition.High;
OutData[1] = FVector4f(Card.WorldOBB.AxisX[0], Card.WorldOBB.AxisY[0], Card.WorldOBB.AxisZ[0], WorldPosition.Low.X);
OutData[2] = FVector4f(Card.WorldOBB.AxisX[1], Card.WorldOBB.AxisY[1], Card.WorldOBB.AxisZ[1], WorldPosition.Low.Y);
OutData[3] = FVector4f(Card.WorldOBB.AxisX[2], Card.WorldOBB.AxisY[2], Card.WorldOBB.AxisZ[2], WorldPosition.Low.Z);
const FIntPoint ResLevelBias = Card.ResLevelToResLevelXYBias();
const uint32 LightingChannelMask = InPrimitiveGroup ? InPrimitiveGroup->LightingChannelMask : UINT32_MAX;
uint32 Packed3W = 0;
Packed3W = uint8(ResLevelBias.X) & 0xFF;
Packed3W |= (uint8(ResLevelBias.Y) & 0xFF) << 8;
Packed3W |= (uint8(Card.AxisAlignedDirectionIndex) & 0xF) << 16;
Packed3W |= (LightingChannelMask & 0xF) << 20;
Packed3W |= Card.bVisible && Card.IsAllocated() ? (1 << 24) : 0;
Packed3W |= Card.bHeightfield && Card.IsAllocated() ? (1 << 25) : 0;
OutData[4] = FVector4f(Card.WorldOBB.Extent.X, Card.WorldOBB.Extent.Y, Card.WorldOBB.Extent.Z, 0.0f);
OutData[4].W = *((float*)&Packed3W);
// Map low-res level for diffuse
uint32 PackedSizeInPages = 0;
uint32 PackedPageTableOffset = 0;
PackSurfaceMipMap(Card, Card.MinAllocatedResLevel, PackedSizeInPages, PackedPageTableOffset);
// Map hi-res for specular
uint32 PackedHiResSizeInPages = 0;
uint32 PackedHiResPageTableOffset = 0;
PackSurfaceMipMap(Card, Card.MaxAllocatedResLevel, PackedHiResSizeInPages, PackedHiResPageTableOffset);
OutData[5].X = *((float*)&PackedSizeInPages);
OutData[5].Y = *((float*)&PackedPageTableOffset);
OutData[5].Z = *((float*)&PackedHiResSizeInPages);
OutData[5].W = *((float*)&PackedHiResPageTableOffset);
float AverageTexelSize = 100.0f;
if (Card.IsAllocated())
{
FLumenMipMapDesc MipMapDesc;
Card.GetMipMapDesc(Card.MinAllocatedResLevel, MipMapDesc);
AverageTexelSize = 0.5f * (Card.MeshCardsOBB.Extent.X / MipMapDesc.Resolution.X + Card.MeshCardsOBB.Extent.Y / MipMapDesc.Resolution.Y);
}
OutData[6] = FVector4f(Card.MeshCardsOBB.AxisX[0], Card.MeshCardsOBB.AxisY[0], Card.MeshCardsOBB.AxisZ[0], Card.MeshCardsOBB.Origin.X);
OutData[7] = FVector4f(Card.MeshCardsOBB.AxisX[1], Card.MeshCardsOBB.AxisY[1], Card.MeshCardsOBB.AxisZ[1], Card.MeshCardsOBB.Origin.Y);
OutData[8] = FVector4f(Card.MeshCardsOBB.AxisX[2], Card.MeshCardsOBB.AxisY[2], Card.MeshCardsOBB.AxisZ[2], Card.MeshCardsOBB.Origin.Z);
OutData[9] = FVector4f(Card.MeshCardsOBB.Extent, AverageTexelSize);
static_assert(DataStrideInFloat4s == 10, "Data stride doesn't match");
}
};
struct FLumenMeshCardsGPUData
{
// Must match LUMEN_MESH_CARDS_DATA_STRIDE in LumenCardCommon.ush
enum { DataStrideInFloat4s = 6 };
enum { DataStrideInBytes = DataStrideInFloat4s * 16 };
static void FillData(const class FLumenMeshCards& RESTRICT MeshCards, FVector4f* RESTRICT OutData);
};
void FLumenMeshCardsGPUData::FillData(const FLumenMeshCards& RESTRICT MeshCards, FVector4f* RESTRICT OutData)
{
// Note: layout must match GetLumenMeshCardsData in usf
const FDFVector3 WorldOrigin(MeshCards.LocalToWorld.GetOrigin());
OutData[0] = WorldOrigin.High;
OutData[1] = FVector4f(FVector4(MeshCards.WorldToLocalRotation.GetScaledAxis(EAxis::X), WorldOrigin.Low.X));
OutData[2] = FVector4f(FVector4(MeshCards.WorldToLocalRotation.GetScaledAxis(EAxis::Y), WorldOrigin.Low.Y));
OutData[3] = FVector4f(FVector4(MeshCards.WorldToLocalRotation.GetScaledAxis(EAxis::Z), WorldOrigin.Low.Z));
uint32 PackedData[4];
PackedData[0] = MeshCards.FirstCardIndex;
PackedData[1] = MeshCards.NumCards & 0xFFFF;
PackedData[1] |= MeshCards.bHeightfield ? 0x10000 : 0;
PackedData[1] |= MeshCards.bMostlyTwoSided ? 0x20000 : 0;
PackedData[2] = MeshCards.CardLookup[0];
PackedData[3] = MeshCards.CardLookup[1];
OutData[4] = *(FVector4f*)&PackedData;
PackedData[0] = MeshCards.CardLookup[2];
PackedData[1] = MeshCards.CardLookup[3];
PackedData[2] = MeshCards.CardLookup[4];
PackedData[3] = MeshCards.CardLookup[5];
OutData[5] = *(FVector4f*)&PackedData;
static_assert(DataStrideInFloat4s == 6, "Data stride doesn't match");
}
struct FLumenPrimitiveGroupGPUData
{
// Must match LUMEN_PRIMITIVE_GROUP_DATA_STRIDE in LumenScene.usf
enum { DataStrideInFloat4s = 2 };
enum { DataStrideInBytes = DataStrideInFloat4s * 16 };
static void FillData(const class FLumenPrimitiveGroup& RESTRICT PrimitiveGroup, const FLumenPrimitiveGroupCullingInfo* RESTRICT CullingInfo, FVector4f* RESTRICT OutData);
};
void FLumenPrimitiveGroupGPUData::FillData(const FLumenPrimitiveGroup& RESTRICT PrimitiveGroup, const FLumenPrimitiveGroupCullingInfo* RESTRICT CullingInfo, FVector4f* RESTRICT OutData)
{
// Note: layout must match GetLumenPrimitiveGroupData in usf
if (CullingInfo)
{
OutData[0] = CullingInfo->WorldSpaceBoundingBox.GetCenter();
OutData[1] = CullingInfo->WorldSpaceBoundingBox.GetExtent();
}
else
{
OutData[0] = FVector4f::Zero();
OutData[1] = FVector4f::Zero();
}
uint32 MeshCardsIndex = PrimitiveGroup.MeshCardsIndex >= 0 ? PrimitiveGroup.MeshCardsIndex : UINT32_MAX;
OutData[0].W = *((float*) &MeshCardsIndex);
uint32 PackedFlags = 0;
PackedFlags |= PrimitiveGroup.bValidMeshCards ? 0x01 : 0;
PackedFlags |= PrimitiveGroup.bFarField ? 0x02 : 0;
PackedFlags |= PrimitiveGroup.bHeightfield ? 0x04 : 0;
PackedFlags |= PrimitiveGroup.bEmissiveLightSource ? 0x08 : 0;
OutData[1].W = *((float*) &PackedFlags);
static_assert(DataStrideInFloat4s == 2, "Data stride doesn't match");
}
void UpdateLumenMeshCards(FRDGBuilder& GraphBuilder, FRDGScatterUploadBuilder& UploadBuilder, const FScene& Scene, const FDistanceFieldSceneData& DistanceFieldSceneData, FLumenSceneFrameTemporaries& FrameTemporaries, FLumenSceneData& LumenSceneData)
{
LLM_SCOPE_BYTAG(Lumen);
QUICK_SCOPE_CYCLE_COUNTER(UpdateLumenMeshCards);
if (LumenSceneData.bReuploadSceneRequest)
{
LumenSceneData.HeightfieldIndicesToUpdateInBuffer.Reset();
for (int32 i = 0; i < LumenSceneData.Heightfields.Num(); ++i)
{
LumenSceneData.HeightfieldIndicesToUpdateInBuffer.Add(i);
}
LumenSceneData.MeshCardsIndicesToUpdateInBuffer.Reset();
for (int32 i = 0; i < LumenSceneData.MeshCards.Num(); ++i)
{
LumenSceneData.MeshCardsIndicesToUpdateInBuffer.Add(i);
}
LumenSceneData.PrimitiveGroupIndicesToUpdateInBuffer.Reset();
for (int32 i = 0; i < LumenSceneData.PrimitiveGroups.Num(); ++i)
{
LumenSceneData.PrimitiveGroupIndicesToUpdateInBuffer.Add(i);
}
}
// Upload primitive groups
{
QUICK_SCOPE_CYCLE_COUNTER(UpdatePrimitiveGroups);
const uint32 NumPrimitiveGroups = LumenSceneData.PrimitiveGroups.Num();
const uint32 PrimitiveGroupNumFloat4s = FMath::RoundUpToPowerOfTwo(NumPrimitiveGroups * FLumenPrimitiveGroupGPUData::DataStrideInFloat4s);
const uint32 PrimitiveGroupNumBytes = PrimitiveGroupNumFloat4s * sizeof(FVector4f);
FRDGBuffer* PrimitiveGroupBuffer = ResizeStructuredBufferIfNeeded(GraphBuilder, LumenSceneData.PrimitiveGroupBuffer, PrimitiveGroupNumBytes, TEXT("Lumen.PrimitiveGroup"));
FrameTemporaries.PrimitiveGroupBufferSRV = GraphBuilder.CreateSRV(PrimitiveGroupBuffer);
const int32 NumPrimitiveGroupUploads = LumenSceneData.PrimitiveGroupIndicesToUpdateInBuffer.Num();
if (NumPrimitiveGroupUploads > 0)
{
UploadBuilder.AddPass(
GraphBuilder,
LumenSceneData.PrimitiveGroupUploadBuffer,
PrimitiveGroupBuffer,
NumPrimitiveGroupUploads,
FLumenPrimitiveGroupGPUData::DataStrideInBytes,
TEXT("Lumen.PrimitiveGroupUpload"),
[&LumenSceneData] (FRDGScatterUploader& Uploader)
{
FLumenPrimitiveGroup NullPrimitiveGroup;
for (int32 Index : LumenSceneData.PrimitiveGroupIndicesToUpdateInBuffer)
{
if (Index < LumenSceneData.PrimitiveGroups.Num())
{
const FLumenPrimitiveGroup* PrimitiveGroup = &NullPrimitiveGroup;
const FLumenPrimitiveGroupCullingInfo* CullingInfo = nullptr;
if (LumenSceneData.PrimitiveGroups.IsAllocated(Index))
{
PrimitiveGroup = &LumenSceneData.PrimitiveGroups[Index];
CullingInfo = &LumenSceneData.GetPrimitiveGroupCullingInfo(*PrimitiveGroup);
}
FVector4f* Data = (FVector4f*)Uploader.Add_GetRef(Index);
FLumenPrimitiveGroupGPUData::FillData(*PrimitiveGroup, CullingInfo, Data);
}
}
});
}
}
// Upload MeshCards
{
QUICK_SCOPE_CYCLE_COUNTER(UpdateMeshCards);
const uint32 NumMeshCards = LumenSceneData.MeshCards.Num();
const uint32 MeshCardsNumFloat4s = FMath::RoundUpToPowerOfTwo(NumMeshCards * FLumenMeshCardsGPUData::DataStrideInFloat4s);
const uint32 MeshCardsNumBytes = MeshCardsNumFloat4s * sizeof(FVector4f);
FRDGBuffer* MeshCardsBuffer = ResizeStructuredBufferIfNeeded(GraphBuilder, LumenSceneData.MeshCardsBuffer, MeshCardsNumBytes, TEXT("Lumen.MeshCards"));
FrameTemporaries.MeshCardsBufferSRV = GraphBuilder.CreateSRV(MeshCardsBuffer);
const int32 NumMeshCardsUploads = LumenSceneData.MeshCardsIndicesToUpdateInBuffer.Num();
if (NumMeshCardsUploads > 0)
{
UploadBuilder.AddPass(
GraphBuilder,
LumenSceneData.MeshCardsUploadBuffer,
MeshCardsBuffer,
NumMeshCardsUploads,
FLumenMeshCardsGPUData::DataStrideInBytes,
TEXT("Lumen.MeshCardsUpload"),
[&LumenSceneData] (FRDGScatterUploader& Uploader)
{
FLumenMeshCards NullMeshCards;
for (int32 Index : LumenSceneData.MeshCardsIndicesToUpdateInBuffer)
{
if (Index < LumenSceneData.MeshCards.Num())
{
const FLumenMeshCards& MeshCards = LumenSceneData.MeshCards.IsAllocated(Index) ? LumenSceneData.MeshCards[Index] : NullMeshCards;
FVector4f* Data = (FVector4f*)Uploader.Add_GetRef(Index);
FLumenMeshCardsGPUData::FillData(MeshCards, Data);
}
}
});
}
}
// Upload Heightfields
{
QUICK_SCOPE_CYCLE_COUNTER(UpdateHeightfields);
const uint32 NumHeightfields = LumenSceneData.Heightfields.Num();
const uint32 HeightfieldsNumFloat4s = FMath::RoundUpToPowerOfTwo(NumHeightfields * FLumenHeightfieldGPUData::DataStrideInFloat4s);
const uint32 HeightfieldsNumBytes = HeightfieldsNumFloat4s * sizeof(FVector4f);
FRDGBuffer* HeightfieldBuffer = ResizeStructuredBufferIfNeeded(GraphBuilder, LumenSceneData.HeightfieldBuffer, HeightfieldsNumBytes, TEXT("Lumen.Heightfield"));
FrameTemporaries.HeightfieldBufferSRV = GraphBuilder.CreateSRV(HeightfieldBuffer);
const int32 NumHeightfieldsUploads = LumenSceneData.HeightfieldIndicesToUpdateInBuffer.Num();
if (NumHeightfieldsUploads > 0)
{
UploadBuilder.AddPass(
GraphBuilder,
LumenSceneData.HeightfieldUploadBuffer,
HeightfieldBuffer,
NumHeightfieldsUploads,
FLumenHeightfieldGPUData::DataStrideInBytes,
TEXT("Lumen.HeightfieldUpload"),
[&LumenSceneData] (FRDGScatterUploader& Uploader)
{
FLumenHeightfield NullHeightfield;
for (int32 Index : LumenSceneData.HeightfieldIndicesToUpdateInBuffer)
{
if (Index < LumenSceneData.Heightfields.Num())
{
const FLumenHeightfield& Heightfield = LumenSceneData.Heightfields.IsAllocated(Index) ? LumenSceneData.Heightfields[Index] : NullHeightfield;
FVector4f* Data = (FVector4f*)Uploader.Add_GetRef(Index);
FLumenHeightfieldGPUData::FillData(Heightfield, LumenSceneData.MeshCards, Data);
}
}
});
}
}
// Upload SceneInstanceIndexToMeshCardsIndexBuffer
{
QUICK_SCOPE_CYCLE_COUNTER(UpdateSceneInstanceIndexToMeshCardsIndexBuffer);
if (LumenSceneData.bReuploadSceneRequest)
{
LumenSceneData.PrimitivesToUpdateMeshCards.Reset();
for (int32 PrimitiveIndex = 0; PrimitiveIndex < Scene.Primitives.Num(); ++PrimitiveIndex)
{
LumenSceneData.PrimitivesToUpdateMeshCards.Add(PrimitiveIndex);
}
}
const int32 NumIndices = FMath::Max(FMath::RoundUpToPowerOfTwo(Scene.GPUScene.GetInstanceIdUpperBoundGPU()), 1024u);
const uint32 IndexSizeInBytes = GPixelFormats[PF_R32_UINT].BlockBytes;
const uint32 IndicesSizeInBytes = NumIndices * IndexSizeInBytes;
FRDGBuffer* SceneInstanceIndexToMeshCardsIndexBuffer = ResizeByteAddressBufferIfNeeded(GraphBuilder, LumenSceneData.SceneInstanceIndexToMeshCardsIndexBuffer, IndicesSizeInBytes, TEXT("Lumen.SceneInstanceIndexToMeshCardsIndexBuffer"));
FrameTemporaries.SceneInstanceIndexToMeshCardsIndexBufferSRV = GraphBuilder.CreateSRV(SceneInstanceIndexToMeshCardsIndexBuffer);
uint32 NumIndexUploads = 0;
for (int32 PrimitiveIndex : LumenSceneData.PrimitivesToUpdateMeshCards)
{
if (PrimitiveIndex < Scene.Primitives.Num())
{
const FPrimitiveSceneInfo* PrimitiveSceneInfo = Scene.Primitives[PrimitiveIndex];
NumIndexUploads += PrimitiveSceneInfo->GetNumInstanceSceneDataEntries();
}
}
if (NumIndexUploads > 0)
{
UploadBuilder.AddPass(
GraphBuilder,
LumenSceneData.SceneInstanceIndexToMeshCardsIndexUploadBuffer,
SceneInstanceIndexToMeshCardsIndexBuffer,
NumIndexUploads,
IndexSizeInBytes,
TEXT("Lumen.SceneInstanceIndexToMeshCardsIndexUploadBuffer"),
[&LumenSceneData, &Scene, NumIndices] (FRDGScatterUploader& Uploader)
{
for (int32 PrimitiveIndex : LumenSceneData.PrimitivesToUpdateMeshCards)
{
if (PrimitiveIndex < Scene.Primitives.Num())
{
const FPrimitiveSceneInfo* PrimitiveSceneInfo = Scene.Primitives[PrimitiveIndex];
const int32 NumInstances = PrimitiveSceneInfo->GetNumInstanceSceneDataEntries();
const int32 InstanceDataOffset = PrimitiveSceneInfo->GetInstanceSceneDataOffset();
for (int32 InstanceIndex = 0; InstanceIndex < NumInstances; ++InstanceIndex)
{
const int32 MeshCardsIndex = LumenSceneData.GetMeshCardsIndex(PrimitiveSceneInfo, InstanceIndex);
int32 DestIndex = InstanceDataOffset + InstanceIndex;
if (DestIndex < NumIndices)
{
Uploader.Add(DestIndex, &MeshCardsIndex);
}
}
}
}
});
}
}
}
void Lumen::UpdateCardSceneBuffer(FRDGBuilder& GraphBuilder, FRDGScatterUploadBuilder& UploadBuilder, FLumenSceneFrameTemporaries& FrameTemporaries, const FSceneViewFamily& ViewFamily, FScene* Scene)
{
LLM_SCOPE_BYTAG(Lumen);
TRACE_CPUPROFILER_EVENT_SCOPE(UpdateCardSceneBuffer);
QUICK_SCOPE_CYCLE_COUNTER(UpdateCardSceneBuffer);
RDG_EVENT_SCOPE(GraphBuilder, "UpdateCardSceneBuffer");
RDG_GPU_MASK_SCOPE(GraphBuilder, FRHIGPUMask::All());
FLumenSceneData& LumenSceneData = *Scene->GetLumenSceneData(*ViewFamily.Views[0]);
// CardBuffer
{
FRDGBuffer* CardBuffer = nullptr;
{
const int32 NumCardEntries = LumenSceneData.Cards.Num();
const uint32 CardSceneNumFloat4s = NumCardEntries * FLumenCardGPUData::DataStrideInFloat4s;
const uint32 CardSceneNumBytes = FMath::DivideAndRoundUp(CardSceneNumFloat4s, 16384u) * 16384 * sizeof(FVector4f);
CardBuffer = ResizeStructuredBufferIfNeeded(GraphBuilder, LumenSceneData.CardBuffer, FMath::RoundUpToPowerOfTwo(CardSceneNumFloat4s) * sizeof(FVector4f), TEXT("Lumen.Cards"));
FrameTemporaries.CardBufferSRV = GraphBuilder.CreateSRV(CardBuffer);
}
if (LumenSceneData.bReuploadSceneRequest)
{
LumenSceneData.CardIndicesToUpdateInBuffer.Reset();
for (int32 i = 0; i < LumenSceneData.Cards.Num(); i++)
{
LumenSceneData.CardIndicesToUpdateInBuffer.Add(i);
}
}
const int32 NumCardDataUploads = LumenSceneData.CardIndicesToUpdateInBuffer.Num();
if (NumCardDataUploads > 0)
{
UploadBuilder.AddPass(
GraphBuilder,
LumenSceneData.CardUploadBuffer,
CardBuffer,
NumCardDataUploads,
FLumenCardGPUData::DataStrideInBytes,
TEXT("Lumen.CardUploadBuffer"),
[&LumenSceneData] (FRDGScatterUploader& Uploader)
{
FLumenCard NullCard;
for (int32 Index : LumenSceneData.CardIndicesToUpdateInBuffer)
{
if (Index < LumenSceneData.Cards.Num())
{
const FLumenCard& Card = LumenSceneData.Cards.IsAllocated(Index) ? LumenSceneData.Cards[Index] : NullCard;
FLumenPrimitiveGroup* PrimitiveGroup = nullptr;
if (Card.MeshCardsIndex >= 0)
{
const FLumenMeshCards& MeshCardsInstance = LumenSceneData.MeshCards[Card.MeshCardsIndex];
if (MeshCardsInstance.PrimitiveGroupIndex >= 0)
{
PrimitiveGroup = &LumenSceneData.PrimitiveGroups[MeshCardsInstance.PrimitiveGroupIndex];
}
}
FVector4f* Data = (FVector4f*)Uploader.Add_GetRef(Index);
FLumenCardGPUData::FillData(Card, PrimitiveGroup, Data);
}
}
LumenSceneData.CardIndicesToUpdateInBuffer.Reset();
});
}
}
UpdateLumenMeshCards(GraphBuilder, UploadBuilder, *Scene, Scene->DistanceFieldSceneData, FrameTemporaries, LumenSceneData);
LumenSceneData.bReuploadSceneRequest = false;
}
int32 FLumenSceneData::GetMeshCardsIndex(const FPrimitiveSceneInfo* PrimitiveSceneInfo, int32 InstanceIndex) const
{
if (PrimitiveSceneInfo->LumenPrimitiveGroupIndices.Num() > 0)
{
const int32 IndexInArray = FMath::Min(InstanceIndex, PrimitiveSceneInfo->LumenPrimitiveGroupIndices.Num() - 1);
const int32 PrimitiveGroupIndex = PrimitiveSceneInfo->LumenPrimitiveGroupIndices[IndexInArray];
const FLumenPrimitiveGroup& PrimitiveGroup = PrimitiveGroups[PrimitiveGroupIndex];
return PrimitiveGroup.MeshCardsIndex;
}
return -1;
}
FLumenPrimitiveGroupCullingInfo& FLumenSceneData::GetPrimitiveGroupCullingInfo(const FLumenPrimitiveGroup& PrimitiveGroup, bool bForcePrimitiveLevel)
{
if (!bForcePrimitiveLevel && PrimitiveGroup.InstanceCullingInfoIndex >= 0)
{
check(PrimitiveGroup.PrimitiveInstanceIndex >= 0);
return InstanceCullingInfos[PrimitiveGroup.InstanceCullingInfoIndex];
}
else
{
return PrimitiveCullingInfos[PrimitiveGroup.PrimitiveCullingInfoIndex];
}
}
const FLumenPrimitiveGroupCullingInfo& FLumenSceneData::GetPrimitiveGroupCullingInfo(const FLumenPrimitiveGroup& PrimitiveGroup, bool bForcePrimitiveLevel) const
{
return const_cast<FLumenSceneData*>(this)->GetPrimitiveGroupCullingInfo(PrimitiveGroup, bForcePrimitiveLevel);
}
void FLumenSceneData::RemovePrimitiveGroupCullingInfo(FLumenPrimitiveGroup& PrimitiveGroup)
{
if (PrimitiveGroup.PrimitiveCullingInfoIndex >= 0)
{
if (PrimitiveCullingInfos.IsAllocated(PrimitiveGroup.PrimitiveCullingInfoIndex))
{
const FLumenPrimitiveGroupCullingInfo& PrimitiveCullingInfo = PrimitiveCullingInfos[PrimitiveGroup.PrimitiveCullingInfoIndex];
if (PrimitiveCullingInfo.NumInstances > 0)
{
check(PrimitiveGroup.PrimitiveInstanceIndex >= 0);
check(PrimitiveGroup.InstanceCullingInfoIndex == PrimitiveCullingInfo.InstanceCullingInfoOffset + PrimitiveGroup.PrimitiveInstanceIndex);
InstanceCullingInfos.RemoveSpan(PrimitiveCullingInfo.InstanceCullingInfoOffset, PrimitiveCullingInfo.NumInstances);
}
PrimitiveCullingInfos.RemoveAt(PrimitiveGroup.PrimitiveCullingInfoIndex);
}
PrimitiveGroup.PrimitiveCullingInfoIndex = INDEX_NONE;
if (PrimitiveGroup.InstanceCullingInfoIndex >= 0)
{
check(!InstanceCullingInfos.IsAllocated(PrimitiveGroup.InstanceCullingInfoIndex));
PrimitiveGroup.InstanceCullingInfoIndex = INDEX_NONE;
}
}
else
{
check(PrimitiveGroup.InstanceCullingInfoIndex == INDEX_NONE);
}
}
void FLumenSceneData::UpdatePrimitiveGroupCullingInfo(const FLumenPrimitiveGroup& PrimitiveGroup, const FRenderBounds& NewWorldBounds, bool bForcePrimitiveLevel)
{
check(!bForcePrimitiveLevel || PrimitiveGroup.InstanceCullingInfoIndex >= 0);
FLumenPrimitiveGroupCullingInfo& CullingInfo = GetPrimitiveGroupCullingInfo(PrimitiveGroup, bForcePrimitiveLevel);
CullingInfo.WorldSpaceBoundingBox = NewWorldBounds;
if (!bForcePrimitiveLevel)
{
CullingInfo.bVisible = PrimitiveGroup.MeshCardsIndex >= 0;
CullingInfo.bValidMeshCards = PrimitiveGroup.bValidMeshCards;
}
}
class FLumenMergedMeshCards
{
public:
FLumenMergedMeshCards()
{
MergedBounds.Init();
for (int32 AxisAlignedDirectionIndex = 0; AxisAlignedDirectionIndex < Lumen::NumAxisAlignedDirections; ++AxisAlignedDirectionIndex)
{
InstanceCardAreaPerDirection[AxisAlignedDirectionIndex] = 0;
}
}
void AddInstance(FBox InstanceBox, FMatrix InstanceToMerged, const FMeshCardsBuildData& MeshCardsBuildData)
{
MergedBounds += InstanceBox.TransformBy(InstanceToMerged);
for (const FLumenCardBuildData& CardBuildData : MeshCardsBuildData.CardBuildData)
{
const FVector3f AxisX = FVector4f(InstanceToMerged.TransformVector((FVector)CardBuildData.OBB.AxisX));
const FVector3f AxisY = FVector4f(InstanceToMerged.TransformVector((FVector)CardBuildData.OBB.AxisY));
const FVector3f AxisZ = FVector4f(InstanceToMerged.TransformVector((FVector)CardBuildData.OBB.AxisZ));
const FVector3f Extent = CardBuildData.OBB.Extent * FVector3f(AxisX.Length(), AxisY.Length(), AxisZ.Length());
const float InstanceCardArea = Extent.X * Extent.Y;
const FVector3f CardDirection = AxisZ.GetUnsafeNormal();
for (int32 AxisAlignedDirectionIndex = 0; AxisAlignedDirectionIndex < Lumen::NumAxisAlignedDirections; ++AxisAlignedDirectionIndex)
{
const FVector3f AxisDirection = MeshCardRepresentation::GetAxisAlignedDirection(AxisAlignedDirectionIndex);
const float AxisProjection = CardDirection.Dot(AxisDirection);
if (AxisProjection > 0.0f)
{
InstanceCardAreaPerDirection[AxisAlignedDirectionIndex] += AxisProjection * InstanceCardArea;
}
}
}
}
FBox MergedBounds;
float InstanceCardAreaPerDirection[Lumen::NumAxisAlignedDirections];
};
void BuildMeshCardsDataForHeightfield(const FLumenPrimitiveGroup& PrimitiveGroup, FMeshCardsBuildData& MeshCardsBuildData, FMatrix& MeshCardsLocalToWorld)
{
const FPrimitiveSceneProxy* Proxy = PrimitiveGroup.Primitives[0]->Proxy;
MeshCardsLocalToWorld = Proxy->GetLocalToWorld();
// Make sure that the card isn't placed directly on the geometry
const FVector BoundsMargin = FVector(CVarLumenSurfaceCacheHeightfieldCaptureMargin.GetValueOnRenderThread()) / MeshCardsLocalToWorld.GetScaleVector();
MeshCardsBuildData.Bounds = Proxy->GetLocalBounds().GetBox().ExpandBy(BoundsMargin);
// Add a single top down card
MeshCardsBuildData.CardBuildData.SetNum(1);
{
FLumenCardBuildData& CardBuildData = MeshCardsBuildData.CardBuildData[0];
// Set rotation
uint32 AxisAlignedDirectionIndex = 5;
CardBuildData.OBB.AxisZ = MeshCardRepresentation::GetAxisAlignedDirection(AxisAlignedDirectionIndex);
CardBuildData.OBB.AxisZ.FindBestAxisVectors(CardBuildData.OBB.AxisX, CardBuildData.OBB.AxisY);
CardBuildData.OBB.AxisX = FVector3f::CrossProduct(CardBuildData.OBB.AxisZ, CardBuildData.OBB.AxisY);
CardBuildData.OBB.AxisX.Normalize();
CardBuildData.OBB.Origin = (FVector3f)MeshCardsBuildData.Bounds.GetCenter();
CardBuildData.OBB.Extent = CardBuildData.OBB.RotateLocalToCard((FVector3f)MeshCardsBuildData.Bounds.GetExtent()).GetAbs();
CardBuildData.AxisAlignedDirectionIndex = AxisAlignedDirectionIndex;
}
}
void BuildMeshCardsDataForMergedInstances(const FLumenPrimitiveGroup& PrimitiveGroup, FMeshCardsBuildData& MeshCardsBuildData, FMatrix& MeshCardsLocalToWorld)
{
MeshCardsLocalToWorld.SetIdentity();
// Pick first largest bbox as a reference frame
float LargestInstanceArea = -1.0f;
for (const FPrimitiveSceneInfo* PrimitiveSceneInfo : PrimitiveGroup.Primitives)
{
const FMatrix& PrimitiveToWorld = PrimitiveSceneInfo->Proxy->GetLocalToWorld();
const FBoxSphereBounds& PrimitiveBounds = PrimitiveSceneInfo->Proxy->GetBounds();
float InstanceArea = BoxSurfaceArea(PrimitiveBounds.BoxExtent);
FMatrix InstanceMeshCardsLocalToWorld = PrimitiveToWorld;
if (const FInstanceSceneDataBuffers *InstanceSceneData = PrimitiveSceneInfo->GetInstanceSceneDataBuffers())
{
// Instance data must be available on CPU.
check(!InstanceSceneData->IsInstanceDataGPUOnly());
for (int32 InstanceIndex = 0; InstanceIndex < InstanceSceneData->GetNumInstances(); ++InstanceIndex)
{
InstanceArea = BoxSurfaceArea((FVector)InstanceSceneData->GetInstanceLocalBounds(InstanceIndex).GetExtent());
InstanceMeshCardsLocalToWorld = InstanceSceneData->GetInstanceToWorld(InstanceIndex);
}
}
if (InstanceArea > LargestInstanceArea)
{
MeshCardsLocalToWorld = InstanceMeshCardsLocalToWorld;
LargestInstanceArea = InstanceArea;
}
}
const FMatrix WorldToMeshCardsLocal = MeshCardsLocalToWorld.Inverse();
MeshCardsBuildData.Bounds.Init();
FLumenMergedMeshCards MergedMeshCards;
for (const FPrimitiveSceneInfo* PrimitiveSceneInfo : PrimitiveGroup.Primitives)
{
const FCardRepresentationData* CardRepresentationData = PrimitiveSceneInfo->Proxy->GetMeshCardRepresentation();
if (CardRepresentationData)
{
const FMatrix& PrimitiveToWorld = PrimitiveSceneInfo->Proxy->GetLocalToWorld();
const FMeshCardsBuildData& PrimitiveMeshCardsBuildData = CardRepresentationData->MeshCardsBuildData;
const FMatrix PrimitiveLocalToMeshCardsLocal = PrimitiveToWorld * WorldToMeshCardsLocal;
if (const FInstanceSceneDataBuffers *InstanceSceneData = PrimitiveSceneInfo->GetInstanceSceneDataBuffers())
{
// Instance data must be available on CPU.
check(!InstanceSceneData->IsInstanceDataGPUOnly());
for (int32 InstanceIndex = 0; InstanceIndex < InstanceSceneData->GetNumInstances(); ++InstanceIndex)
{
FMatrix InstanceToWorld = InstanceSceneData->GetInstanceToWorld(InstanceIndex);
MergedMeshCards.AddInstance(
InstanceSceneData->GetInstanceLocalBounds(InstanceIndex).ToBox(),
InstanceToWorld * WorldToMeshCardsLocal,
PrimitiveMeshCardsBuildData);
}
}
else
{
MergedMeshCards.AddInstance(
PrimitiveSceneInfo->Proxy->GetLocalBounds().GetBox(),
PrimitiveLocalToMeshCardsLocal,
PrimitiveMeshCardsBuildData);
}
}
}
// Spawn cards only on faces passing min area threshold
TArray<int32, TInlineAllocator<Lumen::NumAxisAlignedDirections>> AxisAlignedDirectionsToSpawnCards;
for (int32 AxisAlignedDirectionIndex = 0; AxisAlignedDirectionIndex < Lumen::NumAxisAlignedDirections; ++AxisAlignedDirectionIndex)
{
FVector3f MergedExtent = (FVector3f)MergedMeshCards.MergedBounds.GetExtent();
MergedExtent[AxisAlignedDirectionIndex / 2] = 1.0f;
const float MergedFaceArea = MergedExtent.X * MergedExtent.Y * MergedExtent.Z;
if (MergedMeshCards.InstanceCardAreaPerDirection[AxisAlignedDirectionIndex] > GLumenMeshCardsMergedCardMinSurfaceArea * MergedFaceArea)
{
AxisAlignedDirectionsToSpawnCards.Add(AxisAlignedDirectionIndex);
}
}
if (MergedMeshCards.MergedBounds.IsValid && AxisAlignedDirectionsToSpawnCards.Num() > 0)
{
// Make sure BBox isn't empty and we can generate card representation for it. This handles e.g. infinitely thin planes.
const FVector SafeCenter = MergedMeshCards.MergedBounds.GetCenter();
const FVector SafeExtent = FVector::Max(MergedMeshCards.MergedBounds.GetExtent() + 1.0f, FVector(5.0f));
const FBox SafeMergedBounds = FBox(SafeCenter - SafeExtent, SafeCenter + SafeExtent);
MeshCardsBuildData.Bounds = SafeMergedBounds;
MeshCardsBuildData.CardBuildData.SetNum(AxisAlignedDirectionsToSpawnCards.Num());
uint32 CardBuildDataIndex = 0;
for (int32 AxisAlignedDirectionIndex : AxisAlignedDirectionsToSpawnCards)
{
FLumenCardBuildData& CardBuildData = MeshCardsBuildData.CardBuildData[CardBuildDataIndex];
++CardBuildDataIndex;
// Set rotation
CardBuildData.OBB.AxisZ = MeshCardRepresentation::GetAxisAlignedDirection(AxisAlignedDirectionIndex);
CardBuildData.OBB.AxisZ.FindBestAxisVectors(CardBuildData.OBB.AxisX, CardBuildData.OBB.AxisY);
CardBuildData.OBB.AxisX = FVector3f::CrossProduct(CardBuildData.OBB.AxisZ, CardBuildData.OBB.AxisY);
CardBuildData.OBB.AxisX.Normalize();
CardBuildData.OBB.Origin = (FVector3f)SafeMergedBounds.GetCenter(); // LWC_TODO: Precision Loss
CardBuildData.OBB.Extent = CardBuildData.OBB.RotateLocalToCard((FVector3f)SafeMergedBounds.GetExtent() + FVector3f(1.0f)).GetAbs();
CardBuildData.AxisAlignedDirectionIndex = AxisAlignedDirectionIndex;
}
}
}
void FLumenSceneData::AddMeshCards(int32 PrimitiveGroupIndex)
{
FLumenPrimitiveGroup& PrimitiveGroup = PrimitiveGroups[PrimitiveGroupIndex];
if (PrimitiveGroup.MeshCardsIndex < 0)
{
if (PrimitiveGroup.bHeightfield)
{
// Landscape component handling
FMatrix LocalToWorld;
FMeshCardsBuildData MeshCardsBuildData;
BuildMeshCardsDataForHeightfield(PrimitiveGroup, MeshCardsBuildData, LocalToWorld);
AddMeshCardsFromBuildData(PrimitiveGroupIndex, LocalToWorld, MeshCardsBuildData, PrimitiveGroup);
}
else if (PrimitiveGroup.HasMergedInstances())
{
// Multiple meshes merged together
FMatrix LocalToWorld;
FMeshCardsBuildData MeshCardsBuildData;
BuildMeshCardsDataForMergedInstances(PrimitiveGroup, MeshCardsBuildData, LocalToWorld);
AddMeshCardsFromBuildData(PrimitiveGroupIndex, LocalToWorld, MeshCardsBuildData, PrimitiveGroup);
}
else
{
// Single mesh
ensure(PrimitiveGroup.Primitives.Num() == 1);
const FPrimitiveSceneInfo* PrimitiveSceneInfo = PrimitiveGroup.Primitives[0];
FMatrix LocalToWorld = PrimitiveSceneInfo->Proxy->GetLocalToWorld();
if (const FInstanceSceneDataBuffers *InstanceData = PrimitiveSceneInfo->GetInstanceSceneDataBuffers())
{
// Instance data must be available on CPU.
check(!InstanceData->IsInstanceDataGPUOnly());
const int32 PrimitiveInstanceIndex = FMath::Clamp(PrimitiveGroup.PrimitiveInstanceIndex, 0, InstanceData->GetNumInstances() - 1);
LocalToWorld = InstanceData->GetInstanceToWorld(PrimitiveInstanceIndex);
}
const FCardRepresentationData* CardRepresentationData = PrimitiveSceneInfo->Proxy->GetMeshCardRepresentation();
if (CardRepresentationData)
{
const FMeshCardsBuildData& MeshCardsBuildData = CardRepresentationData->MeshCardsBuildData;
AddMeshCardsFromBuildData(PrimitiveGroupIndex, LocalToWorld, MeshCardsBuildData, PrimitiveGroup);
}
}
FLumenPrimitiveGroupCullingInfo& CullingInfo = GetPrimitiveGroupCullingInfo(PrimitiveGroup);
check(!CullingInfo.bVisible && CullingInfo.bValidMeshCards);
if (PrimitiveGroup.MeshCardsIndex >= 0)
{
// Copy ScenePrimitive->GetIndex() in order to prevent from deferencing possibly deleted ScenePrimitive*
FLumenMeshCards& MeshCardsInstance = MeshCards[PrimitiveGroup.MeshCardsIndex];
MeshCardsInstance.ScenePrimitiveIndices.Reset();
MeshCardsInstance.ScenePrimitiveIndices.Reserve(PrimitiveGroup.Primitives.Num());
for (const FPrimitiveSceneInfo* ScenePrimitive : PrimitiveGroup.Primitives)
{
if (ScenePrimitive->IsIndexValid())
{
MeshCardsInstance.ScenePrimitiveIndices.Add(ScenePrimitive->GetIndex());
PrimitivesToUpdateMeshCards.Add(ScenePrimitive->GetIndex());
}
}
CullingInfo.bVisible = true;
}
else
{
// Can't spawn mesh cards, mark this primitive as invalid
PrimitiveGroup.bValidMeshCards = false;
PrimitiveGroupIndicesToUpdateInBuffer.Add(PrimitiveGroupIndex);
CullingInfo.bValidMeshCards = false;
}
}
}
bool IsMatrixOrthogonal(const FMatrix& Matrix)
{
const FVector MatrixScale = Matrix.GetScaleVector();
if (MatrixScale.GetAbsMin() >= KINDA_SMALL_NUMBER)
{
FVector AxisX;
FVector AxisY;
FVector AxisZ;
Matrix.GetUnitAxes(AxisX, AxisY, AxisZ);
return FMath::Abs(AxisX | AxisY) < KINDA_SMALL_NUMBER
&& FMath::Abs(AxisX | AxisZ) < KINDA_SMALL_NUMBER
&& FMath::Abs(AxisY | AxisZ) < KINDA_SMALL_NUMBER;
}
return false;
}
bool MeshCardCullTest(const FLumenCardBuildData& CardBuildData, const FVector3f LocalToWorldScale, float MinFaceSurfaceArea, int32 CardIndex)
{
#if UE_BUILD_DEBUG || UE_BUILD_DEVELOPMENT
if (GLumenMeshCardsDebugSingleCard >= 0)
{
return GLumenMeshCardsDebugSingleCard == CardIndex;
}
#endif
const FVector3f ScaledBoundsSize = 2.0f * CardBuildData.OBB.Extent * LocalToWorldScale;
const float SurfaceArea = ScaledBoundsSize.X * ScaledBoundsSize.Y;
const bool bCardPassedCulling = (!GLumenMeshCardsCullFaces || SurfaceArea > MinFaceSurfaceArea);
return bCardPassedCulling;
}
void FLumenSceneData::AddMeshCardsFromBuildData(int32 PrimitiveGroupIndex, const FMatrix& LocalToWorld, const FMeshCardsBuildData& MeshCardsBuildData, FLumenPrimitiveGroup& PrimitiveGroup)
{
PrimitiveGroup.MeshCardsIndex = -1;
PrimitiveGroup.HeightfieldIndex = -1;
const FVector3f LocalToWorldScale = (FVector3f)LocalToWorld.GetScaleVector();
const FVector3f ScaledBoundSize = (FVector3f)MeshCardsBuildData.Bounds.GetSize() * LocalToWorldScale;
const FVector3f FaceSurfaceArea(ScaledBoundSize.Y * ScaledBoundSize.Z, ScaledBoundSize.X * ScaledBoundSize.Z, ScaledBoundSize.Y * ScaledBoundSize.X);
const float LargestFaceArea = FaceSurfaceArea.GetMax();
const float MinFaceSurfaceArea = LumenMeshCards::GetCardMinSurfaceArea(PrimitiveGroup.bEmissiveLightSource);
if (LargestFaceArea > MinFaceSurfaceArea
&& IsMatrixOrthogonal(LocalToWorld)) // #lumen_todo: implement card capture for non orthogonal local to world transforms
{
const int32 NumBuildDataCards = MeshCardsBuildData.CardBuildData.Num();
uint32 NumCards = 0;
for (int32 CardIndexInBuildData = 0; CardIndexInBuildData < NumBuildDataCards; ++CardIndexInBuildData)
{
const FLumenCardBuildData& CardBuildData = MeshCardsBuildData.CardBuildData[CardIndexInBuildData];
if (MeshCardCullTest(CardBuildData, LocalToWorldScale, MinFaceSurfaceArea, CardIndexInBuildData))
{
++NumCards;
}
}
if (NumCards > 0)
{
const int32 FirstCardIndex = Cards.AddSpan(NumCards);
const int32 MeshCardsIndex = MeshCards.AddSpan(1);
PrimitiveGroup.MeshCardsIndex = MeshCardsIndex;
FLumenMeshCards& MeshCardsInstance = MeshCards[MeshCardsIndex];
MeshCardsInstance.Initialize(
LocalToWorld,
PrimitiveGroupIndex,
FirstCardIndex,
NumCards,
MeshCardsBuildData,
PrimitiveGroup);
MeshCardsIndicesToUpdateInBuffer.Add(MeshCardsIndex);
if (PrimitiveGroup.bHeightfield)
{
const int32 HeightfieldIndex = Heightfields.AddSpan(1);
PrimitiveGroup.HeightfieldIndex = HeightfieldIndex;
Heightfields[HeightfieldIndex].Initialize(MeshCardsIndex);
HeightfieldIndicesToUpdateInBuffer.Add(HeightfieldIndex);
}
// Add cards
int32 LocalCardIndex = 0;
for (int32 CardIndexInBuildData = 0; CardIndexInBuildData < NumBuildDataCards; ++CardIndexInBuildData)
{
const FLumenCardBuildData& CardBuildData = MeshCardsBuildData.CardBuildData[CardIndexInBuildData];
if (MeshCardCullTest(CardBuildData, LocalToWorldScale, MinFaceSurfaceArea, CardIndexInBuildData))
{
const int32 CardInsertIndex = FirstCardIndex + LocalCardIndex;
FLumenCard& Card = Cards[CardInsertIndex];
Card.Initialize(
PrimitiveGroup.CardResolutionScale,
PrimitiveGroup.CustomId,
LocalToWorld,
MeshCardsInstance,
CardBuildData,
LocalCardIndex,
MeshCardsIndex,
CardIndexInBuildData);
CardIndicesToUpdateInBuffer.Add(CardInsertIndex);
++LocalCardIndex;
}
}
MeshCardsInstance.UpdateLookup(Cards);
PrimitiveGroupIndicesToUpdateInBuffer.Add(PrimitiveGroupIndex);
}
}
}
void FLumenSceneData::RemoveMeshCards(int32 PrimitiveGroupIndex, bool bUpdateCullingInfo)
{
FLumenPrimitiveGroup& PrimitiveGroup = PrimitiveGroups[PrimitiveGroupIndex];
if (PrimitiveGroup.MeshCardsIndex >= 0)
{
FLumenMeshCards& MeshCardsInstance = MeshCards[PrimitiveGroup.MeshCardsIndex];
for (uint32 CardIndex = MeshCardsInstance.FirstCardIndex; CardIndex < MeshCardsInstance.FirstCardIndex + MeshCardsInstance.NumCards; ++CardIndex)
{
RemoveCardFromAtlas(CardIndex);
}
if (PrimitiveGroup.HeightfieldIndex >= 0)
{
Heightfields.RemoveSpan(PrimitiveGroup.HeightfieldIndex, 1);
HeightfieldIndicesToUpdateInBuffer.Add(PrimitiveGroup.HeightfieldIndex);
}
// Update surface cache mapping
for (int32 ScenePrimitiveIndex : MeshCardsInstance.ScenePrimitiveIndices)
{
PrimitivesToUpdateMeshCards.Add(ScenePrimitiveIndex);
}
MeshCardsInstance.ScenePrimitiveIndices.Reset();
Cards.RemoveSpan(MeshCardsInstance.FirstCardIndex, MeshCardsInstance.NumCards);
MeshCards.RemoveSpan(PrimitiveGroup.MeshCardsIndex, 1);
MeshCardsIndicesToUpdateInBuffer.Add(PrimitiveGroup.MeshCardsIndex);
PrimitiveGroup.MeshCardsIndex = -1;
PrimitiveGroup.HeightfieldIndex = -1;
PrimitiveGroupIndicesToUpdateInBuffer.Add(PrimitiveGroupIndex);
if (bUpdateCullingInfo)
{
FLumenPrimitiveGroupCullingInfo& CullingInfo = GetPrimitiveGroupCullingInfo(PrimitiveGroup);
check(CullingInfo.bVisible && CullingInfo.bValidMeshCards);
CullingInfo.bVisible = false;
}
}
}
void FLumenSceneData::UpdateMeshCards(const FMatrix& LocalToWorld, int32 MeshCardsIndex, const FMeshCardsBuildData& MeshCardsBuildData)
{
if (MeshCardsIndex >= 0 && IsMatrixOrthogonal(LocalToWorld))
{
FLumenMeshCards& MeshCardsInstance = MeshCards[MeshCardsIndex];
MeshCardsInstance.SetTransform(LocalToWorld);
MeshCardsIndicesToUpdateInBuffer.Add(MeshCardsIndex);
for (uint32 LocalCardIndex = 0; LocalCardIndex < MeshCardsInstance.NumCards; ++LocalCardIndex)
{
const uint32 CardIndex = MeshCardsInstance.FirstCardIndex + LocalCardIndex;
FLumenCard& Card = Cards[CardIndex];
Card.SetTransform(LocalToWorld, MeshCardsInstance);
CardIndicesToUpdateInBuffer.Add(CardIndex);
}
}
}
void FLumenSceneData::InvalidateSurfaceCache(FRHIGPUMask GPUMask, int32 MeshCardsIndex)
{
if (MeshCardsIndex >= 0)
{
FLumenMeshCards& MeshCardsInstance = MeshCards[MeshCardsIndex];
for (uint32 CardIndex = MeshCardsInstance.FirstCardIndex; CardIndex < MeshCardsInstance.FirstCardIndex + MeshCardsInstance.NumCards; ++CardIndex)
{
const FLumenCard& LumenCard = Cards[CardIndex];
for (int32 ResLevel = LumenCard.MinAllocatedResLevel; ResLevel <= LumenCard.MaxAllocatedResLevel; ++ResLevel)
{
const FLumenSurfaceMipMap& MipMap = LumenCard.GetMipMap(ResLevel);
if (MipMap.IsAllocated())
{
for (int32 LocalPageIndex = 0; LocalPageIndex < MipMap.SizeInPagesX * MipMap.SizeInPagesY; ++LocalPageIndex)
{
const int32 PageIndex = MipMap.GetPageTableIndex(LocalPageIndex);
if (GetPageTableEntry(PageIndex).IsMapped())
{
for (uint32 GPUIndex : GPUMask)
{
if (PagesToRecaptureHeap[GPUIndex].IsPresent(PageIndex))
{
PagesToRecaptureHeap[GPUIndex].Update(GetSurfaceCacheUpdateFrameIndex(), PageIndex);
}
else
{
PagesToRecaptureHeap[GPUIndex].Add(GetSurfaceCacheUpdateFrameIndex(), PageIndex);
}
}
}
}
}
}
}
}
}
void FLumenSceneData::RemoveCardFromAtlas(int32 CardIndex)
{
FLumenCard& Card = Cards[CardIndex];
Card.DesiredLockedResLevel = 0;
FreeVirtualSurface(Card, Card.MinAllocatedResLevel, Card.MaxAllocatedResLevel);
CardIndicesToUpdateInBuffer.Add(CardIndex);
}
FLumenCard::FLumenCard()
{
bVisible = false;
LocalOBB.Reset();
WorldOBB.Reset();
MeshCardsOBB.Reset();
IndexInMeshCards = -1;
}
FLumenCard::~FLumenCard()
{
for (int32 MipIndex = 0; MipIndex < UE_ARRAY_COUNT(SurfaceMipMaps); ++MipIndex)
{
ensure(SurfaceMipMaps[MipIndex].PageTableSpanSize == 0);
}
}
void FLumenCard::Initialize(
float InResolutionScale,
uint32 CustomId,
const FMatrix& LocalToWorld,
const FLumenMeshCards& InMeshCardsInstance,
const FLumenCardBuildData& CardBuildData,
int32 InIndexInMeshCards,
int32 InMeshCardsIndex,
uint8 InIndexInBuildData)
{
check(CardBuildData.AxisAlignedDirectionIndex < Lumen::NumAxisAlignedDirections);
LocalOBB = CardBuildData.OBB;
IndexInMeshCards = InIndexInMeshCards;
MeshCardsIndex = InMeshCardsIndex;
IndexInBuildData = InIndexInBuildData;
ResolutionScale = InResolutionScale;
AxisAlignedDirectionIndex = CardBuildData.AxisAlignedDirectionIndex;
bHeightfield = InMeshCardsInstance.bHeightfield;
DilationMode = CardBuildData.DilationMode;
SetTransform(LocalToWorld, InMeshCardsInstance);
CardAspect = WorldOBB.Extent.X / WorldOBB.Extent.Y;
if (CustomId != UINT32_MAX)
{
const FIntPoint& ResLevelXYBias = ResLevelToResLevelXYBias();
CardSharingId = FLumenCardId(CustomId, AxisAlignedDirectionIndex, ResLevelXYBias.X, ResLevelXYBias.Y);
}
}
void FLumenCard::SetTransform(const FMatrix& LocalToWorld, const FLumenMeshCards& MeshCards)
{
WorldOBB = FLumenCardOBBd(LocalOBB).Transform(LocalToWorld, &bAxisXFlipped);
MeshCardsOBB.AxisX = FVector4f(MeshCards.WorldToLocalRotation.TransformVector(FVector(WorldOBB.AxisX)));
MeshCardsOBB.AxisY = FVector4f(MeshCards.WorldToLocalRotation.TransformVector(FVector(WorldOBB.AxisY)));
MeshCardsOBB.AxisZ = FVector4f(MeshCards.WorldToLocalRotation.TransformVector(FVector(WorldOBB.AxisZ)));
MeshCardsOBB.Origin = LocalOBB.Origin * MeshCards.LocalToWorldScale;
MeshCardsOBB.Extent = LocalOBB.RotateCardToLocal(LocalOBB.Extent).GetAbs() * MeshCards.LocalToWorldScale;
}
void FLumenCard::UpdateMinMaxAllocatedLevel()
{
MinAllocatedResLevel = UINT8_MAX;
MaxAllocatedResLevel = 0;
for (int32 ResLevelIndex = Lumen::MinResLevel; ResLevelIndex <= Lumen::MaxResLevel; ++ResLevelIndex)
{
if (GetMipMap(ResLevelIndex).IsAllocated())
{
MinAllocatedResLevel = FMath::Min<int32>(MinAllocatedResLevel, ResLevelIndex);
MaxAllocatedResLevel = FMath::Max<int32>(MaxAllocatedResLevel, ResLevelIndex);
}
}
}
FIntPoint FLumenCard::ResLevelToResLevelXYBias() const
{
FIntPoint Bias(0, 0);
// ResLevel bias to account for card's aspect
if (CardAspect >= 1.0f)
{
Bias.Y = FMath::FloorLog2(FMath::RoundToInt(CardAspect));
}
else
{
Bias.X = FMath::FloorLog2(FMath::RoundToInt(1.0f / CardAspect));
}
Bias.X = FMath::Clamp<int32>(Bias.X, 0, Lumen::MaxResLevel - Lumen::MinResLevel);
Bias.Y = FMath::Clamp<int32>(Bias.Y, 0, Lumen::MaxResLevel - Lumen::MinResLevel);
return Bias;
}
void FLumenCard::GetMipMapDesc(int32 ResLevel, FLumenMipMapDesc& Desc) const
{
check(ResLevel >= Lumen::MinResLevel && ResLevel <= Lumen::MaxResLevel);
const FIntPoint ResLevelBias = ResLevelToResLevelXYBias();
Desc.ResLevelX = FMath::Clamp<int32>(ResLevel - ResLevelBias.X, (int32)Lumen::MinResLevel, (int32)Lumen::MaxResLevel);
Desc.ResLevelY = FMath::Clamp<int32>(ResLevel - ResLevelBias.Y, (int32)Lumen::MinResLevel, (int32)Lumen::MaxResLevel);
// Allocations which exceed a physical page are aligned to multiples of a virtual page to maximize atlas usage
if (Desc.ResLevelX > Lumen::SubAllocationResLevel || Desc.ResLevelY > Lumen::SubAllocationResLevel)
{
// Clamp res level to page size
Desc.ResLevelX = FMath::Max<int32>(Desc.ResLevelX, Lumen::SubAllocationResLevel);
Desc.ResLevelY = FMath::Max<int32>(Desc.ResLevelY, Lumen::SubAllocationResLevel);
Desc.bSubAllocation = false;
Desc.SizeInPages.X = 1u << (Desc.ResLevelX - Lumen::SubAllocationResLevel);
Desc.SizeInPages.Y = 1u << (Desc.ResLevelY - Lumen::SubAllocationResLevel);
Desc.Resolution.X = Desc.SizeInPages.X * Lumen::VirtualPageSize;
Desc.Resolution.Y = Desc.SizeInPages.Y * Lumen::VirtualPageSize;
Desc.PageResolution.X = Lumen::PhysicalPageSize;
Desc.PageResolution.Y = Lumen::PhysicalPageSize;
}
else
{
Desc.bSubAllocation = true;
Desc.SizeInPages.X = 1;
Desc.SizeInPages.Y = 1;
Desc.Resolution.X = 1 << Desc.ResLevelX;
Desc.Resolution.Y = 1 << Desc.ResLevelY;
Desc.PageResolution.X = Desc.Resolution.X;
Desc.PageResolution.Y = Desc.Resolution.Y;
}
}
void FLumenCard::GetSurfaceStats(const TSparseSpanArray<FLumenPageTableEntry>& PageTable, FSurfaceStats& Stats) const
{
if (IsAllocated())
{
for (int32 ResLevelIndex = MinAllocatedResLevel; ResLevelIndex <= MaxAllocatedResLevel; ++ResLevelIndex)
{
const FLumenSurfaceMipMap& MipMap = GetMipMap(ResLevelIndex);
if (MipMap.IsAllocated())
{
uint32 NumVirtualTexels = 0;
uint32 NumPhysicalTexels = 0;
for (int32 LocalPageIndex = 0; LocalPageIndex < MipMap.SizeInPagesX * MipMap.SizeInPagesY; ++LocalPageIndex)
{
const int32 PageTableIndex = MipMap.GetPageTableIndex(LocalPageIndex);
const FLumenPageTableEntry& PageTableEntry = PageTable[PageTableIndex];
NumVirtualTexels += PageTableEntry.GetNumVirtualTexels();
NumPhysicalTexels += PageTableEntry.GetNumPhysicalTexels();
}
Stats.NumVirtualTexels += NumVirtualTexels;
Stats.NumPhysicalTexels += NumPhysicalTexels;
if (MipMap.bLocked)
{
Stats.NumLockedVirtualTexels += NumVirtualTexels;
Stats.NumLockedPhysicalTexels += NumPhysicalTexels;
}
}
}
if (DesiredLockedResLevel > MinAllocatedResLevel)
{
Stats.DroppedResLevels += DesiredLockedResLevel - MinAllocatedResLevel;
}
}
}
void FLumenMeshCards::Initialize(
const FMatrix& InLocalToWorld,
int32 InPrimitiveGroupIndex,
uint32 InFirstCardIndex,
uint32 InNumCards,
const FMeshCardsBuildData& MeshCardsBuildData,
const FLumenPrimitiveGroup& PrimitiveGroup)
{
PrimitiveGroupIndex = InPrimitiveGroupIndex;
LocalBounds = MeshCardsBuildData.Bounds;
bMostlyTwoSided = MeshCardsBuildData.bMostlyTwoSided;
FirstCardIndex = InFirstCardIndex;
NumCards = InNumCards;
bFarField = PrimitiveGroup.bFarField;
bHeightfield = PrimitiveGroup.bHeightfield;
bEmissiveLightSource = PrimitiveGroup.bEmissiveLightSource;
SetTransform(InLocalToWorld);
}
void FLumenMeshCards::UpdateLookup(const TSparseSpanArray<FLumenCard>& Cards)
{
for (int32 AxisAlignedDirectionIndex = 0; AxisAlignedDirectionIndex < Lumen::NumAxisAlignedDirections; ++AxisAlignedDirectionIndex)
{
CardLookup[AxisAlignedDirectionIndex] = 0;
}
for (uint32 LocalCardIndex = 0; LocalCardIndex < NumCards; ++LocalCardIndex)
{
const uint32 CardIndex = FirstCardIndex + LocalCardIndex;
const FLumenCard& Card = Cards[CardIndex];
const uint32 BitMask = (1 << LocalCardIndex);
CardLookup[Card.AxisAlignedDirectionIndex] |= BitMask;
}
}
void FLumenMeshCards::SetTransform(const FMatrix& InLocalToWorld)
{
LocalToWorld = InLocalToWorld;
LocalToWorldScale = FVector3f(LocalToWorld.GetScaleVector());
WorldToLocalRotation = LocalToWorld;
WorldToLocalRotation.RemoveScaling();
WorldToLocalRotation.SetOrigin(FVector::ZeroVector);
WorldToLocalRotation = WorldToLocalRotation.GetTransposed();
}