Files
UnrealEngine/Engine/Plugins/Experimental/Water/Source/Runtime/Private/WaterQuadTreeGPU.cpp
2025-05-18 13:04:45 +08:00

1080 lines
54 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "WaterQuadTreeGPU.h"
#include "CoreMinimal.h"
#include "GlobalShader.h"
#include "RenderUtils.h"
#include "RenderGraphUtils.h"
#include "RHI.h"
#include "RHIStaticStates.h"
#include "RHIFeatureLevel.h"
#include "PipelineStateCache.h"
#include "PixelShaderUtils.h"
#include "SystemTextures.h"
#include "ShaderPrintParameters.h"
#include "SceneRendering.h"
#include "Math/Halton.h"
#include "HZB.h"
DECLARE_GPU_STAT(FWaterQuadTreeGPU_Init);
DECLARE_GPU_STAT(FWaterQuadTreeGPU_Traverse);
int32 GWaterQuadTreeParallelPrefixSum = 1;
static FAutoConsoleVariableRef CVarWaterQuadTreeParallelPrefixSum(
TEXT("r.Water.WaterMesh.GPUQuadTree.ParallelPrefixSum"),
GWaterQuadTreeParallelPrefixSum,
TEXT("Enables using a parallel prefix sum algorithm for the water quadtree indirect draw call pipeline."),
ECVF_RenderThreadSafe);
float GWaterQuadTreeZBoundsPadding = 200.0f;
static FAutoConsoleVariableRef CVarWaterQuadTreeZBoundsPadding(
TEXT("r.Water.WaterMesh.GPUQuadTree.ZBoundsPadding"),
GWaterQuadTreeZBoundsPadding,
TEXT("Amount of padding to apply to the Z bounds of each quadtree node."
" Necessary for sloped rivers, whose complete Z bounds can be underestimated due to using non-conservative rasterization to build the quadtree."),
ECVF_RenderThreadSafe);
int32 GWaterQuadTreeOcclusionCulling = 3;
static FAutoConsoleVariableRef CVarWaterQuadTreeOcclusionCulling(
TEXT("r.Water.WaterMesh.GPUQuadTree.OcclusionCulling"),
GWaterQuadTreeOcclusionCulling,
TEXT("0: Disabled, 1: HZB Occlusion Queries, 2: Pixel Precise Raster Queries, 3: HZB + Pixel Precise Raster Queries"),
ECVF_RenderThreadSafe);
class FWaterQuadTreeVS : public FGlobalShader
{
public:
DECLARE_GLOBAL_SHADER(FWaterQuadTreeVS);
SHADER_USE_PARAMETER_STRUCT(FWaterQuadTreeVS, FGlobalShader);
class FRasterMode : SHADER_PERMUTATION_SPARSE_INT("RASTER_MODE", 0, 1, 2);
using FPermutationDomain = TShaderPermutationDomain<FRasterMode>;
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER(FMatrix44f, Transform)
SHADER_PARAMETER(FVector2f, JitterScale)
SHADER_PARAMETER(FVector2f, HalfPixelSize)
END_SHADER_PARAMETER_STRUCT()
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
}
};
IMPLEMENT_GLOBAL_SHADER(FWaterQuadTreeVS, "/Plugin/Water/Private/WaterQuadTreeVertexShader.usf", "Main", SF_Vertex);
class FWaterQuadTreePS : public FGlobalShader
{
public:
DECLARE_GLOBAL_SHADER(FWaterQuadTreePS);
SHADER_USE_PARAMETER_STRUCT(FWaterQuadTreePS, FGlobalShader);
class FClipConservativeTriangle : SHADER_PERMUTATION_BOOL("CLIP_CONSERVATIVE_TRIANGLE");
using FPermutationDomain = TShaderPermutationDomain<FClipConservativeTriangle>;
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER(uint32, WaterBodyRenderDataIndex)
SHADER_PARAMETER(int32, Priority)
SHADER_PARAMETER(float, WaterBodyMinZ)
SHADER_PARAMETER(float, WaterBodyMaxZ)
SHADER_PARAMETER(float, MaxWaveHeight)
SHADER_PARAMETER(uint32, bIsRiver)
RENDER_TARGET_BINDING_SLOTS()
END_SHADER_PARAMETER_STRUCT()
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
}
};
IMPLEMENT_GLOBAL_SHADER(FWaterQuadTreePS, "/Plugin/Water/Private/WaterQuadTreePixelShader.usf", "Main", SF_Pixel);
BEGIN_SHADER_PARAMETER_STRUCT(FWaterQuadTreeParameters, )
SHADER_PARAMETER_STRUCT_INCLUDE(FWaterQuadTreeVS::FParameters, VS)
SHADER_PARAMETER_STRUCT_INCLUDE(FWaterQuadTreePS::FParameters, PS)
END_SHADER_PARAMETER_STRUCT()
class FWaterQuadTreeMergePS : public FGlobalShader
{
public:
DECLARE_GLOBAL_SHADER(FWaterQuadTreeMergePS);
SHADER_USE_PARAMETER_STRUCT(FWaterQuadTreeMergePS, FGlobalShader);
class FNumMSAASamples : SHADER_PERMUTATION_SPARSE_INT("NUM_MSAA_SAMPLES", 1, 2, 4, 8);
using FPermutationDomain = TShaderPermutationDomain<FNumMSAASamples>;
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_RDG_TEXTURE(Texture2DMS, WaterBodyRasterTextureMS)
SHADER_PARAMETER_RDG_TEXTURE(Texture2DMS, ZBoundsRasterTextureMS)
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, WaterBodyRasterTexture)
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, ZBoundsRasterTexture)
SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer, WaterBodyRenderData)
SHADER_PARAMETER(int32, SuperSamplingFactor)
SHADER_PARAMETER(float, RcpCaptureDepthRange)
SHADER_PARAMETER(float, ZBoundsPadding)
RENDER_TARGET_BINDING_SLOTS()
END_SHADER_PARAMETER_STRUCT()
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
}
};
IMPLEMENT_GLOBAL_SHADER(FWaterQuadTreeMergePS, "/Plugin/Water/Private/WaterQuadTreeMerge.usf", "Main", SF_Pixel);
class FWaterQuadTreeBuildPS : public FGlobalShader
{
public:
DECLARE_GLOBAL_SHADER(FWaterQuadTreeBuildPS);
SHADER_USE_PARAMETER_STRUCT(FWaterQuadTreeBuildPS, FGlobalShader);
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_RDG_TEXTURE_SRV(Texture2D, QuadTreeTexture)
SHADER_PARAMETER_RDG_TEXTURE_SRV(Texture2D, WaterZBoundsTexture)
SHADER_PARAMETER(int32, InputMipLevelIndex)
RENDER_TARGET_BINDING_SLOTS()
END_SHADER_PARAMETER_STRUCT()
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
}
};
IMPLEMENT_GLOBAL_SHADER(FWaterQuadTreeBuildPS, "/Plugin/Water/Private/WaterQuadTreeBuild.usf", "Main", SF_Pixel);
class FWaterQuadTreeInitializeIndirectArgsCS : public FGlobalShader
{
public:
DECLARE_GLOBAL_SHADER(FWaterQuadTreeInitializeIndirectArgsCS);
SHADER_USE_PARAMETER_STRUCT(FWaterQuadTreeInitializeIndirectArgsCS, FGlobalShader);
class FPreciseOcclusionQueries : SHADER_PERMUTATION_BOOL("PRECISE_OCCLUSION_QUERIES");
using FPermutationDomain = TShaderPermutationDomain<FPreciseOcclusionQueries>;
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer, IndirectArgs)
SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer, OcclusionQueryArgs)
SHADER_PARAMETER(uint32, NumDrawBuckets)
SHADER_PARAMETER(uint32, NumViews)
SHADER_PARAMETER(uint32, NumQuads)
END_SHADER_PARAMETER_STRUCT()
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
OutEnvironment.SetDefine(TEXT("INITIALIZE_INDIRECT_ARGS"), 1);
}
};
IMPLEMENT_GLOBAL_SHADER(FWaterQuadTreeInitializeIndirectArgsCS, "/Plugin/Water/Private/WaterQuadTreeDraws.usf", "MainCS", SF_Compute);
class FWaterQuadTreeClearPerViewBuffersCS : public FGlobalShader
{
public:
DECLARE_GLOBAL_SHADER(FWaterQuadTreeClearPerViewBuffersCS);
SHADER_USE_PARAMETER_STRUCT(FWaterQuadTreeClearPerViewBuffersCS, FGlobalShader);
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_RDG_BUFFER_UAV(RWByteAddressBuffer, BucketCounts)
SHADER_PARAMETER_RDG_BUFFER_UAV(RWByteAddressBuffer, PackedNodes)
SHADER_PARAMETER(uint32, NumDrawBuckets)
END_SHADER_PARAMETER_STRUCT()
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
OutEnvironment.SetDefine(TEXT("CLEAR_PER_VIEW_BUFFERS"), 1);
}
};
IMPLEMENT_GLOBAL_SHADER(FWaterQuadTreeClearPerViewBuffersCS, "/Plugin/Water/Private/WaterQuadTreeDraws.usf", "MainCS", SF_Compute);
class FWaterQuadTreeTraverseCS : public FGlobalShader
{
public:
DECLARE_GLOBAL_SHADER(FWaterQuadTreeTraverseCS);
SHADER_USE_PARAMETER_STRUCT(FWaterQuadTreeTraverseCS, FGlobalShader);
class FPreciseOcclusionQueries : SHADER_PERMUTATION_BOOL("PRECISE_OCCLUSION_QUERIES");
using FPermutationDomain = TShaderPermutationDomain<FPreciseOcclusionQueries>;
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, View)
SHADER_PARAMETER_RDG_BUFFER_UAV(RWByteAddressBuffer, PackedNodes)
SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer, OcclusionQueryBoxes)
SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer, OcclusionVisibility)
SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer, OcclusionQueryArgs)
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, QuadTreeTexture)
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, WaterZBoundsTexture)
SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer, WaterBodyRenderData)
SHADER_PARAMETER_STRUCT_INCLUDE(FHZBParameters, HZBParameters)
SHADER_PARAMETER(FVector4f, CullingBoundsAABB)
SHADER_PARAMETER(FVector3f, QuadTreePosition)
SHADER_PARAMETER(FVector3f, ObserverPosition)
SHADER_PARAMETER(uint32, QuadTreeResolutionX)
SHADER_PARAMETER(uint32, QuadTreeResolutionY)
SHADER_PARAMETER(uint32, ViewIndex)
SHADER_PARAMETER(float, LeafSize)
SHADER_PARAMETER(float, LODScale)
SHADER_PARAMETER(float, CaptureDepthRange)
SHADER_PARAMETER(int32, ForceCollapseDensityLevel)
SHADER_PARAMETER(uint32, NumLODs)
SHADER_PARAMETER(uint32, NumDispatchedThreads)
SHADER_PARAMETER(uint32, bHZBOcclusionCullingEnabled)
END_SHADER_PARAMETER_STRUCT()
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
OutEnvironment.SetDefine(TEXT("QUAD_TREE_TRAVERSE"), 1);
}
};
IMPLEMENT_GLOBAL_SHADER(FWaterQuadTreeTraverseCS, "/Plugin/Water/Private/WaterQuadTreeDraws.usf", "MainCS", SF_Compute);
class FWaterQuadTreeOcclusionQueryVS : public FGlobalShader
{
public:
DECLARE_GLOBAL_SHADER(FWaterQuadTreeOcclusionQueryVS);
SHADER_USE_PARAMETER_STRUCT(FWaterQuadTreeOcclusionQueryVS, FGlobalShader);
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_STRUCT_REF(FViewUniformShaderParameters, View)
SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer, OcclusionQueryBoxes)
END_SHADER_PARAMETER_STRUCT()
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
OutEnvironment.SetDefine(TEXT("OCCLUSION_QUERY_RASTER_VS"), 1);
}
};
IMPLEMENT_GLOBAL_SHADER(FWaterQuadTreeOcclusionQueryVS, "/Plugin/Water/Private/WaterQuadTreeDraws.usf", "MainVS", SF_Vertex);
class FWaterQuadTreeOcclusionQueryPS : public FGlobalShader
{
public:
DECLARE_GLOBAL_SHADER(FWaterQuadTreeOcclusionQueryPS);
SHADER_USE_PARAMETER_STRUCT(FWaterQuadTreeOcclusionQueryPS, FGlobalShader);
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer, Visibility)
RENDER_TARGET_BINDING_SLOTS()
END_SHADER_PARAMETER_STRUCT()
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
OutEnvironment.SetDefine(TEXT("OCCLUSION_QUERY_RASTER_PS"), 1);
}
};
IMPLEMENT_GLOBAL_SHADER(FWaterQuadTreeOcclusionQueryPS, "/Plugin/Water/Private/WaterQuadTreeDraws.usf", "MainPS", SF_Pixel);
BEGIN_SHADER_PARAMETER_STRUCT(FWaterQuadTreeOcclusionQueryParameters, )
RDG_BUFFER_ACCESS(IndirectDrawArgsBuffer, ERHIAccess::IndirectArgs)
SHADER_PARAMETER_STRUCT_INCLUDE(FWaterQuadTreeOcclusionQueryVS::FParameters, VS)
SHADER_PARAMETER_STRUCT_INCLUDE(FWaterQuadTreeOcclusionQueryPS::FParameters, PS)
END_SHADER_PARAMETER_STRUCT()
class FWaterQuadTreeBucketCountsCS : public FGlobalShader
{
public:
DECLARE_GLOBAL_SHADER(FWaterQuadTreeBucketCountsCS);
SHADER_USE_PARAMETER_STRUCT(FWaterQuadTreeBucketCountsCS, FGlobalShader);
class FPreciseOcclusionQueries : SHADER_PERMUTATION_BOOL("PRECISE_OCCLUSION_QUERIES");
using FPermutationDomain = TShaderPermutationDomain<FPreciseOcclusionQueries>;
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_RDG_BUFFER_UAV(RWByteAddressBuffer, BucketCounts)
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, QuadTreeTexture)
SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer, WaterBodyRenderData)
SHADER_PARAMETER_RDG_BUFFER_SRV(ByteAddressBuffer, PackedNodes)
SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer, OcclusionResults)
SHADER_PARAMETER(uint32, NumDispatchedThreads)
SHADER_PARAMETER(uint32, NumDensities)
SHADER_PARAMETER(uint32, NumQuadsLOD0)
SHADER_PARAMETER(uint32, NumQuadsPerDraw)
END_SHADER_PARAMETER_STRUCT()
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
OutEnvironment.SetDefine(TEXT("COMPUTE_BUCKET_COUNTS"), 1);
}
};
IMPLEMENT_GLOBAL_SHADER(FWaterQuadTreeBucketCountsCS, "/Plugin/Water/Private/WaterQuadTreeDraws.usf", "MainCS", SF_Compute);
class FWaterQuadTreeBucketPrefixSumCS : public FGlobalShader
{
public:
DECLARE_GLOBAL_SHADER(FWaterQuadTreeBucketPrefixSumCS);
SHADER_USE_PARAMETER_STRUCT(FWaterQuadTreeBucketPrefixSumCS, FGlobalShader);
class FParallelPrefixSum : SHADER_PERMUTATION_BOOL("PARALLEL_PREFIX_SUM");
using FPermutationDomain = TShaderPermutationDomain<FParallelPrefixSum>;
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer, BucketPrefixSums)
SHADER_PARAMETER_RDG_BUFFER_SRV(ByteAddressBuffer, BucketCounts)
SHADER_PARAMETER(uint32, NumBuckets)
SHADER_PARAMETER(uint32, OutputOffset)
SHADER_PARAMETER(uint32, bWriteTotalSumAtBufferEnd)
END_SHADER_PARAMETER_STRUCT()
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
OutEnvironment.SetDefine(TEXT("COMPUTE_BUCKET_PREFIX_SUM"), 1);
}
};
IMPLEMENT_GLOBAL_SHADER(FWaterQuadTreeBucketPrefixSumCS, "/Plugin/Water/Private/WaterQuadTreeDraws.usf", "MainCS", SF_Compute);
class FWaterQuadTreeGenerateInstanceDataCS : public FGlobalShader
{
public:
DECLARE_GLOBAL_SHADER(FWaterQuadTreeGenerateInstanceDataCS);
SHADER_USE_PARAMETER_STRUCT(FWaterQuadTreeGenerateInstanceDataCS, FGlobalShader);
class FPreciseOcclusionQueries : SHADER_PERMUTATION_BOOL("PRECISE_OCCLUSION_QUERIES");
using FPermutationDomain = TShaderPermutationDomain<FPreciseOcclusionQueries>;
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer, IndirectArgs)
SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer, InstanceData0)
SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer, InstanceData1)
SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer, InstanceData2)
SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer, InstanceData3)
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, QuadTreeTexture)
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, WaterZBoundsTexture)
SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer, WaterBodyRenderData)
SHADER_PARAMETER_RDG_BUFFER_SRV(ByteAddressBuffer, PackedNodes)
SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer, InstanceDataOffsets)
SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer, OcclusionResults)
SHADER_PARAMETER(FVector3f, QuadTreePosition)
SHADER_PARAMETER(FVector3f, ObserverPosition)
SHADER_PARAMETER(uint32, QuadTreeResolutionX)
SHADER_PARAMETER(uint32, QuadTreeResolutionY)
SHADER_PARAMETER(uint32, NumDensities)
SHADER_PARAMETER(uint32, NumMaterials)
SHADER_PARAMETER(uint32, NumDispatchedThreads)
SHADER_PARAMETER(uint32, BucketIndexOffset)
SHADER_PARAMETER(uint32, NumLODs)
SHADER_PARAMETER(uint32, NumQuadsLOD0)
SHADER_PARAMETER(uint32, NumQuadsPerDraw)
SHADER_PARAMETER(float, LeafSize)
SHADER_PARAMETER(float, LODScale)
SHADER_PARAMETER(float, CaptureDepthRange)
SHADER_PARAMETER(uint32, StereoPassInstanceFactor)
SHADER_PARAMETER(uint32, bWithWaterSelectionSupport)
SHADER_PARAMETER(uint32, bLODMorphingEnabled)
SHADER_PARAMETER(uint32, bInstancedStereoRendering)
END_SHADER_PARAMETER_STRUCT()
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
OutEnvironment.SetDefine(TEXT("GENERATE_INSTANCE_DATA"), 1);
}
};
IMPLEMENT_GLOBAL_SHADER(FWaterQuadTreeGenerateInstanceDataCS, "/Plugin/Water/Private/WaterQuadTreeDraws.usf", "MainCS", SF_Compute);
class FWaterQuadTreeDebugCS : public FGlobalShader
{
public:
DECLARE_GLOBAL_SHADER(FWaterQuadTreeDebugCS);
SHADER_USE_PARAMETER_STRUCT(FWaterQuadTreeDebugCS, FGlobalShader);
class FPreciseOcclusionQueries : SHADER_PERMUTATION_BOOL("PRECISE_OCCLUSION_QUERIES");
using FPermutationDomain = TShaderPermutationDomain<FPreciseOcclusionQueries>;
BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
SHADER_PARAMETER_STRUCT_INCLUDE(ShaderPrint::FShaderParameters, ShaderPrintUniformBuffer)
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, QuadTreeTexture)
SHADER_PARAMETER_RDG_TEXTURE(Texture2D, WaterZBoundsTexture)
SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer, WaterBodyRenderData)
SHADER_PARAMETER_RDG_BUFFER_SRV(ByteAddressBuffer, PackedNodes)
SHADER_PARAMETER_RDG_BUFFER_SRV(Buffer, OcclusionResults)
SHADER_PARAMETER(FVector3f, QuadTreePosition)
SHADER_PARAMETER(uint32, NumDispatchedThreads)
SHADER_PARAMETER(float, LeafSize)
SHADER_PARAMETER(float, CaptureDepthRange)
END_SHADER_PARAMETER_STRUCT()
static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters)
{
return ShaderPrint::IsSupported(Parameters.Platform);
}
static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment)
{
FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
ShaderPrint::ModifyCompilationEnvironment(Parameters, OutEnvironment);
OutEnvironment.SetDefine(TEXT("DEBUG_SHOW_TILES"), 1);
}
};
IMPLEMENT_GLOBAL_SHADER(FWaterQuadTreeDebugCS, "/Plugin/Water/Private/WaterQuadTreeDraws.usf", "MainCS", SF_Compute);
class FJitterOffsetVertexBuffer : public FVertexBuffer
{
public:
static constexpr int32 NumHaltonSamples = 16;
virtual void InitRHI(FRHICommandListBase& RHICmdList) override
{
// Create the texture RHI.
const int32 NumMSAASamples = 2 + 4 + 8 + 16;
const int32 NumFloats = 2 * (NumHaltonSamples + NumMSAASamples);
const FRHIBufferCreateDesc CreateDesc =
FRHIBufferCreateDesc::CreateVertex<float>(TEXT("JitterOffsetVertexBuffer"), NumFloats)
.AddUsage(EBufferUsageFlags::Static | EBufferUsageFlags::ShaderResource)
.SetInitActionInitializer()
.DetermineInitialState();
TRHIBufferInitializer<float> Initializer = RHICmdList.CreateBufferInitializer(CreateDesc);
float* BufferData = Initializer.GetWritableData();
// Halton
for (int32 i = 0; i < NumHaltonSamples; ++i)
{
BufferData[i * 2 + 0] = Halton(i + 1, 2) - 0.5f;
BufferData[i * 2 + 1] = Halton(i + 1, 3) - 0.5f;
}
BufferData += NumHaltonSamples * 2;
// MSAA
{
const float Offsets[] =
{
/*2x*/ 4, 4, -4, -4,
/*4x*/ -2, -6, 6, -2, -6, 2, 2, 6,
/*8x*/ 1, -3, -1, 3, 5, 1, -3, -5, -5, 5, -7, -1, 3, 7, 7, -7,
/*16x*/ 1, 1, -1, -3, -3, 2, 4, -1, -5, -2, 2, 5, 5, 3, 3, -5, 2, 6, 0, -7, -4, -6, -6, 4, -8, 0, 7, -4, 6, 7, -7, -8
};
for (int32 i = 0; i < NumMSAASamples; ++i)
{
// Remap from (-8) - 7 to (-0.5) - 0.5
BufferData[i * 2 + 0] = Offsets[i * 2 + 0] / 16.0f;
BufferData[i * 2 + 1] = Offsets[i * 2 + 1] / 16.0f;
}
}
VertexBufferRHI = Initializer.Finalize();
}
static constexpr int32 GetMSAADataOffset(int32 NumSamples)
{
int32 SampleOffset = NumHaltonSamples;
SampleOffset += NumSamples > 2 ? 2 : 0;
SampleOffset += NumSamples > 4 ? 4 : 0;
SampleOffset += NumSamples > 8 ? 8 : 0;
return SampleOffset * 2 * sizeof(float);
}
};
TGlobalResource<FJitterOffsetVertexBuffer> GJitterOffsetVertexBuffer;
class FVector3AndInstancedVector2VertexDeclaration : public FRenderResource
{
public:
FVertexDeclarationRHIRef VertexDeclarationRHI;
virtual void InitRHI(FRHICommandListBase& RHICmdList) override
{
FVertexDeclarationElementList Elements;
Elements.Add(FVertexElement(0, 0, VET_Float3, 0, sizeof(FVector3f)));
Elements.Add(FVertexElement(1, 0, VET_Float2, 1, sizeof(FVector2f), true /*bInUseInstanceIndex*/));
VertexDeclarationRHI = PipelineStateCache::GetOrCreateVertexDeclaration(Elements);
}
virtual void ReleaseRHI() override
{
VertexDeclarationRHI.SafeRelease();
}
};
TGlobalResource<FVector3AndInstancedVector2VertexDeclaration> GVector3AndInstancedVector2VertexDeclaration;
class FVector3AndThreeVector2VertexDeclaration : public FRenderResource
{
public:
FVertexDeclarationRHIRef VertexDeclarationRHI;
virtual void InitRHI(FRHICommandListBase& RHICmdList) override
{
FVertexDeclarationElementList Elements;
Elements.Add(FVertexElement(0, 0, VET_Float3, 0, sizeof(FVector3f)));
Elements.Add(FVertexElement(1, 0, VET_Float2, 1, sizeof(FVector2f) * 3));
Elements.Add(FVertexElement(1, 8, VET_Float2, 2, sizeof(FVector2f) * 3));
Elements.Add(FVertexElement(1, 16, VET_Float2, 3, sizeof(FVector2f) * 3));
VertexDeclarationRHI = PipelineStateCache::GetOrCreateVertexDeclaration(Elements);
}
virtual void ReleaseRHI() override
{
VertexDeclarationRHI.SafeRelease();
}
};
TGlobalResource<FVector3AndThreeVector2VertexDeclaration> GVector3AndAndThreeVector2VertexDeclaration;
void FWaterQuadTreeGPU::Init(FRDGBuilder& GraphBuilder, const FInitParams& Params, TArray<FDraw>& Draws)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FWaterQuadTreeGPU::Init);
RDG_EVENT_SCOPE_STAT(GraphBuilder, FWaterQuadTreeGPU_Init, "FWaterQuadTreeGPU::Init");
RDG_GPU_STAT_SCOPE(GraphBuilder, FWaterQuadTreeGPU_Init);
// Create resources
{
check(!bInitialized);
check(!QuadTreeTexture);
check(!WaterZBoundsTexture);
check(!WaterBodyRenderDataBuffer);
// Make sure the quadtree is a power of two
const FIntPoint ResolutionRaw = Params.RequestedQuadTreeResolution;
const FIntPoint ResolutionPow2 = FIntPoint(FMath::RoundUpToPowerOfTwo(ResolutionRaw.X), FMath::RoundUpToPowerOfTwo(ResolutionRaw.Y));
const int32 MaxDim = (int32)FMath::Max(ResolutionPow2.X, ResolutionPow2.Y);
const int32 NumMipLevels = (int32)FMath::FloorLog2(MaxDim) + 1;
FRHITextureCreateDesc QuadTreeTextureCreateDesc = FRHITextureCreateDesc::Create2D(TEXT("WaterQuadTree.QuadTree"), ResolutionPow2, PF_B8G8R8A8)
.SetNumMips(NumMipLevels)
.SetFlags(TexCreate_RenderTargetable | TexCreate_ShaderResource);
QuadTreeTexture = RHICreateTexture(QuadTreeTextureCreateDesc);
FRHITextureCreateDesc ZBoundsTextureCreateDesc = FRHITextureCreateDesc::Create2D(TEXT("WaterQuadTree.ZBoundsTexture"), ResolutionPow2, PF_A2B10G10R10)
.SetNumMips(NumMipLevels)
.SetFlags(TexCreate_RenderTargetable | TexCreate_ShaderResource);
WaterZBoundsTexture = RHICreateTexture(ZBoundsTextureCreateDesc);
WaterBodyRenderDataBuffer = GraphBuilder.ConvertToExternalBuffer(CreateStructuredBuffer<FWaterBodyRenderDataGPU>(GraphBuilder, TEXT("WaterQuadTree.WaterBodyRenderData"), Params.WaterBodyRenderData));
check(Params.CaptureDepthRange > 0.0f);
CaptureDepthRange = Params.CaptureDepthRange;
bInitialized = true;
}
auto GetNumMSAASamples = [](int32 RequestedNumSamples)
{
int32 Result = 16;
Result = RequestedNumSamples < 16 ? 8 : Result;
Result = RequestedNumSamples < 8 ? 4 : Result;
Result = RequestedNumSamples < 4 ? 2 : Result;
Result = RequestedNumSamples < 2 ? 1 : Result;
return Result;
};
FGlobalShaderMap* ShaderMap = GetGlobalShaderMap(GMaxRHIFeatureLevel);
FRDGTexture* QuadTreeTextureRDG = GraphBuilder.RegisterExternalTexture(CreateRenderTarget(QuadTreeTexture, TEXT("WaterQuadTree.QuadTree")));
FRDGTexture* WaterZBoundsTextureRDG = GraphBuilder.RegisterExternalTexture(CreateRenderTarget(WaterZBoundsTexture, TEXT("WaterQuadTree.ZBoundsTexture")));
FRDGBuffer* WaterBodyRenderDataBufferRDG = GraphBuilder.RegisterExternalBuffer(WaterBodyRenderDataBuffer);
FRDGBufferSRV* WaterBodyRenderDataBufferSRV = GraphBuilder.CreateSRV(FRDGBufferSRVDesc(WaterBodyRenderDataBufferRDG));
const FIntPoint QuadTreeResolution = QuadTreeTextureRDG->Desc.Extent;
// Not necessarily a power of two or a multiple of the quadtree LOD0 resolution. We rely on Texture.Load() to return 0 for out of bounds accesses.
// Padding this texture is not required as we will not need to create a mip chain for it; but rather use it to initialize mip0 of our quadtree texture.
const FIntPoint RasterResolution = Params.RequestedQuadTreeResolution * Params.SuperSamplingFactor;
const int32 NumMipLevels = QuadTreeTextureRDG->Desc.NumMips;
const uint8 NumMSAASamples = FMath::Min(GetNumMSAASamples(Params.NumMSAASamples), 8);
const int32 NumJitterSamples = Params.bUseMSAAJitterPattern ? GetNumMSAASamples(Params.NumJitterSamples) : FMath::Clamp(Params.NumJitterSamples, 1, 16);
FRDGTextureDesc WaterBodyRasterTextureDesc = FRDGTextureDesc::Create2D(RasterResolution, PF_A2B10G10R10, FClearValueBinding::Black, TexCreate_RenderTargetable | TexCreate_ShaderResource, 1, NumMSAASamples);
FRDGTexture* WaterBodyRasterTexture = GraphBuilder.CreateTexture(WaterBodyRasterTextureDesc, TEXT("WaterQuadTree.WaterBodyRasterTexture"));
FRDGTextureDesc ZBoundsRasterTextureDesc = FRDGTextureDesc::Create2D(RasterResolution, PF_A2B10G10R10, FClearValueBinding::Black, TexCreate_RenderTargetable | TexCreate_ShaderResource, 1, NumMSAASamples);
FRDGTexture* ZBoundsRasterTexture = GraphBuilder.CreateTexture(ZBoundsRasterTextureDesc, TEXT("WaterQuadTree.ZBoundsRasterTexture"));
// Raster water body meshes
{
enum class ERasterMode { Regular = 0, Jittered = 1, Conservative = 2 };
const ERasterMode RasterMode = Params.bUseConservativeRasterization ? ERasterMode::Conservative : NumJitterSamples > 1 ? ERasterMode::Jittered : ERasterMode::Regular;
FWaterQuadTreeVS::FPermutationDomain VSPermutationDomain;
VSPermutationDomain.Set<FWaterQuadTreeVS::FRasterMode>(static_cast<int32>(RasterMode));
TShaderMapRef<FWaterQuadTreeVS> VertexShader(ShaderMap, VSPermutationDomain);
FWaterQuadTreePS::FPermutationDomain PSPermutationDomain;
PSPermutationDomain.Set<FWaterQuadTreePS::FClipConservativeTriangle>(RasterMode == ERasterMode::Conservative);
TShaderMapRef<FWaterQuadTreePS> PixelShader(ShaderMap, PSPermutationDomain);
FWaterQuadTreeParameters* PassParameters = GraphBuilder.AllocParameters<FWaterQuadTreeParameters>();
PassParameters->PS.RenderTargets[0] = FRenderTargetBinding(WaterBodyRasterTexture, ERenderTargetLoadAction::EClear);
PassParameters->PS.RenderTargets[1] = FRenderTargetBinding(ZBoundsRasterTexture, ERenderTargetLoadAction::EClear);
GraphBuilder.AddPass(
RDG_EVENT_NAME("WaterQuadTreeRaster"),
PassParameters,
ERDGPassFlags::Raster,
[PassParameters, RasterResolution, NumJitterSamples, RasterMode, bMSAAPattern = Params.bUseMSAAJitterPattern, JitterFootprint = Params.JitterSampleFootprint, Draws = MoveTemp(Draws), VertexShader, PixelShader](FRHICommandList& RHICmdList)
{
RHICmdList.SetViewport(0.0f, 0.0f, 0.0f, RasterResolution.X, RasterResolution.Y, 1.0f);
FGraphicsPipelineStateInitializer GraphicsPSOInit;
RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
GraphicsPSOInit.RasterizerState = TStaticRasterizerState<FM_Solid, CM_None>::GetRHI();
GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI();
GraphicsPSOInit.BlendState = TStaticBlendState<
CW_RGBA, BO_Max, BF_One, BF_One, BO_Max, BF_One, BF_One,
CW_RGBA, BO_Max, BF_One, BF_One, BO_Max, BF_One, BF_One>::GetRHI(); // MAX blending
GraphicsPSOInit.PrimitiveType = PT_TriangleList;
GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader();
GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader();
switch (RasterMode)
{
case ERasterMode::Regular: GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GetVertexDeclarationFVector3(); break;
case ERasterMode::Jittered: GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GVector3AndInstancedVector2VertexDeclaration.VertexDeclarationRHI; break;
case ERasterMode::Conservative: GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GVector3AndAndThreeVector2VertexDeclaration.VertexDeclarationRHI; break;
default: checkNoEntry(); break;
}
SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit, 0);
const FVector2f HalfPixelSize = FVector2f(1.0f / RasterResolution.X, 1.0f / RasterResolution.Y);
const FVector2f JitterScale = JitterFootprint * FVector2f(2.0f / RasterResolution.X, 2.0f / RasterResolution.Y);
if (RasterMode == ERasterMode::Jittered)
{
const int32 JitterVertexBufferOffset = bMSAAPattern ? FJitterOffsetVertexBuffer::GetMSAADataOffset(NumJitterSamples) : 0;
RHICmdList.SetStreamSource(1, GJitterOffsetVertexBuffer.GetRHI(), JitterVertexBufferOffset);
}
for (const FDraw& Draw : Draws)
{
PassParameters->VS.Transform = Draw.Transform;
PassParameters->VS.JitterScale = JitterScale;
PassParameters->VS.HalfPixelSize = HalfPixelSize;
PassParameters->PS.WaterBodyRenderDataIndex = Draw.WaterBodyRenderDataIndex;
PassParameters->PS.Priority = Draw.Priority;
PassParameters->PS.WaterBodyMinZ = Draw.MinZ;
PassParameters->PS.WaterBodyMaxZ = Draw.MaxZ;
PassParameters->PS.MaxWaveHeight = Draw.MaxWaveHeight;
PassParameters->PS.bIsRiver = Draw.bIsRiver;
SetShaderParameters(RHICmdList, VertexShader, VertexShader.GetVertexShader(), PassParameters->VS);
SetShaderParameters(RHICmdList, PixelShader, PixelShader.GetPixelShader(), PassParameters->PS);
RHICmdList.SetStreamSource(0, Draw.VertexBuffer, 0);
switch (RasterMode)
{
case ERasterMode::Regular: // Fallthrough
case ERasterMode::Jittered:
{
const uint32 NumInstances = (RasterMode == ERasterMode::Jittered) ? NumJitterSamples : 1;
RHICmdList.DrawIndexedPrimitive(Draw.IndexBuffer, Draw.BaseVertexIndex, 0, Draw.NumVertices, Draw.FirstIndex, Draw.NumPrimitives, NumInstances);
break;
}
case ERasterMode::Conservative:
{
RHICmdList.SetStreamSource(1, Draw.TexCoordBuffer, 0);
check((Draw.NumVertices % 3) == 0);
RHICmdList.DrawPrimitive(Draw.BaseVertexIndex, Draw.NumVertices / 3, 1);
break;
}
default: checkNoEntry(); break;
}
}
});
}
// Merge river and non-river water bodies and downsample to quad tree LOD0 resolution
{
FWaterQuadTreeMergePS::FPermutationDomain PermutationDomain;
PermutationDomain.Set<FWaterQuadTreeMergePS::FNumMSAASamples>(NumMSAASamples);
TShaderMapRef<FWaterQuadTreeMergePS> PixelShader(ShaderMap, PermutationDomain);
FWaterQuadTreeMergePS::FParameters* PassParameters = GraphBuilder.AllocParameters<FWaterQuadTreeMergePS::FParameters>();
if (NumMSAASamples > 1)
{
PassParameters->WaterBodyRasterTextureMS = WaterBodyRasterTexture;
PassParameters->ZBoundsRasterTextureMS = ZBoundsRasterTexture;
}
else
{
PassParameters->WaterBodyRasterTexture = WaterBodyRasterTexture;
PassParameters->ZBoundsRasterTexture = ZBoundsRasterTexture;
}
PassParameters->WaterBodyRenderData = WaterBodyRenderDataBufferSRV;
PassParameters->SuperSamplingFactor = Params.SuperSamplingFactor;
PassParameters->RcpCaptureDepthRange = 1.0f / Params.CaptureDepthRange;
PassParameters->ZBoundsPadding = GWaterQuadTreeZBoundsPadding / Params.CaptureDepthRange;
PassParameters->RenderTargets[0] = FRenderTargetBinding(QuadTreeTextureRDG, ERenderTargetLoadAction::ENoAction, 0);
PassParameters->RenderTargets[1] = FRenderTargetBinding(WaterZBoundsTextureRDG, ERenderTargetLoadAction::ENoAction, 0);
const FIntRect MergeViewport(0, 0, QuadTreeResolution.X, QuadTreeResolution.Y);
FPixelShaderUtils::AddFullscreenPass(
GraphBuilder,
ShaderMap,
RDG_EVENT_NAME("WaterQuadTreeMerge"),
PixelShader,
PassParameters,
MergeViewport);
}
// Build the mip chain
{
TShaderMapRef<FWaterQuadTreeBuildPS> PixelShader(ShaderMap);
const bool bSupportsTextureViews = GRHISupportsTextureViews;
FIntRect BuildViewport(0, 0, QuadTreeResolution.X, QuadTreeResolution.Y);
for (int32 MipLevel = 1; MipLevel < NumMipLevels; ++MipLevel)
{
FRDGTextureSRVDesc QuadTreeSourceSRVDesc = FRDGTextureSRVDesc::CreateForMipLevel(QuadTreeTextureRDG, MipLevel - 1);
FRDGTextureSRVDesc WaterZBoundsSourceSRVDesc = FRDGTextureSRVDesc::CreateForMipLevel(WaterZBoundsTextureRDG, MipLevel - 1);
if (!bSupportsTextureViews)
{
QuadTreeSourceSRVDesc = FRDGTextureSRVDesc::Create(QuadTreeTextureRDG);
WaterZBoundsSourceSRVDesc = FRDGTextureSRVDesc::Create(WaterZBoundsTextureRDG);
}
FWaterQuadTreeBuildPS::FParameters* PassParameters = GraphBuilder.AllocParameters<FWaterQuadTreeBuildPS::FParameters>();
PassParameters->QuadTreeTexture = GraphBuilder.CreateSRV(QuadTreeSourceSRVDesc);
PassParameters->WaterZBoundsTexture = GraphBuilder.CreateSRV(WaterZBoundsSourceSRVDesc);
PassParameters->InputMipLevelIndex = bSupportsTextureViews ? 0 : (MipLevel - 1);
PassParameters->RenderTargets[0] = FRenderTargetBinding(QuadTreeTextureRDG, ERenderTargetLoadAction::ENoAction, MipLevel);
PassParameters->RenderTargets[1] = FRenderTargetBinding(WaterZBoundsTextureRDG, ERenderTargetLoadAction::ENoAction, MipLevel);
BuildViewport = BuildViewport / 2;
FPixelShaderUtils::AddFullscreenPass(
GraphBuilder,
ShaderMap,
RDG_EVENT_NAME("WaterQuadTreeBuild"),
PixelShader,
PassParameters,
BuildViewport);
}
}
}
void FWaterQuadTreeGPU::Traverse(FRDGBuilder& GraphBuilder, const FTraverseParams& Params) const
{
TRACE_CPUPROFILER_EVENT_SCOPE(FWaterQuadTreeGPU::Traverse);
RDG_EVENT_SCOPE_STAT(GraphBuilder, FWaterQuadTreeGPU_Traverse, "FWaterQuadTreeGPU::Traverse");
RDG_GPU_STAT_SCOPE(GraphBuilder, FWaterQuadTreeGPU_Traverse);
const uint32 NumViews = Params.Views.Num();
FGlobalShaderMap* ShaderMap = GetGlobalShaderMap(GMaxRHIFeatureLevel);
FRDGTexture* QuadTreeTextureRDG = GraphBuilder.RegisterExternalTexture(CreateRenderTarget(QuadTreeTexture, TEXT("WaterQuadTree.QuadTree")));
FRDGTexture* WaterZBoundsTextureRDG = GraphBuilder.RegisterExternalTexture(CreateRenderTarget(WaterZBoundsTexture, TEXT("WaterQuadTree.WaterSurfaceHeight")));
FRDGBuffer* WaterBodyRenderDataBufferRDG = GraphBuilder.RegisterExternalBuffer(WaterBodyRenderDataBuffer);
FRDGBuffer* IndirectArgsBuffer = GraphBuilder.RegisterExternalBuffer(Params.OutIndirectArgsBuffer);
FRDGBuffer* InstanceDataOffsetsBuffer = GraphBuilder.RegisterExternalBuffer(Params.OutInstanceDataOffsetsBuffer);
FRDGBuffer* InstanceData0Buffer = GraphBuilder.RegisterExternalBuffer(Params.OutInstanceData0Buffer);
FRDGBuffer* InstanceData1Buffer = GraphBuilder.RegisterExternalBuffer(Params.OutInstanceData1Buffer);
FRDGBuffer* InstanceData2Buffer = GraphBuilder.RegisterExternalBuffer(Params.OutInstanceData2Buffer);
FRDGBuffer* InstanceData3Buffer = Params.bWithWaterSelectionSupport ?
GraphBuilder.RegisterExternalBuffer(Params.OutInstanceData3Buffer) :
GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateBufferDesc(sizeof(uint32), 1), TEXT("WaterQuadTree.InstanceData3Dummy"));
FRDGBufferUAV* IndirectArgsBufferUAV = GraphBuilder.CreateUAV(FRDGBufferUAVDesc(IndirectArgsBuffer, PF_R32_UINT));
FRDGBufferSRV* WaterBodyRenderDataBufferSRV = GraphBuilder.CreateSRV(FRDGBufferSRVDesc(WaterBodyRenderDataBufferRDG));
const EOcclusionQueryMode OcclusionQueryMode = static_cast<EOcclusionQueryMode>(GWaterQuadTreeOcclusionCulling);
const bool bHZBOcclusionQueries = OcclusionQueryMode == EOcclusionQueryMode::HZB || OcclusionQueryMode == EOcclusionQueryMode::HZBAndPixelPrecise;
const bool bPixelPreciseOcclusionQueries = OcclusionQueryMode == EOcclusionQueryMode::PixelPrecise || OcclusionQueryMode == EOcclusionQueryMode::HZBAndPixelPrecise;
FRDGBuffer* OcclusionQueryIndirectArgsBuffer = nullptr;
FRDGBufferUAV* OcclusionQueryIndirectArgsBufferUAV = nullptr;
if (bPixelPreciseOcclusionQueries)
{
OcclusionQueryIndirectArgsBuffer = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateIndirectDesc<FRHIDrawIndexedIndirectParameters>(NumViews), TEXT("WaterQuadTree.OcclusionQueryIndirectArgs"));
OcclusionQueryIndirectArgsBufferUAV = GraphBuilder.CreateUAV(FRDGBufferUAVDesc(OcclusionQueryIndirectArgsBuffer, PF_R32_UINT));
}
const uint32 NumBucketsPerView = Params.NumMaterials; // The GPU-driven water rendering path only uses a single density mesh tile to draw everything, so NumBuckets is equal to NumMaterials.
const uint32 NumBucketsTotal = NumBucketsPerView * NumViews;
const FIntPoint QuadTreeResolution = QuadTreeTextureRDG->Desc.Extent;
// Initialize indirect args
{
FWaterQuadTreeInitializeIndirectArgsCS::FPermutationDomain PermutationDomain;
PermutationDomain.Set<FWaterQuadTreeInitializeIndirectArgsCS::FPreciseOcclusionQueries>(bPixelPreciseOcclusionQueries);
TShaderMapRef<FWaterQuadTreeInitializeIndirectArgsCS> ComputeShader(ShaderMap, PermutationDomain);
FWaterQuadTreeInitializeIndirectArgsCS::FParameters* PassParameters = GraphBuilder.AllocParameters<FWaterQuadTreeInitializeIndirectArgsCS::FParameters>();
PassParameters->IndirectArgs = IndirectArgsBufferUAV;
PassParameters->OcclusionQueryArgs = OcclusionQueryIndirectArgsBufferUAV;
PassParameters->NumDrawBuckets = Params.NumMaterials;
PassParameters->NumViews = NumViews;
PassParameters->NumQuads = Params.NumQuadsPerTileSide;
FComputeShaderUtils::AddPass(GraphBuilder, RDG_EVENT_NAME("WaterQuadTreeInitIndirectArgs"), ComputeShader, PassParameters, FComputeShaderUtils::GetGroupCount(NumBucketsTotal, 64));
}
// Iterate over all views for which to create indirect water draws
for (uint32 ViewIndex = 0; ViewIndex < NumViews; ++ViewIndex)
{
const FSceneView* View = Params.Views[ViewIndex];
const FViewInfo* ViewInfo = View->bIsViewInfo ? reinterpret_cast<const FViewInfo*>(View) : nullptr;
const FVector PreViewTranslation = View->ViewMatrices.GetPreViewTranslation();
const FVector3f QuadTreePositionTranslatedWorldSpace = FVector3f(Params.QuadTreePosition + PreViewTranslation);
const FVector3f ObserverPositionTranslatedWorldSpace = FVector3f(View->ViewMatrices.GetViewOrigin() + PreViewTranslation);
const uint32 MaxNumDraws = QuadTreeResolution.X * QuadTreeResolution.Y;
FRDGBuffer* PackedNodes = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateByteAddressDesc((1 + MaxNumDraws) * sizeof(uint32)), TEXT("WaterQuadTree.PackedNodes"));
FRDGBuffer* BucketCounts = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateByteAddressDesc(FMath::Max(1u, NumBucketsPerView) * sizeof(uint32)), TEXT("WaterQuadTree.DrawBucketCounts"));
FRDGBufferUAV* PackedNodesUAV = GraphBuilder.CreateUAV(FRDGBufferUAVDesc(PackedNodes, PF_R32_UINT));
FRDGBufferUAV* BucketCountsUAV = GraphBuilder.CreateUAV(FRDGBufferUAVDesc(BucketCounts, PF_R32_UINT));
FRDGBufferSRV* PackedNodesSRV = GraphBuilder.CreateSRV(FRDGBufferSRVDesc(PackedNodes, PF_R32_UINT));
FRDGBufferSRV* BucketCountsSRV = GraphBuilder.CreateSRV(FRDGBufferSRVDesc(BucketCounts, PF_R32_UINT));
const bool bPixelPreciseOQForThisView = bPixelPreciseOcclusionQueries && ViewInfo && Params.bDepthBufferIsPopulated;
FRDGBuffer* OcclusionQueryBoxes = nullptr;
FRDGBuffer* OcclusionQueryResults = nullptr;
FRDGBufferUAV* OcclusionQueryBoxesUAV = nullptr;
FRDGBufferUAV* OcclusionQueryResultsUAV = nullptr;
FRDGBufferSRV* OcclusionQueryBoxesSRV = nullptr;
FRDGBufferSRV* OcclusionQueryResultsSRV = nullptr;
if (bPixelPreciseOQForThisView)
{
OcclusionQueryBoxes = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateBufferDesc(sizeof(float) * 4, MaxNumDraws * 2), TEXT("WaterQuadTree.OcclusionQueryBoxes"));
OcclusionQueryResults = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateBufferDesc(sizeof(uint32), MaxNumDraws), TEXT("WaterQuadTree.OcclusionQueryResults"));
OcclusionQueryBoxesUAV = GraphBuilder.CreateUAV(FRDGBufferUAVDesc(OcclusionQueryBoxes, PF_A32B32G32R32F));
OcclusionQueryResultsUAV = GraphBuilder.CreateUAV(FRDGBufferUAVDesc(OcclusionQueryResults, PF_R32_UINT));
OcclusionQueryBoxesSRV = GraphBuilder.CreateSRV(FRDGBufferSRVDesc(OcclusionQueryBoxes, PF_A32B32G32R32F));
OcclusionQueryResultsSRV = GraphBuilder.CreateSRV(FRDGBufferSRVDesc(OcclusionQueryResults, PF_R32_UINT));
}
// Clear bucket counts buffer and packed nodes counter
{
TShaderMapRef<FWaterQuadTreeClearPerViewBuffersCS> ComputeShader(ShaderMap);
FWaterQuadTreeClearPerViewBuffersCS::FParameters* PassParameters = GraphBuilder.AllocParameters<FWaterQuadTreeClearPerViewBuffersCS::FParameters>();
PassParameters->BucketCounts = BucketCountsUAV;
PassParameters->PackedNodes = PackedNodesUAV;
PassParameters->NumDrawBuckets = NumBucketsPerView;
FComputeShaderUtils::AddPass(GraphBuilder, RDG_EVENT_NAME("WaterQuadTreeClearPerViewBuffers"), ComputeShader, PassParameters, FComputeShaderUtils::GetGroupCount(NumBucketsPerView, 64));
}
// Traverse quadtree
{
FWaterQuadTreeTraverseCS::FPermutationDomain PermutationDomain;
PermutationDomain.Set<FWaterQuadTreeTraverseCS::FPreciseOcclusionQueries>(bPixelPreciseOQForThisView);
TShaderMapRef<FWaterQuadTreeTraverseCS> ComputeShader(ShaderMap, PermutationDomain);
const uint32 NumMipLevels = QuadTreeTextureRDG->Desc.NumMips;
uint32 NumTexelsTotal = 0;
for (uint32 MipLevelIndex = 0; MipLevelIndex < NumMipLevels; ++MipLevelIndex)
{
const FIntPoint MipExtent = FIntPoint(FMath::Max(1, QuadTreeTextureRDG->Desc.Extent.X >> MipLevelIndex), FMath::Max(1, QuadTreeTextureRDG->Desc.Extent.Y >> MipLevelIndex));
NumTexelsTotal += MipExtent.X * MipExtent.Y;
}
FWaterQuadTreeTraverseCS::FParameters* PassParameters = GraphBuilder.AllocParameters<FWaterQuadTreeTraverseCS::FParameters>();
PassParameters->View = GetShaderBinding(View->ViewUniformBuffer);
PassParameters->PackedNodes = PackedNodesUAV;
PassParameters->OcclusionQueryBoxes = OcclusionQueryBoxesUAV;
PassParameters->OcclusionVisibility = OcclusionQueryResultsUAV;
PassParameters->OcclusionQueryArgs = OcclusionQueryIndirectArgsBufferUAV;
PassParameters->QuadTreeTexture = QuadTreeTextureRDG;
PassParameters->WaterZBoundsTexture = WaterZBoundsTextureRDG;
PassParameters->WaterBodyRenderData = WaterBodyRenderDataBufferSRV;
// HZB
// These parameters should be filled with GetHZBParameters() or GetDummyHZBParameters(), but these functions are not exposed as public function.
// This is why these parameters are setup manually.
{
PassParameters->HZBParameters.HZBTexture = (ViewInfo && ViewInfo->HZB) ? ViewInfo->HZB : GSystemTextures.GetBlackDummy(GraphBuilder);
PassParameters->HZBParameters.HZBSampler = TStaticSamplerState<SF_Point, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI();
PassParameters->HZBParameters.HZBSize = ViewInfo ? FVector2f(ViewInfo->HZBMipmap0Size) : FVector2f::ZeroVector;
PassParameters->HZBParameters.HZBViewSize = ViewInfo ? FVector2f(ViewInfo->ViewRect.Size()) : FVector2f::ZeroVector;
}
PassParameters->CullingBoundsAABB = FVector4f(Params.CullingBounds.Min.X, Params.CullingBounds.Min.Y, Params.CullingBounds.Max.X, Params.CullingBounds.Max.Y);
PassParameters->QuadTreePosition = QuadTreePositionTranslatedWorldSpace;
PassParameters->ObserverPosition = ObserverPositionTranslatedWorldSpace;
PassParameters->QuadTreeResolutionX = QuadTreeResolution.X;
PassParameters->QuadTreeResolutionY = QuadTreeResolution.Y;
PassParameters->ViewIndex = ViewIndex;
PassParameters->LeafSize = Params.LeafSize;
PassParameters->LODScale = Params.LODScale;
PassParameters->CaptureDepthRange = CaptureDepthRange;
PassParameters->ForceCollapseDensityLevel = -1; // Params.ForceCollapseDensityLevel; // TODO: Properly implement support for this parameter
PassParameters->NumLODs = NumMipLevels;
PassParameters->NumDispatchedThreads = NumTexelsTotal;
PassParameters->bHZBOcclusionCullingEnabled = bHZBOcclusionQueries && (ViewInfo && ViewInfo->HZB);
FComputeShaderUtils::AddPass(GraphBuilder, RDG_EVENT_NAME("WaterQuadTreeTraverse(View: %i)", ViewIndex), ComputeShader, PassParameters, FComputeShaderUtils::GetGroupCount(NumTexelsTotal, 64));
}
// Raster occlusion queries
if (bPixelPreciseOQForThisView)
{
TShaderMapRef<FWaterQuadTreeOcclusionQueryVS> VertexShader(ShaderMap);
TShaderMapRef<FWaterQuadTreeOcclusionQueryPS> PixelShader(ShaderMap);
FRDGTextureRef DepthTexture = GetIfProduced(ViewInfo->GetSceneTextures().Depth.Target);
check(DepthTexture);
FWaterQuadTreeOcclusionQueryParameters* PassParameters = GraphBuilder.AllocParameters<FWaterQuadTreeOcclusionQueryParameters>();
PassParameters->IndirectDrawArgsBuffer = OcclusionQueryIndirectArgsBuffer;
PassParameters->VS.View = GetShaderBinding(View->ViewUniformBuffer);
PassParameters->VS.OcclusionQueryBoxes = OcclusionQueryBoxesSRV;
PassParameters->PS.Visibility = OcclusionQueryResultsUAV;
PassParameters->PS.RenderTargets.DepthStencil = FDepthStencilBinding(DepthTexture, ERenderTargetLoadAction::ELoad, ERenderTargetLoadAction::ENoAction, FExclusiveDepthStencil::DepthRead_StencilNop);
const FIntVector4 ViewRect = FIntVector4(ViewInfo->ViewRect.Min.X, ViewInfo->ViewRect.Min.Y, ViewInfo->ViewRect.Max.X, ViewInfo->ViewRect.Max.Y);
GraphBuilder.AddPass(
RDG_EVENT_NAME("WaterQuadTreeOcclusionCulling(View: %i)", ViewIndex),
PassParameters,
ERDGPassFlags::Raster,
[PassParameters, ViewRect, ViewIndex, VertexShader, PixelShader](FRHICommandList& RHICmdList)
{
RHICmdList.SetViewport(ViewRect.X, ViewRect.Y, 0.0f, ViewRect.Z, ViewRect.W, 1.0f);
FGraphicsPipelineStateInitializer GraphicsPSOInit;
RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit);
GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GetVertexDeclarationFVector4();
GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader();
GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader();
GraphicsPSOInit.BlendState = TStaticBlendState<>::GetRHI();
GraphicsPSOInit.RasterizerState = TStaticRasterizerState<FM_Solid, CM_CCW>::GetRHI();
GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_DepthNearOrEqual>::GetRHI();
GraphicsPSOInit.PrimitiveType = PT_TriangleList;
SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit, 0);
SetShaderParameters(RHICmdList, VertexShader, VertexShader.GetVertexShader(), PassParameters->VS);
SetShaderParameters(RHICmdList, PixelShader, PixelShader.GetPixelShader(), PassParameters->PS);
PassParameters->IndirectDrawArgsBuffer->MarkResourceAsUsed();
RHICmdList.SetStreamSource(0, GetUnitCubeVertexBuffer(), 0);
RHICmdList.DrawIndexedPrimitiveIndirect(GetUnitCubeIndexBuffer(), PassParameters->IndirectDrawArgsBuffer->GetRHI(), ViewIndex * sizeof(FRHIDrawIndexedIndirectParameters));
});
}
// Compute bucket counts
{
FWaterQuadTreeBucketCountsCS::FPermutationDomain PermutationDomain;
PermutationDomain.Set<FWaterQuadTreeBucketCountsCS::FPreciseOcclusionQueries>(bPixelPreciseOQForThisView);
TShaderMapRef<FWaterQuadTreeBucketCountsCS> ComputeShader(ShaderMap, PermutationDomain);
FWaterQuadTreeBucketCountsCS::FParameters* PassParameters = GraphBuilder.AllocParameters<FWaterQuadTreeBucketCountsCS::FParameters>();
PassParameters->BucketCounts = BucketCountsUAV;
PassParameters->QuadTreeTexture = QuadTreeTextureRDG;
PassParameters->WaterBodyRenderData = WaterBodyRenderDataBufferSRV;
PassParameters->PackedNodes = PackedNodesSRV;
PassParameters->OcclusionResults = OcclusionQueryResultsSRV;
PassParameters->NumDispatchedThreads = FMath::Min(FMath::DivideAndRoundUp(MaxNumDraws, 64u), 65535u) * 64u; // The maximum number of dispatched groups is 64k
PassParameters->NumDensities = Params.NumDensities;
PassParameters->NumQuadsLOD0 = Params.NumQuadsLOD0;
PassParameters->NumQuadsPerDraw = Params.NumQuadsPerTileSide;
FComputeShaderUtils::AddPass(GraphBuilder, RDG_EVENT_NAME("WaterQuadTreeDrawBucketCounts(View: %i)", ViewIndex), ComputeShader, PassParameters, FComputeShaderUtils::GetGroupCount(PassParameters->NumDispatchedThreads, 64));
}
// Compute bucket prefix sums
{
FWaterQuadTreeBucketPrefixSumCS::FPermutationDomain PermutationVector;
PermutationVector.Set<FWaterQuadTreeBucketPrefixSumCS::FParallelPrefixSum>(GWaterQuadTreeParallelPrefixSum != 0);
TShaderMapRef<FWaterQuadTreeBucketPrefixSumCS> ComputeShader(ShaderMap, PermutationVector);
FWaterQuadTreeBucketPrefixSumCS::FParameters* PassParameters = GraphBuilder.AllocParameters<FWaterQuadTreeBucketPrefixSumCS::FParameters>();
PassParameters->BucketPrefixSums = GraphBuilder.CreateUAV(FRDGBufferUAVDesc(InstanceDataOffsetsBuffer, PF_R32_UINT));
PassParameters->BucketCounts = GraphBuilder.CreateSRV(FRDGBufferSRVDesc(BucketCounts));
PassParameters->NumBuckets = NumBucketsPerView;
PassParameters->OutputOffset = ViewIndex * NumBucketsPerView;
PassParameters->bWriteTotalSumAtBufferEnd = Params.Views.IsValidIndex(ViewIndex + 1); // Propagate total sum so the next iteration/view can compute correct global prefix sums/offsets.
FComputeShaderUtils::AddPass(GraphBuilder, RDG_EVENT_NAME("WaterQuadTreeDrawBucketPrefixSums(View: %i)", ViewIndex), ComputeShader, PassParameters, FIntVector3(1, 1, 1));
}
// Generate instance data and fill indirect args
{
FWaterQuadTreeGenerateInstanceDataCS::FPermutationDomain PermutationDomain;
PermutationDomain.Set<FWaterQuadTreeGenerateInstanceDataCS::FPreciseOcclusionQueries>(bPixelPreciseOQForThisView);
TShaderMapRef<FWaterQuadTreeGenerateInstanceDataCS> ComputeShader(ShaderMap, PermutationDomain);
FWaterQuadTreeGenerateInstanceDataCS::FParameters* PassParameters = GraphBuilder.AllocParameters<FWaterQuadTreeGenerateInstanceDataCS::FParameters>();
PassParameters->IndirectArgs = IndirectArgsBufferUAV;
PassParameters->InstanceData0 = GraphBuilder.CreateUAV(FRDGBufferUAVDesc(InstanceData0Buffer, PF_R32_UINT));
PassParameters->InstanceData1 = GraphBuilder.CreateUAV(FRDGBufferUAVDesc(InstanceData1Buffer, PF_R32_UINT));
PassParameters->InstanceData2 = GraphBuilder.CreateUAV(FRDGBufferUAVDesc(InstanceData2Buffer, PF_R32_UINT));
PassParameters->InstanceData3 = GraphBuilder.CreateUAV(FRDGBufferUAVDesc(InstanceData3Buffer, PF_R32_UINT));
PassParameters->QuadTreeTexture = QuadTreeTextureRDG;
PassParameters->WaterZBoundsTexture = WaterZBoundsTextureRDG;
PassParameters->WaterBodyRenderData = WaterBodyRenderDataBufferSRV;
PassParameters->PackedNodes = PackedNodesSRV;
PassParameters->InstanceDataOffsets = GraphBuilder.CreateSRV(FRDGBufferSRVDesc(InstanceDataOffsetsBuffer, PF_R32_UINT));
PassParameters->OcclusionResults = OcclusionQueryResultsSRV;
PassParameters->QuadTreePosition = QuadTreePositionTranslatedWorldSpace;
PassParameters->ObserverPosition = ObserverPositionTranslatedWorldSpace;
PassParameters->QuadTreeResolutionX = QuadTreeResolution.X;
PassParameters->QuadTreeResolutionY = QuadTreeResolution.Y;
PassParameters->NumDensities = Params.NumDensities;
PassParameters->NumMaterials = Params.NumMaterials;
PassParameters->NumDispatchedThreads = FMath::Min(FMath::DivideAndRoundUp(MaxNumDraws, 64u), 65535u) * 64u; // The maximum number of dispatched groups is 64k
PassParameters->BucketIndexOffset = ViewIndex * NumBucketsPerView;
PassParameters->NumLODs = QuadTreeTextureRDG->Desc.NumMips;
PassParameters->NumQuadsLOD0 = Params.NumQuadsLOD0;
PassParameters->NumQuadsPerDraw = Params.NumQuadsPerTileSide;
PassParameters->LeafSize = Params.LeafSize;
PassParameters->LODScale = Params.LODScale;
PassParameters->CaptureDepthRange = CaptureDepthRange;
PassParameters->StereoPassInstanceFactor = View->GetStereoPassInstanceFactor();
PassParameters->bWithWaterSelectionSupport = Params.bWithWaterSelectionSupport;
PassParameters->bLODMorphingEnabled = Params.bLODMorphingEnabled;
PassParameters->bInstancedStereoRendering = View->bIsInstancedStereoEnabled;
FComputeShaderUtils::AddPass(GraphBuilder, RDG_EVENT_NAME("WaterQuadTreeGenerateDraws(View: %i)", ViewIndex), ComputeShader, PassParameters, FComputeShaderUtils::GetGroupCount(PassParameters->NumDispatchedThreads, 64));
}
// Debug show tiles
if (Params.DebugShowTile != 0)
{
ShaderPrint::SetEnabled(true);
const bool bEnableShaderPrint = ViewInfo != nullptr
&& ShaderPrint::IsEnabled()
&& ShaderPrint::IsEnabled(ViewInfo->ShaderPrintData)
&& ShaderPrint::IsValid(ViewInfo->ShaderPrintData)
&& ShaderPrint::IsSupported(GetFeatureLevelShaderPlatform(GMaxRHIFeatureLevel));
if (bEnableShaderPrint)
{
// We'll be potentially drawing a lot of bounding boxes, each made up of 12 line segments, so reserve some space
ShaderPrint::RequestSpaceForLines(12 * MaxNumDraws);
FWaterQuadTreeDebugCS::FPermutationDomain PermutationDomain;
PermutationDomain.Set<FWaterQuadTreeDebugCS::FPreciseOcclusionQueries>(bPixelPreciseOQForThisView);
TShaderMapRef<FWaterQuadTreeDebugCS> ComputeShader(ShaderMap, PermutationDomain);
FWaterQuadTreeDebugCS::FParameters* PassParameters = GraphBuilder.AllocParameters<FWaterQuadTreeDebugCS::FParameters>();
ShaderPrint::SetParameters(GraphBuilder, ViewInfo->ShaderPrintData, PassParameters->ShaderPrintUniformBuffer);
PassParameters->QuadTreeTexture = QuadTreeTextureRDG;
PassParameters->WaterZBoundsTexture = WaterZBoundsTextureRDG;
PassParameters->WaterBodyRenderData = WaterBodyRenderDataBufferSRV;
PassParameters->PackedNodes = PackedNodesSRV;
PassParameters->OcclusionResults = OcclusionQueryResultsSRV;
PassParameters->QuadTreePosition = QuadTreePositionTranslatedWorldSpace;
PassParameters->NumDispatchedThreads = FMath::Min(FMath::DivideAndRoundUp(MaxNumDraws, 64u), 65535u) * 64u; // The maximum number of dispatched groups is 64k
PassParameters->LeafSize = Params.LeafSize;
PassParameters->CaptureDepthRange = CaptureDepthRange;
FComputeShaderUtils::AddPass(GraphBuilder, RDG_EVENT_NAME("WaterQuadTreeDebug(View: %i)", ViewIndex), ComputeShader, PassParameters, FComputeShaderUtils::GetGroupCount(PassParameters->NumDispatchedThreads, 64));
}
}
}
GraphBuilder.UseExternalAccessMode(IndirectArgsBuffer, ERHIAccess::IndirectArgs);
GraphBuilder.UseExternalAccessMode(InstanceDataOffsetsBuffer, ERHIAccess::SRVMask);
GraphBuilder.UseExternalAccessMode(InstanceData0Buffer, ERHIAccess::SRVMask | ERHIAccess::VertexOrIndexBuffer);
GraphBuilder.UseExternalAccessMode(InstanceData1Buffer, ERHIAccess::SRVMask | ERHIAccess::VertexOrIndexBuffer);
GraphBuilder.UseExternalAccessMode(InstanceData2Buffer, ERHIAccess::SRVMask | ERHIAccess::VertexOrIndexBuffer);
if (Params.bWithWaterSelectionSupport)
{
GraphBuilder.UseExternalAccessMode(InstanceData3Buffer, ERHIAccess::SRVMask | ERHIAccess::VertexOrIndexBuffer);
}
}