1205 lines
36 KiB
C++
1205 lines
36 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#pragma once
|
|
|
|
#include "SceneVisibility.h"
|
|
#include "ScenePrivate.h"
|
|
#include "Containers/ConsumeAllMpmcQueue.h"
|
|
#include "DynamicPrimitiveDrawing.h"
|
|
#include "Async/TaskGraphInterfaces.h"
|
|
#include "Async/Mutex.h"
|
|
#include "Async/UniqueLock.h"
|
|
|
|
class FInstanceCullingManager;
|
|
class FComputeAndMarkRelevance;
|
|
class FGPUOcclusion;
|
|
class FGPUOcclusionSerial;
|
|
class FGPUOcclusionParallel;
|
|
class FGPUOcclusionParallelPacket;
|
|
class FRelevancePacket;
|
|
class FVisibilityTaskData;
|
|
class FVirtualTextureUpdater;
|
|
|
|
/** An async MPSC queue that can schedule serialized tasks onto the render thread or a task thread. When a new command is enqueued into an empty queue, a
|
|
* task is launched to process all pending elements in the queue as soon as possible. Each command must be reserved with AddNumCommands,
|
|
* which acts as a reference count on the number of pending commands in the pipe. This is necessary to determine when the pipe is 'done' and
|
|
* can signal a completion event or callback.
|
|
*/
|
|
template <typename CommandType>
|
|
class TCommandPipe
|
|
{
|
|
public:
|
|
using CommandFunctionType = TFunction<void(CommandType&&)>;
|
|
using EmptyFunctionType = TFunction<void()>;
|
|
|
|
TCommandPipe(const TCHAR* InName)
|
|
: Pipe(InName)
|
|
{}
|
|
|
|
~TCommandPipe()
|
|
{
|
|
check(IsEmpty());
|
|
Wait();
|
|
}
|
|
|
|
void SetCommandFunction(CommandFunctionType&& InCommandFunction)
|
|
{
|
|
CommandFunction = Forward<CommandFunctionType&&>(InCommandFunction);
|
|
}
|
|
|
|
void SetEmptyFunction(TFunction<void()>&& InEmptyFunction)
|
|
{
|
|
EmptyFunction = Forward<EmptyFunctionType&&>(InEmptyFunction);
|
|
}
|
|
|
|
void SetPrerequisiteTask(const UE::Tasks::FTask& InPrerequisiteTask)
|
|
{
|
|
PrerequisiteTask = InPrerequisiteTask;
|
|
}
|
|
|
|
void AddNumCommands(int32 InNumCommands)
|
|
{
|
|
NumCommands.fetch_add(InNumCommands, std::memory_order_relaxed);
|
|
}
|
|
|
|
void ReleaseNumCommands(int32 InNumCommands)
|
|
{
|
|
int32 FinalNumCommands = NumCommands.fetch_sub(InNumCommands, std::memory_order_acq_rel) - InNumCommands;
|
|
check(FinalNumCommands >= 0);
|
|
|
|
if (FinalNumCommands == 0)
|
|
{
|
|
if (EmptyFunction)
|
|
{
|
|
EmptyFunction();
|
|
}
|
|
}
|
|
}
|
|
|
|
template <typename... ArgTypes>
|
|
void EnqueueCommand(ArgTypes&&... Args)
|
|
{
|
|
check(CommandFunction);
|
|
|
|
QueueMutex.Lock();
|
|
const bool bWasEmpty = Queue.IsEmpty();
|
|
Queue.Emplace(Forward<ArgTypes>(Args)...);
|
|
QueueMutex.Unlock();
|
|
|
|
if (bWasEmpty)
|
|
{
|
|
Pipe.Launch(Pipe.GetDebugName(), [this]
|
|
{
|
|
FTaskTagScope Scope(ETaskTag::EParallelRenderingThread);
|
|
SCOPED_NAMED_EVENT_TCHAR(Pipe.GetDebugName(), FColor::Magenta);
|
|
|
|
TArray<CommandType, SceneRenderingAllocator> Commands;
|
|
|
|
QueueMutex.Lock();
|
|
Commands = MoveTemp(Queue);
|
|
QueueMutex.Unlock();
|
|
|
|
int32 NumProcessedCommands = 0;
|
|
|
|
for (CommandType& Command : Commands)
|
|
{
|
|
CommandFunction(MoveTemp(Command));
|
|
NumProcessedCommands++;
|
|
}
|
|
|
|
if (NumProcessedCommands)
|
|
{
|
|
ReleaseNumCommands(NumProcessedCommands);
|
|
}
|
|
|
|
}, PrerequisiteTask, UE::Tasks::ETaskPriority::High);
|
|
}
|
|
}
|
|
|
|
bool IsEmpty() const
|
|
{
|
|
return Queue.IsEmpty();
|
|
}
|
|
|
|
void Wait()
|
|
{
|
|
Pipe.WaitUntilEmpty();
|
|
}
|
|
|
|
private:
|
|
UE::Tasks::FTask PrerequisiteTask;
|
|
CommandFunctionType CommandFunction;
|
|
EmptyFunctionType EmptyFunction;
|
|
UE::FMutex QueueMutex;
|
|
TArray<CommandType, SceneRenderingAllocator> Queue;
|
|
UE::Tasks::FPipe Pipe;
|
|
std::atomic_int32_t NumCommands{ 0 };
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
inline UE::Tasks::EExtendedTaskPriority GetExtendedTaskPriority(bool bExecuteInParallel)
|
|
{
|
|
return bExecuteInParallel ? UE::Tasks::EExtendedTaskPriority::None : UE::Tasks::EExtendedTaskPriority::Inline;
|
|
}
|
|
|
|
using FPrimitiveIndexList = TArray<int32, SceneRenderingAllocator>;
|
|
|
|
struct FPrimitiveRange
|
|
{
|
|
int32 StartIndex;
|
|
int32 EndIndex;
|
|
};
|
|
|
|
struct FDynamicPrimitive
|
|
{
|
|
int32 PrimitiveIndex;
|
|
int32 ViewIndex;
|
|
int32 StartElementIndex;
|
|
int32 EndElementIndex;
|
|
};
|
|
|
|
struct FDynamicPrimitiveIndex
|
|
{
|
|
FDynamicPrimitiveIndex() = default;
|
|
|
|
FDynamicPrimitiveIndex(int32 InIndex, uint8 InViewMask)
|
|
: Index(InIndex)
|
|
, ViewMask(InViewMask)
|
|
{}
|
|
|
|
uint32 Index : 24;
|
|
uint32 ViewMask : 8;
|
|
};
|
|
|
|
struct FDynamicPrimitiveIndexList
|
|
{
|
|
using FList = TArray<FDynamicPrimitiveIndex, SceneRenderingAllocator>;
|
|
|
|
bool IsEmpty() const
|
|
{
|
|
return Primitives.IsEmpty()
|
|
#if WITH_EDITOR
|
|
&& EditorPrimitives.IsEmpty()
|
|
#endif
|
|
;
|
|
}
|
|
|
|
FList Primitives;
|
|
|
|
#if WITH_EDITOR
|
|
FList EditorPrimitives;
|
|
#endif
|
|
};
|
|
|
|
class FDynamicPrimitiveIndexQueue
|
|
{
|
|
public:
|
|
FDynamicPrimitiveIndexQueue(FDynamicPrimitiveIndexList&& InList)
|
|
: List(InList)
|
|
{}
|
|
|
|
bool Pop(FDynamicPrimitiveIndex& PrimitiveIndex)
|
|
{
|
|
const int32 Index = NextIndex.fetch_add(1, std::memory_order_relaxed);
|
|
if (Index < List.Primitives.Num())
|
|
{
|
|
PrimitiveIndex = List.Primitives[Index];
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
bool PopEditor(FDynamicPrimitiveIndex& PrimitiveIndex)
|
|
{
|
|
const int32 Index = NextEditorIndex.fetch_add(1, std::memory_order_relaxed);
|
|
if (Index < List.EditorPrimitives.Num())
|
|
{
|
|
PrimitiveIndex = List.EditorPrimitives[Index];
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
private:
|
|
FDynamicPrimitiveIndexList List;
|
|
std::atomic_int32_t NextIndex = { 0 };
|
|
#if WITH_EDITOR
|
|
std::atomic_int32_t NextEditorIndex = { 0 };
|
|
#endif
|
|
};
|
|
|
|
struct FDynamicPrimitiveViewMasks
|
|
{
|
|
FPrimitiveViewMasks Primitives;
|
|
|
|
#if WITH_EDITOR
|
|
FPrimitiveViewMasks EditorPrimitives;
|
|
#endif
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
class FDynamicMeshElementContext
|
|
{
|
|
public:
|
|
FDynamicMeshElementContext(FSceneRenderer& SceneRenderer);
|
|
|
|
FGraphEventRef LaunchRenderThreadTask(FDynamicPrimitiveIndexList&& PrimitiveIndexList);
|
|
|
|
UE::Tasks::FTask LaunchAsyncTask(FDynamicPrimitiveIndexQueue* PrimitiveIndexQueue, UE::Tasks::ETaskPriority TaskPriority);
|
|
|
|
void GatherDynamicMeshElementsForPrimitive(FPrimitiveSceneInfo* Primitive, uint8 ViewMask);
|
|
|
|
void GatherDynamicMeshElementsForEditorPrimitive(FPrimitiveSceneInfo* Primitive, uint8 ViewMask);
|
|
|
|
private:
|
|
void Finish();
|
|
|
|
struct FViewFamilyGroup
|
|
{
|
|
const FSceneViewFamily* Family;
|
|
uint8 ViewSubsetMask; // Subset of views with the given view family
|
|
};
|
|
|
|
struct FViewMeshArrays
|
|
{
|
|
TArray<FMeshBatchAndRelevance, SceneRenderingAllocator> DynamicMeshElements;
|
|
FSimpleElementCollector SimpleElementCollector;
|
|
|
|
#if WITH_EDITOR
|
|
TArray<FMeshBatchAndRelevance, SceneRenderingAllocator> DynamicEditorMeshElements;
|
|
FSimpleElementCollector EditorSimpleElementCollector;
|
|
#endif
|
|
|
|
#if UE_ENABLE_DEBUG_DRAWING
|
|
FSimpleElementCollector DebugSimpleElementCollector;
|
|
#endif
|
|
};
|
|
|
|
const FSceneViewFamily& FirstViewFamily;
|
|
TArrayView<FViewInfo*> Views;
|
|
TArrayView<FPrimitiveSceneInfo*> Primitives;
|
|
TArray<FViewFamilyGroup, TInlineAllocator<1>> ViewFamilyGroups;
|
|
TArray<FViewMeshArrays, TInlineAllocator<2>> ViewMeshArraysPerView;
|
|
TArray<FDynamicPrimitive, SceneRenderingAllocator> DynamicPrimitives;
|
|
FMeshElementCollector MeshCollector;
|
|
#if WITH_EDITOR
|
|
FMeshElementCollector EditorMeshCollector;
|
|
#endif
|
|
FRHICommandList* RHICmdList;
|
|
FGlobalDynamicVertexBuffer DynamicVertexBuffer;
|
|
FGlobalDynamicIndexBuffer DynamicIndexBuffer;
|
|
UE::Tasks::FPipe Pipe{UE_SOURCE_LOCATION};
|
|
|
|
friend class FDynamicMeshElementContextContainer;
|
|
};
|
|
|
|
class FDynamicMeshElementContextContainer
|
|
{
|
|
public:
|
|
~FDynamicMeshElementContextContainer();
|
|
|
|
int32 GetNumAsyncContexts() const
|
|
{
|
|
return Contexts.Num() - 1;
|
|
}
|
|
|
|
FDynamicMeshElementContext* GetRenderThreadContext() const
|
|
{
|
|
check(!bFinished);
|
|
return Contexts.Last();
|
|
}
|
|
|
|
FGraphEventRef LaunchRenderThreadTask(FDynamicPrimitiveIndexList&& PrimitiveIndexList);
|
|
|
|
UE::Tasks::FTask LaunchAsyncTask(FDynamicPrimitiveIndexQueue* PrimitiveIndexQueue, int32 Index, UE::Tasks::ETaskPriority TaskPriority);
|
|
|
|
void Init(FSceneRenderer& InSceneRenderer, int32 NumAsyncContexts);
|
|
|
|
void MergeContexts(TArray<FDynamicPrimitive, SceneRenderingAllocator>& OutDynamicPrimitives);
|
|
|
|
void Submit(FRHICommandListImmediate& RHICmdList);
|
|
|
|
private:
|
|
using FDynamicMeshElementContextArray = TArray<FDynamicMeshElementContext*>;
|
|
|
|
TArrayView<FViewInfo*> Views;
|
|
FDynamicMeshElementContextArray Contexts;
|
|
TArray<FRHICommandListImmediate::FQueuedCommandList, FConcurrentLinearArrayAllocator> CommandLists;
|
|
bool bFinished = false;
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
enum class EVisibilityTaskSchedule
|
|
{
|
|
// Visibility is processed on the render thread with assistance from other threads via parallel for.
|
|
RenderThread,
|
|
|
|
// Visibility is processed as an async task graph with only the dynamic mesh element gather on the render thread.
|
|
Parallel,
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Configuration state for visibility task granularity and priority.
|
|
class FVisibilityTaskConfig
|
|
{
|
|
public:
|
|
FVisibilityTaskConfig(const FScene& Scene, TConstArrayView<FViewInfo*> Views);
|
|
|
|
EVisibilityTaskSchedule Schedule;
|
|
|
|
// Task priorities for non-specific tasks related to visibility.
|
|
UE::Tasks::ETaskPriority TaskPriority = UE::Tasks::ETaskPriority::High;
|
|
|
|
uint32 NumVisiblePrimitives = 0;
|
|
uint32 NumTestedPrimitives = 0;
|
|
|
|
struct FAlwaysVisible
|
|
{
|
|
static constexpr uint32 MinWordsPerTask = 32;
|
|
|
|
const UE::Tasks::ETaskPriority TaskPriority = UE::Tasks::ETaskPriority::High;
|
|
|
|
// Always visible tasks are fixed size and process the same number of primitives.
|
|
uint32 NumTasks = 0;
|
|
uint32 NumWordsPerTask = 0;
|
|
uint32 NumPrimitivesPerTask = 0;
|
|
|
|
} AlwaysVisible;
|
|
|
|
struct FFrustumCull
|
|
{
|
|
static constexpr uint32 MinWordsPerTask = 32;
|
|
|
|
const UE::Tasks::ETaskPriority TaskPriority = UE::Tasks::ETaskPriority::High;
|
|
|
|
// Frustum culling tasks are fixed size and process the same number of primitives.
|
|
uint32 NumTasks = 0;
|
|
uint32 NumWordsPerTask = 0;
|
|
uint32 NumPrimitivesPerTask = 0;
|
|
std::atomic_uint32_t NumCulledPrimitives{ 0 };
|
|
|
|
} FrustumCull;
|
|
|
|
struct FOcclusionCull
|
|
{
|
|
static constexpr uint32 MinQueriesPerTask = 64;
|
|
|
|
const UE::Tasks::ETaskPriority TaskPriority = UE::Tasks::ETaskPriority::High;
|
|
const UE::Tasks::ETaskPriority FinalizeTaskPriority = UE::Tasks::ETaskPriority::High;
|
|
|
|
struct FView
|
|
{
|
|
uint32 MaxQueriesPerTask = 0;
|
|
};
|
|
|
|
TArray<FView, TInlineAllocator<2, SceneRenderingAllocator>> Views;
|
|
std::atomic_uint32_t NumCulledPrimitives{ 0 };
|
|
std::atomic_uint32_t NumTestedQueries{ 0 };
|
|
|
|
} OcclusionCull;
|
|
|
|
struct FRelevance
|
|
{
|
|
static constexpr uint32 MinPrimitivesPerTask = 32;
|
|
static constexpr uint32 MaxPrimitivesPerTask = 2048;
|
|
|
|
const UE::Tasks::ETaskPriority ComputeRelevanceTaskPriority = UE::Tasks::ETaskPriority::High;
|
|
|
|
uint32 NumEstimatedPackets = 0;
|
|
uint32 NumPrimitivesPerPacket = 0;
|
|
uint32 NumPrimitivesProcessed = 0;
|
|
|
|
} Relevance;
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
class FVisibilityViewPacket
|
|
{
|
|
friend FVisibilityTaskData;
|
|
friend FGPUOcclusionParallel;
|
|
friend FGPUOcclusionParallelPacket;
|
|
public:
|
|
FVisibilityViewPacket(FVisibilityTaskData& TaskData, FScene& InScene, FViewInfo& InView, int32 ViewIndex);
|
|
|
|
FVisibilityTaskData& TaskData;
|
|
FVisibilityTaskConfig& TaskConfig;
|
|
FScene& Scene;
|
|
FViewInfo& View;
|
|
FSceneViewState* ViewState;
|
|
int32 ViewIndex;
|
|
FViewElementPDI ViewElementPDI;
|
|
|
|
private:
|
|
void BeginInitVisibility();
|
|
|
|
struct FOcclusionCull
|
|
{
|
|
FGPUOcclusionSerial* ContextIfSerial = nullptr;
|
|
FGPUOcclusionParallel* ContextIfParallel = nullptr;
|
|
TCommandPipe<FPrimitiveRange> CommandPipe{ TEXT("OcclusionCullPipe") };
|
|
|
|
} OcclusionCull;
|
|
|
|
struct FRelevance
|
|
{
|
|
FComputeAndMarkRelevance* Context = nullptr;
|
|
TCommandPipe<FPrimitiveIndexList> CommandPipe{ TEXT("RelevancePipe") };
|
|
|
|
} Relevance;
|
|
|
|
struct FTasks
|
|
{
|
|
UE::Tasks::FTaskEvent AlwaysVisible{ UE_SOURCE_LOCATION };
|
|
UE::Tasks::FTaskEvent FrustumCull{ UE_SOURCE_LOCATION };
|
|
UE::Tasks::FTaskEvent OcclusionCull{ UE_SOURCE_LOCATION };
|
|
UE::Tasks::FTaskEvent ComputeRelevance{ UE_SOURCE_LOCATION };
|
|
UE::Tasks::FTaskEvent LightVisibility{ UE_SOURCE_LOCATION };
|
|
|
|
} Tasks;
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
/*
|
|
This class manages all state related to visibility computation for all views associated with a specific scene renderer. When in parallel mode, a complex task graph
|
|
processes each visibility stage and pipelines results from one stage to the next. This avoids major join / fork sync points except for the dynamic mesh elements
|
|
gather which is currently confined to the render thread. For platforms that don't benefit from parallelism or don't support it, a render-thread centric mode is
|
|
also supported which processes visibility on the render thread with some parallel for support from task threads.
|
|
|
|
Visibility is processed for all views independently where each view performs multiple stages of pipelined task work. The view stages are as follows:
|
|
|
|
Frustum Cull - Primitives are frustum / distance culled and visible primitives are emitted.
|
|
Occlusion Cull - Primitives are culled against occluders in the scene and visible primitives are emitted.
|
|
Compute Relevance - Primitives are queried for view relevance information dynamic primitives are identified and emitted, and static meshes are filtered
|
|
into various mesh passes
|
|
|
|
The following stages are then performed for all views:
|
|
|
|
Gather Dynamic Mesh Elements (GDME) - Primitives identified to have dynamic relevance are queried with a view mask to supply dynamic meshes.
|
|
Setup Mesh Passes - Tasks are launched to generate mesh draw commands for static and dynamic meshes.
|
|
|
|
In order to facilitate processing of emitted data from each stage, and ultimately spawning load balanced tasks for the next stage, the visibility pipeline utilizes
|
|
'command pipes' which are serial queues that run between stages to launch work for the next stage. Each view has two pipes: OcclusionCull, and Relevance. The OcclusionCull
|
|
pipe can either process occlusion tasks, or act as the relevance pipe if occlusion is disabled. The relevance pipe only launches relevance tasks.
|
|
|
|
The render thread performs the GDME stage which syncs the compute relevance stage for all views. Setup Mesh Passes then runs once all the GDME tasks
|
|
have completed. When the renderer has only one view, GDME utilizes a command pipe to process requests from relevance as quickly as possible and achieves some overlap
|
|
with relevance, reducing the critical path. With multiple views, it's necessary to sync beforehand as the gather requires a view mask for each dynamic primitive. To
|
|
that end, GDME supports two paths for processing dynamic primitives: an index list or view bit mask. The former just supplies a list of dynamic primitives and the view
|
|
bits are assumed to be 0x1. The latter requires deriving dynamic primitive indices by scanning the view mask array for non-zero elements.
|
|
|
|
IMPORTANT:
|
|
Accessing any visibility data prior to calling Finish must be done with extreme caution and synchronize with the various stages manually using the provided tasks.
|
|
Unprotected access of visibility state is ONLY safe after calling Finish.
|
|
*/
|
|
class FVisibilityTaskData : public IVisibilityTaskData
|
|
{
|
|
friend FVisibilityViewPacket;
|
|
friend FRelevancePacket;
|
|
friend FComputeAndMarkRelevance;
|
|
public:
|
|
FVisibilityTaskData(FRHICommandListImmediate& RHICmdList, FSceneRenderer& SceneRenderer);
|
|
|
|
~FVisibilityTaskData() override
|
|
{
|
|
check(bFinished);
|
|
}
|
|
|
|
void LaunchVisibilityTasks(const UE::Tasks::FTask& BeginInitVisibilityPrerequisites);
|
|
|
|
void ProcessRenderThreadTasks() override;
|
|
|
|
void StartGatherDynamicMeshElements() override
|
|
{
|
|
if (!Tasks.bDynamicMeshElementsPrerequisitesTriggered)
|
|
{
|
|
Tasks.DynamicMeshElementsPrerequisites.Trigger();
|
|
Tasks.bDynamicMeshElementsPrerequisitesTriggered = true;
|
|
}
|
|
}
|
|
|
|
void FinishGatherDynamicMeshElements(FExclusiveDepthStencil::Type BasePassDepthStencilAccess, FInstanceCullingManager& InstanceCullingManager, FVirtualTextureUpdater* VirtualTextureUpdater) override;
|
|
|
|
void Finish() override;
|
|
|
|
TArrayView<FViewCommands> GetViewCommandsPerView() override
|
|
{
|
|
return DynamicMeshElements.ViewCommandsPerView;
|
|
}
|
|
|
|
UE::Tasks::FTask GetFrustumCullTask() const override
|
|
{
|
|
return Tasks.FrustumCull;
|
|
}
|
|
|
|
UE::Tasks::FTask GetComputeRelevanceTask() const override
|
|
{
|
|
return Tasks.FinalizeRelevance;
|
|
}
|
|
|
|
UE::Tasks::FTask GetLightVisibilityTask() const override
|
|
{
|
|
return Tasks.LightVisibility;
|
|
}
|
|
|
|
bool IsTaskWaitingAllowed() const override
|
|
{
|
|
return Tasks.bWaitingAllowed;
|
|
}
|
|
|
|
private:
|
|
void GatherDynamicMeshElements(FDynamicPrimitiveIndexList&& Primitives);
|
|
void GatherDynamicMeshElements(const FDynamicPrimitiveViewMasks& Primitives);
|
|
|
|
void SetupMeshPasses(FExclusiveDepthStencil::Type BasePassDepthStencilAccess, FInstanceCullingManager& InstanceCullingManager);
|
|
|
|
FRHICommandListImmediate& RHICmdList;
|
|
FSceneRenderer& SceneRenderer;
|
|
FScene& Scene;
|
|
TArrayView<FViewInfo*> Views;
|
|
FViewFamilyInfo& ViewFamily;
|
|
EShadingPath ShadingPath;
|
|
FSceneRenderingBulkObjectAllocator Allocator;
|
|
TArray<FVisibilityViewPacket, SceneRenderingAllocator> ViewPackets;
|
|
|
|
struct FDynamicMeshElements
|
|
{
|
|
// The command pipe and event are only non-null when in single-view mode.
|
|
TCommandPipe<FDynamicPrimitiveIndexList>* CommandPipe = nullptr;
|
|
|
|
// Primitive view masks are only non-null when in multi-view mode.
|
|
FDynamicPrimitiveViewMasks* PrimitiveViewMasks = nullptr;
|
|
|
|
FDynamicMeshElementContextContainer ContextContainer;
|
|
TArray<FViewCommands, TInlineAllocator<4>> ViewCommandsPerView;
|
|
TArray<FDynamicPrimitive, SceneRenderingAllocator> DynamicPrimitives;
|
|
TArray<UE::Tasks::FPipe, SceneRenderingAllocator> DynamicPrimitiveTaskPipes;
|
|
|
|
} DynamicMeshElements;
|
|
|
|
struct FTasks
|
|
{
|
|
// These legacy tasks are used to interface with the jobs launched prior to gather dynamic mesh elements.
|
|
FGraphEventRef FrustumCullLegacyTask;
|
|
FGraphEventRef ComputeRelevanceLegacyTask;
|
|
|
|
FGraphEventRef DynamicMeshElementsPipe;
|
|
FGraphEventRef DynamicMeshElementsRenderThread;
|
|
|
|
UE::Tasks::FTaskEvent LightVisibility{ UE_SOURCE_LOCATION };
|
|
UE::Tasks::FTaskEvent BeginInitVisibility{ UE_SOURCE_LOCATION };
|
|
UE::Tasks::FTaskEvent FrustumCull{ UE_SOURCE_LOCATION };
|
|
UE::Tasks::FTaskEvent OcclusionCull{ UE_SOURCE_LOCATION };
|
|
UE::Tasks::FTaskEvent ComputeRelevance{ UE_SOURCE_LOCATION };
|
|
UE::Tasks::FTaskEvent DynamicMeshElementsPrerequisites{ UE_SOURCE_LOCATION };
|
|
UE::Tasks::FTaskEvent DynamicMeshElements{ UE_SOURCE_LOCATION };
|
|
UE::Tasks::FTask FinalizeRelevance;
|
|
UE::Tasks::FTask MeshPassSetup;
|
|
|
|
bool bDynamicMeshElementsPrerequisitesTriggered = false;
|
|
bool bWaitingAllowed = false;
|
|
|
|
} Tasks;
|
|
|
|
FVisibilityTaskConfig TaskConfig;
|
|
|
|
const bool bAddNaniteRelevance;
|
|
const bool bAddLightmapDensityCommands;
|
|
bool bFinished = false;
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
template<class T, int TAmplifyFactor = 1>
|
|
struct FRelevancePrimSet
|
|
{
|
|
TArray<T, SceneRenderingAllocator> Prims;
|
|
const int32 MaxOutputPrims;
|
|
|
|
FORCEINLINE FRelevancePrimSet(int32 InputPrimCount)
|
|
: MaxOutputPrims(InputPrimCount* TAmplifyFactor)
|
|
{}
|
|
|
|
FORCEINLINE void AddPrim(T Prim)
|
|
{
|
|
if (IsEmpty())
|
|
{
|
|
Prims.Reserve(MaxOutputPrims);
|
|
}
|
|
|
|
Prims.Add(Prim);
|
|
}
|
|
FORCEINLINE bool IsFull() const
|
|
{
|
|
return Prims.Num() >= MaxOutputPrims;
|
|
}
|
|
FORCEINLINE bool IsEmpty() const
|
|
{
|
|
return Prims.IsEmpty();
|
|
}
|
|
template<class TARRAY>
|
|
FORCEINLINE void AppendTo(TARRAY& DestArray)
|
|
{
|
|
DestArray.Append(Prims);
|
|
}
|
|
};
|
|
|
|
struct FFilterStaticMeshesForViewData
|
|
{
|
|
FVector ViewOrigin;
|
|
int32 ForcedLODLevel;
|
|
float LODScale;
|
|
float MinScreenRadiusForDepthPrepassSquared;
|
|
bool bFullEarlyZPass;
|
|
|
|
FFilterStaticMeshesForViewData(FViewInfo& View);
|
|
};
|
|
|
|
namespace EMarkMaskBits
|
|
{
|
|
enum Type
|
|
{
|
|
StaticMeshVisibilityMapMask = 0x2,
|
|
StaticMeshFadeOutDitheredLODMapMask = 0x10,
|
|
StaticMeshFadeInDitheredLODMapMask = 0x20,
|
|
};
|
|
}
|
|
|
|
using FPassDrawCommandArray = TArray<FVisibleMeshDrawCommand>;
|
|
using FPassDrawCommandBuildRequestArray = TArray<const FStaticMeshBatch*>;
|
|
using FPassDrawCommandBuildFlagsArray = TArray<EMeshDrawCommandCullingPayloadFlags>;
|
|
|
|
struct FDrawCommandRelevancePacket
|
|
{
|
|
FDrawCommandRelevancePacket();
|
|
|
|
FPassDrawCommandArray VisibleCachedDrawCommands[EMeshPass::Num];
|
|
FPassDrawCommandBuildRequestArray DynamicBuildRequests[EMeshPass::Num];
|
|
FPassDrawCommandBuildFlagsArray DynamicBuildFlags[EMeshPass::Num];
|
|
int32 NumDynamicBuildRequestElements[EMeshPass::Num];
|
|
bool bUseCachedMeshDrawCommands;
|
|
|
|
void AddCommandsForMesh(
|
|
int32 PrimitiveIndex,
|
|
const FPrimitiveSceneInfo* InPrimitiveSceneInfo,
|
|
const FStaticMeshBatchRelevance& RESTRICT StaticMeshRelevance,
|
|
const FStaticMeshBatch& RESTRICT StaticMesh,
|
|
EMeshDrawCommandCullingPayloadFlags CullingPayloadFlags,
|
|
const FScene& Scene,
|
|
bool bCanCache,
|
|
EMeshPass::Type PassType);
|
|
};
|
|
|
|
class FRelevancePacket : public FSceneRenderingAllocatorObject<FRelevancePacket>
|
|
{
|
|
public:
|
|
FRelevancePacket(
|
|
FVisibilityTaskData& InTaskData,
|
|
const FViewInfo& InView,
|
|
int32 InViewIndex,
|
|
const FFilterStaticMeshesForViewData& InViewData,
|
|
uint8* InMarkMasks,
|
|
const UE::Tasks::FTask& PrerequisitesTask);
|
|
|
|
void LaunchComputeRelevanceTask();
|
|
|
|
void Finalize();
|
|
|
|
private:
|
|
void ComputeRelevance(FDynamicPrimitiveIndexList& DynamicPrimitiveIndexList);
|
|
|
|
friend FComputeAndMarkRelevance;
|
|
|
|
const float CurrentWorldTime;
|
|
const float DeltaWorldTime;
|
|
|
|
FVisibilityTaskData& TaskData;
|
|
FVisibilityTaskConfig& TaskConfig;
|
|
const FScene& Scene;
|
|
const FViewInfo& View;
|
|
const FViewCommands& ViewCommands;
|
|
const uint8 ViewBit;
|
|
const FFilterStaticMeshesForViewData& ViewData;
|
|
FDynamicPrimitiveViewMasks* DynamicPrimitiveViewMasks;
|
|
uint8* RESTRICT MarkMasks;
|
|
UE::Tasks::FTask PrerequisitesTask;
|
|
|
|
FRelevancePrimSet<int32> Input;
|
|
FRelevancePrimSet<int32> NotDrawRelevant;
|
|
FRelevancePrimSet<int32> TranslucentSelfShadowPrimitives;
|
|
FRelevancePrimSet<FPrimitiveSceneInfo*> VisibleDynamicPrimitivesWithSimpleLights;
|
|
int32 NumVisibleDynamicPrimitives = 0;
|
|
int32 NumVisibleDynamicEditorPrimitives = 0;
|
|
FMeshPassMask VisibleDynamicMeshesPassMask;
|
|
FTranslucenyPrimCount TranslucentPrimCount;
|
|
FRelevancePrimSet<FPrimitiveSceneInfo*> DirtyIndirectLightingCacheBufferPrimitives;
|
|
#if WITH_EDITOR
|
|
TArray<Nanite::FInstanceDraw> EditorVisualizeLevelInstancesNanite;
|
|
TArray<Nanite::FInstanceDraw> EditorSelectedInstancesNanite;
|
|
TArray<Nanite::FInstanceDraw> EditorOverlaidInstancesNanite;
|
|
TArray<uint32> EditorSelectedNaniteHitProxyIds;
|
|
#endif
|
|
|
|
TArray<FVolumetricMeshBatch, SceneRenderingAllocator> VolumetricMeshBatches;
|
|
TArray<FVolumetricMeshBatch, SceneRenderingAllocator> HeterogeneousVolumesMeshBatches;
|
|
TArray<FSkyMeshBatch, SceneRenderingAllocator> SkyMeshBatches;
|
|
TArray<FSortedTrianglesMeshBatch, SceneRenderingAllocator> SortedTrianglesMeshBatches;
|
|
FDrawCommandRelevancePacket DrawCommandPacket;
|
|
TSet<uint32, DefaultKeyFuncs<uint32>, SceneRenderingSetAllocator> CustomDepthStencilValues;
|
|
FRelevancePrimSet<FPrimitiveInstanceRange> NaniteCustomDepthInstances;
|
|
|
|
struct FPrimitiveLODMask
|
|
{
|
|
FPrimitiveLODMask()
|
|
: PrimitiveIndex(INDEX_NONE)
|
|
{}
|
|
|
|
FPrimitiveLODMask(const int32 InPrimitiveIndex, const FLODMask& InLODMask)
|
|
: PrimitiveIndex(InPrimitiveIndex)
|
|
, LODMask(InLODMask)
|
|
{}
|
|
|
|
int32 PrimitiveIndex;
|
|
FLODMask LODMask;
|
|
};
|
|
|
|
FRelevancePrimSet<FPrimitiveLODMask> PrimitivesLODMask; // group both lod mask with primitive index to be able to properly merge them in the view
|
|
|
|
UE::Tasks::FTask ComputeRelevanceTask;
|
|
uint16 CombinedShadingModelMask = 0;
|
|
uint8 SubstrateUintPerPixel = 0;
|
|
uint8 SubstrateClosureCountMask = 0;
|
|
bool bUsesComplexSpecialRenderPath = false;
|
|
bool bHasDistortionPrimitives = false;
|
|
bool bHasCustomDepthPrimitives = false;
|
|
bool bUsesGlobalDistanceField = false;
|
|
bool bUsesLightingChannels = false;
|
|
bool bTranslucentSurfaceLighting = false;
|
|
bool bUsesCustomDepth = false;
|
|
bool bUsesCustomStencil = false;
|
|
bool bSceneHasSkyMaterial = false;
|
|
bool bHasSingleLayerWaterMaterial = false;
|
|
bool bUsesSecondStageDepthPass = false;
|
|
bool bAddLightmapDensityCommands = false;
|
|
bool bComputeRelevanceTaskLaunched = false;
|
|
};
|
|
|
|
class FComputeAndMarkRelevance
|
|
{
|
|
public:
|
|
FComputeAndMarkRelevance(FVisibilityTaskData& InTaskData, FScene& InScene, FViewInfo& InView, uint8 InViewIndex, const UE::Tasks::FTask& PreprequisitesTask);
|
|
|
|
~FComputeAndMarkRelevance()
|
|
{
|
|
check(Packets.IsEmpty());
|
|
check(bFinished && bFinalized);
|
|
}
|
|
|
|
void AddPrimitives(FPrimitiveIndexList&& PrimitiveIndexList);
|
|
void AddPrimitive(int32 Index);
|
|
|
|
// Call when all primitives have been added.
|
|
void Finish(UE::Tasks::FTaskEvent& ComputeRelevanceTaskEvent);
|
|
|
|
// Call in a dependent task when both compute relevance and filter static mesh task events have completed.
|
|
void Finalize();
|
|
|
|
private:
|
|
FRelevancePacket* CreateRelevancePacket()
|
|
{
|
|
check(!bLaunchOnAddPrimitive || !bFinished);
|
|
return Packets.Emplace_GetRef(new FRelevancePacket(TaskData, View, ViewIndex, ViewData, MarkMasks, PrerequisitesTask));
|
|
}
|
|
|
|
FVisibilityTaskData& TaskData;
|
|
FScene& Scene;
|
|
FViewInfo& View;
|
|
FViewCommands& ViewCommands;
|
|
int32 ViewIndex;
|
|
const FFilterStaticMeshesForViewData ViewData;
|
|
const uint32 NumMeshes;
|
|
const uint32 NumPrimitivesPerPacket;
|
|
uint8* MarkMasks;
|
|
TArray<FRelevancePacket*, SceneRenderingAllocator> Packets;
|
|
UE::Tasks::FTask PrerequisitesTask;
|
|
const bool bLaunchOnAddPrimitive;
|
|
bool bFinished = false;
|
|
bool bFinalized = false;
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
struct FOcclusionBounds
|
|
{
|
|
FOcclusionBounds(FVector InOrigin, FVector& InExtent)
|
|
: Origin(InOrigin)
|
|
, Extent(InExtent)
|
|
{}
|
|
|
|
const FVector Origin;
|
|
const FVector Extent;
|
|
};
|
|
|
|
struct FThrottledOcclusionQuery
|
|
{
|
|
FThrottledOcclusionQuery(FPrimitiveOcclusionHistoryKey InPrimitiveOcclusionHistoryKey, FVector InBoundsOrigin, FVector InBoundsExtent, uint32 InLastQuerySubmitFrame)
|
|
: PrimitiveOcclusionHistoryKey(InPrimitiveOcclusionHistoryKey)
|
|
, Bounds(InBoundsOrigin, InBoundsExtent)
|
|
, LastQuerySubmitFrame(InLastQuerySubmitFrame)
|
|
{}
|
|
|
|
const FPrimitiveOcclusionHistoryKey PrimitiveOcclusionHistoryKey;
|
|
const FOcclusionBounds Bounds;
|
|
const uint32 LastQuerySubmitFrame;
|
|
};
|
|
|
|
struct FOcclusionQuery
|
|
{
|
|
FOcclusionQuery(FPrimitiveOcclusionHistory* InPrimitiveOcclusionHistory, FVector InBoundsOrigin, FVector InBoundsExtent, bool bInGroupedQuery)
|
|
: PrimitiveOcclusionHistory(InPrimitiveOcclusionHistory)
|
|
, Bounds(InBoundsOrigin, InBoundsExtent)
|
|
, bGroupedQuery(bInGroupedQuery)
|
|
{}
|
|
|
|
FPrimitiveOcclusionHistory* PrimitiveOcclusionHistory;
|
|
const FOcclusionBounds Bounds;
|
|
const bool bGroupedQuery;
|
|
};
|
|
|
|
struct FOcclusionFeedbackEntry
|
|
{
|
|
FOcclusionFeedbackEntry(const FPrimitiveOcclusionHistoryKey& PrimitiveKey, FVector InBoundsOrigin, FVector InBoundsExtent)
|
|
: PrimitiveKey(PrimitiveKey)
|
|
, Bounds(InBoundsOrigin, InBoundsExtent)
|
|
{}
|
|
|
|
const FPrimitiveOcclusionHistoryKey PrimitiveKey;
|
|
const FOcclusionBounds Bounds;
|
|
};
|
|
|
|
struct FHZBBound
|
|
{
|
|
FHZBBound(FPrimitiveOcclusionHistory* InTargetHistory, const FVector& InBoundsOrigin, const FVector& InBoundsExtent)
|
|
: TargetHistory(InTargetHistory)
|
|
, BoundsOrigin(InBoundsOrigin)
|
|
, BoundsExtent(InBoundsExtent)
|
|
{}
|
|
|
|
FPrimitiveOcclusionHistory* const TargetHistory;
|
|
const FVector BoundsOrigin;
|
|
const FVector BoundsExtent;
|
|
};
|
|
|
|
struct FGPUOcclusionState
|
|
{
|
|
int32 ReadBackLagTolerance = 0;
|
|
int32 NumBufferedFrames = 0;
|
|
int32 NumPrimitiveRangeTasks = 0;
|
|
|
|
bool bSubmitQueries = false;
|
|
bool bHZBOcclusion = false;
|
|
bool bUseRoundRobinOcclusion = false;
|
|
bool bAllowSubQueries = false;
|
|
};
|
|
|
|
struct FOcclusionCullResult
|
|
{
|
|
uint32 NumCulledPrimitives = 0;
|
|
uint32 NumTestedQueries = 0;
|
|
};
|
|
|
|
class FGPUOcclusionPacket
|
|
{
|
|
public:
|
|
FGPUOcclusionPacket(FVisibilityViewPacket& InViewPacket, const FGPUOcclusionState& InOcclusionState);
|
|
|
|
inline bool CanBeOccluded(int32 PrimitiveIndex, EOcclusionFlags::Type& OutOcclusionFlags) const
|
|
{
|
|
OutOcclusionFlags = static_cast<EOcclusionFlags::Type>(Scene.PrimitiveOcclusionFlags[PrimitiveIndex]);
|
|
|
|
bool bCanBeOccluded = EnumHasAnyFlags(EOcclusionFlags::CanBeOccluded, OutOcclusionFlags);
|
|
#if WITH_EDITOR
|
|
if (GIsEditor)
|
|
{
|
|
if (Scene.PrimitivesSelected[PrimitiveIndex])
|
|
{
|
|
// to render occluded outline for selected objects
|
|
bCanBeOccluded = false;
|
|
}
|
|
}
|
|
#endif
|
|
return bCanBeOccluded;
|
|
}
|
|
|
|
void RecordOcclusionCullResult(FOcclusionCullResult Result)
|
|
{
|
|
FVisibilityTaskConfig& TaskConfig = ViewPacket.TaskConfig;
|
|
TaskConfig.OcclusionCull.NumCulledPrimitives.fetch_add(Result.NumCulledPrimitives, std::memory_order_relaxed);
|
|
TaskConfig.OcclusionCull.NumTestedQueries.fetch_add(Result.NumTestedQueries, std::memory_order_relaxed);
|
|
}
|
|
|
|
template <bool bIsParallel, typename VisitorType>
|
|
bool OcclusionCullPrimitive(VisitorType& Visitor, FOcclusionCullResult& Result, int32 Index);
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
// Visitor variant for tasks that records commands to replay later.
|
|
struct FRecordVisitor
|
|
{
|
|
void AddThrottledOcclusionQuery(const FThrottledOcclusionQuery& Query)
|
|
{
|
|
// Throttled occlusion queries are not supported in the recorded path.
|
|
checkNoEntry();
|
|
}
|
|
|
|
FPrimitiveOcclusionHistory* AddOcclusionHistory(const FPrimitiveOcclusionHistory& History)
|
|
{
|
|
return &OcclusionHistories[OcclusionHistories.AddElement(History)];
|
|
}
|
|
|
|
void AddHZBBounds(const FHZBBound& Bounds)
|
|
{
|
|
HZBBounds.Emplace(Bounds);
|
|
}
|
|
|
|
void AddOcclusionFeedback(const FOcclusionFeedbackEntry& Entry)
|
|
{
|
|
OcclusionFeedbacks.Emplace(Entry);
|
|
}
|
|
|
|
void AddVisualizeQuery(const FBox& Box)
|
|
{
|
|
VisualizeQueries.Emplace(Box);
|
|
}
|
|
|
|
void AddOcclusionQuery(const FOcclusionQuery& Query)
|
|
{
|
|
OcclusionQueries.Emplace(Query);
|
|
}
|
|
|
|
static constexpr uint32 ArrayChunkSize = 1024;
|
|
|
|
TChunkedArray<FPrimitiveOcclusionHistory, ArrayChunkSize, SceneRenderingAllocator> OcclusionHistories;
|
|
TArray<FOcclusionQuery, SceneRenderingAllocator> OcclusionQueries;
|
|
TArray<FOcclusionFeedbackEntry, SceneRenderingAllocator> OcclusionFeedbacks;
|
|
TArray<FHZBBound, SceneRenderingAllocator> HZBBounds;
|
|
TArray<FBox, SceneRenderingAllocator> VisualizeQueries;
|
|
};
|
|
|
|
// Visitor variant that will process the requests immediately or replay recorded commands.
|
|
struct FProcessVisitor
|
|
{
|
|
FProcessVisitor(FGPUOcclusionPacket& InPacket, FRHICommandList& InRHICmdList, FGlobalDynamicVertexBuffer& InDynamicVertexBuffer)
|
|
: Packet(InPacket)
|
|
, RHICmdList(InRHICmdList)
|
|
, DynamicVertexBuffer(InDynamicVertexBuffer)
|
|
, PrimitiveOcclusionHistorySet(Packet.ViewState.Occlusion.PrimitiveOcclusionHistorySet)
|
|
{}
|
|
|
|
void AddThrottledOcclusionQuery(const FThrottledOcclusionQuery& Query)
|
|
{
|
|
ThrottledOcclusionQueries.Emplace(Query);
|
|
}
|
|
|
|
FPrimitiveOcclusionHistory* AddOcclusionHistory(const FPrimitiveOcclusionHistory& History)
|
|
{
|
|
return &PrimitiveOcclusionHistorySet[PrimitiveOcclusionHistorySet.Add(History)];
|
|
}
|
|
|
|
void AddOcclusionFeedback(const FOcclusionFeedbackEntry& Entry)
|
|
{
|
|
Packet.OcclusionFeedback.AddPrimitive(Entry.PrimitiveKey, Entry.Bounds.Origin, Entry.Bounds.Extent, DynamicVertexBuffer);
|
|
}
|
|
|
|
void AddVisualizeQuery(const FBox& Box)
|
|
{
|
|
DrawWireBox(&Packet.ViewElementPDI, Box, FColor(50, 255, 50), SDPG_Foreground);
|
|
}
|
|
|
|
void AddHZBBounds(const FHZBBound& HZBBounds)
|
|
{
|
|
HZBBounds.TargetHistory->HZBTestIndex = Packet.HZBOcclusionTests.AddBounds(HZBBounds.BoundsOrigin, HZBBounds.BoundsExtent);
|
|
}
|
|
|
|
void AddOcclusionQuery(const FOcclusionQuery& Query);
|
|
|
|
void Replay(const FRecordVisitor& RecordVisitor)
|
|
{
|
|
for (const FOcclusionFeedbackEntry& Entry : RecordVisitor.OcclusionFeedbacks)
|
|
{
|
|
AddOcclusionFeedback(Entry);
|
|
}
|
|
|
|
for (const FOcclusionQuery& Query : RecordVisitor.OcclusionQueries)
|
|
{
|
|
AddOcclusionQuery(Query);
|
|
}
|
|
|
|
for (const FBox& Box : RecordVisitor.VisualizeQueries)
|
|
{
|
|
AddVisualizeQuery(Box);
|
|
}
|
|
|
|
for (const FHZBBound& HZBBounds : RecordVisitor.HZBBounds)
|
|
{
|
|
AddHZBBounds(HZBBounds);
|
|
}
|
|
}
|
|
|
|
void SubmitThrottledOcclusionQueries();
|
|
|
|
FGPUOcclusionPacket& Packet;
|
|
FRHICommandList& RHICmdList;
|
|
FGlobalDynamicVertexBuffer& DynamicVertexBuffer;
|
|
TArray<FThrottledOcclusionQuery, SceneRenderingAllocator> ThrottledOcclusionQueries;
|
|
TSet<FPrimitiveOcclusionHistory, FPrimitiveOcclusionHistoryKeyFuncs>& PrimitiveOcclusionHistorySet;
|
|
};
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
protected:
|
|
static constexpr uint32 SubIsOccludedPageSize = 1024;
|
|
|
|
FVisibilityViewPacket& ViewPacket;
|
|
FViewInfo& View;
|
|
FSceneViewState& ViewState;
|
|
FViewElementPDI& ViewElementPDI;
|
|
FHZBOcclusionTester& HZBOcclusionTests;
|
|
FOcclusionFeedback& OcclusionFeedback;
|
|
TSet<FPrimitiveOcclusionHistory, FPrimitiveOcclusionHistoryKeyFuncs>& PrimitiveOcclusionHistorySet;
|
|
|
|
TArray<bool>* SubIsOccluded = nullptr;
|
|
|
|
const FScene& Scene;
|
|
const FGPUOcclusionState& OcclusionState;
|
|
const FVector ViewOrigin;
|
|
const uint32 OcclusionFrameCounter;
|
|
const float PrimitiveProbablyVisibleTime;
|
|
const float CurrentRealTime;
|
|
const float NeverOcclusionTestDistanceSquared;
|
|
const bool bUseOcclusionFeedback;
|
|
const bool bNewlyConsideredBBoxExpandActive;
|
|
};
|
|
|
|
class FGPUOcclusion
|
|
{
|
|
public:
|
|
virtual ~FGPUOcclusion() = default;
|
|
|
|
FGPUOcclusion(FVisibilityViewPacket& InViewPacket);
|
|
|
|
virtual void AddPrimitives(FPrimitiveRange PrimitiveRange) = 0;
|
|
|
|
virtual void Map(FRHICommandListImmediate& RHICmdList);
|
|
virtual void Unmap(FRHICommandListImmediate& RHICmdList);
|
|
|
|
protected:
|
|
void WaitForLastOcclusionQuery();
|
|
|
|
FGlobalDynamicVertexBuffer DynamicVertexBuffer;
|
|
FVisibilityViewPacket& ViewPacket;
|
|
const FScene& Scene;
|
|
FViewInfo& View;
|
|
FSceneViewState& ViewState;
|
|
FGPUOcclusionState State;
|
|
};
|
|
|
|
class FGPUOcclusionParallelPacket final
|
|
: public FGPUOcclusionPacket
|
|
, public FSceneRenderingAllocatorObject<FGPUOcclusionParallelPacket>
|
|
{
|
|
public:
|
|
FGPUOcclusionParallelPacket(FVisibilityViewPacket& InViewPacket, const FGPUOcclusionState& InOcclusionState)
|
|
: FGPUOcclusionPacket(InViewPacket, InOcclusionState)
|
|
, MaxInputSubQueries(InViewPacket.TaskConfig.OcclusionCull.Views[InViewPacket.ViewIndex].MaxQueriesPerTask)
|
|
{}
|
|
|
|
bool AddPrimitive(int32 PrimitiveIndex);
|
|
|
|
bool IsFull() const
|
|
{
|
|
return NumInputSubQueries >= MaxInputSubQueries;
|
|
}
|
|
|
|
void LaunchOcclusionCullTask();
|
|
|
|
private:
|
|
friend FGPUOcclusionParallel;
|
|
|
|
FOcclusionCullResult OcclusionCullTask(FPrimitiveIndexList& PrimitiveIndexList);
|
|
|
|
static constexpr uint32 ArrayChunkSize = 1024;
|
|
|
|
const uint32 MaxInputSubQueries;
|
|
uint32 NumInputSubQueries = 0;
|
|
TChunkedArray<int32, ArrayChunkSize, SceneRenderingAllocator> Input;
|
|
FRecordVisitor RecordVisitor;
|
|
UE::Tasks::FTask Task;
|
|
bool bTaskLaunched = false;
|
|
};
|
|
|
|
class FGPUOcclusionParallel final : public FGPUOcclusion
|
|
{
|
|
public:
|
|
FGPUOcclusionParallel(FVisibilityViewPacket& InViewPacket)
|
|
: FGPUOcclusion(InViewPacket)
|
|
, MaxNonOccludedPrimitives(ViewPacket.TaskConfig.Relevance.NumPrimitivesPerPacket)
|
|
{
|
|
NonOccludedPrimitives.Reserve(MaxNonOccludedPrimitives);
|
|
CreateOcclusionPacket();
|
|
}
|
|
|
|
~FGPUOcclusionParallel()
|
|
{
|
|
check(bFinished);
|
|
}
|
|
|
|
void Finish(UE::Tasks::FTaskEvent& OcclusionCullTasks);
|
|
void Finalize();
|
|
|
|
void AddPrimitives(FPrimitiveRange PrimitiveRange) override;
|
|
void Map(FRHICommandListImmediate& RHICmdListImmediate) override;
|
|
void Unmap(FRHICommandListImmediate& RHICmdListImmediate) override;
|
|
|
|
private:
|
|
friend FGPUOcclusionParallelPacket;
|
|
|
|
void CreateOcclusionPacket()
|
|
{
|
|
Packets.Emplace(new FGPUOcclusionParallelPacket(ViewPacket, State));
|
|
}
|
|
|
|
const uint32 MaxNonOccludedPrimitives;
|
|
TArray<FGPUOcclusionParallelPacket*, SceneRenderingAllocator> Packets;
|
|
FPrimitiveIndexList NonOccludedPrimitives;
|
|
FRHICommandList* RHICmdList = nullptr;
|
|
UE::Tasks::FTaskEvent FinalizeTask{ UE_SOURCE_LOCATION };
|
|
bool bFinished = false;
|
|
bool bFinalized = false;
|
|
};
|
|
|
|
class FGPUOcclusionSerial final : public FGPUOcclusion
|
|
{
|
|
public:
|
|
FGPUOcclusionSerial(FVisibilityViewPacket& InViewPacket)
|
|
: FGPUOcclusion(InViewPacket)
|
|
, Packet(InViewPacket, State)
|
|
, ProcessVisitor(Packet, FRHICommandListExecutor::GetImmediateCommandList(), DynamicVertexBuffer)
|
|
{}
|
|
|
|
void AddPrimitives(FPrimitiveRange PrimitiveRange) override;
|
|
void Map(FRHICommandListImmediate& RHICmdListImmediate) override;
|
|
void Unmap(FRHICommandListImmediate& RHICmdListImmediate) override;
|
|
|
|
private:
|
|
FGPUOcclusionPacket Packet;
|
|
FGPUOcclusionPacket::FProcessVisitor ProcessVisitor;
|
|
FOcclusionCullResult OcclusionCullResult;
|
|
}; |