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

1805 lines
75 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
DistanceFieldStreaming.cpp
=============================================================================*/
#include "CoreMinimal.h"
#include "DataDrivenShaderPlatformInfo.h"
#include "Stats/Stats.h"
#include "HAL/IConsoleManager.h"
#include "IO/IoDispatcher.h"
#include "Async/ParallelFor.h"
#include "RHI.h"
#include "RenderResource.h"
#include "ShaderParameters.h"
#include "RendererInterface.h"
#include "Shader.h"
#include "SceneUtils.h"
#include "GlobalShader.h"
#include "DeferredShadingRenderer.h"
#include "ScenePrivate.h"
#include "DistanceFieldAtlas.h"
#include "DistanceFieldLightingShared.h"
#include "DistanceFieldAmbientOcclusion.h"
#include "GlobalDistanceField.h"
#include "ProfilingDebugging/IoStoreTrace.h"
CSV_DEFINE_CATEGORY(DistanceField, true);
extern int32 GDFReverseAtlasAllocationOrder;
static TAutoConsoleVariable<int32> CVarBrickAtlasSizeXYInBricks(
TEXT("r.DistanceFields.BrickAtlasSizeXYInBricks"),
128,
TEXT("Controls the allocation granularity of the atlas, which grows in Z."),
ECVF_RenderThreadSafe | ECVF_ReadOnly);
static TAutoConsoleVariable<int32> CVarMaxAtlasDepthInBricks(
TEXT("r.DistanceFields.BrickAtlasMaxSizeZ"),
32,
TEXT("Target for maximum depth of the Mesh Distance Field atlas, in 8^3 bricks. 32 => 128 * 128 * 32 * 8^3 = 256Mb. Actual atlas size can go over since mip2 is always loaded."),
ECVF_RenderThreadSafe);
static TAutoConsoleVariable<int32> CVarTextureUploadLimitKBytes(
TEXT("r.DistanceFields.TextureUploadLimitKBytes"),
8192,
TEXT("Max KB of distance field texture data to upload per frame from streaming requests."),
ECVF_RenderThreadSafe);
int32 GDistanceFieldOffsetDataStructure = 0;
static FAutoConsoleVariableRef CVarShadowOffsetDataStructure(
TEXT("r.DistanceFields.OffsetDataStructure"),
GDistanceFieldOffsetDataStructure,
TEXT("Which data structure to store offset in, 0 - base, 1 - buffer, 2 - texture"),
ECVF_RenderThreadSafe | ECVF_ReadOnly);
static int32 MinIndirectionAtlasSizeXYZ = 64;
static FAutoConsoleVariableRef CVarMinIndirectionAtlasSizeXYZ(
TEXT("r.DistanceFields.MinIndirectionAtlasSizeXYZ"),
MinIndirectionAtlasSizeXYZ,
TEXT("Minimum size of indirection atlas texture"),
ECVF_RenderThreadSafe | ECVF_ReadOnly);
static TAutoConsoleVariable<int32> CVarMaxIndirectionAtlasSizeXYZ(
TEXT("r.DistanceFields.MaxIndirectionAtlasSizeXYZ"),
512,
TEXT("Maximum size of indirection atlas texture"),
ECVF_RenderThreadSafe | ECVF_ReadOnly);
static TAutoConsoleVariable<int32> CVarDefragmentIndirectionAtlas(
TEXT("r.DistanceFields.DefragmentIndirectionAtlas"),
1,
TEXT("Whether to defragment the Distance Field indirection atlas when it requires resizing."),
ECVF_RenderThreadSafe);
static TAutoConsoleVariable<int32> CVarResizeAtlasEveryFrame(
TEXT("r.DistanceFields.Debug.ResizeAtlasEveryFrame"),
0,
TEXT("Whether to resize the Distance Field atlas every frame, which is useful for debugging."),
ECVF_RenderThreadSafe);
static TAutoConsoleVariable<int32> CVarDebugForceNumMips(
TEXT("r.DistanceFields.Debug.ForceNumMips"),
0,
TEXT("When set to > 0, overrides the requested number of mips for streaming. 1 = only lowest resolution mip loaded, 3 = all mips loaded. Mips will still be clamped by available space in the atlas."),
ECVF_RenderThreadSafe);
static int32 GDistanceFieldAtlasLogStats = 0;
static FAutoConsoleVariableRef CVarDistanceFieldAtlasLogStats(
TEXT("r.DistanceFields.LogAtlasStats"),
GDistanceFieldAtlasLogStats,
TEXT("Set to 1 to dump atlas stats, set to 2 to dump atlas and SDF asset stats."),
ECVF_RenderThreadSafe
);
static int32 GDistanceFieldBlockAllocatorSizeInBricks = 16;
static FAutoConsoleVariableRef CVarDistanceFieldBlockAllocatorSizeInBricks(
TEXT("r.DistanceFields.BlockAllocatorSizeInBricks"),
GDistanceFieldBlockAllocatorSizeInBricks,
TEXT("Allocation granularity of the distance field block allocator. Higher number may cause more memory wasted on padding but allocation may be faster."),
ECVF_RenderThreadSafe | ECVF_ReadOnly);
static const int32 MaxStreamingRequests = 4095;
static const float IndirectionAtlasGrowMult = 2.0f;
class FCopyDistanceFieldAtlasCS : public FGlobalShader
{
DECLARE_GLOBAL_SHADER(FCopyDistanceFieldAtlasCS)
SHADER_USE_PARAMETER_STRUCT(FCopyDistanceFieldAtlasCS, FGlobalShader)
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture3D<UNORM float>, RWDistanceFieldBrickAtlas)
SHADER_PARAMETER_STRUCT_INCLUDE(FDistanceFieldAtlasParameters, DistanceFieldAtlas)
END_SHADER_PARAMETER_STRUCT()
using FPermutationDomain = TShaderPermutationDomain<>;
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
return DoesPlatformSupportDistanceFields(Parameters.Platform);
}
static int32 GetGroupSize()
{
return 4;
}
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
OutEnvironment.SetDefine(TEXT("THREADGROUP_SIZE"), GetGroupSize());
}
};
IMPLEMENT_GLOBAL_SHADER(FCopyDistanceFieldAtlasCS, "/Engine/Private/DistanceFieldStreaming.usf", "CopyDistanceFieldAtlasCS", SF_Compute);
class FScatterUploadDistanceFieldAtlasCS : public FGlobalShader
{
DECLARE_GLOBAL_SHADER(FScatterUploadDistanceFieldAtlasCS)
SHADER_USE_PARAMETER_STRUCT(FScatterUploadDistanceFieldAtlasCS, FGlobalShader)
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture3D<UNORM float>, RWDistanceFieldBrickAtlas)
SHADER_PARAMETER_SRV(Buffer<uint3>, BrickUploadCoordinates)
SHADER_PARAMETER_SRV(Buffer<float>, BrickUploadData)
SHADER_PARAMETER(uint32, StartBrickIndex)
SHADER_PARAMETER(uint32, NumBrickUploads)
SHADER_PARAMETER(uint32, BrickSize)
END_SHADER_PARAMETER_STRUCT()
using FPermutationDomain = TShaderPermutationDomain<>;
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
return DoesPlatformSupportDistanceFields(Parameters.Platform);
}
static int32 GetGroupSize()
{
return 4;
}
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
OutEnvironment.SetDefine(TEXT("THREADGROUP_SIZE"), GetGroupSize());
}
};
IMPLEMENT_GLOBAL_SHADER(FScatterUploadDistanceFieldAtlasCS, "/Engine/Private/DistanceFieldStreaming.usf", "ScatterUploadDistanceFieldAtlasCS", SF_Compute);
class FScatterUploadDistanceFieldIndirectionAtlasCS : public FGlobalShader
{
DECLARE_GLOBAL_SHADER(FScatterUploadDistanceFieldIndirectionAtlasCS)
SHADER_USE_PARAMETER_STRUCT(FScatterUploadDistanceFieldIndirectionAtlasCS, FGlobalShader)
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture3D<float4>, RWIndirectionAtlas)
SHADER_PARAMETER_SRV(Buffer<uint>, IndirectionUploadIndices)
SHADER_PARAMETER_SRV(Buffer<float4>, IndirectionUploadData)
SHADER_PARAMETER(FIntVector, IndirectionAtlasSize)
SHADER_PARAMETER(uint32, NumIndirectionUploads)
END_SHADER_PARAMETER_STRUCT()
using FPermutationDomain = TShaderPermutationDomain<>;
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
return DoesPlatformSupportDistanceFields(Parameters.Platform);
}
static int32 GetGroupSize()
{
return 64;
}
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
OutEnvironment.SetDefine(TEXT("THREADGROUP_SIZE"), GetGroupSize());
}
};
IMPLEMENT_GLOBAL_SHADER(FScatterUploadDistanceFieldIndirectionAtlasCS, "/Engine/Private/DistanceFieldStreaming.usf", "ScatterUploadDistanceFieldIndirectionAtlasCS", SF_Compute);
class FCopyDistanceFieldIndirectionAtlasCS : public FGlobalShader
{
DECLARE_GLOBAL_SHADER(FCopyDistanceFieldIndirectionAtlasCS)
SHADER_USE_PARAMETER_STRUCT(FCopyDistanceFieldIndirectionAtlasCS, FGlobalShader)
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_RDG_TEXTURE_UAV(RWTexture3D<float4>, RWIndirectionAtlas)
SHADER_PARAMETER_RDG_TEXTURE(Texture3D<float4>, DistanceFieldIndirectionAtlas)
SHADER_PARAMETER(FIntVector, IndirectionDimensions)
SHADER_PARAMETER(FIntVector, SrcPosition)
SHADER_PARAMETER(FIntVector, DstPosition)
SHADER_PARAMETER(uint32, NumAssets)
END_SHADER_PARAMETER_STRUCT()
using FPermutationDomain = TShaderPermutationDomain<>;
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
return DoesPlatformSupportDistanceFields(Parameters.Platform);
}
static int32 GetGroupSize()
{
return 64;
}
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
OutEnvironment.SetDefine(TEXT("THREADGROUP_SIZE"), GetGroupSize());
}
};
IMPLEMENT_GLOBAL_SHADER(FCopyDistanceFieldIndirectionAtlasCS, "/Engine/Private/DistanceFieldStreaming.usf", "CopyDistanceFieldIndirectionAtlasCS", SF_Compute);
class FComputeDistanceFieldAssetWantedMipsCS : public FGlobalShader
{
DECLARE_GLOBAL_SHADER(FComputeDistanceFieldAssetWantedMipsCS)
SHADER_USE_PARAMETER_STRUCT(FComputeDistanceFieldAssetWantedMipsCS, FGlobalShader)
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer<uint>, RWDistanceFieldAssetWantedNumMips)
SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer<uint>, RWDistanceFieldAssetStreamingRequests)
SHADER_PARAMETER_STRUCT_INCLUDE(FDistanceFieldObjectBufferParameters, DistanceFieldObjectBuffers)
SHADER_PARAMETER(int32, DebugForceNumMips)
SHADER_PARAMETER(FVector3f, Mip1WorldTranslatedCenter)
SHADER_PARAMETER(FVector3f, Mip1WorldExtent)
SHADER_PARAMETER(FVector3f, Mip2WorldTranslatedCenter)
SHADER_PARAMETER(FVector3f, Mip2WorldExtent)
SHADER_PARAMETER(FVector3f, PreViewTranslationHigh)
SHADER_PARAMETER(FVector3f, PreViewTranslationLow)
END_SHADER_PARAMETER_STRUCT()
using FPermutationDomain = TShaderPermutationDomain<>;
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
return DoesPlatformSupportDistanceFields(Parameters.Platform);
}
static int32 GetGroupSize()
{
return 64;
}
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
OutEnvironment.SetDefine(TEXT("THREADGROUP_SIZE"), GetGroupSize());
}
};
IMPLEMENT_GLOBAL_SHADER(FComputeDistanceFieldAssetWantedMipsCS, "/Engine/Private/DistanceFieldStreaming.usf", "ComputeDistanceFieldAssetWantedMipsCS", SF_Compute);
class FGenerateDistanceFieldAssetStreamingRequestsCS : public FGlobalShader
{
DECLARE_GLOBAL_SHADER(FGenerateDistanceFieldAssetStreamingRequestsCS)
SHADER_USE_PARAMETER_STRUCT(FGenerateDistanceFieldAssetStreamingRequestsCS, FGlobalShader)
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_RDG_BUFFER_UAV(RWStructuredBuffer<uint>, RWDistanceFieldAssetStreamingRequests)
SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer<uint>, DistanceFieldAssetWantedNumMips)
SHADER_PARAMETER_STRUCT_INCLUDE(FDistanceFieldObjectBufferParameters, DistanceFieldObjectBuffers)
SHADER_PARAMETER_STRUCT_INCLUDE(FDistanceFieldAtlasParameters, DistanceFieldAtlasParameters)
SHADER_PARAMETER(uint32, NumDistanceFieldAssets)
SHADER_PARAMETER(uint32, MaxNumStreamingRequests)
END_SHADER_PARAMETER_STRUCT()
using FPermutationDomain = TShaderPermutationDomain<>;
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
return DoesPlatformSupportDistanceFields(Parameters.Platform);
}
static int32 GetGroupSize()
{
return 64;
}
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
OutEnvironment.SetDefine(TEXT("THREADGROUP_SIZE"), GetGroupSize());
}
};
IMPLEMENT_GLOBAL_SHADER(FGenerateDistanceFieldAssetStreamingRequestsCS, "/Engine/Private/DistanceFieldStreaming.usf", "GenerateDistanceFieldAssetStreamingRequestsCS", SF_Compute);
const int32 AssetDataMipStrideFloat4s = 3;
FIntVector GetBrickCoordinate(int32 BrickIndex, FIntVector BrickAtlasSize)
{
return FIntVector(
BrickIndex % BrickAtlasSize.X,
(BrickIndex / BrickAtlasSize.X) % BrickAtlasSize.Y,
BrickIndex / (BrickAtlasSize.X * BrickAtlasSize.Y));
}
class FDistanceFieldAtlasUpload
{
public:
FReadBuffer& BrickUploadCoordinatesBuffer;
FReadBuffer& BrickUploadDataBuffer;
FIntVector4* BrickUploadCoordinatesPtr = nullptr;
uint8* BrickUploadDataPtr = nullptr;
uint32 NumBrickUploadCoordinatesBytes = 0;
uint32 NumBrickUploadDataBytes = 0;
FDistanceFieldAtlasUpload(
FReadBuffer& InBrickUploadCoordinatesBuffer,
FReadBuffer& InBrickUploadDataBuffer) :
BrickUploadCoordinatesBuffer(InBrickUploadCoordinatesBuffer),
BrickUploadDataBuffer(InBrickUploadDataBuffer)
{}
void Allocate(FRHICommandListBase& RHICmdList, uint32 NumBrickUploads, uint32 BrickSize)
{
const uint32 NumCoordElements = FMath::RoundUpToPowerOfTwo(NumBrickUploads);
const uint32 CoordNumBytesPerElement = GPixelFormats[PF_R32G32B32A32_UINT].BlockBytes;
NumBrickUploadCoordinatesBytes = NumCoordElements * CoordNumBytesPerElement;
if (BrickUploadCoordinatesBuffer.NumBytes < NumBrickUploadCoordinatesBytes)
{
BrickUploadCoordinatesBuffer.Initialize(RHICmdList, TEXT("DistanceFields.BrickUploadCoordinatesBuffer"), CoordNumBytesPerElement, NumCoordElements, PF_R32G32B32A32_UINT, BUF_Volatile);
}
const uint32 NumBrickDataElements = FMath::RoundUpToPowerOfTwo(NumBrickUploads) * BrickSize * BrickSize * BrickSize;
const uint32 BrickDataNumBytesPerElement = GPixelFormats[DistanceField::DistanceFieldFormat].BlockBytes;
NumBrickUploadDataBytes = NumBrickDataElements * BrickDataNumBytesPerElement;
if (BrickUploadDataBuffer.NumBytes < NumBrickUploadDataBytes || BrickUploadDataBuffer.NumBytes > 2 * NumBrickUploadDataBytes)
{
BrickUploadDataBuffer.Initialize(RHICmdList, TEXT("DistanceFields.BrickUploadDataBuffer"), BrickDataNumBytesPerElement, NumBrickDataElements, DistanceField::DistanceFieldFormat, BUF_Volatile);
}
}
void Lock(FRHICommandListBase& RHICmdList)
{
if (NumBrickUploadCoordinatesBytes != 0)
{
BrickUploadCoordinatesPtr = (FIntVector4*)RHICmdList.LockBuffer(BrickUploadCoordinatesBuffer.Buffer, 0, NumBrickUploadCoordinatesBytes, RLM_WriteOnly);
}
if (NumBrickUploadDataBytes != 0)
{
BrickUploadDataPtr = (uint8*)RHICmdList.LockBuffer(BrickUploadDataBuffer.Buffer, 0, NumBrickUploadDataBytes, RLM_WriteOnly);
}
}
void Unlock(FRHICommandListBase& RHICmdList)
{
if (BrickUploadCoordinatesPtr)
{
RHICmdList.UnlockBuffer(BrickUploadCoordinatesBuffer.Buffer);
}
if (BrickUploadDataPtr)
{
RHICmdList.UnlockBuffer(BrickUploadDataBuffer.Buffer);
}
}
};
class FDistanceFieldIndirectionAtlasUpload
{
public:
FReadBuffer& IndirectionUploadIndicesBuffer;
FReadBuffer& IndirectionUploadDataBuffer;
uint32* IndirectionUploadIndicesPtr = nullptr;
FVector4f* IndirectionUploadDataPtr = nullptr;
uint32 NumIndirectionUploadIndicesBytes = 0;
uint32 NumIndirectionUploadDataBytes = 0;
FDistanceFieldIndirectionAtlasUpload(
FReadBuffer& InIndirectionUploadIndicesBuffer,
FReadBuffer& InIndirectionUploadDataBuffer) :
IndirectionUploadIndicesBuffer(InIndirectionUploadIndicesBuffer),
IndirectionUploadDataBuffer(InIndirectionUploadDataBuffer)
{}
void Allocate(FRHICommandListBase& RHICmdList, uint32 NumIndirectionUploads)
{
const uint32 NumElements = FMath::RoundUpToPowerOfTwo(NumIndirectionUploads);
NumIndirectionUploadIndicesBytes = NumElements * sizeof(uint32);
if (IndirectionUploadIndicesBuffer.NumBytes < NumIndirectionUploadIndicesBytes)
{
IndirectionUploadIndicesBuffer.Initialize(RHICmdList, TEXT("DistanceFields.IndirectionUploadIndicesBuffer"), sizeof(uint32), NumElements, PF_R32_UINT, BUF_Volatile);
}
NumIndirectionUploadDataBytes = NumElements * sizeof(FVector4f);
if (IndirectionUploadDataBuffer.NumBytes < NumIndirectionUploadDataBytes)
{
IndirectionUploadDataBuffer.Initialize(RHICmdList, TEXT("DistanceFields.IndirectionUploadDataBuffer"), sizeof(FVector4f), NumElements, PF_A32B32G32R32F, BUF_Volatile);
}
}
void Lock(FRHICommandListBase& RHICmdList)
{
if (NumIndirectionUploadIndicesBytes != 0)
{
IndirectionUploadIndicesPtr = (uint32*)RHICmdList.LockBuffer(IndirectionUploadIndicesBuffer.Buffer, 0, NumIndirectionUploadIndicesBytes, RLM_WriteOnly);
}
if (NumIndirectionUploadDataBytes != 0)
{
IndirectionUploadDataPtr = (FVector4f*)RHICmdList.LockBuffer(IndirectionUploadDataBuffer.Buffer, 0, NumIndirectionUploadDataBytes, RLM_WriteOnly);
}
}
void Unlock(FRHICommandListBase& RHICmdList) const
{
if (IndirectionUploadIndicesPtr)
{
RHICmdList.UnlockBuffer(IndirectionUploadIndicesBuffer.Buffer);
}
if (IndirectionUploadDataPtr)
{
RHICmdList.UnlockBuffer(IndirectionUploadDataBuffer.Buffer);
}
}
};
void FDistanceFieldBlockAllocator::Allocate(int32 NumBlocks, TArray<int32, TInlineAllocator<4>>& OutBlocks)
{
OutBlocks.Empty(NumBlocks);
OutBlocks.AddUninitialized(NumBlocks);
const int32 NumFree = FMath::Min(NumBlocks, FreeBlocks.Num());
if (NumFree > 0)
{
for (int32 i = 0; i < NumFree; i++)
{
OutBlocks[i] = FreeBlocks[FreeBlocks.Num() - i - 1];
}
FreeBlocks.RemoveAt(FreeBlocks.Num() - NumFree, NumFree, EAllowShrinking::No);
}
const int32 NumRemaining = NumBlocks - NumFree;
for (int32 i = 0; i < NumRemaining; i++)
{
OutBlocks[i + NumFree] = MaxNumBlocks + i;
}
MaxNumBlocks += NumRemaining;
}
void FDistanceFieldBlockAllocator::Free(const TArray<int32, TInlineAllocator<4>>& ElementRange)
{
FreeBlocks.Append(ElementRange);
}
struct FDistanceFieldReadRequest
{
// SDF scene context
FSetElementId AssetSetId;
int32 ReversedMipIndex = 0;
int32 NumDistanceFieldBricks = 0;
uint64 BuiltDataId = 0;
// Used when BulkData is nullptr
const uint8* AlwaysLoadedDataPtr = nullptr;
// Inputs of read request
const FByteBulkData* BulkData = nullptr;
uint32 BulkOffset = 0;
uint32 BulkSize = 0;
// Outputs of read request
uint8* ReadOutputDataPtr = nullptr;
FIoRequest Request;
IAsyncReadFileHandle* AsyncHandle = nullptr;
IAsyncReadRequest* AsyncRequest = nullptr;
};
struct FDistanceFieldAsyncUpdateParameters
{
FDistanceFieldAsyncUpdateParameters(
FReadBuffer& InBrickUploadCoordinatesBuffer,
FReadBuffer& InBrickUploadDataBuffer,
FReadBuffer& InIndirectionUploadIndicesBuffer,
FReadBuffer& InIndirectionUploadDataBuffer)
: AtlasUpload(InBrickUploadCoordinatesBuffer, InBrickUploadDataBuffer)
, IndirectionAtlasUpload(InIndirectionUploadIndicesBuffer, InIndirectionUploadDataBuffer)
{}
void Lock(FRHICommandListBase& RHICmdList)
{
AtlasUpload.Lock(RHICmdList);
IndirectionAtlasUpload.Lock(RHICmdList);
if (IndirectionTableUploader)
{
IndirectionTableUploader->Lock(RHICmdList);
}
}
void Unlock(FRHICommandListBase& RHICmdList)
{
AtlasUpload.Unlock(RHICmdList);
IndirectionAtlasUpload.Unlock(RHICmdList);
if (IndirectionTableUploader)
{
IndirectionTableUploader->Unlock(RHICmdList);
}
}
FDistanceFieldSceneData* DistanceFieldSceneData = nullptr;
FDistanceFieldAtlasUpload AtlasUpload;
FDistanceFieldIndirectionAtlasUpload IndirectionAtlasUpload;
FRDGScatterUploader* IndirectionTableUploader = nullptr;
TArray<FDistanceFieldReadRequest> NewReadRequests;
TArray<FDistanceFieldReadRequest> ReadRequestsToUpload;
TArray<FDistanceFieldReadRequest> ReadRequestsToCleanUp;
};
FDistanceFieldSceneData::FDistanceFieldSceneData(FDistanceFieldSceneData&&) = default;
FDistanceFieldSceneData::FDistanceFieldSceneData(EShaderPlatform ShaderPlatform)
: NumObjectsInBuffer(0)
, IndirectionAtlasLayout(8, 8, 8, 512, 512, 512, false, true, false)
, HeightFieldAtlasGeneration(0)
, HFVisibilityAtlasGenerattion(0)
{
ObjectBuffers = nullptr;
HeightFieldObjectBuffers = nullptr;
bTrackAllPrimitives = ShouldAllPrimitivesHaveDistanceField(ShaderPlatform);
bCanUse16BitObjectIndices = RHISupportsBufferLoadTypeConversion(ShaderPlatform);
StreamingRequestReadbackBuffers.AddZeroed(MaxStreamingReadbackBuffers);
}
FDistanceFieldSceneData::~FDistanceFieldSceneData()
{
delete ObjectBuffers;
delete HeightFieldObjectBuffers;
}
void FDistanceFieldSceneData::AsyncUpdate(FRHICommandListBase& RHICmdList, FDistanceFieldAsyncUpdateParameters& UpdateParameters)
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_FDistanceFieldSceneData_AsyncUpdate);
TRACE_CPUPROFILER_EVENT_SCOPE(FDistanceFieldSceneData::AsyncUpdate);
const uint32 BrickSizeBytes = GPixelFormats[DistanceField::DistanceFieldFormat].BlockBytes * DistanceField::BrickSize * DistanceField::BrickSize * DistanceField::BrickSize;
const FIntVector IndirectionTextureSize = IndirectionAtlas ? IndirectionAtlas->GetDesc().GetSize() : FIntVector::ZeroValue;
const float InvMaxIndirectionDimensionMinusOne = 1.f / (DistanceField::MaxIndirectionDimension - 1);
int32 BrickUploadIndex = 0;
int32 IndirectionUploadIndex = 0;
UpdateParameters.Lock(RHICmdList);
for (FDistanceFieldReadRequest ReadRequest : UpdateParameters.ReadRequestsToUpload)
{
const FDistanceFieldAssetState& AssetState = AssetStateArray[ReadRequest.AssetSetId];
const int32 ReversedMipIndex = ReadRequest.ReversedMipIndex;
const FDistanceFieldAssetMipState& MipState = AssetState.ReversedMips[ReversedMipIndex];
const int32 MipIndex = AssetState.BuiltData->Mips.Num() - ReversedMipIndex - 1;
const FSparseDistanceFieldMip& MipBuiltData = AssetState.BuiltData->Mips[MipIndex];
const uint8* BulkDataReadPtr = ReadRequest.BulkData ? ReadRequest.ReadOutputDataPtr : ReadRequest.AlwaysLoadedDataPtr;
#if WITH_EDITOR
if (ReadRequest.BulkData)
{
check((ReadRequest.BulkData->IsBulkDataLoaded() ||ReadRequest.BulkData->CanLoadFromDisk()) && ReadRequest.BulkData->GetBulkDataSize() > 0);
BulkDataReadPtr = (const uint8*)ReadRequest.BulkData->LockReadOnly() + ReadRequest.BulkOffset;
}
#endif
const int32 NumIndirectionEntries = MipBuiltData.IndirectionDimensions.X * MipBuiltData.IndirectionDimensions.Y * MipBuiltData.IndirectionDimensions.Z;
const uint32 ExpectedBulkSize = NumIndirectionEntries * sizeof(uint32) + ReadRequest.NumDistanceFieldBricks * BrickSizeBytes;
check(ReadRequest.BuiltDataId == AssetState.BuiltData->GetId());
checkf(ReadRequest.BulkSize == ExpectedBulkSize,
TEXT("Bulk size mismatch: BulkSize %u, ExpectedSize %u, NumIndirectionEntries %u, NumBricks %u, ReversedMip %u"),
ReadRequest.BulkSize,
ExpectedBulkSize,
NumIndirectionEntries,
ReadRequest.NumDistanceFieldBricks,
ReversedMipIndex);
const uint32* SourceIndirectionTable = (const uint32*)BulkDataReadPtr;
const int32* RESTRICT GlobalBlockOffsets = MipState.AllocatedBlocks.GetData();
uint32* DestIndirectionTable = nullptr;
FVector4f* DestIndirection2Table = nullptr;
if (GDistanceFieldOffsetDataStructure == 0)
{
DestIndirectionTable = (uint32*)UpdateParameters.IndirectionTableUploader->Add_GetRef(MipState.IndirectionTableOffset, NumIndirectionEntries);
}
else if (GDistanceFieldOffsetDataStructure == 1)
{
DestIndirection2Table = (FVector4f*)UpdateParameters.IndirectionTableUploader->Add_GetRef(MipState.IndirectionTableOffset, NumIndirectionEntries);
}
// Add global allocated brick offset to indirection table entries as we upload them
for (int32 i = 0; i < NumIndirectionEntries; i++)
{
const uint32 BrickIndex = SourceIndirectionTable[i];
uint32 GlobalBrickIndex = DistanceField::InvalidBrickIndex;
FVector4f BrickOffset(0, 0, 0, 0);
if (BrickIndex != DistanceField::InvalidBrickIndex)
{
const int32 BlockIndex = BrickIndex / GDistanceFieldBlockAllocatorSizeInBricks;
if (BlockIndex < MipState.AllocatedBlocks.Num())
{
GlobalBrickIndex = BrickIndex % GDistanceFieldBlockAllocatorSizeInBricks + GlobalBlockOffsets[BlockIndex] * GDistanceFieldBlockAllocatorSizeInBricks;
const FIntVector BrickTextureCoordinate = GetBrickCoordinate(GlobalBrickIndex, BrickTextureDimensionsInBricks);
BrickOffset.X = BrickTextureCoordinate.X * InvMaxIndirectionDimensionMinusOne;
BrickOffset.Y = BrickTextureCoordinate.Y * InvMaxIndirectionDimensionMinusOne;
BrickOffset.Z = BrickTextureCoordinate.Z * InvMaxIndirectionDimensionMinusOne;
BrickOffset.W = 1.0f;
}
}
if (GDistanceFieldOffsetDataStructure == 0)
{
DestIndirectionTable[i] = GlobalBrickIndex;
}
else if (GDistanceFieldOffsetDataStructure == 1)
{
// This null check isn't really needed but to make static analysis happy
if (DestIndirection2Table)
{
DestIndirection2Table[i] = BrickOffset;
}
}
else if (GDistanceFieldOffsetDataStructure == 2)
{
const FIntVector IndirectionCoord = FIntVector(
i % MipState.IndirectionDimensions.X,
i / MipState.IndirectionDimensions.X % MipState.IndirectionDimensions.Y,
i / (MipState.IndirectionDimensions.X * MipState.IndirectionDimensions.Y));
const FIntVector IndirectionAtlasPosition = MipState.IndirectionAtlasOffset + IndirectionCoord;
const uint32 IndirectionIndex = IndirectionAtlasPosition.X + IndirectionTextureSize.X * (IndirectionAtlasPosition.Y + IndirectionTextureSize.Y * IndirectionAtlasPosition.Z);
UpdateParameters.IndirectionAtlasUpload.IndirectionUploadIndicesPtr[IndirectionUploadIndex] = IndirectionIndex;
UpdateParameters.IndirectionAtlasUpload.IndirectionUploadDataPtr[IndirectionUploadIndex] = BrickOffset;
++IndirectionUploadIndex;
}
}
check(MipState.NumBricks == ReadRequest.NumDistanceFieldBricks);
const uint8* DistanceFieldBrickDataPtr = BulkDataReadPtr + NumIndirectionEntries * sizeof(uint32);
const SIZE_T DistanceFieldBrickDataSizeBytes = ReadRequest.NumDistanceFieldBricks * BrickSizeBytes;
FMemory::Memcpy(UpdateParameters.AtlasUpload.BrickUploadDataPtr + BrickUploadIndex * BrickSizeBytes, DistanceFieldBrickDataPtr, DistanceFieldBrickDataSizeBytes);
for (int32 BrickIndex = 0; BrickIndex < MipState.NumBricks; BrickIndex++)
{
const int32 GlobalBrickIndex = BrickIndex % GDistanceFieldBlockAllocatorSizeInBricks + GlobalBlockOffsets[BrickIndex / GDistanceFieldBlockAllocatorSizeInBricks] * GDistanceFieldBlockAllocatorSizeInBricks;
const FIntVector BrickTextureCoordinate = GetBrickCoordinate(GlobalBrickIndex, BrickTextureDimensionsInBricks);
UpdateParameters.AtlasUpload.BrickUploadCoordinatesPtr[BrickUploadIndex + BrickIndex] = FIntVector4(BrickTextureCoordinate.X, BrickTextureCoordinate.Y, BrickTextureCoordinate.Z, 0);
}
#if WITH_EDITOR
if (ReadRequest.BulkData)
{
ReadRequest.BulkData->Unlock();
}
#endif
BrickUploadIndex += MipState.NumBricks;
}
#if !WITH_EDITOR
for (FDistanceFieldReadRequest ReadRequest : UpdateParameters.ReadRequestsToCleanUp)
{
if (ReadRequest.AsyncRequest)
{
check(ReadRequest.AsyncRequest->PollCompletion());
delete ReadRequest.AsyncRequest;
delete ReadRequest.AsyncHandle;
}
else
{
check(ReadRequest.Request.Status().IsCompleted());
}
FMemory::Free(ReadRequest.ReadOutputDataPtr);
}
FIoBatch Batch;
for (FDistanceFieldReadRequest& ReadRequest : UpdateParameters.NewReadRequests)
{
check(ReadRequest.BulkSize > 0);
ReadRequest.ReadOutputDataPtr = (uint8*)FMemory::Malloc(ReadRequest.BulkSize);
const bool bIODispatcher = ReadRequest.BulkData->IsUsingIODispatcher();
if (bIODispatcher)
{
TRACE_IOSTORE_METADATA_SCOPE_TAG("DistanceField");
// Use IODispatcher when available
FIoChunkId ChunkID = ReadRequest.BulkData->CreateChunkId();
FIoReadOptions ReadOptions;
ReadOptions.SetRange(ReadRequest.BulkData->GetBulkDataOffsetInFile() + ReadRequest.BulkOffset, ReadRequest.BulkSize);
ReadOptions.SetTargetVa(ReadRequest.ReadOutputDataPtr);
ReadRequest.Request = Batch.Read(ChunkID, ReadOptions, IoDispatcherPriority_Low);
}
else
{
// Compatibility path without IODispatcher
ReadRequest.AsyncHandle = ReadRequest.BulkData->OpenAsyncReadHandle();
ReadRequest.AsyncRequest = ReadRequest.AsyncHandle->ReadRequest(ReadRequest.BulkData->GetBulkDataOffsetInFile() + ReadRequest.BulkOffset, ReadRequest.BulkSize, AIOP_Low, nullptr, ReadRequest.ReadOutputDataPtr);
}
}
Batch.Issue();
#endif
ReadRequests.Append(UpdateParameters.NewReadRequests);
UpdateParameters.Unlock(RHICmdList);
}
bool AssetHasOutstandingRequest(FSetElementId AssetSetId, const TArray<FDistanceFieldReadRequest>& ReadRequests)
{
for (const FDistanceFieldReadRequest& ReadRequest : ReadRequests)
{
if (ReadRequest.AssetSetId == AssetSetId)
{
return true;
}
}
return false;
}
void FDistanceFieldSceneData::ProcessStreamingRequestsFromGPU(
TArray<FDistanceFieldReadRequest>& NewReadRequests,
TArray<FDistanceFieldAssetMipId>& AssetDataUploads)
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_DistanceFieldProcessStreamingRequests);
TRACE_CPUPROFILER_EVENT_SCOPE(DistanceFieldProcessStreamingRequests);
FRHIGPUBufferReadback* LatestReadbackBuffer = nullptr;
{
// Find latest buffer that is ready
while (ReadbackBuffersNumPending > 0)
{
uint32 Index = (ReadbackBuffersWriteIndex + MaxStreamingReadbackBuffers - ReadbackBuffersNumPending) % MaxStreamingReadbackBuffers;
if (StreamingRequestReadbackBuffers[Index]->IsReady())
{
ReadbackBuffersNumPending--;
LatestReadbackBuffer = StreamingRequestReadbackBuffers[Index];
}
else
{
break;
}
}
}
const int32 BrickAtlasSizeXYInBricks = CVarBrickAtlasSizeXYInBricks.GetValueOnRenderThread();
const int32 NumBricksBeforeDroppingMips = FMath::Max((CVarMaxAtlasDepthInBricks.GetValueOnRenderThread() - 1) * BrickAtlasSizeXYInBricks * BrickAtlasSizeXYInBricks, 0);
int32 NumAllocatedDistanceFieldBricks = DistanceFieldAtlasBlockAllocator.GetAllocatedSize() * GDistanceFieldBlockAllocatorSizeInBricks;
for (const FDistanceFieldReadRequest& ReadRequest : ReadRequests)
{
// Account for size that will be added when all async read requests complete
NumAllocatedDistanceFieldBricks += ReadRequest.NumDistanceFieldBricks;
}
if (LatestReadbackBuffer)
{
const uint32* LatestReadbackBufferPtr = (const uint32*)LatestReadbackBuffer->Lock((MaxStreamingRequests * 2 + 1) * sizeof(uint32));
const uint32 NumStreamingRequests = FMath::Min<uint32>(LatestReadbackBufferPtr[0], MaxStreamingRequests);
// Process streaming requests in two passes so that mip1 requests will be allocated before mip2
for (int32 PassIndex = 0; PassIndex < 2; PassIndex++)
{
const bool bFirstPass = PassIndex == 0;
for (uint32 StreamingRequestIndex = 0; StreamingRequestIndex < NumStreamingRequests; StreamingRequestIndex++)
{
const int32 AssetIndex = LatestReadbackBufferPtr[1 + StreamingRequestIndex * 2 + 0];
const FSetElementId AssetSetId = FSetElementId::FromInteger(AssetIndex);
if (AssetStateArray.IsValidId(AssetSetId))
{
FDistanceFieldAssetState& AssetState = AssetStateArray[AssetSetId];
const int32 WantedNumMips = LatestReadbackBufferPtr[1 + StreamingRequestIndex * 2 + 1];
check(WantedNumMips <= DistanceField::NumMips && WantedNumMips <= AssetState.BuiltData->Mips.Num());
AssetState.WantedNumMips = WantedNumMips;
if (WantedNumMips < AssetState.ReversedMips.Num() && bFirstPass)
{
check(AssetState.ReversedMips.Num() > 1);
const FDistanceFieldAssetMipState MipState = AssetState.ReversedMips.Pop();
if (GDistanceFieldOffsetDataStructure == 0 || GDistanceFieldOffsetDataStructure == 1)
{
IndirectionTableAllocator.Free(MipState.IndirectionTableOffset, MipState.IndirectionDimensions.X * MipState.IndirectionDimensions.Y * MipState.IndirectionDimensions.Z);
}
else
{
IndirectionAtlasLayout.RemoveElement(MipState.IndirectionAtlasOffset.X, MipState.IndirectionAtlasOffset.Y, MipState.IndirectionAtlasOffset.Z,
MipState.IndirectionDimensions.X, MipState.IndirectionDimensions.Y, MipState.IndirectionDimensions.Z);
}
if (MipState.NumBricks > 0)
{
check(MipState.AllocatedBlocks.Num() > 0);
DistanceFieldAtlasBlockAllocator.Free(MipState.AllocatedBlocks);
}
// Re-upload mip0 to push the new NumMips to the shader
AssetDataUploads.Add(FDistanceFieldAssetMipId(AssetSetId, 0));
}
else if (WantedNumMips > AssetState.ReversedMips.Num())
{
const int32 ReversedMipIndexToAdd = AssetState.ReversedMips.Num();
// Don't allocate mip if we are close to the max size
const bool bAllowedToAllocateMipBricks = NumAllocatedDistanceFieldBricks <= NumBricksBeforeDroppingMips;
// Only allocate mip2 requests in the second pass after all mip1 requests have succeeded
const bool bShouldProcessThisPass = ((bFirstPass && ReversedMipIndexToAdd < DistanceField::NumMips - 1) || (!bFirstPass && ReversedMipIndexToAdd == DistanceField::NumMips - 1));
if (bAllowedToAllocateMipBricks
&& bShouldProcessThisPass
// Only allow one IO request in flight for a given asset
&& !AssetHasOutstandingRequest(AssetSetId, ReadRequests))
{
const int32 MipIndexToAdd = AssetState.BuiltData->Mips.Num() - ReversedMipIndexToAdd - 1;
const FSparseDistanceFieldMip& MipBuiltData = AssetState.BuiltData->Mips[MipIndexToAdd];
//@todo - this condition shouldn't be possible as the built data always has non-zero size, needs more investigation
if (MipBuiltData.BulkSize > 0)
{
FDistanceFieldReadRequest ReadRequest;
ReadRequest.AssetSetId = AssetSetId;
ReadRequest.BuiltDataId = AssetState.BuiltData->GetId();
ReadRequest.ReversedMipIndex = ReversedMipIndexToAdd;
ReadRequest.NumDistanceFieldBricks = MipBuiltData.NumDistanceFieldBricks;
ReadRequest.BulkData = &AssetState.BuiltData->StreamableMips;
ReadRequest.BulkOffset = MipBuiltData.BulkOffset;
ReadRequest.BulkSize = MipBuiltData.BulkSize;
check(ReadRequest.BulkSize > 0);
NewReadRequests.Add(MoveTemp(ReadRequest));
NumAllocatedDistanceFieldBricks += MipBuiltData.NumDistanceFieldBricks;
}
}
}
}
}
}
LatestReadbackBuffer->Unlock();
}
}
void FDistanceFieldSceneData::ProcessReadRequests(
TArray<FDistanceFieldAssetMipId>& AssetDataUploads,
TArray<FDistanceFieldAssetMipId>& DistanceFieldAssetMipAdds,
TArray<FDistanceFieldReadRequest>& ReadRequestsToUpload,
TArray<FDistanceFieldReadRequest>& ReadRequestsToCleanUp)
{
const uint32 BrickSizeBytes = GPixelFormats[DistanceField::DistanceFieldFormat].BlockBytes * DistanceField::BrickSize * DistanceField::BrickSize * DistanceField::BrickSize;
const SIZE_T TextureUploadLimitBytes = (SIZE_T)CVarTextureUploadLimitKBytes.GetValueOnRenderThread() * 1024;
SIZE_T TextureUploadBytes = 0;
// At this point DistanceFieldAssetMipAdds contains only lowest resolution mip adds which are always loaded
// Forward these to the Requests to Upload list, with a null BulkData
for (FDistanceFieldAssetMipId AssetMipAdd : DistanceFieldAssetMipAdds)
{
const FDistanceFieldAssetState& AssetState = AssetStateArray[AssetMipAdd.AssetId];
const int32 ReversedMipIndex = AssetMipAdd.ReversedMipIndex;
check(ReversedMipIndex == 0);
const int32 MipIndex = AssetState.BuiltData->Mips.Num() - ReversedMipIndex - 1;
const FSparseDistanceFieldMip& MipBuiltData = AssetState.BuiltData->Mips[MipIndex];
TextureUploadBytes += MipBuiltData.NumDistanceFieldBricks * BrickSizeBytes;
FDistanceFieldReadRequest NewReadRequest;
NewReadRequest.AssetSetId = AssetMipAdd.AssetId;
NewReadRequest.BuiltDataId = AssetState.BuiltData->GetId();
NewReadRequest.ReversedMipIndex = AssetMipAdd.ReversedMipIndex;
NewReadRequest.NumDistanceFieldBricks = MipBuiltData.NumDistanceFieldBricks;
NewReadRequest.AlwaysLoadedDataPtr = AssetState.BuiltData->AlwaysLoadedMip.GetData();
NewReadRequest.BulkSize = AssetState.BuiltData->AlwaysLoadedMip.Num();
ReadRequestsToUpload.Add(MoveTemp(NewReadRequest));
}
for (int32 RequestIndex = 0; RequestIndex < ReadRequests.Num(); RequestIndex++)
{
const FDistanceFieldReadRequest ReadRequest = ReadRequests[RequestIndex];
bool bReady = true;
#if !WITH_EDITOR
if (ReadRequest.AsyncRequest)
{
bReady = bReady && ReadRequest.AsyncRequest->PollCompletion();
}
else
{
bReady = bReady && ReadRequest.Request.Status().IsCompleted();
}
#endif
if (bReady)
{
ReadRequests.RemoveAtSwap(RequestIndex);
RequestIndex--;
if (AssetStateArray.IsValidId(ReadRequest.AssetSetId)
// Prevent attempting to upload after a different asset has been allocated at the same index
&& ReadRequest.BuiltDataId == AssetStateArray[ReadRequest.AssetSetId].BuiltData->GetId()
// Shader requires sequential reversed mips starting from 0, skip upload if the IO request got out of sync with the streaming feedback requests
&& ReadRequest.ReversedMipIndex == AssetStateArray[ReadRequest.AssetSetId].ReversedMips.Num())
{
TextureUploadBytes += ReadRequest.NumDistanceFieldBricks * BrickSizeBytes;
DistanceFieldAssetMipAdds.Add(FDistanceFieldAssetMipId(ReadRequest.AssetSetId, ReadRequest.ReversedMipIndex));
// Re-upload mip0 to push the new NumMips to the shader
AssetDataUploads.Add(FDistanceFieldAssetMipId(ReadRequest.AssetSetId, 0));
ReadRequestsToUpload.Add(ReadRequest);
}
ReadRequestsToCleanUp.Add(ReadRequest);
}
// Stop uploading when we reach the limit
// In practice we can still exceed the limit with a single large upload request
if (TextureUploadBytes >= TextureUploadLimitBytes)
{
break;
}
}
// Re-upload asset data for all mips we are uploading this frame
AssetDataUploads.Append(DistanceFieldAssetMipAdds);
}
FRDGTexture* FDistanceFieldSceneData::ResizeBrickAtlasIfNeeded(FRDGBuilder& GraphBuilder, FGlobalShaderMap* GlobalShaderMap)
{
// Mask should be set in FSceneRenderer::PrepareDistanceFieldScene before calling this
check(GraphBuilder.RHICmdList.GetGPUMask() == FRHIGPUMask::All());
const int32 BrickAtlasSizeXYInBricks = CVarBrickAtlasSizeXYInBricks.GetValueOnRenderThread();
int32 DesiredZSizeInBricks = FMath::DivideAndRoundUp(DistanceFieldAtlasBlockAllocator.GetMaxSize() * GDistanceFieldBlockAllocatorSizeInBricks, BrickAtlasSizeXYInBricks * BrickAtlasSizeXYInBricks);
if (DesiredZSizeInBricks <= CVarMaxAtlasDepthInBricks.GetValueOnRenderThread())
{
DesiredZSizeInBricks = FMath::RoundUpToPowerOfTwo(DesiredZSizeInBricks);
}
else
{
DesiredZSizeInBricks = FMath::DivideAndRoundUp(DesiredZSizeInBricks, 4) * 4;
}
const FIntVector DesiredBrickTextureDimensionsInBricks = FIntVector(BrickAtlasSizeXYInBricks, BrickAtlasSizeXYInBricks, DesiredZSizeInBricks);
const bool bResizeAtlasEveryFrame = CVarResizeAtlasEveryFrame.GetValueOnRenderThread() != 0;
if (!DistanceFieldBrickVolumeTexture
|| DistanceFieldBrickVolumeTexture->GetDesc().GetSize() != DesiredBrickTextureDimensionsInBricks * DistanceField::BrickSize
|| bResizeAtlasEveryFrame)
{
const FRDGTextureDesc BrickVolumeTextureDesc = FRDGTextureDesc::Create3D(
DesiredBrickTextureDimensionsInBricks * DistanceField::BrickSize,
DistanceField::DistanceFieldFormat,
FClearValueBinding::Black,
TexCreate_ShaderResource | TexCreate_UAV | TexCreate_3DTiling);
FRDGTextureRef DistanceFieldBrickVolumeTextureRDG = GraphBuilder.CreateTexture(BrickVolumeTextureDesc, TEXT("DistanceFields.DistanceFieldBrickTexture"));
if (DistanceFieldBrickVolumeTexture)
{
FCopyDistanceFieldAtlasCS::FParameters* PassParameters = GraphBuilder.AllocParameters<FCopyDistanceFieldAtlasCS::FParameters>();
PassParameters->RWDistanceFieldBrickAtlas = GraphBuilder.CreateUAV(DistanceFieldBrickVolumeTextureRDG);
PassParameters->DistanceFieldAtlas = DistanceField::SetupAtlasParameters(GraphBuilder, *this);
auto ComputeShader = GlobalShaderMap->GetShader<FCopyDistanceFieldAtlasCS>();
FComputeShaderUtils::AddPass(
GraphBuilder,
RDG_EVENT_NAME("CopyDistanceFieldAtlas"),
ComputeShader,
PassParameters,
FComputeShaderUtils::GetGroupCount(DistanceFieldBrickVolumeTexture->GetDesc().GetSize(), FCopyDistanceFieldAtlasCS::GetGroupSize()));
}
BrickTextureDimensionsInBricks = DesiredBrickTextureDimensionsInBricks;
DistanceFieldBrickVolumeTexture = GraphBuilder.ConvertToExternalTexture(DistanceFieldBrickVolumeTextureRDG);
return DistanceFieldBrickVolumeTextureRDG;
}
return GraphBuilder.RegisterExternalTexture(DistanceFieldBrickVolumeTexture);
}
static FIntVector CalculateDesiredSize(FIntVector CurrentSize, FIntVector RequiredSize)
{
FIntVector DesiredSize = CurrentSize;
DesiredSize.X = FMath::Max(DesiredSize.X, MinIndirectionAtlasSizeXYZ);
DesiredSize.Y = FMath::Max(DesiredSize.Y, MinIndirectionAtlasSizeXYZ);
DesiredSize.Z = FMath::Max(DesiredSize.Z, MinIndirectionAtlasSizeXYZ);
while (DesiredSize.X < RequiredSize.X)
{
DesiredSize.X *= IndirectionAtlasGrowMult;
}
while (DesiredSize.Y < RequiredSize.Y)
{
DesiredSize.Y *= IndirectionAtlasGrowMult;
}
while (DesiredSize.Z < RequiredSize.Z)
{
DesiredSize.Z *= IndirectionAtlasGrowMult;
}
return DesiredSize;
}
bool FDistanceFieldSceneData::ResizeIndirectionAtlasIfNeeded(FRDGBuilder& GraphBuilder, FGlobalShaderMap* GlobalShaderMap, FRDGTexture*& OutTexture)
{
// Mask should be set in FSceneRenderer::PrepareDistanceFieldScene before calling this
check(GraphBuilder.RHICmdList.GetGPUMask() == FRHIGPUMask::All());
QUICK_SCOPE_CYCLE_COUNTER(STAT_ResizeIndirectionAtlasIfNeeded);
TRACE_CPUPROFILER_EVENT_SCOPE(FDistanceFieldSceneData::ResizeIndirectionAtlasIfNeeded);
const FIntVector CurrentSize = IndirectionAtlas ? IndirectionAtlas->GetDesc().GetSize() : FIntVector::ZeroValue;
FIntVector DesiredSize = CalculateDesiredSize(CurrentSize, IndirectionAtlasLayout.GetSize());
const bool bDefragment = (CurrentSize != DesiredSize) && CVarDefragmentIndirectionAtlas.GetValueOnRenderThread() != 0;
TArray<FDistanceFieldAssetMipRelocation> Relocations;
if (bDefragment)
{
CSV_EVENT(DistanceField, TEXT("DefragmentIndirectionAtlas"));
DefragmentIndirectionAtlas(FIntVector(MinIndirectionAtlasSizeXYZ), Relocations);
DesiredSize = CalculateDesiredSize(CurrentSize, IndirectionAtlasLayout.GetSize());
}
else if (CurrentSize != DesiredSize)
{
for (TSet<FDistanceFieldAssetState, TFDistanceFieldAssetStateFuncs>::TConstIterator It(AssetStateArray); It; ++It)
{
const FDistanceFieldAssetState& AssetState = *It;
for (int32 ReversedMipIndex = 0; ReversedMipIndex < AssetState.ReversedMips.Num(); ReversedMipIndex++)
{
const FDistanceFieldAssetMipState& MipState = AssetState.ReversedMips[ReversedMipIndex];
Relocations.Add(FDistanceFieldAssetMipRelocation(MipState.IndirectionDimensions, MipState.IndirectionAtlasOffset, MipState.IndirectionAtlasOffset));
}
}
}
if (!IndirectionAtlas || (CurrentSize != DesiredSize) || !Relocations.IsEmpty())
{
const FRDGTextureDesc TextureDesc = FRDGTextureDesc::Create3D(
DesiredSize,
PF_A2B10G10R10,
FClearValueBinding::Black,
TexCreate_ShaderResource | TexCreate_UAV | TexCreate_3DTiling);
FRDGTextureRef NewIndirectAtlasRDG = GraphBuilder.CreateTexture(TextureDesc, TEXT("DistanceFields.DistanceFieldIndirectionAtlas"));
check(NewIndirectAtlasRDG);
if (IndirectionAtlas)
{
TShaderMapRef<FCopyDistanceFieldIndirectionAtlasCS> ComputeShader(GlobalShaderMap);
FRDGTexture* OldIndirectAtlasRDG = GraphBuilder.RegisterExternalTexture(IndirectionAtlas);
FRDGTextureUAV* NewIndirectAtlasUAV = GraphBuilder.CreateUAV(NewIndirectAtlasRDG, ERDGUnorderedAccessViewFlags::SkipBarrier);
for (int32 RelocationIndex = 0; RelocationIndex < Relocations.Num(); ++RelocationIndex)
{
const FDistanceFieldAssetMipRelocation& Relocation = Relocations[RelocationIndex];
auto* PassParameters = GraphBuilder.AllocParameters<FCopyDistanceFieldIndirectionAtlasCS::FParameters>();
PassParameters->RWIndirectionAtlas = NewIndirectAtlasUAV;
PassParameters->DistanceFieldIndirectionAtlas = OldIndirectAtlasRDG;
PassParameters->IndirectionDimensions = Relocation.IndirectionDimensions;
PassParameters->SrcPosition = Relocation.SrcPosition;
PassParameters->DstPosition = Relocation.DstPosition;
PassParameters->NumAssets = 1;
FComputeShaderUtils::AddPass(
GraphBuilder,
RDG_EVENT_NAME("CopyDistanceFieldIndirectionAtlas (Index %d)", RelocationIndex),
ComputeShader,
PassParameters,
FComputeShaderUtils::GetGroupCount(
Relocation.IndirectionDimensions.X * Relocation.IndirectionDimensions.Y * Relocation.IndirectionDimensions.Z,
FCopyDistanceFieldIndirectionAtlasCS::GetGroupSize()));
}
}
IndirectionAtlas = GraphBuilder.ConvertToExternalTexture(NewIndirectAtlasRDG);
OutTexture = NewIndirectAtlasRDG;
UE_LOG(LogDistanceField, Log, TEXT("New indirection table size: %s (%s required)"), *IndirectionAtlas->GetDesc().GetSize().ToString(), *IndirectionAtlasLayout.GetSize().ToString());
return true;
}
OutTexture = GraphBuilder.RegisterExternalTexture(IndirectionAtlas);
return false;
}
void FDistanceFieldSceneData::DefragmentIndirectionAtlas(FIntVector MinSize, TArray<FDistanceFieldAssetMipRelocation>& Relocations)
{
struct FEntry
{
FSetElementId AssetSetId;
uint32 ReversedMipIndex;
SIZE_T Size;
};
TArray<FEntry> Entries;
for (TSet<FDistanceFieldAssetState, TFDistanceFieldAssetStateFuncs>::TConstIterator It(AssetStateArray); It; ++It)
{
const FDistanceFieldAssetState& AssetState = *It;
for (int32 ReversedMipIndex = 0; ReversedMipIndex < AssetState.ReversedMips.Num(); ReversedMipIndex++)
{
const FDistanceFieldAssetMipState& MipState = AssetState.ReversedMips[ReversedMipIndex];
FEntry Entry;
Entry.AssetSetId = It.GetId();
Entry.ReversedMipIndex = ReversedMipIndex;
Entry.Size = MipState.IndirectionDimensions.X * MipState.IndirectionDimensions.Y * MipState.IndirectionDimensions.Z;
Entries.Add(Entry);
}
}
struct FMeshDistanceFieldSorter
{
bool operator()(const FEntry& A, const FEntry& B) const
{
return A.Size > B.Size;
}
};
Entries.Sort(FMeshDistanceFieldSorter());
const int32 MaxIndirectionAtlasSizeXYZ = CVarMaxIndirectionAtlasSizeXYZ.GetValueOnRenderThread();
IndirectionAtlasLayout = FTextureLayout3d(MinSize.X, MinSize.Y, MinSize.Z, MaxIndirectionAtlasSizeXYZ, MaxIndirectionAtlasSizeXYZ, MaxIndirectionAtlasSizeXYZ, false, false);
for (int32 EntryIndex = 0; EntryIndex < Entries.Num(); EntryIndex++)
{
const FEntry& Entry = Entries[EntryIndex];
const FDistanceFieldAssetState& AssetState = AssetStateArray[Entry.AssetSetId];
const FDistanceFieldAssetMipState& MipState = AssetState.ReversedMips[Entry.ReversedMipIndex];
FIntVector PrevPosition = MipState.IndirectionAtlasOffset;
IndirectionAtlasLayout.AddElement((uint32&)MipState.IndirectionAtlasOffset.X, (uint32&)MipState.IndirectionAtlasOffset.Y, (uint32&)MipState.IndirectionAtlasOffset.Z,
MipState.IndirectionDimensions.X, MipState.IndirectionDimensions.Y, MipState.IndirectionDimensions.Z);
Relocations.Add(FDistanceFieldAssetMipRelocation(MipState.IndirectionDimensions, PrevPosition, MipState.IndirectionAtlasOffset));
}
}
void FDistanceFieldSceneData::GenerateStreamingRequests(FRDGBuilder& GraphBuilder, const FSceneRenderUpdateInputs& SceneUpdateInputs)
{
// It is not safe to EnqueueCopy on a buffer that already has a pending copy.
if (ReadbackBuffersNumPending < MaxStreamingReadbackBuffers && NumObjectsInBuffer > 0)
{
RDG_GPU_MASK_SCOPE(GraphBuilder, FRHIGPUMask::All());
if (!StreamingRequestReadbackBuffers[ReadbackBuffersWriteIndex])
{
FRHIGPUBufferReadback* GPUBufferReadback = new FRHIGPUBufferReadback(TEXT("DistanceFields.StreamingRequestReadBack"));
StreamingRequestReadbackBuffers[ReadbackBuffersWriteIndex] = GPUBufferReadback;
}
const uint32 NumAssets = AssetStateArray.GetMaxIndex();
FRDGBufferDesc WantedNumMipsDesc = FRDGBufferDesc::CreateStructuredDesc(sizeof(uint32), FMath::RoundUpToPowerOfTwo(NumAssets));
FRDGBufferRef WantedNumMips = GraphBuilder.CreateBuffer(WantedNumMipsDesc, TEXT("DistanceFields.DistanceFieldAssetWantedNumMips"));
// Every asset wants at least 1 mipmap
AddClearUAVPass(GraphBuilder, GraphBuilder.CreateUAV(FRDGBufferUAVDesc(WantedNumMips)), 1);
FRDGBufferDesc StreamingRequestsDesc = FRDGBufferDesc::CreateStructuredDesc(sizeof(uint32), MaxStreamingRequests * 2 + 1);
StreamingRequestsDesc.Usage = EBufferUsageFlags(StreamingRequestsDesc.Usage | BUF_SourceCopy);
FRDGBufferRef StreamingRequestsBuffer = GraphBuilder.CreateBuffer(StreamingRequestsDesc, TEXT("DistanceFields.DistanceFieldStreamingRequests"));
SceneUpdateInputs.ForEachView([&] (const FSceneRenderer* Renderer, const FViewInfo& View)
{
const bool bLumenEnabled = Renderer->IsLumenEnabled(View);
FDFVector3 PreViewTranslation(View.ViewMatrices.GetPreViewTranslation());
FComputeDistanceFieldAssetWantedMipsCS::FParameters* PassParameters = GraphBuilder.AllocParameters<FComputeDistanceFieldAssetWantedMipsCS::FParameters>();
checkf(DistanceField::NumMips == 3, TEXT("Shader needs to be updated"));
PassParameters->RWDistanceFieldAssetWantedNumMips = GraphBuilder.CreateUAV(WantedNumMips);
PassParameters->RWDistanceFieldAssetStreamingRequests = GraphBuilder.CreateUAV(StreamingRequestsBuffer);
PassParameters->DistanceFieldObjectBuffers = DistanceField::SetupObjectBufferParameters(GraphBuilder, *this);
PassParameters->DebugForceNumMips = FMath::Clamp(CVarDebugForceNumMips.GetValueOnRenderThread(), 0, DistanceField::NumMips);
extern int32 GAOGlobalDistanceFieldNumClipmaps;
// Request Mesh SDF mips based off of the Global SDF clipmaps
PassParameters->Mip1WorldTranslatedCenter = FVector3f(View.ViewMatrices.GetViewOrigin() + View.ViewMatrices.GetPreViewTranslation());
PassParameters->Mip1WorldExtent = FVector3f(GlobalDistanceField::GetClipmapExtent(GAOGlobalDistanceFieldNumClipmaps - 1, SceneUpdateInputs.Scene, bLumenEnabled));
PassParameters->Mip2WorldTranslatedCenter = FVector3f(View.ViewMatrices.GetViewOrigin() + View.ViewMatrices.GetPreViewTranslation());
PassParameters->Mip2WorldExtent = FVector3f(GlobalDistanceField::GetClipmapExtent(FMath::Max<int32>(GAOGlobalDistanceFieldNumClipmaps / 2 - 1, 0), SceneUpdateInputs.Scene, bLumenEnabled));
PassParameters->PreViewTranslationHigh = PreViewTranslation.High;
PassParameters->PreViewTranslationLow = PreViewTranslation.Low;
auto ComputeShader = SceneUpdateInputs.GlobalShaderMap->GetShader<FComputeDistanceFieldAssetWantedMipsCS>();
FComputeShaderUtils::AddPass(
GraphBuilder,
RDG_EVENT_NAME("ComputeWantedMips"),
ComputeShader,
PassParameters,
FComputeShaderUtils::GetGroupCountWrapped(NumObjectsInBuffer, FComputeDistanceFieldAssetWantedMipsCS::GetGroupSize()));
return true;
});
{
FGenerateDistanceFieldAssetStreamingRequestsCS::FParameters* PassParameters = GraphBuilder.AllocParameters<FGenerateDistanceFieldAssetStreamingRequestsCS::FParameters>();
PassParameters->RWDistanceFieldAssetStreamingRequests = GraphBuilder.CreateUAV(StreamingRequestsBuffer);
PassParameters->DistanceFieldAssetWantedNumMips = GraphBuilder.CreateSRV(FRDGBufferSRVDesc(WantedNumMips));
PassParameters->DistanceFieldObjectBuffers = DistanceField::SetupObjectBufferParameters(GraphBuilder, *this);
PassParameters->DistanceFieldAtlasParameters = DistanceField::SetupAtlasParameters(GraphBuilder, *this);
PassParameters->NumDistanceFieldAssets = NumAssets;
PassParameters->MaxNumStreamingRequests = MaxStreamingRequests;
auto ComputeShader = SceneUpdateInputs.GlobalShaderMap->GetShader<FGenerateDistanceFieldAssetStreamingRequestsCS>();
FComputeShaderUtils::AddPass(
GraphBuilder,
RDG_EVENT_NAME("GenerateStreamingRequests"),
ComputeShader,
PassParameters,
FComputeShaderUtils::GetGroupCount(NumAssets, FGenerateDistanceFieldAssetStreamingRequestsCS::GetGroupSize()));
}
FRHIGPUBufferReadback* ReadbackBuffer = StreamingRequestReadbackBuffers[ReadbackBuffersWriteIndex];
AddReadbackBufferPass(GraphBuilder, RDG_EVENT_NAME("DistanceFieldAssetReadback"), StreamingRequestsBuffer,
[ReadbackBuffer, StreamingRequestsBuffer](FRDGAsyncTask, FRHICommandList& RHICmdList)
{
ReadbackBuffer->EnqueueCopy(RHICmdList, StreamingRequestsBuffer->GetRHI(), 0u);
});
ReadbackBuffersWriteIndex = (ReadbackBuffersWriteIndex + 1u) % MaxStreamingReadbackBuffers;
ReadbackBuffersNumPending = FMath::Min(ReadbackBuffersNumPending + 1u, MaxStreamingReadbackBuffers);
}
}
void EncodeAssetData(const FDistanceFieldAssetState& AssetState, const int32 ReversedMipIndex, FVector4f* OutAssetData)
{
const FDistanceFieldAssetMipState& MipState = AssetState.ReversedMips[ReversedMipIndex];
const int32 MipIndex = AssetState.BuiltData->Mips.Num() - ReversedMipIndex - 1;
const FSparseDistanceFieldMip& MipBuiltData = AssetState.BuiltData->Mips[MipIndex];
const FVector2f DistanceFieldToVolumeScaleBias = MipBuiltData.DistanceFieldToVolumeScaleBias;
const int32 NumMips = AssetState.ReversedMips.Num();
check(NumMips <= DistanceField::NumMips);
check(DistanceField::NumMips < 4);
check(MipBuiltData.IndirectionDimensions.X < DistanceField::MaxIndirectionDimension
&& MipBuiltData.IndirectionDimensions.Y < DistanceField::MaxIndirectionDimension
&& MipBuiltData.IndirectionDimensions.Z < DistanceField::MaxIndirectionDimension);
uint32 IntVector0[4] =
{
(uint32)MipBuiltData.IndirectionDimensions.X | (uint32)(MipBuiltData.IndirectionDimensions.Y << 10) | (uint32)(MipBuiltData.IndirectionDimensions.Z << 20) | (uint32)(NumMips << 30),
(uint32)MipState.IndirectionTableOffset,
0,
0
};
// Bypass NaN checks in FVector4f ctors
FVector4f FloatVector0;
FloatVector0.X = *(const float*)&IntVector0[0];
FloatVector0.Y = *(const float*)&IntVector0[1];
FloatVector0.Z = *(const float*)&IntVector0[2];
FloatVector0.W = *(const float*)&IntVector0[3];
FVector4f VolumeToIndirectionScale = FVector4f(MipBuiltData.VolumeToVirtualUVScale, DistanceFieldToVolumeScaleBias.X);
VolumeToIndirectionScale.X *= MipBuiltData.IndirectionDimensions.X;
VolumeToIndirectionScale.Y *= MipBuiltData.IndirectionDimensions.Y;
VolumeToIndirectionScale.Z *= MipBuiltData.IndirectionDimensions.Z;
FVector4f VolumeToIndirectionAdd = FVector4f(MipBuiltData.VolumeToVirtualUVAdd, DistanceFieldToVolumeScaleBias.Y);
VolumeToIndirectionAdd.X *= MipBuiltData.IndirectionDimensions.X;
VolumeToIndirectionAdd.Y *= MipBuiltData.IndirectionDimensions.Y;
VolumeToIndirectionAdd.Z *= MipBuiltData.IndirectionDimensions.Z;
if (GDistanceFieldOffsetDataStructure != 0)
{
VolumeToIndirectionAdd.X += MipState.IndirectionAtlasOffset.X;
VolumeToIndirectionAdd.Y += MipState.IndirectionAtlasOffset.Y;
VolumeToIndirectionAdd.Z += MipState.IndirectionAtlasOffset.Z;
}
OutAssetData[0] = FloatVector0;
OutAssetData[1] = VolumeToIndirectionScale;
OutAssetData[2] = VolumeToIndirectionAdd;
}
void FDistanceFieldSceneData::UploadAssetData(FRDGBuilder& GraphBuilder, const TArray<FDistanceFieldAssetMipId>& AssetDataUploads, FRDGBuffer* AssetDataBufferRDG)
{
if (AssetDataUploads.IsEmpty())
{
return;
}
// Mask should be set in FSceneRenderer::PrepareDistanceFieldScene before calling this
check(GraphBuilder.RHICmdList.GetGPUMask() == FRHIGPUMask::All());
AssetDataUploadBuffer.Init(GraphBuilder, AssetDataUploads.Num(), AssetDataMipStrideFloat4s * sizeof(FVector4f), true, TEXT("DistanceFields.DFAssetDataAssetDataUploadBuffer"));
for (FDistanceFieldAssetMipId AssetMipUpload : AssetDataUploads)
{
const int32 ReversedMipIndex = AssetMipUpload.ReversedMipIndex;
FVector4f* UploadAssetData = (FVector4f*)AssetDataUploadBuffer.Add_GetRef(AssetMipUpload.AssetId.AsInteger() * DistanceField::NumMips + ReversedMipIndex);
if (AssetStateArray.IsValidId(AssetMipUpload.AssetId))
{
const FDistanceFieldAssetState& AssetState = AssetStateArray[AssetMipUpload.AssetId];
EncodeAssetData(AssetState, ReversedMipIndex, UploadAssetData);
}
else
{
// Clear invalid entries to zero
UploadAssetData[0] = FVector4f(0.0f, 0.0f, 0.0f, 0.0f);
UploadAssetData[1] = FVector4f(0.0f, 0.0f, 0.0f, 0.0f);
UploadAssetData[2] = FVector4f(0.0f, 0.0f, 0.0f, 0.0f);
}
}
AssetDataUploadBuffer.ResourceUploadTo(GraphBuilder, AssetDataBufferRDG);
}
void FDistanceFieldSceneData::UploadAllAssetData(FRDGBuilder& GraphBuilder, FRDGBuffer* AssetDataBufferRDG)
{
uint32 NumUploads = AssetStateArray.Num() * DistanceField::NumMips;
if (NumUploads == 0)
{
return;
}
// Mask should be set in FSceneRenderer::PrepareDistanceFieldScene before calling this
check(GraphBuilder.RHICmdList.GetGPUMask() == FRHIGPUMask::All());
AssetDataUploadBuffer.Init(GraphBuilder, NumUploads, AssetDataMipStrideFloat4s * sizeof(FVector4f), true, TEXT("DistanceFields.DFAssetDataUploadBuffer"));
for (TSet<FDistanceFieldAssetState, TFDistanceFieldAssetStateFuncs>::TConstIterator It(AssetStateArray); It; ++It)
{
const FDistanceFieldAssetState& AssetState = *It;
const FSetElementId AssetId = It.GetId();
if (AssetStateArray.IsValidId(AssetId))
{
for (int32 ReversedMipIndex = 0; ReversedMipIndex < AssetState.ReversedMips.Num(); ReversedMipIndex++)
{
//const FDistanceFieldAssetMipState& MipState = AssetState.ReversedMips[ReversedMipIndex];
FVector4f* UploadAssetData = (FVector4f*)AssetDataUploadBuffer.Add_GetRef(AssetId.AsInteger() * DistanceField::NumMips + ReversedMipIndex);
EncodeAssetData(AssetStateArray[AssetId], ReversedMipIndex, UploadAssetData);
}
}
else
{
FVector4f* UploadAssetData = (FVector4f*)AssetDataUploadBuffer.Add_GetRef(AssetId.AsInteger() * DistanceField::NumMips);
// Clear invalid entries to zero
UploadAssetData[0] = FVector4f(0.0f, 0.0f, 0.0f, 0.0f);
UploadAssetData[1] = FVector4f(0.0f, 0.0f, 0.0f, 0.0f);
UploadAssetData[2] = FVector4f(0.0f, 0.0f, 0.0f, 0.0f);
}
}
AssetDataUploadBuffer.ResourceUploadTo(GraphBuilder, AssetDataBufferRDG);
}
void FDistanceFieldSceneData::UpdateDistanceFieldAtlas(
FRDGBuilder& GraphBuilder,
FRDGExternalAccessQueue& ExternalAccessQueue,
const FSceneRenderUpdateInputs& SceneUpdateInputs,
TArray<FDistanceFieldAssetMipId>& DistanceFieldAssetMipAdds,
TArray<FSetElementId>& DistanceFieldAssetRemoves)
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_UpdateDistanceFieldAtlas);
TRACE_CPUPROFILER_EVENT_SCOPE(FDistanceFieldSceneData::UpdateDistanceFieldAtlas);
RDG_EVENT_SCOPE(GraphBuilder, "UpdateDistanceFieldAtlas");
// Mask should be set in FSceneRenderer::PrepareDistanceFieldScene before calling this
check(GraphBuilder.RHICmdList.GetGPUMask() == FRHIGPUMask::All());
TArray<FDistanceFieldAssetMipId> AssetDataUploads;
for (FSetElementId AssetSetId : DistanceFieldAssetRemoves)
{
const FDistanceFieldAssetState& AssetState = AssetStateArray[AssetSetId];
check(AssetState.RefCount == 0);
for (const FDistanceFieldAssetMipState& MipState : AssetState.ReversedMips)
{
if (GDistanceFieldOffsetDataStructure == 0 || GDistanceFieldOffsetDataStructure == 1)
{
IndirectionTableAllocator.Free(MipState.IndirectionTableOffset, MipState.IndirectionDimensions.X * MipState.IndirectionDimensions.Y * MipState.IndirectionDimensions.Z);
}
else
{
IndirectionAtlasLayout.RemoveElement(MipState.IndirectionAtlasOffset.X, MipState.IndirectionAtlasOffset.Y, MipState.IndirectionAtlasOffset.Z,
MipState.IndirectionDimensions.X, MipState.IndirectionDimensions.Y, MipState.IndirectionDimensions.Z);
}
if (MipState.NumBricks > 0)
{
check(MipState.AllocatedBlocks.Num() > 0);
DistanceFieldAtlasBlockAllocator.Free(MipState.AllocatedBlocks);
}
}
// Clear GPU data for removed asset
AssetDataUploads.Add(FDistanceFieldAssetMipId(AssetSetId, 0));
AssetStateArray.Remove(AssetSetId);
}
TArray<FDistanceFieldReadRequest> NewReadRequests;
// Lock the most recent streaming request buffer from the GPU, create new read requests for mips we want to load in the Async Task
ProcessStreamingRequestsFromGPU(NewReadRequests, AssetDataUploads);
TArray<FDistanceFieldReadRequest> ReadRequestsToUpload;
TArray<FDistanceFieldReadRequest> ReadRequestsToCleanUp;
// Build a list of completed read requests that should be uploaded to the GPU this frame
ProcessReadRequests(AssetDataUploads, DistanceFieldAssetMipAdds, ReadRequestsToUpload, ReadRequestsToCleanUp);
int32 NumIndirectionTableAdds = 0;
int32 NumBrickUploads = 0;
// Allocate the mips we are adding this frame from the IndirectionTable and BrickAtlas
for (int32 MipAddIndex = 0; MipAddIndex < DistanceFieldAssetMipAdds.Num(); MipAddIndex++)
{
const int32 Index = GDFReverseAtlasAllocationOrder ? DistanceFieldAssetMipAdds.Num() - MipAddIndex - 1 : MipAddIndex;
FSetElementId AssetSetId = DistanceFieldAssetMipAdds[Index].AssetId;
FDistanceFieldAssetState& AssetState = AssetStateArray[AssetSetId];
const int32 ReversedMipIndex = DistanceFieldAssetMipAdds[Index].ReversedMipIndex;
// Shader requires sequential reversed mips starting from 0
check(ReversedMipIndex == AssetState.ReversedMips.Num());
const int32 MipIndex = AssetState.BuiltData->Mips.Num() - ReversedMipIndex - 1;
const FSparseDistanceFieldMip& MipBuiltData = AssetState.BuiltData->Mips[MipIndex];
FDistanceFieldAssetMipState NewMipState;
NewMipState.NumBricks = MipBuiltData.NumDistanceFieldBricks;
DistanceFieldAtlasBlockAllocator.Allocate(FMath::DivideAndRoundUp(MipBuiltData.NumDistanceFieldBricks, GDistanceFieldBlockAllocatorSizeInBricks), NewMipState.AllocatedBlocks);
NewMipState.IndirectionDimensions = MipBuiltData.IndirectionDimensions;
const int32 NumIndirectionEntries = NewMipState.IndirectionDimensions.X * NewMipState.IndirectionDimensions.Y * NewMipState.IndirectionDimensions.Z;
if (GDistanceFieldOffsetDataStructure == 0 || GDistanceFieldOffsetDataStructure == 1)
{
NewMipState.IndirectionTableOffset = IndirectionTableAllocator.Allocate(NumIndirectionEntries);
}
else
{
IndirectionAtlasLayout.AddElement((uint32&)NewMipState.IndirectionAtlasOffset.X, (uint32&)NewMipState.IndirectionAtlasOffset.Y, (uint32&)NewMipState.IndirectionAtlasOffset.Z,
NewMipState.IndirectionDimensions.X, NewMipState.IndirectionDimensions.Y, NewMipState.IndirectionDimensions.Z);
}
AssetState.ReversedMips.Add(MoveTemp(NewMipState));
NumIndirectionTableAdds += NumIndirectionEntries;
NumBrickUploads += MipBuiltData.NumDistanceFieldBricks;
}
// Now that DistanceFieldAtlasBlockAllocator has been modified, potentially resize the atlas
FRDGTextureRef DistanceFieldBrickVolumeTextureRDG = ResizeBrickAtlasIfNeeded(GraphBuilder, SceneUpdateInputs.GlobalShaderMap);
const uint32 NumAssets = AssetStateArray.GetMaxIndex();
const int32 AssetDataStrideFloat4s = DistanceField::NumMips * AssetDataMipStrideFloat4s;
bool bIndirectionAtlasResized = false;
const uint32 AssetDataSizeBytes = FMath::RoundUpToPowerOfTwo(NumAssets) * AssetDataStrideFloat4s * sizeof(FVector4f);
FRDGBuffer* AssetDataBufferRDG = ResizeStructuredBufferIfNeeded(GraphBuilder, AssetDataBuffer, AssetDataSizeBytes, TEXT("DistanceFields.AssetData"));
FRDGBuffer* IndirectionTableRDG = nullptr;
FRDGTexture* IndirectionAtlasRDG = nullptr;
if (GDistanceFieldOffsetDataStructure == 0)
{
const uint32 IndirectionTableSizeBytes = FMath::Max<uint32>(FMath::RoundUpToPowerOfTwo(IndirectionTableAllocator.GetMaxSize()) * sizeof(uint32), 16);
IndirectionTableRDG = ResizeByteAddressBufferIfNeeded(GraphBuilder, IndirectionTable, IndirectionTableSizeBytes, TEXT("DistanceFields.IndirectionTable.Uint"));
}
else if (GDistanceFieldOffsetDataStructure == 1)
{
const uint32 Indirection2TableNumElements = FMath::Max<uint32>(FMath::RoundUpToPowerOfTwo(IndirectionTableAllocator.GetMaxSize()), 16);
IndirectionTableRDG = ResizeBufferIfNeeded(GraphBuilder, IndirectionTable, PF_A2B10G10R10, Indirection2TableNumElements, TEXT("DistanceFields.IndirectionTable.Float"));
}
else
{
bIndirectionAtlasResized = ResizeIndirectionAtlasIfNeeded(GraphBuilder, SceneUpdateInputs.GlobalShaderMap, IndirectionAtlasRDG);
}
{
const FIntVector AtlasDimensions = BrickTextureDimensionsInBricks * DistanceField::BrickSize;
const SIZE_T AtlasSizeBytes = AtlasDimensions.X * AtlasDimensions.Y * AtlasDimensions.Z * GPixelFormats[DistanceField::DistanceFieldFormat].BlockBytes;
const SIZE_T IndirectionTableBytes = TryGetSize(IndirectionTable);
const FIntVector IndirectionAtlasSize = IndirectionAtlas ? IndirectionAtlas->GetDesc().GetSize() : FIntVector::ZeroValue;
const SIZE_T IndirectionTextureBytes = IndirectionAtlasSize.X * IndirectionAtlasSize.Y * IndirectionAtlasSize.Z * GPixelFormats[PF_A2B10G10R10].BlockBytes;
float AtlasSizeMB = float(AtlasSizeBytes) / (1024.0f * 1024.0f);
float IndirectionTableSizeMB = float(IndirectionTableBytes) / (1024.0f * 1024.0f);
float IndirectionAtlasSizeMB = float(IndirectionTextureBytes) / (1024.0f * 1024.0f);
CSV_CUSTOM_STAT(DistanceField, AtlasMB, AtlasSizeMB, ECsvCustomStatOp::Set);
CSV_CUSTOM_STAT(DistanceField, IndirectionTableMB, IndirectionTableSizeMB, ECsvCustomStatOp::Set);
CSV_CUSTOM_STAT(DistanceField, IndirectionAtlasMB, IndirectionAtlasSizeMB, ECsvCustomStatOp::Set);
}
{
FDistanceFieldAsyncUpdateParameters& UpdateParameters = *GraphBuilder.AllocObject<FDistanceFieldAsyncUpdateParameters>(BrickUploadCoordinatesBuffer, BrickUploadDataBuffer, IndirectionUploadIndicesBuffer, IndirectionUploadDataBuffer);
UpdateParameters.DistanceFieldSceneData = this;
FDistanceFieldIndirectionAtlasUpload& IndirectionAtlasUpload = UpdateParameters.IndirectionAtlasUpload;
FDistanceFieldAtlasUpload& AtlasUpload = UpdateParameters.AtlasUpload;
check(ReadRequestsToUpload.Num() == 0 && NumIndirectionTableAdds == 0 || ReadRequestsToUpload.Num() > 0 && NumIndirectionTableAdds > 0);
if (NumIndirectionTableAdds > 0)
{
// Allocate staging buffer space for the indirection table compute scatter
if (GDistanceFieldOffsetDataStructure == 0)
{
UpdateParameters.IndirectionTableUploader = IndirectionTableUploadBuffer.Begin(GraphBuilder, IndirectionTableRDG, NumIndirectionTableAdds, sizeof(uint32), TEXT("DistanceFields.IndirectionTableUploadBuffer.Uint"));
}
else if (GDistanceFieldOffsetDataStructure == 1)
{
UpdateParameters.IndirectionTableUploader = IndirectionTableUploadBuffer.Begin(GraphBuilder, IndirectionTableRDG, NumIndirectionTableAdds, sizeof(FVector4f), TEXT("DistanceFields.IndirectionFloatUploadBuffer.Float"));
}
else
{
// Allocate staging buffer space for the indirection atlas compute scatter
IndirectionAtlasUpload.Allocate(GraphBuilder.RHICmdList, NumIndirectionTableAdds);
}
}
if (NumBrickUploads > 0)
{
// Allocate staging buffer space for the brick atlas compute scatter
AtlasUpload.Allocate(GraphBuilder.RHICmdList, NumBrickUploads, DistanceField::BrickSize);
}
if (NewReadRequests.Num() || ReadRequestsToUpload.Num() || ReadRequestsToCleanUp.Num())
{
UpdateParameters.NewReadRequests = MoveTemp(NewReadRequests);
UpdateParameters.ReadRequestsToUpload = MoveTemp(ReadRequestsToUpload);
UpdateParameters.ReadRequestsToCleanUp = MoveTemp(ReadRequestsToCleanUp);
GraphBuilder.AddCommandListSetupTask([this, &UpdateParameters] (FRHICommandListBase& RHICmdList)
{
AsyncUpdate(RHICmdList, UpdateParameters);
});
}
if (NumBrickUploads > 0 || NumIndirectionTableAdds > 0)
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_WaitOnDistanceFieldStreamingUpdate);
TRACE_CPUPROFILER_EVENT_SCOPE(WaitOnDistanceFieldStreamingUpdate);
if (NumIndirectionTableAdds > 0)
{
if (GDistanceFieldOffsetDataStructure == 0 || GDistanceFieldOffsetDataStructure == 1)
{
IndirectionTableUploadBuffer.End(GraphBuilder, UpdateParameters.IndirectionTableUploader);
ExternalAccessQueue.Add(IndirectionTableRDG, ERHIAccess::SRVMask, ERHIPipeline::All);
}
else
{
TShaderMapRef<FScatterUploadDistanceFieldIndirectionAtlasCS> ComputeShader(SceneUpdateInputs.GlobalShaderMap);
auto* PassParameters = GraphBuilder.AllocParameters<FScatterUploadDistanceFieldIndirectionAtlasCS::FParameters>();
PassParameters->RWIndirectionAtlas = GraphBuilder.CreateUAV(IndirectionAtlasRDG);
PassParameters->IndirectionUploadIndices = IndirectionAtlasUpload.IndirectionUploadIndicesBuffer.SRV;
PassParameters->IndirectionUploadData = IndirectionAtlasUpload.IndirectionUploadDataBuffer.SRV;
PassParameters->IndirectionAtlasSize = IndirectionAtlasRDG->Desc.GetSize();
PassParameters->NumIndirectionUploads = NumIndirectionTableAdds;
FComputeShaderUtils::AddPass(
GraphBuilder,
RDG_EVENT_NAME("ScatterUploadDistanceFieldIndirectionAtlas"),
ComputeShader,
PassParameters,
FComputeShaderUtils::GetGroupCount(NumIndirectionTableAdds, FScatterUploadDistanceFieldIndirectionAtlasCS::GetGroupSize()));
ExternalAccessQueue.Add(IndirectionAtlasRDG, ERHIAccess::SRVMask);
}
}
}
if (NumBrickUploads > 0)
{
// GRHIMaxDispatchThreadGroupsPerDimension can be MAX_int32 so we need to do this math in 64-bit.
const int32 MaxBrickUploadsPerPass = (int32)FMath::Min<int64>((int64)GRHIMaxDispatchThreadGroupsPerDimension.Z * FScatterUploadDistanceFieldAtlasCS::GetGroupSize() / DistanceField::BrickSize, MAX_int32);
for (int32 StartBrickIndex = 0; StartBrickIndex < NumBrickUploads; StartBrickIndex += MaxBrickUploadsPerPass)
{
const int32 NumBrickUploadsThisPass = FMath::Min(MaxBrickUploadsPerPass, NumBrickUploads - StartBrickIndex);
FScatterUploadDistanceFieldAtlasCS::FParameters* PassParameters = GraphBuilder.AllocParameters<FScatterUploadDistanceFieldAtlasCS::FParameters>();
PassParameters->RWDistanceFieldBrickAtlas = GraphBuilder.CreateUAV(DistanceFieldBrickVolumeTextureRDG);
PassParameters->BrickUploadCoordinates = AtlasUpload.BrickUploadCoordinatesBuffer.SRV;
PassParameters->BrickUploadData = AtlasUpload.BrickUploadDataBuffer.SRV;
PassParameters->StartBrickIndex = StartBrickIndex;
PassParameters->NumBrickUploads = NumBrickUploadsThisPass;
PassParameters->BrickSize = DistanceField::BrickSize;
auto ComputeShader = SceneUpdateInputs.GlobalShaderMap->GetShader<FScatterUploadDistanceFieldAtlasCS>();
FComputeShaderUtils::AddPass(
GraphBuilder,
RDG_EVENT_NAME("ScatterUploadDistanceFieldAtlas"),
ComputeShader,
PassParameters,
FComputeShaderUtils::GetGroupCount(FIntVector(DistanceField::BrickSize, DistanceField::BrickSize, NumBrickUploadsThisPass * DistanceField::BrickSize), FScatterUploadDistanceFieldAtlasCS::GetGroupSize()));
}
}
}
if (bIndirectionAtlasResized)
{
UploadAllAssetData(GraphBuilder, AssetDataBufferRDG);
}
else
{
UploadAssetData(GraphBuilder, AssetDataUploads, AssetDataBufferRDG);
}
GenerateStreamingRequests(GraphBuilder, SceneUpdateInputs);
if (GDistanceFieldAtlasLogStats)
{
const bool bDumpAssetStats = GDistanceFieldAtlasLogStats > 1;
ListMeshDistanceFields(bDumpAssetStats);
GDistanceFieldAtlasLogStats = 0;
}
ExternalAccessQueue.Add(DistanceFieldBrickVolumeTextureRDG, ERHIAccess::SRVMask, ERHIPipeline::All);
ExternalAccessQueue.Add(AssetDataBufferRDG, ERHIAccess::SRVMask, ERHIPipeline::All);
}
void FDistanceFieldSceneData::ListMeshDistanceFields(bool bDumpAssetStats) const
{
SIZE_T BlockAllocatorWasteBytes = 0;
struct FMeshDistanceFieldStats
{
int32 LoadedMips;
int32 WantedMips;
SIZE_T BrickMemoryBytes;
SIZE_T IndirectionMemoryBytes;
FIntVector Resolution;
FName AssetName;
};
struct FMipStats
{
SIZE_T BrickMemoryBytes;
SIZE_T IndirectionMemoryBytes;
};
TArray<FMeshDistanceFieldStats> AssetStats;
TArray<FMipStats> MipStats;
MipStats.AddZeroed(DistanceField::NumMips);
const uint32 BrickSizeBytes = GPixelFormats[DistanceField::DistanceFieldFormat].BlockBytes * DistanceField::BrickSize * DistanceField::BrickSize * DistanceField::BrickSize;
for (TSet<FDistanceFieldAssetState, TFDistanceFieldAssetStateFuncs>::TConstIterator It(AssetStateArray); It; ++It)
{
const FDistanceFieldAssetState& AssetState = *It;
FMeshDistanceFieldStats Stats;
Stats.Resolution = AssetState.BuiltData->Mips[0].IndirectionDimensions * DistanceField::UniqueDataBrickSize;
Stats.BrickMemoryBytes = 0;
Stats.IndirectionMemoryBytes = 0;
Stats.AssetName = AssetState.BuiltData->AssetName;
Stats.LoadedMips = AssetState.ReversedMips.Num();
Stats.WantedMips = AssetState.WantedNumMips;
for (int32 ReversedMipIndex = 0; ReversedMipIndex < AssetState.ReversedMips.Num(); ReversedMipIndex++)
{
const FDistanceFieldAssetMipState& MipState = AssetState.ReversedMips[ReversedMipIndex];
const SIZE_T MipBrickBytes = MipState.NumBricks * BrickSizeBytes;
BlockAllocatorWasteBytes += MipState.AllocatedBlocks.Num() * GDistanceFieldBlockAllocatorSizeInBricks * BrickSizeBytes - MipBrickBytes;
MipStats[ReversedMipIndex].BrickMemoryBytes += MipBrickBytes;
Stats.BrickMemoryBytes += MipBrickBytes;
const SIZE_T MipIndirectionBytes = MipState.IndirectionDimensions.X * MipState.IndirectionDimensions.Y * MipState.IndirectionDimensions.Z * sizeof(uint32);
MipStats[ReversedMipIndex].IndirectionMemoryBytes += MipIndirectionBytes;
Stats.IndirectionMemoryBytes += MipIndirectionBytes;
}
AssetStats.Add(Stats);
}
struct FMeshDistanceFieldStatsSorter
{
bool operator()( const FMeshDistanceFieldStats& A, const FMeshDistanceFieldStats& B ) const
{
return A.BrickMemoryBytes > B.BrickMemoryBytes;
}
};
AssetStats.Sort(FMeshDistanceFieldStatsSorter());
const FIntVector AtlasDimensions = BrickTextureDimensionsInBricks * DistanceField::BrickSize;
const SIZE_T AtlasSizeBytes = AtlasDimensions.X * AtlasDimensions.Y * AtlasDimensions.Z * GPixelFormats[DistanceField::DistanceFieldFormat].BlockBytes;
const SIZE_T AtlasUsedBytes = DistanceFieldAtlasBlockAllocator.GetAllocatedSize() * GDistanceFieldBlockAllocatorSizeInBricks * BrickSizeBytes;
const float BlockAllocatorWasteMb = BlockAllocatorWasteBytes / 1024.0f / 1024.0f;
const SIZE_T IndirectionTableBytes = TryGetSize(IndirectionTable);
const FIntVector IndirectionAtlasSize = IndirectionAtlas ? IndirectionAtlas->GetDesc().GetSize() : FIntVector::ZeroValue;
const SIZE_T IndirectionTextureBytes = IndirectionAtlasSize.X * IndirectionAtlasSize.Y * IndirectionAtlasSize.Z * GPixelFormats[PF_A2B10G10R10].BlockBytes;
const int32 BrickAtlasSizeXYInBricks = CVarBrickAtlasSizeXYInBricks.GetValueOnRenderThread();
const float MaxAtlasSizeMb = CVarMaxAtlasDepthInBricks.GetValueOnRenderThread() * BrickAtlasSizeXYInBricks * BrickAtlasSizeXYInBricks * BrickSizeBytes / 1024.0f / 1024.0f;
UE_LOG(LogDistanceField, Log,
TEXT("Mesh Distance Field Atlas %ux%ux%u = %.1fMb (%.1fMb target max), with %.1fMb free, %.1fMb block allocator waste, Indirection Table %.1fMb, Indirection Texture %.1fMb"),
AtlasDimensions.X,
AtlasDimensions.Y,
AtlasDimensions.Z,
AtlasSizeBytes / 1024.0f / 1024.0f,
MaxAtlasSizeMb,
(AtlasSizeBytes - AtlasUsedBytes) / 1024.0f / 1024.0f,
BlockAllocatorWasteMb,
IndirectionTableBytes / 1024.0f / 1024.0f,
IndirectionTextureBytes / 1024.0f / 1024.0f);
for (int32 ReversedMipIndex = 0; ReversedMipIndex < DistanceField::NumMips; ReversedMipIndex++)
{
UE_LOG(LogDistanceField, Log, TEXT(" Bricks at Mip%u: %.1fMb, %.1f%%"),
ReversedMipIndex,
MipStats[ReversedMipIndex].BrickMemoryBytes / 1024.0f / 1024.0f,
100.0f * MipStats[ReversedMipIndex].BrickMemoryBytes / (float)AtlasUsedBytes);
}
if (bDumpAssetStats)
{
UE_LOG(LogDistanceField, Log, TEXT(""));
UE_LOG(LogDistanceField, Log, TEXT("Dumping mesh distance fields for %u mesh assets"), AssetStats.Num());
UE_LOG(LogDistanceField, Log, TEXT(" Memory Mb, Loaded Mips / Wanted Mips, Mip0 Resolution, Asset Name"));
for (int32 EntryIndex = 0; EntryIndex < AssetStats.Num(); EntryIndex++)
{
const FMeshDistanceFieldStats& MeshStats = AssetStats[EntryIndex];
UE_LOG(LogDistanceField, Log, TEXT(" %.2fMb, %u%s, %dx%dx%d, %s"),
(MeshStats.BrickMemoryBytes + MeshStats.IndirectionMemoryBytes) / 1024.0f / 1024.0f,
MeshStats.LoadedMips,
MeshStats.LoadedMips == MeshStats.WantedMips ? TEXT("") : *FString::Printf(TEXT(" / %u"), MeshStats.WantedMips),
MeshStats.Resolution.X,
MeshStats.Resolution.Y,
MeshStats.Resolution.Z,
*MeshStats.AssetName.ToString());
}
}
}