// Copyright Epic Games, Inc. All Rights Reserved. #include "LandscapeCulling.h" #include "LandscapeRender.h" #include "SceneView.h" #include "ShaderParameterStruct.h" #include "RenderGraphBuilder.h" #include "RenderGraphUtils.h" #include "ShaderPlatformCachedIniValue.h" #include "MeshMaterialShader.h" #include "DataDrivenShaderPlatformInfo.h" static TAutoConsoleVariable CVarLandscapeSupportCulling( TEXT("landscape.SupportGPUCulling"), 1, TEXT("Whether to support landscape GPU culling"), ECVF_ReadOnly | ECVF_RenderThreadSafe); static TAutoConsoleVariable CVarLandscapeEnableGPUCulling( TEXT("landscape.EnableGPUCulling"), 1, TEXT("Whether to use landscape GPU culling when it's supported. Allows to toggle culling at runtime"), ECVF_RenderThreadSafe); static TAutoConsoleVariable CVarLandscapeEnableGPUCullingShadows( TEXT("landscape.EnableGPUCullingShadows"), 1, TEXT("Whether to use landscape GPU culling for a shadow views when it's supported. Allows to toggle shadow views culling at runtime"), ECVF_RenderThreadSafe); /** Vertex factory for a tiled landscape rendering */ class FLandscapeTileVertexFactory : public FLandscapeVertexFactory { DECLARE_VERTEX_FACTORY_TYPE(FLandscapeTileVertexFactory); public: FLandscapeTileVertexFactory(ERHIFeatureLevel::Type InFeatureLevel) : FLandscapeVertexFactory(InFeatureLevel) { } struct FDataType : public FLandscapeVertexFactory::FDataType { FVertexStreamComponent TileDataComponent; }; void SetData(const FDataType& InData) { FLandscapeVertexFactory::Data = InData; Data = InData; UpdateRHI(FRHICommandListImmediate::Get()); } /** * Should we cache the material's shadertype on this platform with this vertex factory? */ static bool ShouldCompilePermutation(const FVertexFactoryShaderPermutationParameters& Parameters); /** * Can be overridden by FVertexFactory subclasses to modify their compile environment just before compilation occurs. */ static void ModifyCompilationEnvironment(const FVertexFactoryShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment); /** * Get vertex elements used when during PSO precaching materials using this vertex factory type */ static void GetPSOPrecacheVertexFetchElements(EVertexInputStreamType VertexInputStreamType, FVertexDeclarationElementList& Elements); void Copy(const FLandscapeTileVertexFactory& Other); // FRenderResource interface. virtual void InitRHI(FRHICommandListBase& RHICmdList) override; FDataType Data; }; // // FLandscapeTileVertexFactory // void FLandscapeTileVertexFactory::InitRHI(FRHICommandListBase& RHICmdList) { // list of declaration items FVertexDeclarationElementList Elements; // position decls Elements.Add(AccessStreamComponent(Data.PositionComponent, 0)); // Tile per-instance data Elements.Add(AccessStreamComponent(Data.TileDataComponent, 1)); // create the actual device decls InitDeclaration(Elements); } /** * Copy the data from another vertex factory * @param Other - factory to copy from */ void FLandscapeTileVertexFactory::Copy(const FLandscapeTileVertexFactory& Other) { FLandscapeTileVertexFactory* VertexFactory = this; const FDataType* DataCopy = &Other.Data; ENQUEUE_RENDER_COMMAND(FLandscapeTileVertexFactoryCopyData)( [VertexFactory, DataCopy](FRHICommandListImmediate& RHICmdList) { VertexFactory->Data = *DataCopy; }); BeginUpdateResourceRHI(this); } void FLandscapeTileVertexFactory::GetPSOPrecacheVertexFetchElements(EVertexInputStreamType VertexInputStreamType, FVertexDeclarationElementList& Elements) { Elements.Add(FVertexElement(0, 0, VET_UByte4, 0, sizeof(FLandscapeVertex), false)); // Position Elements.Add(FVertexElement(1, 0, VET_UByte4, 1, 4u, true)); // Tile per-instance data } bool FLandscapeTileVertexFactory::ShouldCompilePermutation(const FVertexFactoryShaderPermutationParameters& Parameters) { static FShaderPlatformCachedIniValue LandscapeSupportCullingIniValue(TEXT("landscape.SupportGPUCulling")); return FLandscapeVertexFactory::ShouldCompilePermutation(Parameters) && LandscapeSupportCullingIniValue.Get(Parameters.Platform) != 0; } void FLandscapeTileVertexFactory::ModifyCompilationEnvironment(const FVertexFactoryShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { FLandscapeVertexFactory::ModifyCompilationEnvironment(Parameters, OutEnvironment); OutEnvironment.SetDefine(TEXT("LANDSCAPE_TILE"), TEXT("1")); } IMPLEMENT_VERTEX_FACTORY_PARAMETER_TYPE(FLandscapeTileVertexFactory, SF_Vertex, FLandscapeVertexFactoryVertexShaderParameters); IMPLEMENT_VERTEX_FACTORY_PARAMETER_TYPE(FLandscapeTileVertexFactory, SF_Pixel, FLandscapeVertexFactoryPixelShaderParameters); IMPLEMENT_VERTEX_FACTORY_TYPE(FLandscapeTileVertexFactory, "/Engine/Private/LandscapeVertexFactory.ush", EVertexFactoryFlags::UsedWithMaterials | EVertexFactoryFlags::SupportsStaticLighting | EVertexFactoryFlags::SupportsDynamicLighting | EVertexFactoryFlags::SupportsLightmapBaking | EVertexFactoryFlags::SupportsPSOPrecaching | EVertexFactoryFlags::SupportsLandscape ); namespace UE::Landscape::Culling { constexpr static uint32 LANDSCAPE_TILE_QUADS = 4u; constexpr static uint32 MIN_SECTION_SIZE_QUADS = 31u; constexpr static uint32 INDIRECT_ARGS_NUM_WORDS = 5u; constexpr static uint32 TILE_DATA_ENTRY_NUM_BYTES = 4u; static void LandscapeComponentsRecreateRenderState(IConsoleVariable* Variable) { for (auto* LandscapeComponent : TObjectRange()) { LandscapeComponent->MarkRenderStateDirty(); } } struct FLumenCVarsState { FLumenCVarsState() { IConsoleVariable* SupportedCVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Lumen.Supported")); bLumenSupported = (SupportedCVar && SupportedCVar->GetInt() != 0); IConsoleVariable* DiffuseIndirectCVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.Lumen.DiffuseIndirect.Allow")); if (bLumenSupported && DiffuseIndirectCVar) { DiffuseIndirectCVar->SetOnChangedCallback(FConsoleVariableDelegate::CreateStatic(&LandscapeComponentsRecreateRenderState)); } DiffuseIndirectInt = DiffuseIndirectCVar->AsVariableInt(); } bool IsActive() const { return bLumenSupported && (DiffuseIndirectInt && DiffuseIndirectInt->GetValueOnAnyThread() != 0); } TConsoleVariableData* DiffuseIndirectInt; bool bLumenSupported = false; }; bool UseCulling(EShaderPlatform Platform) { static FLumenCVarsState LumenCVarsState; static FShaderPlatformCachedIniValue LandscapeSupportCullingIniValue(TEXT("landscape.SupportGPUCulling")); return LandscapeSupportCullingIniValue.Get(Platform) != 0 && // These features require VF PrimitiveID support which is not possible with culling VF atm // Note that VSM and Lumen test includes runtime logic, so this function can't be used in cook time decisions !UseVirtualShadowMaps(Platform, GMaxRHIFeatureLevel) && !(FDataDrivenShaderPlatformInfo::GetSupportsLumenGI(Platform) && LumenCVarsState.IsActive()); } FVertexFactoryType* GetTileVertexFactoryType() { return &FLandscapeTileVertexFactory::StaticType; } class FLandscapeTileMesh final : public FRenderResource { public: FLandscapeTileMesh(FRHICommandListBase& RHICmdList) { InitResource(RHICmdList); } /** Destructor. */ virtual ~FLandscapeTileMesh() { ReleaseResource(); } virtual void InitResource(FRHICommandListBase& RHICmdList) override; virtual void ReleaseResource() override; virtual void InitRHI(FRHICommandListBase& RHICmdList) override; public: FVertexBuffer VertexBuffer; FIndexBuffer IndexBuffer; }; void FLandscapeTileMesh::InitResource(FRHICommandListBase& RHICmdList) { FRenderResource::InitResource(RHICmdList); VertexBuffer.InitResource(RHICmdList); IndexBuffer.InitResource(RHICmdList); } void FLandscapeTileMesh::ReleaseResource() { FRenderResource::ReleaseResource(); VertexBuffer.ReleaseResource(); IndexBuffer.ReleaseResource(); } void FLandscapeTileMesh::InitRHI(FRHICommandListBase& RHICmdList) { const uint32 TileSizeVertx = LANDSCAPE_TILE_QUADS + 1u; // create a static vertex buffer { const uint32 NumVertx = FMath::Square(TileSizeVertx); const uint32 Stride = sizeof(FLandscapeVertex); const FRHIBufferCreateDesc CreateDesc = FRHIBufferCreateDesc::Create(TEXT("FLandscapeTileMeshVertexBuffer"), NumVertx * Stride, Stride, EBufferUsageFlags::Static | EBufferUsageFlags::VertexBuffer) .SetInitialState(ERHIAccess::VertexOrIndexBuffer) .SetInitActionInitializer(); TRHIBufferInitializer Initializer = RHICmdList.CreateBufferInitializer(CreateDesc); uint32 VertexIndex = 0; for (uint32 y = 0; y < TileSizeVertx; y++) { for (uint32 x = 0; x < TileSizeVertx; x++) { FLandscapeVertex Vertex{}; Vertex.VertexX = static_cast(x); Vertex.VertexY = static_cast(y); Vertex.SubX = 0; Vertex.SubY = 0; Initializer.WriteValueAtIndex(VertexIndex, Vertex); VertexIndex++; } } VertexBuffer.VertexBufferRHI = Initializer.Finalize(); } // { const int32 NumIndices = FMath::Square(LANDSCAPE_TILE_QUADS) * 6u; const uint32 Stride = sizeof(uint16); const FRHIBufferCreateDesc CreateDesc = FRHIBufferCreateDesc::Create(TEXT("FLandscapeTileMeshIndexBuffer"), NumIndices * Stride, Stride, EBufferUsageFlags::Static | EBufferUsageFlags::IndexBuffer) .SetInitialState(ERHIAccess::VertexOrIndexBuffer) .SetInitActionInitializer(); TRHIBufferInitializer Initializer = RHICmdList.CreateBufferInitializer(CreateDesc); uint32 IndexOffset = 0; for (uint32 y = 0; y < LANDSCAPE_TILE_QUADS; y++) { for (uint32 x = 0; x < LANDSCAPE_TILE_QUADS; x++) { uint16 i00 = static_cast((x + 0) + (y + 0) * (TileSizeVertx)); uint16 i10 = static_cast((x + 1) + (y + 0) * (TileSizeVertx)); uint16 i11 = static_cast((x + 1) + (y + 1) * (TileSizeVertx)); uint16 i01 = static_cast((x + 0) + (y + 1) * (TileSizeVertx)); Initializer[IndexOffset + 0] = i00; Initializer[IndexOffset + 1] = i11; Initializer[IndexOffset + 2] = i10; Initializer[IndexOffset + 3] = i00; Initializer[IndexOffset + 4] = i01; Initializer[IndexOffset + 5] = i11; IndexOffset += 6; } } IndexBuffer.IndexBufferRHI = Initializer.Finalize(); } } class FLandscapeTileDataBuffer final : public FVertexBuffer { uint32 NumSubsections; uint32 SubsectionSizeQuads; public: FLandscapeTileDataBuffer(FRHICommandListBase& RHICmdList, uint32 InNumSubsections, uint32 InSubsectionSizeQuads) : NumSubsections(InNumSubsections) , SubsectionSizeQuads(InSubsectionSizeQuads) { InitResource(RHICmdList); } /** Destructor. */ virtual ~FLandscapeTileDataBuffer() { ReleaseResource(); } virtual void InitRHI(FRHICommandListBase& RHICmdList) override; }; void FLandscapeTileDataBuffer::InitRHI(FRHICommandListBase& RHICmdList) { const uint32 SubsectionTilesRow = FMath::DivideAndRoundUp(SubsectionSizeQuads, LANDSCAPE_TILE_QUADS); const uint32 SubsectionTiles = SubsectionTilesRow * SubsectionTilesRow; const uint32 ComponentTiles = SubsectionTiles * NumSubsections * NumSubsections; const uint32 Stride = TILE_DATA_ENTRY_NUM_BYTES; const FRHIBufferCreateDesc CreateDesc = FRHIBufferCreateDesc::Create(TEXT("FLandscapeTileVertexBuffer"), ComponentTiles * Stride, Stride, EBufferUsageFlags::Static | EBufferUsageFlags::VertexBuffer) .SetInitialState(ERHIAccess::VertexOrIndexBuffer) .SetInitActionInitializer(); TRHIBufferInitializer Initializer = RHICmdList.CreateBufferInitializer(CreateDesc); uint32 TileOffset = 0; for (uint32 SubY = 0; SubY < NumSubsections; SubY++) { for (uint32 SubX = 0; SubX < NumSubsections; SubX++) { for (uint32 y = 0; y < SubsectionTilesRow; y++) { for (uint32 x = 0; x < SubsectionTilesRow; x++) { Initializer[TileOffset + 0] = static_cast(x * LANDSCAPE_TILE_QUADS); Initializer[TileOffset + 1] = static_cast(y * LANDSCAPE_TILE_QUADS); Initializer[TileOffset + 2] = static_cast(SubX); Initializer[TileOffset + 3] = static_cast(SubY); TileOffset += Stride; } } } } VertexBufferRHI = Initializer.Finalize(); } class FBuildLandscapeTileDataCS : public FGlobalShader { DECLARE_GLOBAL_SHADER(FBuildLandscapeTileDataCS); SHADER_USE_PARAMETER_STRUCT(FBuildLandscapeTileDataCS, FGlobalShader) public: static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { static FShaderPlatformCachedIniValue LandscapeSupportCullingIniValue(TEXT("landscape.SupportGPUCulling")); return LandscapeSupportCullingIniValue.Get(Parameters.Platform) != 0; } static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment); OutEnvironment.SetDefine(TEXT("INDIRECT_ARGS_NUM_WORDS"), INDIRECT_ARGS_NUM_WORDS); OutEnvironment.SetDefine(TEXT("LANDSCAPE_TILE_QUADS"), LANDSCAPE_TILE_QUADS); } struct FLandscapeView { FMatrix44f ViewToClip; FMatrix44f TranslatedWorldToClip; FVector3f RelativePreViewTranslation; float _Pad0; FVector3f ViewTilePosition; float _Pad1; }; struct FLandscapeSection { FMatrix44f LocalToRelativeWorld; FVector3f TilePosition; float LocalZ; float HalfHeight; float NeighborLODExtent; float _Pad[2]; }; BEGIN_SHADER_PARAMETER_STRUCT(FParameters, ) SHADER_PARAMETER_RDG_BUFFER_UAV(RWBuffer, IndirectArgsBufferOut) SHADER_PARAMETER_RDG_BUFFER_UAV(RWByteAddressBuffer, TileDataOut) SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer, LandscapeViews) SHADER_PARAMETER_RDG_BUFFER_SRV(StructuredBuffer, LandscapeSections) SHADER_PARAMETER(uint32, NumLandscapeSections) SHADER_PARAMETER(uint32, NumLandscapeViews) SHADER_PARAMETER(uint32, SubsectionSizeTiles) SHADER_PARAMETER(uint32, NumSubsections) SHADER_PARAMETER(uint32, NearClip) END_SHADER_PARAMETER_STRUCT() }; IMPLEMENT_GLOBAL_SHADER(FBuildLandscapeTileDataCS, "/Engine/Private/Landscape/LandscapeCulling.usf", "BuildLandscapeTileDataCS", SF_Compute); struct FViewStateIntermediateData { FRDGBufferRef SectionsBufferRDG; TArray> SectionRenderCoords; }; struct FDispatchIntermediates { TRefCountPtr IndirectArgsBuffer; TRefCountPtr TileDataBuffer; }; struct FArgumentsKey { const void* ViewPtr; FIntPoint RenderCoord; uint32 ViewStateKey; bool operator==(const FArgumentsKey& Other) const { return ViewPtr == Other.ViewPtr && RenderCoord == Other.RenderCoord && ViewStateKey == Other.ViewStateKey; } }; uint32 GetTypeHash(const FArgumentsKey& Key) { uint32 Hash = PointerHash(Key.ViewPtr); Hash = HashCombine(Hash, GetTypeHash(Key.RenderCoord)); Hash = HashCombine(Hash, ::GetTypeHash(Key.ViewStateKey)); return Hash; } struct FCullingEntry { uint32 LandscapeKey; int32 ReferenceCount; uint32 NumSubsections; uint32 SubsectionSizeQuads; TArray> IntermediateData; TArray> DispatchIntermediateData; TMap CullingArguments; }; struct FCullingSystem { TArray Landscapes; TArray> Views; }; FCullingSystem GCullingSystem; static bool EnableGPUCullingForSection(int32 SubsectionSizeVerts, int32 NumSubsections) { uint32 SectionSizeQuads = (SubsectionSizeVerts - 1) * NumSubsections; return (SectionSizeQuads >= MIN_SECTION_SIZE_QUADS); } static void ResetIntermediateData() { for (int32 LandscapeIdx = 0; LandscapeIdx < GCullingSystem.Landscapes.Num(); ++LandscapeIdx) { FCullingEntry& CullingEntry = GCullingSystem.Landscapes[LandscapeIdx]; CullingEntry.IntermediateData.Reset(); CullingEntry.DispatchIntermediateData.Reset(); CullingEntry.CullingArguments.Reset(); } GCullingSystem.Views.Reset(); } void PreRenderViewFamily(FSceneViewFamily& InViewFamily) { ResetIntermediateData(); } void InitSharedBuffers(FRHICommandListBase& RHICmdList, FLandscapeSharedBuffers& SharedBuffers, const ERHIFeatureLevel::Type InFeatureLevel) { if (SharedBuffers.TileVertexFactory == nullptr) { uint32 SubsectionSizeQuads = (SharedBuffers.SubsectionSizeVerts - 1); FLandscapeTileMesh* TileMesh = new FLandscapeTileMesh(RHICmdList); FLandscapeTileDataBuffer* TileDataBuffer = new FLandscapeTileDataBuffer(RHICmdList, SharedBuffers.NumSubsections, SubsectionSizeQuads); FLandscapeTileVertexFactory* TileVF = new FLandscapeTileVertexFactory(InFeatureLevel); uint32 TileDataStride = TILE_DATA_ENTRY_NUM_BYTES; TileVF->Data.PositionComponent = FVertexStreamComponent(&TileMesh->VertexBuffer, 0, sizeof(FLandscapeVertex), VET_UByte4); TileVF->Data.TileDataComponent = FVertexStreamComponent(TileDataBuffer, 0, TileDataStride, VET_UByte4, EVertexStreamUsage::Instancing); TileVF->InitResource(RHICmdList); SharedBuffers.TileMesh = TileMesh; SharedBuffers.TileVertexFactory = TileVF; SharedBuffers.TileDataBuffer = TileDataBuffer; } } void SetupMeshBatch(const FLandscapeSharedBuffers& SharedBuffers, FMeshBatch& MeshBatch) { if (!EnableGPUCullingForSection(SharedBuffers.SubsectionSizeVerts, SharedBuffers.NumSubsections)) { return; } // TileVertexFactory can be temporarily null when we switch conditions on landscape culling. In this // case, we just fallback to the non landscape culling path. if (MeshBatch.LODIndex == 0 && SharedBuffers.TileVertexFactory != nullptr) { uint32 SubsectionSizeQuads = (SharedBuffers.SubsectionSizeVerts - 1); MeshBatch.bViewDependentArguments = true; MeshBatch.VertexFactory = SharedBuffers.TileVertexFactory; FMeshBatchElement& BatchElement = MeshBatch.Elements[0]; BatchElement.IndexBuffer = &static_cast(SharedBuffers.TileMesh)->IndexBuffer; BatchElement.NumPrimitives = FMath::Square(LANDSCAPE_TILE_QUADS) * 2u; BatchElement.NumInstances = FMath::Square(FMath::DivideAndRoundUp(SubsectionSizeQuads, LANDSCAPE_TILE_QUADS)) * FMath::Square(SharedBuffers.NumSubsections); BatchElement.FirstIndex = 0; BatchElement.MinVertexIndex = 0; BatchElement.MaxVertexIndex = FMath::Square(LANDSCAPE_TILE_QUADS + 1u) - 1u; } } void RegisterLandscape(FRHICommandListBase& RHICmdList, FLandscapeSharedBuffers& SharedBuffers, ERHIFeatureLevel::Type FeatureLevel, uint32 LandscapeKey, int32 SubsectionSizeVerts, int32 NumSubsections) { if (!EnableGPUCullingForSection(SubsectionSizeVerts, NumSubsections)) { return; } InitSharedBuffers(RHICmdList, SharedBuffers, FeatureLevel); FCullingEntry* CullingEntryPtr = GCullingSystem.Landscapes.FindByPredicate([LandscapeKey](const FCullingEntry& Other) { return Other.LandscapeKey == LandscapeKey; }); if (!CullingEntryPtr) { FCullingEntry& CullingEntry = GCullingSystem.Landscapes.AddDefaulted_GetRef(); CullingEntry.LandscapeKey = LandscapeKey; CullingEntry.NumSubsections = NumSubsections; CullingEntry.SubsectionSizeQuads = (SubsectionSizeVerts - 1); CullingEntry.ReferenceCount = 1; } else { CullingEntryPtr->ReferenceCount++; } } void UnregisterLandscape(uint32 LandscapeKey) { for (int32 i = GCullingSystem.Landscapes.Num() - 1; i >= 0; --i) { FCullingEntry& CullingEntry = GCullingSystem.Landscapes[i]; if (CullingEntry.LandscapeKey == LandscapeKey) { CullingEntry.ReferenceCount--; if (CullingEntry.ReferenceCount <= 0) { GCullingSystem.Landscapes.RemoveAtSwap(i); } break; } } } static float ComputeNeighborsMaxLOD(const FLandscapeRenderSystem& RenderSystem, const TResourceArray& SectionLODValues, FIntPoint RenderCoord) { static const FIntPoint NeightborBaseOffsets[4] { {1, 0}, {-1, 0}, {0, 1}, {0, -1} }; float NeighborsMaxLOD = 0.f; for (int32 i = 0; i < UE_ARRAY_COUNT(NeightborBaseOffsets); ++i) { if (RenderSystem.IsValidCoord(RenderCoord + NeightborBaseOffsets[i])) { int32 SectionIdx = RenderSystem.GetSectionLinearIndex(RenderCoord + NeightborBaseOffsets[i]); if (SectionLODValues.IsValidIndex(SectionIdx)) { NeighborsMaxLOD = FMath::Max(SectionLODValues[SectionIdx], NeighborsMaxLOD); } } } return NeighborsMaxLOD; } static void ComputeSectionIntermediateData(FRDGBuilder& GraphBuilder, TArrayView Views) { TArray> SectionsData; for (int32 ViewIdx = 0; ViewIdx < Views.Num(); ++ViewIdx) { const FSceneView& View = *Views[ViewIdx]; GCullingSystem.Views.Add(&View); // Collect all LOD0 sections for each ViewState+Landscape and upload to GPU for (int32 LandscapeIdx = 0; LandscapeIdx < GCullingSystem.Landscapes.Num(); ++LandscapeIdx) { FCullingEntry& CullingEntry = GCullingSystem.Landscapes[LandscapeIdx]; FViewStateIntermediateData& ViewStateIntermediates = CullingEntry.IntermediateData.AddDefaulted_GetRef(); ViewStateIntermediates.SectionsBufferRDG = nullptr; const FLandscapeRenderSystem* RenderSystem = FLandscapeSceneViewExtension::GetLandscapeRenderSystem(View.Family->Scene, CullingEntry.LandscapeKey); // This landscape render system might not correspond to the scene we're rendering, so we might end up with nothing here : if (RenderSystem != nullptr) { const TResourceArray& SectionLODValues = RenderSystem->GetCachedSectionLODValues(View); for (int32 SectionIdx = 0; SectionIdx < SectionLODValues.Num(); ++SectionIdx) { const int32 LODValue = static_cast(SectionLODValues[SectionIdx]); FLandscapeSectionInfo* SectionInfo = RenderSystem->SectionInfos[SectionIdx]; if (LODValue == 0 && SectionInfo != nullptr) { FBuildLandscapeTileDataCS::FLandscapeSection& Section = SectionsData.AddDefaulted_GetRef(); FBoxSphereBounds SectionLocalBounds; FMatrix SectionLocalToWorld; SectionInfo->GetSectionBoundsAndLocalToWorld(SectionLocalBounds, SectionLocalToWorld); const FLargeWorldRenderPosition SectionAbsoluteOrigin(SectionLocalToWorld.GetOrigin()); const int32 NeighborsMaxLOD = static_cast(FMath::RoundFromZero(ComputeNeighborsMaxLOD(*RenderSystem, SectionLODValues, SectionInfo->RenderCoord))); Section.LocalToRelativeWorld = FLargeWorldRenderScalar::MakeToRelativeWorldMatrix(SectionAbsoluteOrigin.GetTileOffset(), SectionLocalToWorld); Section.TilePosition = SectionAbsoluteOrigin.GetTile(); Section.LocalZ = static_cast(SectionLocalBounds.Origin.Z); Section.HalfHeight = static_cast(SectionLocalBounds.BoxExtent.Z); // How many quads to add to each tile extent to compensate for a neighbors LOD Section.NeighborLODExtent = FMath::Max(static_cast((1 << NeighborsMaxLOD) - 1), 1.f); ViewStateIntermediates.SectionRenderCoords.Add(SectionInfo->RenderCoord); } } if (SectionsData.Num() != 0) { ViewStateIntermediates.SectionsBufferRDG = CreateStructuredBuffer(GraphBuilder, TEXT("LandscapeCulling.SectionsData"), SectionsData); SectionsData.Reset(); } } } } } static void DispatchCulling(FRDGBuilder& GraphBuilder, TArrayView CullingViews, TArrayView CullingViewMatrices, bool bNearClip) { // A separate dispatch for each Landscape and ViewState for (int32 ViewStateIdx = 0; ViewStateIdx < GCullingSystem.Views.Num(); ++ViewStateIdx) { const FSceneView* View = GCullingSystem.Views[ViewStateIdx]; uint32 ViewStateKey = View->GetViewKey(); TArray> CullingViewsData; CullingViewsData.Reserve(CullingViews.Num()); TArray> FilteredViews; FilteredViews.Reserve(CullingViews.Num()); // Filter out views for a current ViewState for (int32 CullingViewIdx = 0; CullingViewIdx < CullingViews.Num(); ++CullingViewIdx) { const FSceneView& CullingView = *CullingViews[CullingViewIdx]; if (&CullingView != View) { continue; } FilteredViews.Add(&CullingView); FBuildLandscapeTileDataCS::FLandscapeView& LandscapeView = CullingViewsData.AddDefaulted_GetRef(); const FViewMatrices& ViewMatrices = CullingViewMatrices[CullingViewIdx]; const FLargeWorldRenderPosition AbsoluteViewOrigin(ViewMatrices.GetViewOrigin()); const FVector ViewTileOffset = AbsoluteViewOrigin.GetTileOffset(); const FVector3f ViewTilePosition = AbsoluteViewOrigin.GetTile(); LandscapeView.ViewToClip = FMatrix44f(ViewMatrices.GetProjectionMatrix()); LandscapeView.TranslatedWorldToClip = FMatrix44f(ViewMatrices.GetTranslatedViewProjectionMatrix()); LandscapeView.RelativePreViewTranslation = FVector3f(ViewMatrices.GetPreViewTranslation() + ViewTileOffset); LandscapeView.ViewTilePosition = ViewTilePosition; } // Share view data between all landscapes FRDGBufferRef CullingViewsBufferRDG = nullptr; for (int32 LandscapeIdx = 0; LandscapeIdx < GCullingSystem.Landscapes.Num(); ++LandscapeIdx) { FCullingEntry& CullingEntry = GCullingSystem.Landscapes[LandscapeIdx]; FViewStateIntermediateData& ViewStateIntermediates = CullingEntry.IntermediateData[ViewStateIdx]; uint32 NumCullingViews = CullingViewsData.Num(); uint32 NumCullingSections = ViewStateIntermediates.SectionRenderCoords.Num(); uint32 NumCullingItems = NumCullingSections * NumCullingViews; if (NumCullingItems == 0) { continue; } TArray IndirectArgsParameters; IndirectArgsParameters.Init(FRHIDrawIndexedIndirectParameters{ FMath::Square(LANDSCAPE_TILE_QUADS) * 6u, 0u, 0u, 0u, 0u }, NumCullingItems); const uint32 IndirectArgsBufferElements = INDIRECT_ARGS_NUM_WORDS * IndirectArgsParameters.Num(); FRDGBufferRef IndirectArgsRDG = GraphBuilder.CreateBuffer(FRDGBufferDesc::CreateIndirectDesc(IndirectArgsBufferElements), TEXT("LandscapeCulling.DrawIndirectArgsBuffer")); const uint32 NumTilesSectionRow = FMath::DivideAndRoundUp(CullingEntry.SubsectionSizeQuads, LANDSCAPE_TILE_QUADS) * CullingEntry.NumSubsections; const uint32 NumTilesSection = FMath::Square(NumTilesSectionRow); const uint32 TileDataBufferElements = NumTilesSection * NumCullingItems; FRDGBufferDesc TileDataBufferDesc = FRDGBufferDesc::CreateByteAddressDesc(TileDataBufferElements * TILE_DATA_ENTRY_NUM_BYTES); TileDataBufferDesc.Usage |= BUF_VertexBuffer; FRDGBufferRef TileDataRDG = GraphBuilder.CreateBuffer(TileDataBufferDesc, TEXT("LandscapeCulling.CulledTileDataBuffer")); // Create buffer for indirect args and upload draw arg data, also clears the instance to zero GraphBuilder.QueueBufferUpload(IndirectArgsRDG, IndirectArgsParameters.GetData(), IndirectArgsParameters.Num() * sizeof(FRHIDrawIndexedIndirectParameters)); if (CullingViewsBufferRDG == nullptr) { CullingViewsBufferRDG = CreateStructuredBuffer(GraphBuilder, TEXT("LandscapeCulling.ViewsData"), CullingViewsData); } { ERHIFeatureLevel::Type FeatureLevel = CullingViews[0]->GetFeatureLevel(); FGlobalShaderMap* ShaderMap = GetGlobalShaderMap(FeatureLevel); FIntVector SizeInTiles{ (int32)NumTilesSectionRow, (int32)NumTilesSectionRow, (int32)NumCullingItems }; FIntVector GroupSize{ 8, 8, 1 }; auto PassParameters = GraphBuilder.AllocParameters(); PassParameters->IndirectArgsBufferOut = GraphBuilder.CreateUAV(IndirectArgsRDG); PassParameters->TileDataOut = GraphBuilder.CreateUAV(TileDataRDG); PassParameters->LandscapeViews = GraphBuilder.CreateSRV(CullingViewsBufferRDG); PassParameters->LandscapeSections = GraphBuilder.CreateSRV(ViewStateIntermediates.SectionsBufferRDG); PassParameters->SubsectionSizeTiles = FMath::DivideAndRoundUp(CullingEntry.SubsectionSizeQuads, LANDSCAPE_TILE_QUADS); PassParameters->NumSubsections = CullingEntry.NumSubsections; PassParameters->NumLandscapeViews = NumCullingViews; PassParameters->NumLandscapeSections = NumCullingSections; PassParameters->NearClip = bNearClip ? 1 : 0; auto ComputeShader = ShaderMap->GetShader(); FComputeShaderUtils::AddPass( GraphBuilder, RDG_EVENT_NAME("LandscapeCulling, SubsectionSizeQuads %d, Subsections %d, Views:%d", CullingEntry.SubsectionSizeQuads, CullingEntry.NumSubsections, NumCullingViews), ComputeShader, PassParameters, FComputeShaderUtils::GetGroupCount(SizeInTiles, GroupSize) ); } FDispatchIntermediates& DispatchIntermediates = CullingEntry.DispatchIntermediateData.AddDefaulted_GetRef(); DispatchIntermediates.IndirectArgsBuffer = ConvertToExternalAccessBuffer(GraphBuilder, IndirectArgsRDG, ERHIAccess::IndirectArgs); DispatchIntermediates.TileDataBuffer = ConvertToExternalAccessBuffer(GraphBuilder, TileDataRDG, ERHIAccess::VertexOrIndexBuffer); FRHIBuffer* IndirectArgsBufferRHI = DispatchIntermediates.IndirectArgsBuffer->GetRHI(); FRHIBuffer* TileDataBufferRHI = DispatchIntermediates.TileDataBuffer->GetRHI(); // Create a map for each view+section combo to its draw args for (int32 ViewIdx = 0; ViewIdx < FilteredViews.Num(); ++ViewIdx) { const FSceneView* ViewPtr = FilteredViews[ViewIdx]; for (int32 SectionIdx = 0; SectionIdx < ViewStateIntermediates.SectionRenderCoords.Num(); ++SectionIdx) { FIntPoint RenderCoord = ViewStateIntermediates.SectionRenderCoords[SectionIdx]; check(RenderCoord.X > INT32_MIN); FArguments& Args = CullingEntry.CullingArguments.Add(FArgumentsKey{ ViewPtr, RenderCoord, ViewStateKey }); Args.IndirectArgsBuffer = IndirectArgsBufferRHI; Args.TileDataVertexBuffer = TileDataBufferRHI; // offsets are in bytes Args.IndirectArgsOffset = (4u * INDIRECT_ARGS_NUM_WORDS) * ((ViewIdx * NumCullingSections) + SectionIdx); Args.TileDataOffset = TILE_DATA_ENTRY_NUM_BYTES * NumTilesSection * ((ViewIdx * NumCullingSections) + SectionIdx); } } } } } void InitMainViews(FRDGBuilder& GraphBuilder, TArrayView Views) { if (GCullingSystem.Landscapes.Num() == 0 || CVarLandscapeEnableGPUCulling.GetValueOnRenderThread() == 0) { return; } QUICK_SCOPE_CYCLE_COUNTER(STAT_LandscapeCulling_InitMainViews); check(GCullingSystem.Views.Num() == 0); ComputeSectionIntermediateData(GraphBuilder, Views); TArray> CullingViewMatrices; for (const FSceneView* View : Views) { CullingViewMatrices.Add(View->ViewMatrices); } DispatchCulling(GraphBuilder, Views, CullingViewMatrices, true); } void InitShadowViews(FRDGBuilder& GraphBuilder, TArrayView ShadowDepthViews, TArrayView ShadowViewMatrices) { if (GCullingSystem.Landscapes.Num() == 0 || CVarLandscapeEnableGPUCulling.GetValueOnRenderThread() == 0 || CVarLandscapeEnableGPUCullingShadows.GetValueOnRenderThread() == 0) { return; } QUICK_SCOPE_CYCLE_COUNTER(STAT_LandscapeCulling_InitShadowViews); DispatchCulling(GraphBuilder, ShadowDepthViews, ShadowViewMatrices, false); } bool GetViewArguments(const FSceneView& View, uint32 LandscapeKey, FIntPoint RenderCoord, int32 LODIndex, FArguments& Args) { if (LODIndex != 0 || GCullingSystem.Landscapes.Num() == 0 || CVarLandscapeEnableGPUCulling.GetValueOnRenderThread() == 0) { return false; } const FCullingEntry* CullingEntryPtr = GCullingSystem.Landscapes.FindByPredicate([LandscapeKey](const FCullingEntry& Other) { return Other.LandscapeKey == LandscapeKey; }); if (CullingEntryPtr == nullptr) { return false; } check(RenderCoord.X > INT32_MIN); FArgumentsKey ArgumentsKey{ &View, RenderCoord, View.GetViewKey() }; const FArguments* ArgsPtr = CullingEntryPtr->CullingArguments.Find(ArgumentsKey); if (ArgsPtr) { Args = *ArgsPtr; return true; } return false; } }//namespace UE::Landscape::Culling