Files
UnrealEngineUWP/Engine/Source/Runtime/Renderer/Private/MeshDrawCommands.cpp

1259 lines
47 KiB
C++
Raw Normal View History

// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
/*=============================================================================
MeshDrawCommandSetup.cpp: Mesh draw command setup.
=============================================================================*/
#include "MeshDrawCommands.h"
#include "RendererModule.h"
#include "ScenePrivate.h"
#include "TranslucentRendering.h"
TGlobalResource<FPrimitiveIdVertexBufferPool> GPrimitiveIdVertexBufferPool;
static TAutoConsoleVariable<int32> CVarMeshDrawCommandsParallelPassSetup(
TEXT("r.MeshDrawCommands.ParallelPassSetup"),
1,
TEXT("Whether to setup mesh draw command pass in parallel."),
ECVF_RenderThreadSafe);
static TAutoConsoleVariable<int32> CVarMobileMeshSortingMethod(
TEXT("r.Mobile.MeshSortingMethod"),
0,
TEXT("How to sort mesh commands on mobile:\n")
TEXT("\t0: Sort by state, roughly front to back (Default).\n")
TEXT("\t1: Strict front to back sorting.\n"),
ECVF_RenderThreadSafe);
FPrimitiveIdVertexBufferPool::FPrimitiveIdVertexBufferPool()
: DiscardId(0)
{
}
FPrimitiveIdVertexBufferPool::~FPrimitiveIdVertexBufferPool()
{
check(!Entries.Num());
}
FVertexBufferRHIParamRef FPrimitiveIdVertexBufferPool::Allocate(int32 BufferSize)
{
BufferSize = Align(BufferSize, 1024);
// First look for a smallest unused one.
int32 BestFitBufferIndex = -1;
for (int32 Index = 0; Index < Entries.Num(); ++Index)
{
// Unused and fits?
if (Entries[Index].LastDiscardId != DiscardId && Entries[Index].BufferSize >= BufferSize)
{
// Is it a bet fit than current BestFitBufferIndex?
if (BestFitBufferIndex == -1 || Entries[Index].BufferSize < Entries[BestFitBufferIndex].BufferSize)
{
BestFitBufferIndex = Index;
if (Entries[BestFitBufferIndex].BufferSize == BufferSize)
{
break;
}
}
}
}
if (BestFitBufferIndex >=0 )
{
// Reuse existing buffer.
Entries[BestFitBufferIndex].LastDiscardId = DiscardId;
return Entries[BestFitBufferIndex].BufferRHI;
}
else
{
// Allocate new one.
FPrimitiveIdVertexBufferPoolEntry NewEntry;
NewEntry.LastDiscardId = DiscardId;
NewEntry.BufferSize = BufferSize;
FRHIResourceCreateInfo CreateInfo;
NewEntry.BufferRHI = RHICreateVertexBuffer(NewEntry.BufferSize, BUF_Volatile, CreateInfo);
Entries.Add(NewEntry);
return NewEntry.BufferRHI;
}
}
void FPrimitiveIdVertexBufferPool::DiscardAll()
{
++DiscardId;
// Remove old unused pool entries.
for (int32 Index = 0; Index < Entries.Num();)
{
if (DiscardId - Entries[Index].LastDiscardId > 1000u)
{
Entries.RemoveAtSwap(Index);
}
else
{
++Index;
}
}
}
void FPrimitiveIdVertexBufferPool::ReleaseDynamicRHI()
{
Entries.Empty();
}
struct FCompareFMeshDrawCommands
{
FORCEINLINE bool operator() (FVisibleMeshDrawCommand A, FVisibleMeshDrawCommand B) const
{
// First order by a sort key.
if (A.SortKey != B.SortKey)
{
return A.SortKey < B.SortKey;
}
// Next order by instancing bucket.
if (A.StateBucketId != B.StateBucketId)
{
return A.StateBucketId < B.StateBucketId;
}
return false;
}
};
uint32 BitInvertIfNegativeFloat(uint32 f)
{
unsigned mask = -int32(f >> 31) | 0x80000000;
return f ^ mask;
}
/**
* Update mesh sort keys with view dependent data.
*/
void UpdateTranslucentMeshSortKeys(
ETranslucentSortPolicy::Type TranslucentSortPolicy,
const FVector& TranslucentSortAxis,
const FVector& ViewOrigin,
const FMatrix& ViewMatrix,
const TArray<struct FPrimitiveBounds>& PrimitiveBounds,
ETranslucencyPass::Type TranslucencyPass,
FMeshCommandOneFrameArray& VisibleMeshCommands
)
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_UpdateTranslucentMeshSortKeys);
for (int32 CommandIndex = 0; CommandIndex < VisibleMeshCommands.Num(); ++CommandIndex)
{
FVisibleMeshDrawCommand& VisibleCommand = VisibleMeshCommands[CommandIndex];
FVector BoundsOrigin = FVector::ZeroVector;
if (VisibleCommand.DrawPrimitiveId < PrimitiveBounds.Num())
{
BoundsOrigin = PrimitiveBounds[VisibleCommand.DrawPrimitiveId].BoxSphereBounds.Origin;
}
float Distance = 0.0f;
if (TranslucentSortPolicy == ETranslucentSortPolicy::SortByDistance)
{
//sort based on distance to the view position, view rotation is not a factor
Distance = (BoundsOrigin - ViewOrigin).Size();
}
else if (TranslucentSortPolicy == ETranslucentSortPolicy::SortAlongAxis)
{
// Sort based on enforced orthogonal distance
const FVector CameraToObject = BoundsOrigin - ViewOrigin;
Distance = FVector::DotProduct(CameraToObject, TranslucentSortAxis);
}
else
{
// Sort based on projected Z distance
check(TranslucentSortPolicy == ETranslucentSortPolicy::SortByProjectedZ);
Distance = ViewMatrix.TransformPosition(BoundsOrigin).Z;
}
// Patch distance inside translucent mesh sort key.
FMeshDrawCommandSortKey SortKey;
SortKey.PackedData = VisibleCommand.SortKey.PackedData;
SortKey.Translucent.Distance = (uint32)~BitInvertIfNegativeFloat(*((uint32*)&Distance));
VisibleCommand.SortKey.PackedData = SortKey.PackedData;
}
}
static uint64 GetMobileBasePassSortKey_FrontToBack(bool bMasked, bool bBackground, uint32 PipelineId, int32 StateBucketId, float PrimitiveDistance)
{
union
{
uint64 PackedInt;
struct
{
uint64 StateBucketId : 27; // Order by state bucket
uint64 PipelineId : 20; // Order by PSO
uint64 DepthBits : 15; // Order by primitive depth
uint64 Background : 1; // Non-background meshes first
uint64 Masked : 1; // Non-masked first
} Fields;
} Key;
union FFloatToInt { float F; uint32 I; };
FFloatToInt F2I;
Key.Fields.Masked = bMasked;
Key.Fields.Background = bBackground;
F2I.F = PrimitiveDistance;
Key.Fields.DepthBits = ((-int32(F2I.I >> 31) | 0x80000000) ^ F2I.I) >> 17;
Key.Fields.PipelineId = PipelineId;
Key.Fields.StateBucketId = StateBucketId;
return Key.PackedInt;
}
static uint64 GetMobileBasePassSortKey_ByState(bool bMasked, bool bBackground, int32 PipelineId, int32 StateBucketId, float PipelineDistance, float PrimitiveDistance)
{
const float PrimitiveDepthQuantization = ((1 << 14) - 1);
union
{
uint64 PackedInt;
struct
{
uint64 DepthBits : 14; // Order by primitive depth
uint64 StateBucketId : 20; // Order by state bucket
uint64 PipelineId : 20; // Order by PSO
uint64 PipelineDepthBits : 8; // Order PSOs front to back
uint64 Background : 1; // Non-background meshes first
uint64 Masked : 1; // Non-masked first
} Fields;
} Key;
union FFloatToInt { float F; uint32 I; };
FFloatToInt F2I;
Key.PackedInt = 0;
Key.Fields.Masked = bMasked;
Key.Fields.Background = bBackground;
F2I.F = PipelineDistance / HALF_WORLD_MAX;
Key.Fields.PipelineDepthBits = (F2I.I >> 23) & 0xff; // 8 bit exponent
Key.Fields.PipelineId = PipelineId;
Key.Fields.StateBucketId = StateBucketId;
Key.Fields.DepthBits = int32((FMath::Min<float>(PrimitiveDistance, HALF_WORLD_MAX) / HALF_WORLD_MAX) * PrimitiveDepthQuantization);
return Key.PackedInt;
}
/**
* Merge mobile BasePass with BasePassCSM based on CSM visibility in order to select appropriate shader for given command.
*/
void MergeMobileBasePassMeshDrawCommands(
const FMobileCSMVisibilityInfo& MobileCSMVisibilityInfo,
int32 ScenePrimitiveNum,
FMeshCommandOneFrameArray& MeshCommands,
FMeshCommandOneFrameArray& MeshCommandsCSM
)
{
if (MobileCSMVisibilityInfo.bMobileDynamicCSMInUse)
{
// determine per view CSM visibility
checkf(MeshCommands.Num() == MeshCommandsCSM.Num(), TEXT("VisibleMeshDrawCommands of BasePass and MobileBasePassCSM are expected to match."));
for (int32 i = MeshCommands.Num() - 1; i >= 0; --i)
{
FVisibleMeshDrawCommand& MeshCommand = MeshCommands[i];
FVisibleMeshDrawCommand& MeshCommandCSM = MeshCommandsCSM[i];
if (MobileCSMVisibilityInfo.bAlwaysUseCSM
|| (MeshCommand.DrawPrimitiveId < ScenePrimitiveNum && MobileCSMVisibilityInfo.MobilePrimitiveCSMReceiverVisibilityMap[MeshCommand.DrawPrimitiveId]))
{
checkf(MeshCommand.DrawPrimitiveId == MeshCommandCSM.DrawPrimitiveId, TEXT("VisibleMeshDrawCommands of BasePass and MobileBasePassCSM are expected to match."));
// Use CSM's VisibleMeshDrawCommand.
MeshCommand = MeshCommandCSM;
}
}
MeshCommandsCSM.Reset();
}
}
/**
* Compute mesh sort keys for the mobile base pass
*/
void UpdateMobileBasePassMeshSortKeys(
const FVector& ViewOrigin,
const TArray<struct FPrimitiveBounds>& ScenePrimitiveBounds,
FMeshCommandOneFrameArray& VisibleMeshCommands
)
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_UpdateMobileBasePassMeshSortKeys);
int32 NumCmds = VisibleMeshCommands.Num();
int32 MeshSortingMethod = CVarMobileMeshSortingMethod.GetValueOnAnyThread();
if (MeshSortingMethod == 1) //strict front to back sorting
{
// compute sort key for each mesh command
for (int32 CmdIdx = 0; CmdIdx < NumCmds; ++CmdIdx)
{
FVisibleMeshDrawCommand& Cmd = VisibleMeshCommands[CmdIdx];
// Set in MobileBasePass.cpp - GetBasePassStaticSortKey;
bool bMasked = Cmd.SortKey.PackedData & 0x1 ? true : false;
bool bBackground = Cmd.SortKey.PackedData & 0x2 ? true : false;
float PrimitiveDistance = 0;
if (Cmd.DrawPrimitiveId < ScenePrimitiveBounds.Num())
{
const FPrimitiveBounds& PrimitiveBounds = ScenePrimitiveBounds[Cmd.DrawPrimitiveId];
PrimitiveDistance = (PrimitiveBounds.BoxSphereBounds.Origin - ViewOrigin).Size();
bBackground|= (PrimitiveBounds.BoxSphereBounds.SphereRadius > HALF_WORLD_MAX / 4.0f);
}
uint32 PipelineId = Cmd.MeshDrawCommand->CachedPipelineId.GetId();
int32 StateBucketId = PointerHash(Cmd.MeshDrawCommand->IndexBuffer);
Cmd.SortKey.PackedData = GetMobileBasePassSortKey_FrontToBack(bMasked, bBackground, PipelineId, StateBucketId, PrimitiveDistance);
}
}
else // prefer state then distance
{
TMap<uint32, float> PipelineDistances;
PipelineDistances.Reserve(256);
// pre-compute distance to a group of meshes that share same PSO
for (int32 CmdIdx = 0; CmdIdx < NumCmds; ++CmdIdx)
{
FVisibleMeshDrawCommand& Cmd = VisibleMeshCommands[CmdIdx];
float PrimitiveDistance = 0;
if (Cmd.DrawPrimitiveId < ScenePrimitiveBounds.Num())
{
const FPrimitiveBounds& PrimitiveBounds = ScenePrimitiveBounds[Cmd.DrawPrimitiveId];
PrimitiveDistance = (PrimitiveBounds.BoxSphereBounds.Origin - ViewOrigin).Size();
}
float& PipelineDistance = PipelineDistances.FindOrAdd(Cmd.MeshDrawCommand->CachedPipelineId.GetId());
// not sure what could be better: average distance, max or min
PipelineDistance = FMath::Max(PipelineDistance, PrimitiveDistance);
}
// compute sort key for each mesh command
for (int32 CmdIdx = 0; CmdIdx < NumCmds; ++CmdIdx)
{
FVisibleMeshDrawCommand& Cmd = VisibleMeshCommands[CmdIdx];
// Set in MobileBasePass.cpp - GetBasePassStaticSortKey;
bool bMasked = Cmd.SortKey.PackedData & 0x1 ? true : false;
bool bBackground = Cmd.SortKey.PackedData & 0x2 ? true : false;
float PrimitiveDistance = 0;
if (Cmd.DrawPrimitiveId < ScenePrimitiveBounds.Num())
{
const FPrimitiveBounds& PrimitiveBounds = ScenePrimitiveBounds[Cmd.DrawPrimitiveId];
PrimitiveDistance = (PrimitiveBounds.BoxSphereBounds.Origin - ViewOrigin).Size();
bBackground|= (PrimitiveBounds.BoxSphereBounds.SphereRadius > HALF_WORLD_MAX / 4.0f);
}
int32 PipelineId = Cmd.MeshDrawCommand->CachedPipelineId.GetId();
float PipelineDistance = PipelineDistances.FindRef(PipelineId);
// poor man StateID, can't use Cmd.StateBucketId as it is unique for each primitive if platform does not support auto-instancing
int32 StateBucketId = PointerHash(Cmd.MeshDrawCommand->IndexBuffer);
Cmd.SortKey.PackedData = GetMobileBasePassSortKey_ByState(bMasked, bBackground, PipelineId, StateBucketId, PipelineDistance, PrimitiveDistance);
}
}
}
/**
* Build mesh draw command primitive Id buffer for instancing.
* TempVisibleMeshDrawCommands must be presized for NewPassVisibleMeshDrawCommands.
*/
void BuildMeshDrawCommandPrimitiveIdBuffer(
bool bDynamicInstancing,
FMeshCommandOneFrameArray& VisibleMeshDrawCommands,
FDynamicMeshDrawCommandStorage& MeshDrawCommandStorage,
void* RESTRICT PrimitiveIdData,
int32 PrimitiveIdDataSize,
FMeshCommandOneFrameArray& TempVisibleMeshDrawCommands,
int32& MaxInstances,
int32& VisibleMeshDrawCommandsNum,
int32& NewPassVisibleMeshDrawCommandsNum,
uint32 InstanceFactor = 1
)
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_BuildMeshDrawCommandPrimitiveIdBuffer);
check(PrimitiveIdData && PrimitiveIdDataSize > 0);
const FVisibleMeshDrawCommand* RESTRICT PassVisibleMeshDrawCommands = VisibleMeshDrawCommands.GetData();
const int32 NumDrawCommands = VisibleMeshDrawCommands.Num();
uint32 PrimitiveIdIndex = 0;
int32* RESTRICT PrimitiveIds = (int32*)PrimitiveIdData;
const uint32 MaxPrimitiveId = PrimitiveIdDataSize / sizeof(int32);
if (bDynamicInstancing)
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_DynamicInstancingOfVisibleMeshDrawCommands);
check(VisibleMeshDrawCommands.Num() <= TempVisibleMeshDrawCommands.Max() && TempVisibleMeshDrawCommands.Num() == 0);
int32 CurrentStateBucketId = -1;
uint32* RESTRICT CurrentDynamicallyInstancedMeshCommandNumInstances = nullptr;
MaxInstances = 1;
for (int32 DrawCommandIndex = 0; DrawCommandIndex < NumDrawCommands; DrawCommandIndex++)
{
const FVisibleMeshDrawCommand& RESTRICT VisibleMeshDrawCommand = PassVisibleMeshDrawCommands[DrawCommandIndex];
if (VisibleMeshDrawCommand.StateBucketId == CurrentStateBucketId && VisibleMeshDrawCommand.StateBucketId != -1)
{
if (CurrentDynamicallyInstancedMeshCommandNumInstances)
{
const int32 CurrentNumInstances = *CurrentDynamicallyInstancedMeshCommandNumInstances;
*CurrentDynamicallyInstancedMeshCommandNumInstances = CurrentNumInstances + 1;
MaxInstances = FMath::Max(MaxInstances, CurrentNumInstances + 1);
}
else
{
FVisibleMeshDrawCommand NewVisibleMeshDrawCommand = VisibleMeshDrawCommand;
NewVisibleMeshDrawCommand.PrimitiveIdBufferOffset = PrimitiveIdIndex;
TempVisibleMeshDrawCommands.Emplace(MoveTemp(NewVisibleMeshDrawCommand));
}
}
else
{
// First time state bucket setup
CurrentStateBucketId = VisibleMeshDrawCommand.StateBucketId;
if (VisibleMeshDrawCommand.MeshDrawCommand->PrimitiveIdStreamIndex >= 0
&& VisibleMeshDrawCommand.MeshDrawCommand->NumInstances == 1
// Don't create a new FMeshDrawCommand for the last command and make it safe for us to look at the next command
&& DrawCommandIndex + 1 < NumDrawCommands
// Only create a new FMeshDrawCommand if more than one draw in the state bucket
&& CurrentStateBucketId == PassVisibleMeshDrawCommands[DrawCommandIndex + 1].StateBucketId)
{
const int32 Index = MeshDrawCommandStorage.MeshDrawCommands.AddElement(*VisibleMeshDrawCommand.MeshDrawCommand);
FMeshDrawCommand& NewCommand = MeshDrawCommandStorage.MeshDrawCommands[Index];
FVisibleMeshDrawCommand NewVisibleMeshDrawCommand;
NewVisibleMeshDrawCommand.Setup(
&NewCommand,
VisibleMeshDrawCommand.DrawPrimitiveId,
VisibleMeshDrawCommand.StateBucketId,
VisibleMeshDrawCommand.MeshFillMode,
VisibleMeshDrawCommand.MeshCullMode,
VisibleMeshDrawCommand.SortKey);
NewVisibleMeshDrawCommand.PrimitiveIdBufferOffset = PrimitiveIdIndex;
TempVisibleMeshDrawCommands.Emplace(MoveTemp(NewVisibleMeshDrawCommand));
CurrentDynamicallyInstancedMeshCommandNumInstances = &NewCommand.NumInstances;
}
else
{
CurrentDynamicallyInstancedMeshCommandNumInstances = nullptr;
FVisibleMeshDrawCommand NewVisibleMeshDrawCommand = VisibleMeshDrawCommand;
NewVisibleMeshDrawCommand.PrimitiveIdBufferOffset = PrimitiveIdIndex;
TempVisibleMeshDrawCommands.Emplace(MoveTemp(NewVisibleMeshDrawCommand));
}
}
//@todo - refactor into instance step rate in the RHI
for (uint32 InstanceFactorIndex = 0; InstanceFactorIndex < InstanceFactor; InstanceFactorIndex++, PrimitiveIdIndex++)
{
//@todo - refactor into memcpy
checkSlow(PrimitiveIdIndex < MaxPrimitiveId);
PrimitiveIds[PrimitiveIdIndex] = VisibleMeshDrawCommand.DrawPrimitiveId;
}
}
// Setup instancing stats for logging.
VisibleMeshDrawCommandsNum = VisibleMeshDrawCommands.Num();
NewPassVisibleMeshDrawCommandsNum = TempVisibleMeshDrawCommands.Num();
// Replace VisibleMeshDrawCommands
FMemory::Memswap(&VisibleMeshDrawCommands, &TempVisibleMeshDrawCommands, sizeof(TempVisibleMeshDrawCommands));
TempVisibleMeshDrawCommands.Reset();
}
else
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_BuildVisibleMeshDrawCommandPrimitiveIdBuffers);
for (int32 DrawCommandIndex = 0; DrawCommandIndex < NumDrawCommands; DrawCommandIndex++)
{
const FVisibleMeshDrawCommand& VisibleMeshDrawCommand = VisibleMeshDrawCommands[DrawCommandIndex];
for (uint32 InstanceFactorIndex = 0; InstanceFactorIndex < InstanceFactor; InstanceFactorIndex++, PrimitiveIdIndex++)
{
checkSlow(PrimitiveIdIndex < MaxPrimitiveId);
PrimitiveIds[PrimitiveIdIndex] = VisibleMeshDrawCommand.DrawPrimitiveId;
}
}
}
}
/**
* Converts each FMeshBatch into a set of FMeshDrawCommands for a specific mesh pass type.
*/
void GenerateDynamicMeshDrawCommands(
const FViewInfo& View,
EShadingPath ShadingPath,
EMeshPass::Type PassType,
FMeshPassProcessor* PassMeshProcessor,
const TArray<FMeshBatchAndRelevance, SceneRenderingAllocator>& DynamicMeshElements,
const TArray<FMeshPassMask, SceneRenderingAllocator>* DynamicMeshElementsPassRelevance,
int32 MaxNumDynamicMeshElements,
const TArray<const FStaticMeshBatch*, SceneRenderingAllocator>& DynamicMeshCommandBuildRequests,
int32 MaxNumBuildRequestElements,
FMeshCommandOneFrameArray& VisibleCommands,
FDynamicMeshDrawCommandStorage& MeshDrawCommandStorage,
FGraphicsMinimalPipelineStateSet& MinimalPipelineStatePassSet
)
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_GenerateDynamicMeshDrawCommands);
check(PassMeshProcessor);
check((PassType == EMeshPass::Num) == (DynamicMeshElementsPassRelevance == nullptr));
FDynamicPassMeshDrawListContext DynamicPassMeshDrawListContext(
MeshDrawCommandStorage,
VisibleCommands,
MinimalPipelineStatePassSet
);
PassMeshProcessor->SetDrawListContext(&DynamicPassMeshDrawListContext);
{
const int32 NumCommandsBefore = VisibleCommands.Num();
const int32 NumDynamicMeshBatches = DynamicMeshElements.Num();
for (int32 MeshIndex = 0; MeshIndex < NumDynamicMeshBatches; MeshIndex++)
{
if (!DynamicMeshElementsPassRelevance || (*DynamicMeshElementsPassRelevance)[MeshIndex].Get(PassType))
{
const FMeshBatchAndRelevance& MeshAndRelevance = DynamicMeshElements[MeshIndex];
check(!MeshAndRelevance.Mesh->bRequiresPerElementVisibility);
const uint64 BatchElementMask = ~0ull;
PassMeshProcessor->AddMeshBatch(*MeshAndRelevance.Mesh, BatchElementMask, MeshAndRelevance.PrimitiveSceneProxy);
}
}
const int32 NumCommandsGenerated = VisibleCommands.Num() - NumCommandsBefore;
checkf(NumCommandsGenerated <= MaxNumDynamicMeshElements,
TEXT("Generated %d mesh draw commands for DynamicMeshElements, while preallocating resources only for %d of them."), NumCommandsGenerated, MaxNumDynamicMeshElements);
}
{
const int32 NumCommandsBefore = VisibleCommands.Num();
const int32 NumStaticMeshBatches = DynamicMeshCommandBuildRequests.Num();
for (int32 MeshIndex = 0; MeshIndex < NumStaticMeshBatches; MeshIndex++)
{
const FStaticMeshBatch* StaticMeshBatch = DynamicMeshCommandBuildRequests[MeshIndex];
const uint64 BatchElementMask = StaticMeshBatch->bRequiresPerElementVisibility ? View.StaticMeshBatchVisibility[StaticMeshBatch->BatchVisibilityId] : ~0ull;
PassMeshProcessor->AddMeshBatch(*StaticMeshBatch, BatchElementMask, StaticMeshBatch->PrimitiveSceneInfo->Proxy, StaticMeshBatch->Id);
}
const int32 NumCommandsGenerated = VisibleCommands.Num() - NumCommandsBefore;
checkf(NumCommandsGenerated <= MaxNumBuildRequestElements,
TEXT("Generated %d mesh draw commands for DynamicMeshCommandBuildRequests, while preallocating resources only for %d of them."), NumCommandsGenerated, MaxNumBuildRequestElements);
}
}
/**
* Special version of GenerateDynamicMeshDrawCommands for the mobile base pass.
* Based on CSM visibility it will generate mesh draw commands using either normal base pass processor or CSM base pass processor.
*/
void GenerateMobileBasePassDynamicMeshDrawCommands(
const FViewInfo& View,
EShadingPath ShadingPath,
EMeshPass::Type PassType,
FMeshPassProcessor* PassMeshProcessor,
FMeshPassProcessor* MobilePassCSMPassMeshProcessor,
const TArray<FMeshBatchAndRelevance, SceneRenderingAllocator>& DynamicMeshElements,
const TArray<FMeshPassMask, SceneRenderingAllocator>* DynamicMeshElementsPassRelevance,
int32 MaxNumDynamicMeshElements,
const TArray<const FStaticMeshBatch*, SceneRenderingAllocator>& DynamicMeshCommandBuildRequests,
int32 MaxNumBuildRequestElements,
FMeshCommandOneFrameArray& VisibleCommands,
FDynamicMeshDrawCommandStorage& MeshDrawCommandStorage,
FGraphicsMinimalPipelineStateSet& GraphicsMinimalPipelineStateSet
)
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_GenerateMobileBasePassDynamicMeshDrawCommands);
check(PassMeshProcessor && MobilePassCSMPassMeshProcessor);
check((PassType == EMeshPass::Num) == (DynamicMeshElementsPassRelevance == nullptr));
FDynamicPassMeshDrawListContext DynamicPassMeshDrawListContext(
MeshDrawCommandStorage,
VisibleCommands,
GraphicsMinimalPipelineStateSet
);
PassMeshProcessor->SetDrawListContext(&DynamicPassMeshDrawListContext);
MobilePassCSMPassMeshProcessor->SetDrawListContext(&DynamicPassMeshDrawListContext);
const FMobileCSMVisibilityInfo& MobileCSMVisibilityInfo = View.MobileCSMVisibilityInfo;
{
const int32 NumCommandsBefore = VisibleCommands.Num();
const int32 NumDynamicMeshBatches = DynamicMeshElements.Num();
for (int32 MeshIndex = 0; MeshIndex < NumDynamicMeshBatches; MeshIndex++)
{
if (!DynamicMeshElementsPassRelevance || (*DynamicMeshElementsPassRelevance)[MeshIndex].Get(PassType))
{
const FMeshBatchAndRelevance& MeshAndRelevance = DynamicMeshElements[MeshIndex];
check(!MeshAndRelevance.Mesh->bRequiresPerElementVisibility);
const uint64 BatchElementMask = ~0ull;
const int32 PrimitiveIndex = MeshAndRelevance.PrimitiveSceneProxy->GetPrimitiveSceneInfo()->GetIndex();
if (MobileCSMVisibilityInfo.bMobileDynamicCSMInUse
&& (MobileCSMVisibilityInfo.bAlwaysUseCSM || MobileCSMVisibilityInfo.MobilePrimitiveCSMReceiverVisibilityMap[PrimitiveIndex]))
{
MobilePassCSMPassMeshProcessor->AddMeshBatch(*MeshAndRelevance.Mesh, BatchElementMask, MeshAndRelevance.PrimitiveSceneProxy);
}
else
{
PassMeshProcessor->AddMeshBatch(*MeshAndRelevance.Mesh, BatchElementMask, MeshAndRelevance.PrimitiveSceneProxy);
}
}
}
const int32 NumCommandsGenerated = VisibleCommands.Num() - NumCommandsBefore;
checkf(NumCommandsGenerated <= MaxNumDynamicMeshElements,
TEXT("Generated %d mesh draw commands for DynamicMeshElements, while preallocating resources only for %d of them."), NumCommandsGenerated, MaxNumDynamicMeshElements);
}
{
const int32 NumCommandsBefore = VisibleCommands.Num();
const int32 NumStaticMeshBatches = DynamicMeshCommandBuildRequests.Num();
for (int32 MeshIndex = 0; MeshIndex < NumStaticMeshBatches; MeshIndex++)
{
const FStaticMeshBatch* StaticMeshBatch = DynamicMeshCommandBuildRequests[MeshIndex];
const uint64 BatchElementMask = StaticMeshBatch->bRequiresPerElementVisibility ? View.StaticMeshBatchVisibility[StaticMeshBatch->BatchVisibilityId] : ~0ull;
const int32 PrimitiveIndex = StaticMeshBatch->PrimitiveSceneInfo->Proxy->GetPrimitiveSceneInfo()->GetIndex();
if (MobileCSMVisibilityInfo.bMobileDynamicCSMInUse
&& (MobileCSMVisibilityInfo.bAlwaysUseCSM || MobileCSMVisibilityInfo.MobilePrimitiveCSMReceiverVisibilityMap[PrimitiveIndex]))
{
MobilePassCSMPassMeshProcessor->AddMeshBatch(*StaticMeshBatch, BatchElementMask, StaticMeshBatch->PrimitiveSceneInfo->Proxy, StaticMeshBatch->Id);
}
else
{
PassMeshProcessor->AddMeshBatch(*StaticMeshBatch, BatchElementMask, StaticMeshBatch->PrimitiveSceneInfo->Proxy, StaticMeshBatch->Id);
}
}
const int32 NumCommandsGenerated = VisibleCommands.Num() - NumCommandsBefore;
checkf(NumCommandsGenerated <= MaxNumBuildRequestElements,
TEXT("Generated %d mesh draw commands for DynamicMeshCommandBuildRequests, while preallocating resources only for %d of them."), NumCommandsGenerated, MaxNumBuildRequestElements);
}
}
/**
* Apply view overrides to existing mesh draw commands (e.g. reverse culling mode for rendering planar reflections).
* TempVisibleMeshDrawCommands must be presized for NewPassVisibleMeshDrawCommands.
*/
void ApplyViewOverridesToMeshDrawCommands(
EShadingPath ShadingPath,
EMeshPass::Type PassType,
bool bReverseCulling,
bool bRenderSceneTwoSided,
FExclusiveDepthStencil::Type BasePassDepthStencilAccess,
FExclusiveDepthStencil::Type DefaultBasePassDepthStencilAccess,
FMeshCommandOneFrameArray& VisibleMeshDrawCommands,
FDynamicMeshDrawCommandStorage& MeshDrawCommandStorage,
FGraphicsMinimalPipelineStateSet& MinimalPipelineStatePassSet,
FMeshCommandOneFrameArray& TempVisibleMeshDrawCommands
)
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_ApplyViewOverridesToMeshDrawCommands);
check(VisibleMeshDrawCommands.Num() <= TempVisibleMeshDrawCommands.Max() && TempVisibleMeshDrawCommands.Num() == 0 && PassType != EMeshPass::Num);
if ((FPassProcessorManager::GetPassFlags(ShadingPath, PassType) & EMeshPassFlags::MainView) != EMeshPassFlags::None)
{
if (bReverseCulling || bRenderSceneTwoSided || (BasePassDepthStencilAccess != DefaultBasePassDepthStencilAccess && PassType == EMeshPass::BasePass))
{
for (int32 MeshCommandIndex = 0; MeshCommandIndex < VisibleMeshDrawCommands.Num(); MeshCommandIndex++)
{
MeshDrawCommandStorage.MeshDrawCommands.Add(1);
FMeshDrawCommand& NewMeshCommand = MeshDrawCommandStorage.MeshDrawCommands[MeshDrawCommandStorage.MeshDrawCommands.Num() - 1];
const FVisibleMeshDrawCommand& VisibleMeshDrawCommand = VisibleMeshDrawCommands[MeshCommandIndex];
const FMeshDrawCommand& MeshCommand = *VisibleMeshDrawCommand.MeshDrawCommand;
NewMeshCommand = MeshCommand;
const ERasterizerCullMode LocalCullMode = bRenderSceneTwoSided ? CM_None : bReverseCulling ? FMeshPassProcessor::InverseCullMode(VisibleMeshDrawCommand.MeshCullMode) : VisibleMeshDrawCommand.MeshCullMode;
FGraphicsMinimalPipelineStateInitializer PipelineState = MeshCommand.CachedPipelineId.GetPipelineState(MinimalPipelineStatePassSet);
Moving Daniel Wright's HLR memory optimizations from Dev-Rendering. CL 4871955 Fixed TInlineAllocator::GetAllocatedSize reporting SecondaryData AllocatedSize even when there wasn't any SecondaryData CL 4871994 MeshDrawCommand memory optimizations * When GPUScene is enabled, cached FMeshDrawCommands are only stored in FScene::CachedMeshDrawCommandStateBuckets, so we only have one command per state bucket. * Removed FGraphicsMinimalPipelineStateInitializer, now relying on index into the global table * Union IndirectArgsBuffer with draw params * Trimmed debug data * Removed duplicated space for SRV values, now using a bitmask to indicate SRV or TextureRHI * Deferred renderer GPUPerfTest cached MeshDrawCommands 28Mb -> 2.1Mb * Mobile renderer GPUPerfTest cached MeshDrawCommands 30.7Mb -> 20.5Mb CL 4892243 Resupported r.DoLazyStaticMeshUpdate with mesh draw commands. Now enabled when in game mode, no longer requires cooked build. * GPUPerfTest Total cached MeshDrawCommands 2.324Mb -> 1.691Mb and Primitive StaticMeshCommandInfos 3529.2Kb -> 563.7Kb CL 4925071 Allocate MeshDrawCommands at lowest free index instead of last freed index Shrink MeshDrawCommands periodically when r.DoLazyStaticMeshUpdate is enabled Measured in replay ES31: Cached MeshDrawCommands 5.236Mb -> 2.216Mb CL 4937280 Removed r.DeferUniformBufferUpdatesUntilVisible since it is no longer supported with MeshDrawCommands, which cache the uniform buffer pointer at AddToScene time. Removed dead code referencing it. CL 4937591 Fixed FMeshDrawCommandStateCache always resetting resources at index 0. Found by Rolando. CL 4951781 Attempt to silence static analysis warning #jira none #rb none #rnx #ROBOMERGE-OWNER: ryan.vance #ROBOMERGE-AUTHOR: krzysztof.narkowicz #ROBOMERGE-SOURCE: CL 4993090 in //UE4/Release-4.22/... via CL 4993091 #ROBOMERGE-BOT: DEVVR (Main -> Dev-VR) [CL 5035029 by krzysztof narkowicz in Dev-VR branch]
2019-02-16 08:15:21 -05:00
PipelineState.RasterizerState = GetStaticRasterizerState<true>(VisibleMeshDrawCommand.MeshFillMode, LocalCullMode);
if (BasePassDepthStencilAccess != DefaultBasePassDepthStencilAccess && PassType == EMeshPass::BasePass)
{
FMeshPassProcessorRenderState PassDrawRenderState;
SetupBasePassState(BasePassDepthStencilAccess, false, PassDrawRenderState);
Moving Daniel Wright's HLR memory optimizations from Dev-Rendering. CL 4871955 Fixed TInlineAllocator::GetAllocatedSize reporting SecondaryData AllocatedSize even when there wasn't any SecondaryData CL 4871994 MeshDrawCommand memory optimizations * When GPUScene is enabled, cached FMeshDrawCommands are only stored in FScene::CachedMeshDrawCommandStateBuckets, so we only have one command per state bucket. * Removed FGraphicsMinimalPipelineStateInitializer, now relying on index into the global table * Union IndirectArgsBuffer with draw params * Trimmed debug data * Removed duplicated space for SRV values, now using a bitmask to indicate SRV or TextureRHI * Deferred renderer GPUPerfTest cached MeshDrawCommands 28Mb -> 2.1Mb * Mobile renderer GPUPerfTest cached MeshDrawCommands 30.7Mb -> 20.5Mb CL 4892243 Resupported r.DoLazyStaticMeshUpdate with mesh draw commands. Now enabled when in game mode, no longer requires cooked build. * GPUPerfTest Total cached MeshDrawCommands 2.324Mb -> 1.691Mb and Primitive StaticMeshCommandInfos 3529.2Kb -> 563.7Kb CL 4925071 Allocate MeshDrawCommands at lowest free index instead of last freed index Shrink MeshDrawCommands periodically when r.DoLazyStaticMeshUpdate is enabled Measured in replay ES31: Cached MeshDrawCommands 5.236Mb -> 2.216Mb CL 4937280 Removed r.DeferUniformBufferUpdatesUntilVisible since it is no longer supported with MeshDrawCommands, which cache the uniform buffer pointer at AddToScene time. Removed dead code referencing it. CL 4937591 Fixed FMeshDrawCommandStateCache always resetting resources at index 0. Found by Rolando. CL 4951781 Attempt to silence static analysis warning #jira none #rb none #rnx #ROBOMERGE-OWNER: ryan.vance #ROBOMERGE-AUTHOR: krzysztof.narkowicz #ROBOMERGE-SOURCE: CL 4993090 in //UE4/Release-4.22/... via CL 4993091 #ROBOMERGE-BOT: DEVVR (Main -> Dev-VR) [CL 5035029 by krzysztof narkowicz in Dev-VR branch]
2019-02-16 08:15:21 -05:00
PipelineState.DepthStencilState = PassDrawRenderState.GetDepthStencilState();
}
const FGraphicsMinimalPipelineStateId PipelineId = FGraphicsMinimalPipelineStateId::GetPipelineStateId(PipelineState, MinimalPipelineStatePassSet);
NewMeshCommand.Finalize(PipelineId, nullptr);
FVisibleMeshDrawCommand NewVisibleMeshDrawCommand;
NewVisibleMeshDrawCommand.Setup(
&NewMeshCommand,
VisibleMeshDrawCommand.DrawPrimitiveId,
VisibleMeshDrawCommand.StateBucketId,
VisibleMeshDrawCommand.MeshFillMode,
VisibleMeshDrawCommand.MeshCullMode,
VisibleMeshDrawCommand.SortKey);
TempVisibleMeshDrawCommands.Add(NewVisibleMeshDrawCommand);
}
// Replace VisibleMeshDrawCommands
FMemory::Memswap(&VisibleMeshDrawCommands, &TempVisibleMeshDrawCommands, sizeof(TempVisibleMeshDrawCommands));
TempVisibleMeshDrawCommands.Reset();
}
}
}
FAutoConsoleTaskPriority CPrio_FMeshDrawCommandPassSetupTask(
TEXT("TaskGraph.TaskPriorities.FMeshDrawCommandPassSetupTask"),
TEXT("Task and thread priority for FMeshDrawCommandPassSetupTask."),
ENamedThreads::NormalThreadPriority,
ENamedThreads::HighTaskPriority
);
/**
* Task for a parallel setup of mesh draw commands. Includes generation of dynamic mesh draw commands, sorting, merging etc.
*/
class FMeshDrawCommandPassSetupTask
{
public:
FMeshDrawCommandPassSetupTask(FMeshDrawCommandPassSetupTaskContext& InContext)
: Context(InContext)
{
}
FORCEINLINE TStatId GetStatId() const
{
RETURN_QUICK_DECLARE_CYCLE_STAT(FMeshDrawCommandPassSetupTask, STATGROUP_TaskGraphTasks);
}
ENamedThreads::Type GetDesiredThread()
{
return CPrio_FMeshDrawCommandPassSetupTask.Get();
}
static ESubsequentsMode::Type GetSubsequentsMode()
{
return ESubsequentsMode::TrackSubsequents;
}
void AnyThreadTask()
{
// Mobile base pass is a special case, as final lists is created from two mesh passes based on CSM visibility.
const bool bMobileBasePass = Context.ShadingPath == EShadingPath::Mobile && Context.PassType == EMeshPass::BasePass;
if (bMobileBasePass)
{
MergeMobileBasePassMeshDrawCommands(
Context.View->MobileCSMVisibilityInfo,
Context.PrimitiveBounds->Num(),
Context.MeshDrawCommands,
Context.MobileBasePassCSMMeshDrawCommands
);
GenerateMobileBasePassDynamicMeshDrawCommands(
*Context.View,
Context.ShadingPath,
Context.PassType,
Context.MeshPassProcessor,
Context.MobileBasePassCSMMeshPassProcessor,
*Context.DynamicMeshElements,
Context.DynamicMeshElementsPassRelevance,
Context.NumDynamicMeshElements,
Context.DynamicMeshCommandBuildRequests,
Context.NumDynamicMeshCommandBuildRequestElements,
Context.MeshDrawCommands,
Context.MeshDrawCommandStorage,
Context.MinimalPipelineStatePassSet
);
}
else
{
GenerateDynamicMeshDrawCommands(
*Context.View,
Context.ShadingPath,
Context.PassType,
Context.MeshPassProcessor,
*Context.DynamicMeshElements,
Context.DynamicMeshElementsPassRelevance,
Context.NumDynamicMeshElements,
Context.DynamicMeshCommandBuildRequests,
Context.NumDynamicMeshCommandBuildRequestElements,
Context.MeshDrawCommands,
Context.MeshDrawCommandStorage,
Context.MinimalPipelineStatePassSet
);
}
if (Context.MeshDrawCommands.Num() > 0)
{
if (Context.PassType != EMeshPass::Num)
{
ApplyViewOverridesToMeshDrawCommands(
Context.ShadingPath,
Context.PassType,
Context.bReverseCulling,
Context.bRenderSceneTwoSided,
Context.BasePassDepthStencilAccess,
Context.DefaultBasePassDepthStencilAccess,
Context.MeshDrawCommands,
Context.MeshDrawCommandStorage,
Context.MinimalPipelineStatePassSet,
Context.TempVisibleMeshDrawCommands
);
}
// Update sort keys.
if (bMobileBasePass)
{
UpdateMobileBasePassMeshSortKeys(
Context.ViewOrigin,
*Context.PrimitiveBounds,
Context.MeshDrawCommands
);
}
else if (Context.TranslucencyPass != ETranslucencyPass::TPT_MAX)
{
UpdateTranslucentMeshSortKeys(
Context.TranslucentSortPolicy,
Context.TranslucentSortAxis,
Context.ViewOrigin,
Context.ViewMatrix,
*Context.PrimitiveBounds,
Context.TranslucencyPass,
Context.MeshDrawCommands
);
}
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_SortVisibleMeshDrawCommands);
Context.MeshDrawCommands.Sort(FCompareFMeshDrawCommands());
}
if (Context.bUseGPUScene)
{
BuildMeshDrawCommandPrimitiveIdBuffer(
Context.bDynamicInstancing,
Context.MeshDrawCommands,
Context.MeshDrawCommandStorage,
Context.PrimitiveIdBufferData,
Context.PrimitiveIdBufferDataSize,
Context.TempVisibleMeshDrawCommands,
Context.MaxInstances,
Context.VisibleMeshDrawCommandsNum,
Context.NewPassVisibleMeshDrawCommandsNum,
Context.InstanceFactor
);
}
}
}
void DoTask(ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent)
{
AnyThreadTask();
}
private:
FMeshDrawCommandPassSetupTaskContext& Context;
};
/*
* Used by various dynamic passes to sort/merge mesh draw commands immediately on a rendering thread.
*/
void SortAndMergeDynamicPassMeshDrawCommands(
ERHIFeatureLevel::Type FeatureLevel,
FMeshCommandOneFrameArray& VisibleMeshDrawCommands,
FDynamicMeshDrawCommandStorage& MeshDrawCommandStorage,
FVertexBufferRHIParamRef& OutPrimitiveIdVertexBuffer,
uint32 InstanceFactor)
{
const bool bUseGPUScene = UseGPUScene(GMaxRHIShaderPlatform, FeatureLevel);
const int32 NumDrawCommands = VisibleMeshDrawCommands.Num();
if (NumDrawCommands > 0)
{
FMeshCommandOneFrameArray NewPassVisibleMeshDrawCommands;
int32 MaxInstances = 1;
int32 VisibleMeshDrawCommandsNum = 0;
int32 NewPassVisibleMeshDrawCommandsNum = 0;
VisibleMeshDrawCommands.Sort(FCompareFMeshDrawCommands());
if (bUseGPUScene)
{
const bool bDynamicInstancing = IsDynamicInstancingEnabled(FeatureLevel);
if (bDynamicInstancing)
{
NewPassVisibleMeshDrawCommands.Empty(NumDrawCommands);
}
const int32 PrimitiveIdBufferDataSize = InstanceFactor * NumDrawCommands * sizeof(int32);
OutPrimitiveIdVertexBuffer = GPrimitiveIdVertexBufferPool.Allocate(PrimitiveIdBufferDataSize);
void* PrimitiveIdBufferData = RHILockVertexBuffer(OutPrimitiveIdVertexBuffer, 0, PrimitiveIdBufferDataSize, RLM_WriteOnly);
BuildMeshDrawCommandPrimitiveIdBuffer(
bDynamicInstancing,
VisibleMeshDrawCommands,
MeshDrawCommandStorage,
PrimitiveIdBufferData,
PrimitiveIdBufferDataSize,
NewPassVisibleMeshDrawCommands,
MaxInstances,
VisibleMeshDrawCommandsNum,
NewPassVisibleMeshDrawCommandsNum,
InstanceFactor
);
RHIUnlockVertexBuffer(OutPrimitiveIdVertexBuffer);
}
}
}
void FParallelMeshDrawCommandPass::DispatchPassSetup(
FScene* Scene,
const FViewInfo& View,
EMeshPass::Type PassType,
FExclusiveDepthStencil::Type BasePassDepthStencilAccess,
FMeshPassProcessor* MeshPassProcessor,
const TArray<FMeshBatchAndRelevance, SceneRenderingAllocator>& DynamicMeshElements,
const TArray<FMeshPassMask, SceneRenderingAllocator>* DynamicMeshElementsPassRelevance,
int32 NumDynamicMeshElements,
TArray<const FStaticMeshBatch*, SceneRenderingAllocator>& InOutDynamicMeshCommandBuildRequests,
int32 NumDynamicMeshCommandBuildRequestElements,
FMeshCommandOneFrameArray& InOutMeshDrawCommands,
FMeshPassProcessor* MobileBasePassCSMMeshPassProcessor,
FMeshCommandOneFrameArray* InOutMobileBasePassCSMMeshDrawCommands
)
{
check(!TaskEventRef.IsValid() && MeshPassProcessor != nullptr && TaskContext.PrimitiveIdBufferData == nullptr);
check((PassType == EMeshPass::Num) == (DynamicMeshElementsPassRelevance == nullptr));
MaxNumDraws = InOutMeshDrawCommands.Num() + NumDynamicMeshElements + NumDynamicMeshCommandBuildRequestElements;
TaskContext.MeshPassProcessor = MeshPassProcessor;
TaskContext.MobileBasePassCSMMeshPassProcessor = MobileBasePassCSMMeshPassProcessor;
TaskContext.DynamicMeshElements = &DynamicMeshElements;
TaskContext.DynamicMeshElementsPassRelevance = DynamicMeshElementsPassRelevance;
TaskContext.View = &View;
TaskContext.ShadingPath = Scene->GetShadingPath();
TaskContext.PassType = PassType;
TaskContext.bUseGPUScene = UseGPUScene(GMaxRHIShaderPlatform, View.GetFeatureLevel());
TaskContext.bDynamicInstancing = IsDynamicInstancingEnabled(View.GetFeatureLevel());
TaskContext.bReverseCulling = View.bReverseCulling;
TaskContext.bRenderSceneTwoSided = View.bRenderSceneTwoSided;
TaskContext.BasePassDepthStencilAccess = BasePassDepthStencilAccess;
TaskContext.DefaultBasePassDepthStencilAccess = Scene->DefaultBasePassDepthStencilAccess;
TaskContext.NumDynamicMeshElements = NumDynamicMeshElements;
TaskContext.NumDynamicMeshCommandBuildRequestElements = NumDynamicMeshCommandBuildRequestElements;
// Only apply instancing for ISR to main view passes
const bool bIsMainViewPass = PassType != EMeshPass::Num && (FPassProcessorManager::GetPassFlags(TaskContext.ShadingPath, TaskContext.PassType) & EMeshPassFlags::MainView) != EMeshPassFlags::None;
TaskContext.InstanceFactor = (bIsMainViewPass && View.IsInstancedStereoPass()) ? 2 : 1;
// Setup translucency sort key update pass based on view.
TaskContext.TranslucencyPass = ETranslucencyPass::TPT_MAX;
TaskContext.TranslucentSortPolicy = View.TranslucentSortPolicy;
TaskContext.TranslucentSortAxis = View.TranslucentSortAxis;
TaskContext.ViewOrigin = View.ViewMatrices.GetViewOrigin();
TaskContext.ViewMatrix = View.ViewMatrices.GetViewMatrix();
TaskContext.PrimitiveBounds = &Scene->PrimitiveBounds;
switch (PassType)
{
case EMeshPass::TranslucencyStandard: TaskContext.TranslucencyPass = ETranslucencyPass::TPT_StandardTranslucency; break;
case EMeshPass::TranslucencyAfterDOF: TaskContext.TranslucencyPass = ETranslucencyPass::TPT_TranslucencyAfterDOF; break;
case EMeshPass::TranslucencyAll: TaskContext.TranslucencyPass = ETranslucencyPass::TPT_AllTranslucency; break;
case EMeshPass::MobileInverseOpacity: TaskContext.TranslucencyPass = ETranslucencyPass::TPT_StandardTranslucency; break;
}
FMemory::Memswap(&TaskContext.MeshDrawCommands, &InOutMeshDrawCommands, sizeof(InOutMeshDrawCommands));
FMemory::Memswap(&TaskContext.DynamicMeshCommandBuildRequests, &InOutDynamicMeshCommandBuildRequests, sizeof(InOutDynamicMeshCommandBuildRequests));
if (TaskContext.ShadingPath == EShadingPath::Mobile && TaskContext.PassType == EMeshPass::BasePass)
{
FMemory::Memswap(&TaskContext.MobileBasePassCSMMeshDrawCommands, InOutMobileBasePassCSMMeshDrawCommands, sizeof(*InOutMobileBasePassCSMMeshDrawCommands));
}
else
{
check(MobileBasePassCSMMeshPassProcessor == nullptr && InOutMobileBasePassCSMMeshDrawCommands == nullptr);
}
if (MaxNumDraws > 0)
{
// Preallocate resources on rendering thread based on MaxNumDraws.
bPrimitiveIdBufferDataOwnedByRHIThread = false;
TaskContext.PrimitiveIdBufferDataSize = TaskContext.InstanceFactor * MaxNumDraws * sizeof(int32);
TaskContext.PrimitiveIdBufferData = FMemory::Malloc(TaskContext.PrimitiveIdBufferDataSize);
PrimitiveIdVertexBufferRHI = GPrimitiveIdVertexBufferPool.Allocate(TaskContext.PrimitiveIdBufferDataSize);
TaskContext.MeshDrawCommands.Reserve(MaxNumDraws);
TaskContext.TempVisibleMeshDrawCommands.Reserve(MaxNumDraws);
const bool bExecuteInParallel = FApp::ShouldUseThreadingForPerformance()
&& CVarMeshDrawCommandsParallelPassSetup.GetValueOnRenderThread() > 0
&& GRenderingThread; // Rendering thread is required to safely use rendering resources in parallel.
if (bExecuteInParallel)
{
TaskEventRef = TGraphTask<FMeshDrawCommandPassSetupTask>::CreateTask(
nullptr, ENamedThreads::GetRenderThread()).ConstructAndDispatchWhenReady(TaskContext);
}
else
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_MeshPassSetupImmediate);
FMeshDrawCommandPassSetupTask Task(TaskContext);
Task.AnyThreadTask();
}
}
}
void FParallelMeshDrawCommandPass::WaitForMeshPassSetupTask() const
{
if (TaskEventRef.IsValid())
{
// Need to wait on GetRenderThread_Local, as mesh pass setup task can wait on rendering thread inside InitResourceFromPossiblyParallelRendering().
QUICK_SCOPE_CYCLE_COUNTER(STAT_WaitForMeshPassSetupTask);
FTaskGraphInterface::Get().WaitUntilTaskCompletes(TaskEventRef, ENamedThreads::GetRenderThread_Local());
}
}
void FParallelMeshDrawCommandPass::WaitForTasksAndEmpty()
{
// Need to wait in case if someone dispatched sort and draw merge task, but didn't draw it.
WaitForMeshPassSetupTask();
TaskEventRef = nullptr;
DumpInstancingStats();
if (TaskContext.MeshPassProcessor)
{
TaskContext.MeshPassProcessor->~FMeshPassProcessor();
TaskContext.MeshPassProcessor = nullptr;
}
if (TaskContext.MobileBasePassCSMMeshPassProcessor)
{
TaskContext.MobileBasePassCSMMeshPassProcessor->~FMeshPassProcessor();
TaskContext.MobileBasePassCSMMeshPassProcessor = nullptr;
}
if (!bPrimitiveIdBufferDataOwnedByRHIThread)
{
FMemory::Free(TaskContext.PrimitiveIdBufferData);
}
bPrimitiveIdBufferDataOwnedByRHIThread = false;
MaxNumDraws = 0;
PassNameForStats.Empty();
TaskContext.DynamicMeshElements = nullptr;
TaskContext.DynamicMeshElementsPassRelevance = nullptr;
TaskContext.MeshDrawCommands.Empty();
TaskContext.MeshDrawCommandStorage.MeshDrawCommands.Empty();
FGraphicsMinimalPipelineStateId::AddSizeToLocalPipelineIdTableSize(TaskContext.MinimalPipelineStatePassSet.GetAllocatedSize());
TaskContext.MinimalPipelineStatePassSet.Empty();
TaskContext.MobileBasePassCSMMeshDrawCommands.Empty();
TaskContext.DynamicMeshCommandBuildRequests.Empty();
TaskContext.TempVisibleMeshDrawCommands.Empty();
TaskContext.PrimitiveIdBufferData = nullptr;
TaskContext.PrimitiveIdBufferDataSize = 0;
}
FParallelMeshDrawCommandPass::~FParallelMeshDrawCommandPass()
{
check(TaskEventRef == nullptr);
}
class FDrawVisibleMeshCommandsAnyThreadTask : public FRenderTask
{
FRHICommandList& RHICmdList;
const FMeshCommandOneFrameArray& VisibleMeshDrawCommands;
const FGraphicsMinimalPipelineStateSet& GraphicsMinimalPipelineStateSet;
FVertexBufferRHIParamRef PrimitiveIdsBuffer;
int32 BasePrimitiveIdsOffset;
bool bDynamicInstancing;
uint32 InstanceFactor;
int32 TaskIndex;
int32 TaskNum;
public:
FDrawVisibleMeshCommandsAnyThreadTask(
FRHICommandList& InRHICmdList,
const FMeshCommandOneFrameArray& InVisibleMeshDrawCommands,
const FGraphicsMinimalPipelineStateSet& InGraphicsMinimalPipelineStateSet,
FVertexBufferRHIParamRef InPrimitiveIdsBuffer,
int32 InBasePrimitiveIdsOffset,
bool bInDynamicInstancing,
uint32 InInstanceFactor,
int32 InTaskIndex,
int32 InTaskNum
)
: RHICmdList(InRHICmdList)
, VisibleMeshDrawCommands(InVisibleMeshDrawCommands)
, GraphicsMinimalPipelineStateSet(InGraphicsMinimalPipelineStateSet)
, PrimitiveIdsBuffer(InPrimitiveIdsBuffer)
, BasePrimitiveIdsOffset(InBasePrimitiveIdsOffset)
, bDynamicInstancing(bInDynamicInstancing)
, InstanceFactor(InInstanceFactor)
, TaskIndex(InTaskIndex)
, TaskNum(InTaskNum)
{}
FORCEINLINE TStatId GetStatId() const
{
RETURN_QUICK_DECLARE_CYCLE_STAT(FDrawVisibleMeshCommandsAnyThreadTask, STATGROUP_TaskGraphTasks);
}
static ESubsequentsMode::Type GetSubsequentsMode() { return ESubsequentsMode::TrackSubsequents; }
void DoTask(ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent)
{
checkSlow(RHICmdList.IsInsideRenderPass());
// Recompute draw range.
const int32 DrawNum = VisibleMeshDrawCommands.Num();
const int32 NumDrawsPerTask = FMath::DivideAndRoundUp(DrawNum, TaskNum);
const int32 StartIndex = TaskIndex * NumDrawsPerTask;
const int32 NumDraws = FMath::Min(NumDrawsPerTask, DrawNum - StartIndex);
SubmitMeshDrawCommandsRange(VisibleMeshDrawCommands, GraphicsMinimalPipelineStateSet, PrimitiveIdsBuffer, BasePrimitiveIdsOffset, bDynamicInstancing, StartIndex, NumDraws, InstanceFactor, RHICmdList);
RHICmdList.EndRenderPass();
RHICmdList.HandleRTThreadTaskCompletion(MyCompletionGraphEvent);
}
};
/*
* Copies provided vertex data (assumed to be on MemStack) to a vertex buffer.
*/
struct FRHICommandUpdatePrimitiveIdBuffer : public FRHICommand<FRHICommandUpdatePrimitiveIdBuffer>
{
FVertexBufferRHIParamRef VertexBuffer;
void* VertexBufferData;
int32 VertexBufferDataSize;
virtual ~FRHICommandUpdatePrimitiveIdBuffer() {}
FORCEINLINE_DEBUGGABLE FRHICommandUpdatePrimitiveIdBuffer(
FVertexBufferRHIParamRef InVertexBuffer,
void* InVertexBufferData,
int32 InVertexBufferDataSize)
: VertexBuffer(InVertexBuffer)
, VertexBufferData(InVertexBufferData)
, VertexBufferDataSize(InVertexBufferDataSize)
{
}
void Execute(FRHICommandListBase& CmdList)
{
// Upload vertex buffer data.
void* RESTRICT Data = (void* RESTRICT)GDynamicRHI->RHILockVertexBuffer(VertexBuffer, 0, VertexBufferDataSize, RLM_WriteOnly);
FMemory::Memcpy(Data, VertexBufferData, VertexBufferDataSize);
GDynamicRHI->RHIUnlockVertexBuffer(VertexBuffer);
FMemory::Free(VertexBufferData);
}
};
void FParallelMeshDrawCommandPass::DispatchDraw(FParallelCommandListSet* ParallelCommandListSet, FRHICommandList& RHICmdList) const
{
if (MaxNumDraws <= 0)
{
return;
}
FVertexBufferRHIParamRef PrimitiveIdsBuffer = PrimitiveIdVertexBufferRHI;
const int32 BasePrimitiveIdsOffset = 0;
if (ParallelCommandListSet)
{
if (TaskContext.bUseGPUScene)
{
// Queue a command on the RHI thread which will upload PrimitiveIdVertexBuffer after finishing FMeshDrawCommandPassSetupTask.
FRHICommandListImmediate &RHICommandList = GetImmediateCommandList_ForRenderCommand();
if (TaskEventRef.IsValid())
{
RHICommandList.AddDispatchPrerequisite(TaskEventRef);
}
new (RHICommandList.AllocCommand<FRHICommandUpdatePrimitiveIdBuffer>())FRHICommandUpdatePrimitiveIdBuffer(
PrimitiveIdVertexBufferRHI,
TaskContext.PrimitiveIdBufferData,
TaskContext.PrimitiveIdBufferDataSize
);
RHICommandList.RHIThreadFence(true);
bPrimitiveIdBufferDataOwnedByRHIThread = true;
}
const ENamedThreads::Type RenderThread = ENamedThreads::GetRenderThread();
FGraphEventArray Prereqs;
if (ParallelCommandListSet->GetPrereqs())
{
Prereqs.Append(*ParallelCommandListSet->GetPrereqs());
}
if (TaskEventRef.IsValid())
{
Prereqs.Add(TaskEventRef);
}
// Distribute work evenly to the available task graph workers based on NumEstimatedDraws.
// Every task will then adjust it's working range based on FVisibleMeshDrawCommandProcessTask results.
const int32 NumThreads = FMath::Min<int32>(FTaskGraphInterface::Get().GetNumWorkerThreads(), ParallelCommandListSet->Width);
const int32 NumTasks = FMath::Min<int32>(NumThreads, FMath::DivideAndRoundUp(MaxNumDraws, ParallelCommandListSet->MinDrawsPerCommandList));
const int32 NumDrawsPerTask = FMath::DivideAndRoundUp(MaxNumDraws, NumTasks);
for (int32 TaskIndex = 0; TaskIndex < NumTasks; TaskIndex++)
{
const int32 StartIndex = TaskIndex * NumDrawsPerTask;
const int32 NumDraws = FMath::Min(NumDrawsPerTask, MaxNumDraws - StartIndex);
checkSlow(NumDraws > 0);
FRHICommandList* CmdList = ParallelCommandListSet->NewParallelCommandList();
FGraphEventRef AnyThreadCompletionEvent = TGraphTask<FDrawVisibleMeshCommandsAnyThreadTask>::CreateTask(&Prereqs, RenderThread)
.ConstructAndDispatchWhenReady(*CmdList, TaskContext.MeshDrawCommands, TaskContext.MinimalPipelineStatePassSet, PrimitiveIdsBuffer, BasePrimitiveIdsOffset, TaskContext.bDynamicInstancing, TaskContext.InstanceFactor, TaskIndex, NumTasks);
ParallelCommandListSet->AddParallelCommandList(CmdList, AnyThreadCompletionEvent, NumDraws);
}
}
else
{
QUICK_SCOPE_CYCLE_COUNTER(STAT_MeshPassDrawImmediate);
WaitForMeshPassSetupTask();
if (TaskContext.bUseGPUScene)
{
// Can immediately upload vertex buffer data, as there is no parallel draw task.
void* RESTRICT Data = RHILockVertexBuffer(PrimitiveIdVertexBufferRHI, 0, TaskContext.PrimitiveIdBufferDataSize, RLM_WriteOnly);
FMemory::Memcpy(Data, TaskContext.PrimitiveIdBufferData, TaskContext.PrimitiveIdBufferDataSize);
RHIUnlockVertexBuffer(PrimitiveIdVertexBufferRHI);
}
SubmitMeshDrawCommandsRange(TaskContext.MeshDrawCommands, TaskContext.MinimalPipelineStatePassSet, PrimitiveIdsBuffer, BasePrimitiveIdsOffset, TaskContext.bDynamicInstancing, 0, TaskContext.MeshDrawCommands.Num(), TaskContext.InstanceFactor, RHICmdList);
}
}
void FParallelMeshDrawCommandPass::DumpInstancingStats() const
{
if (!PassNameForStats.IsEmpty() && TaskContext.VisibleMeshDrawCommandsNum > 0)
{
UE_LOG(LogRenderer, Log, TEXT("Instancing stats for %s"), *PassNameForStats);
UE_LOG(LogRenderer, Log, TEXT(" %i Mesh Draw Commands in %i instancing state buckets"), TaskContext.VisibleMeshDrawCommandsNum, TaskContext.NewPassVisibleMeshDrawCommandsNum);
UE_LOG(LogRenderer, Log, TEXT(" Largest %i"), TaskContext.MaxInstances);
UE_LOG(LogRenderer, Log, TEXT(" %.1f Dynamic Instancing draw call reduction factor"), TaskContext.VisibleMeshDrawCommandsNum / (float)TaskContext.NewPassVisibleMeshDrawCommandsNum);
}
}
void FParallelMeshDrawCommandPass::SetDumpInstancingStats(const FString& InPassNameForStats)
{
PassNameForStats = InPassNameForStats;
}