You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
- useful to gather shader bindings to evaluate materials in callable shaders. - similar to how it supports compute and hit group shaders. #rb yuriy.odonnell #preflight 6287503ac057ee6e23f2f8dc [CL 20292265 by tiago costa in ue5-main branch]
2128 lines
76 KiB
C++
2128 lines
76 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
/*=============================================================================
|
|
PrimitiveSceneInfo.cpp: Primitive scene info implementation.
|
|
=============================================================================*/
|
|
|
|
#include "PrimitiveSceneInfo.h"
|
|
#include "PrimitiveSceneProxy.h"
|
|
#include "Components/PrimitiveComponent.h"
|
|
#include "SceneManagement.h"
|
|
#include "SceneCore.h"
|
|
#include "VelocityRendering.h"
|
|
#include "ScenePrivate.h"
|
|
#include "RendererModule.h"
|
|
#include "HAL/LowLevelMemTracker.h"
|
|
#include "RayTracing/RayTracingMaterialHitShaders.h"
|
|
#include "VT/RuntimeVirtualTextureSceneProxy.h"
|
|
#include "VT/VirtualTextureSystem.h"
|
|
#include "GPUScene.h"
|
|
#include "Async/ParallelFor.h"
|
|
#include "ProfilingDebugging/ExternalProfiler.h"
|
|
#include "Nanite/Nanite.h"
|
|
#include "Rendering/NaniteResources.h"
|
|
#include "Lumen/LumenSceneRendering.h"
|
|
#include "NaniteSceneProxy.h"
|
|
#include "RayTracingDefinitions.h"
|
|
|
|
extern int32 GGPUSceneInstanceClearList;
|
|
extern int32 GGPUSceneInstanceBVH;
|
|
|
|
static int32 GMeshDrawCommandsCacheMultithreaded = 1;
|
|
static FAutoConsoleVariableRef CVarDrawCommandsCacheMultithreaded(
|
|
TEXT("r.MeshDrawCommands.CacheMultithreaded"),
|
|
GMeshDrawCommandsCacheMultithreaded,
|
|
TEXT("Enable multithreading of draw command caching for static meshes. 0=disabled, 1=enabled (default)"),
|
|
ECVF_RenderThreadSafe);
|
|
|
|
static int32 GNaniteDrawCommandCacheMultithreaded = 1;
|
|
static FAutoConsoleVariableRef CVarNaniteDrawCommandCacheMultithreaded(
|
|
TEXT("r.Nanite.MeshDrawCommands.CacheMultithreaded"),
|
|
GNaniteDrawCommandCacheMultithreaded,
|
|
TEXT("Enable multithreading of draw command caching for Nanite materials. 0=disabled, 1=enabled (default)"),
|
|
ECVF_RenderThreadSafe);
|
|
|
|
static int32 GRayTracingPrimitiveCacheMultithreaded = 1;
|
|
static FAutoConsoleVariableRef CVarRayTracingPrimitiveCacheMultithreaded(
|
|
TEXT("r.RayTracing.MeshDrawCommands.CacheMultithreaded"),
|
|
GRayTracingPrimitiveCacheMultithreaded,
|
|
TEXT("Enable multithreading of raytracing primitive mesh command caching. 0=disabled, 1=enabled (default)"),
|
|
ECVF_RenderThreadSafe);
|
|
|
|
/** An implementation of FStaticPrimitiveDrawInterface that stores the drawn elements for the rendering thread to use. */
|
|
class FBatchingSPDI : public FStaticPrimitiveDrawInterface
|
|
{
|
|
public:
|
|
|
|
// Constructor.
|
|
FBatchingSPDI(FPrimitiveSceneInfo* InPrimitiveSceneInfo):
|
|
PrimitiveSceneInfo(InPrimitiveSceneInfo)
|
|
{}
|
|
|
|
// FStaticPrimitiveDrawInterface.
|
|
virtual void SetHitProxy(HHitProxy* HitProxy) final override
|
|
{
|
|
CurrentHitProxy = HitProxy;
|
|
|
|
if(HitProxy)
|
|
{
|
|
// Only use static scene primitive hit proxies in the editor.
|
|
if(GIsEditor)
|
|
{
|
|
// Keep a reference to the hit proxy from the FPrimitiveSceneInfo, to ensure it isn't deleted while the static mesh still
|
|
// uses its id.
|
|
PrimitiveSceneInfo->HitProxies.Add(HitProxy);
|
|
}
|
|
}
|
|
}
|
|
|
|
virtual void ReserveMemoryForMeshes(int32 MeshNum)
|
|
{
|
|
PrimitiveSceneInfo->StaticMeshRelevances.Reserve(PrimitiveSceneInfo->StaticMeshRelevances.Num() + MeshNum);
|
|
PrimitiveSceneInfo->StaticMeshes.Reserve(PrimitiveSceneInfo->StaticMeshes.Num() + MeshNum);
|
|
}
|
|
|
|
virtual void DrawMesh(const FMeshBatch& Mesh, float ScreenSize) final override
|
|
{
|
|
if (Mesh.HasAnyDrawCalls())
|
|
{
|
|
checkSlow(IsInParallelRenderingThread());
|
|
|
|
FPrimitiveSceneProxy* PrimitiveSceneProxy = PrimitiveSceneInfo->Proxy;
|
|
const ERHIFeatureLevel::Type FeatureLevel = PrimitiveSceneInfo->Scene->GetFeatureLevel();
|
|
|
|
if (!Mesh.Validate(PrimitiveSceneProxy, FeatureLevel))
|
|
{
|
|
return;
|
|
}
|
|
|
|
FStaticMeshBatch* StaticMesh = new(PrimitiveSceneInfo->StaticMeshes) FStaticMeshBatch(
|
|
PrimitiveSceneInfo,
|
|
Mesh,
|
|
CurrentHitProxy ? CurrentHitProxy->Id : FHitProxyId()
|
|
);
|
|
|
|
StaticMesh->PreparePrimitiveUniformBuffer(PrimitiveSceneProxy, FeatureLevel);
|
|
// Volumetric self shadow mesh commands need to be generated every frame, as they depend on single frame uniform buffers with self shadow data.
|
|
const bool bSupportsCachingMeshDrawCommands = SupportsCachingMeshDrawCommands(*StaticMesh, FeatureLevel) && !PrimitiveSceneProxy->CastsVolumetricTranslucentShadow();
|
|
|
|
const FMaterial& Material = Mesh.MaterialRenderProxy->GetIncompleteMaterialWithFallback(FeatureLevel);
|
|
bool bUseSkyMaterial = Material.IsSky();
|
|
bool bUseSingleLayerWaterMaterial = Material.GetShadingModels().HasShadingModel(MSM_SingleLayerWater);
|
|
bool bUseAnisotropy = Material.GetShadingModels().HasAnyShadingModel({MSM_DefaultLit, MSM_ClearCoat}) && Material.MaterialUsesAnisotropy_RenderThread();
|
|
bool bSupportsNaniteRendering = SupportsNaniteRendering(StaticMesh->VertexFactory, PrimitiveSceneProxy, Mesh.MaterialRenderProxy, FeatureLevel);
|
|
bool bSupportsGPUScene = StaticMesh->VertexFactory->SupportsGPUScene(FeatureLevel);
|
|
|
|
FStaticMeshBatchRelevance* StaticMeshRelevance = new(PrimitiveSceneInfo->StaticMeshRelevances) FStaticMeshBatchRelevance(
|
|
*StaticMesh,
|
|
ScreenSize,
|
|
bSupportsCachingMeshDrawCommands,
|
|
bUseSkyMaterial,
|
|
bUseSingleLayerWaterMaterial,
|
|
bUseAnisotropy,
|
|
bSupportsNaniteRendering,
|
|
bSupportsGPUScene,
|
|
FeatureLevel
|
|
);
|
|
}
|
|
}
|
|
|
|
private:
|
|
FPrimitiveSceneInfo* PrimitiveSceneInfo;
|
|
TRefCountPtr<HHitProxy> CurrentHitProxy;
|
|
};
|
|
|
|
FPrimitiveSceneInfo::FPrimitiveSceneInfoEvent FPrimitiveSceneInfo::OnGPUSceneInstancesAllocated;
|
|
FPrimitiveSceneInfo::FPrimitiveSceneInfoEvent FPrimitiveSceneInfo::OnGPUSceneInstancesFreed;
|
|
|
|
FPrimitiveFlagsCompact::FPrimitiveFlagsCompact(const FPrimitiveSceneProxy* Proxy)
|
|
: bCastDynamicShadow(Proxy->CastsDynamicShadow())
|
|
, bStaticLighting(Proxy->HasStaticLighting())
|
|
, bCastStaticShadow(Proxy->CastsStaticShadow())
|
|
, bIsNaniteMesh(Proxy->IsNaniteMesh())
|
|
, bSupportsGPUScene(Proxy->SupportsGPUScene())
|
|
{}
|
|
|
|
FPrimitiveSceneInfoCompact::FPrimitiveSceneInfoCompact(FPrimitiveSceneInfo* InPrimitiveSceneInfo) :
|
|
PrimitiveFlagsCompact(InPrimitiveSceneInfo->Proxy)
|
|
{
|
|
PrimitiveSceneInfo = InPrimitiveSceneInfo;
|
|
Proxy = PrimitiveSceneInfo->Proxy;
|
|
Bounds = FCompactBoxSphereBounds(PrimitiveSceneInfo->Proxy->GetBounds());
|
|
MinDrawDistance = PrimitiveSceneInfo->Proxy->GetMinDrawDistance();
|
|
MaxDrawDistance = PrimitiveSceneInfo->Proxy->GetMaxDrawDistance();
|
|
|
|
VisibilityId = PrimitiveSceneInfo->Proxy->GetVisibilityId();
|
|
}
|
|
|
|
FPrimitiveSceneInfo::FPrimitiveSceneInfo(UPrimitiveComponent* InComponent,FScene* InScene):
|
|
Proxy(InComponent->SceneProxy),
|
|
PrimitiveComponentId(InComponent->ComponentId),
|
|
RegistrationSerialNumber(InComponent->RegistrationSerialNumber),
|
|
OwnerLastRenderTime(FActorLastRenderTime::GetPtr(InComponent->GetOwner())),
|
|
IndirectLightingCacheAllocation(NULL),
|
|
CachedPlanarReflectionProxy(NULL),
|
|
CachedReflectionCaptureProxy(NULL),
|
|
bNeedsCachedReflectionCaptureUpdate(true),
|
|
DefaultDynamicHitProxy(NULL),
|
|
LightList(NULL),
|
|
LastRenderTime(-FLT_MAX),
|
|
Scene(InScene),
|
|
NumMobileMovableLocalLights(0),
|
|
bShouldRenderInMainPass(InComponent->SceneProxy->ShouldRenderInMainPass()),
|
|
bVisibleInRealTimeSkyCapture(InComponent->SceneProxy->IsVisibleInRealTimeSkyCaptures()),
|
|
#if RHI_RAYTRACING
|
|
bDrawInGame(Proxy->IsDrawnInGame()),
|
|
bRayTracingFarField(Proxy->IsRayTracingFarField()),
|
|
bIsVisibleInSceneCaptures(!InComponent->SceneProxy->IsHiddenInSceneCapture()),
|
|
bIsVisibleInSceneCapturesOnly(InComponent->SceneProxy->IsVisibleInSceneCaptureOnly()),
|
|
bIsRayTracingRelevant(InComponent->SceneProxy->IsRayTracingRelevant()),
|
|
bIsRayTracingStaticRelevant(InComponent->SceneProxy->IsRayTracingStaticRelevant()),
|
|
bIsVisibleInRayTracing(InComponent->SceneProxy->IsVisibleInRayTracing()),
|
|
CoarseMeshStreamingHandle(INDEX_NONE),
|
|
#endif
|
|
PackedIndex(INDEX_NONE),
|
|
PersistentIndex(FPersistentPrimitiveIndex{ INDEX_NONE }),
|
|
ComponentForDebuggingOnly(InComponent),
|
|
bNeedsStaticMeshUpdateWithoutVisibilityCheck(false),
|
|
bNeedsUniformBufferUpdate(false),
|
|
bIndirectLightingCacheBufferDirty(false),
|
|
bRegisteredVirtualTextureProducerCallback(false),
|
|
bRegisteredWithVelocityData(false),
|
|
LevelUpdateNotificationIndex(INDEX_NONE),
|
|
InstanceSceneDataOffset(INDEX_NONE),
|
|
NumInstanceSceneDataEntries(0),
|
|
InstancePayloadDataOffset(INDEX_NONE),
|
|
InstancePayloadDataStride(0),
|
|
LightmapDataOffset(INDEX_NONE),
|
|
NumLightmapDataEntries(0)
|
|
{
|
|
check(ComponentForDebuggingOnly);
|
|
check(PrimitiveComponentId.IsValid());
|
|
check(Proxy);
|
|
|
|
const UPrimitiveComponent* SearchParentComponent = InComponent->GetLightingAttachmentRoot();
|
|
|
|
if (SearchParentComponent && SearchParentComponent != InComponent)
|
|
{
|
|
LightingAttachmentRoot = SearchParentComponent->ComponentId;
|
|
}
|
|
|
|
// Only create hit proxies in the Editor as that's where they are used.
|
|
if (GIsEditor)
|
|
{
|
|
// Create a dynamic hit proxy for the primitive.
|
|
DefaultDynamicHitProxy = Proxy->CreateHitProxies(InComponent,HitProxies);
|
|
if( DefaultDynamicHitProxy )
|
|
{
|
|
DefaultDynamicHitProxyId = DefaultDynamicHitProxy->Id;
|
|
}
|
|
}
|
|
|
|
// set LOD parent info if exists
|
|
UPrimitiveComponent* LODParent = InComponent->GetLODParentPrimitive();
|
|
if (LODParent)
|
|
{
|
|
LODParentComponentId = LODParent->ComponentId;
|
|
}
|
|
|
|
FMemory::Memzero(CachedReflectionCaptureProxies);
|
|
|
|
#if RHI_RAYTRACING
|
|
RayTracingGeometries = InComponent->SceneProxy->MoveRayTracingGeometries();
|
|
#endif
|
|
}
|
|
|
|
FPrimitiveSceneInfo::~FPrimitiveSceneInfo()
|
|
{
|
|
check(!OctreeId.IsValidId());
|
|
for (int32 PassIndex = 0; PassIndex < EMeshPass::Num; PassIndex++)
|
|
{
|
|
check(StaticMeshCommandInfos.Num() == 0);
|
|
}
|
|
}
|
|
|
|
#if RHI_RAYTRACING
|
|
FRHIRayTracingGeometry* FPrimitiveSceneInfo::GetStaticRayTracingGeometryInstance(int LodLevel) const
|
|
{
|
|
if (RayTracingGeometries.Num() > LodLevel)
|
|
{
|
|
// TODO: Select different LOD, when build is still pending for this LOD?
|
|
if (RayTracingGeometries[LodLevel]->HasPendingBuildRequest())
|
|
{
|
|
RayTracingGeometries[LodLevel]->BoostBuildPriority();
|
|
return nullptr;
|
|
}
|
|
else if (RayTracingGeometries[LodLevel]->IsValid())
|
|
{
|
|
return RayTracingGeometries[LodLevel]->RayTracingGeometryRHI;
|
|
}
|
|
else
|
|
{
|
|
return nullptr;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return nullptr;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void FPrimitiveSceneInfo::CacheMeshDrawCommands(FRHICommandListImmediate& RHICmdList, FScene* Scene, const TArrayView<FPrimitiveSceneInfo*>& SceneInfos)
|
|
{
|
|
//@todo - only need material uniform buffers to be created since we are going to cache pointers to them
|
|
// Any updates (after initial creation) don't need to be forced here
|
|
FMaterialRenderProxy::UpdateDeferredCachedUniformExpressions();
|
|
|
|
SCOPED_NAMED_EVENT(FPrimitiveSceneInfo_CacheMeshDrawCommands, FColor::Emerald);
|
|
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(FPrimitiveSceneInfo_CacheMeshDrawCommands);
|
|
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_CacheMeshDrawCommands);
|
|
FMemMark Mark(FMemStack::Get());
|
|
|
|
static constexpr int BATCH_SIZE = 64;
|
|
const int NumBatches = (SceneInfos.Num() + BATCH_SIZE - 1) / BATCH_SIZE;
|
|
|
|
auto DoWorkLambda = [Scene, SceneInfos](FCachedPassMeshDrawListContext& DrawListContext, int32 Index)
|
|
{
|
|
SCOPED_NAMED_EVENT(FPrimitiveSceneInfo_CacheMeshDrawCommand, FColor::Green);
|
|
|
|
FMemMark Mark(FMemStack::Get());
|
|
|
|
struct FMeshInfoAndIndex
|
|
{
|
|
int32 InfoIndex;
|
|
int32 MeshIndex;
|
|
};
|
|
|
|
TArray<FMeshInfoAndIndex, TMemStackAllocator<>> MeshBatches;
|
|
MeshBatches.Reserve(3 * BATCH_SIZE);
|
|
|
|
int LocalNum = FMath::Min((Index * BATCH_SIZE) + BATCH_SIZE, SceneInfos.Num());
|
|
for (int LocalIndex = (Index * BATCH_SIZE); LocalIndex < LocalNum; LocalIndex++)
|
|
{
|
|
FPrimitiveSceneInfo* SceneInfo = SceneInfos[LocalIndex];
|
|
check(SceneInfo->StaticMeshCommandInfos.Num() == 0);
|
|
SceneInfo->StaticMeshCommandInfos.AddDefaulted(EMeshPass::Num * SceneInfo->StaticMeshes.Num());
|
|
FPrimitiveSceneProxy* SceneProxy = SceneInfo->Proxy;
|
|
|
|
// Volumetric self shadow mesh commands need to be generated every frame, as they depend on single frame uniform buffers with self shadow data.
|
|
if (!SceneProxy->CastsVolumetricTranslucentShadow())
|
|
{
|
|
for (int32 MeshIndex = 0; MeshIndex < SceneInfo->StaticMeshes.Num(); MeshIndex++)
|
|
{
|
|
FStaticMeshBatch& Mesh = SceneInfo->StaticMeshes[MeshIndex];
|
|
if (SupportsCachingMeshDrawCommands(Mesh))
|
|
{
|
|
MeshBatches.Add(FMeshInfoAndIndex{ LocalIndex, MeshIndex });
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int32 PassIndex = 0; PassIndex < EMeshPass::Num; PassIndex++)
|
|
{
|
|
const EShadingPath ShadingPath = Scene->GetShadingPath();
|
|
EMeshPass::Type PassType = (EMeshPass::Type)PassIndex;
|
|
|
|
if ((FPassProcessorManager::GetPassFlags(ShadingPath, PassType) & EMeshPassFlags::CachedMeshCommands) != EMeshPassFlags::None)
|
|
{
|
|
FCachedPassMeshDrawListContext::FMeshPassScope MeshPassScope(DrawListContext, PassType);
|
|
|
|
PassProcessorCreateFunction CreateFunction = FPassProcessorManager::GetCreateFunction(ShadingPath, PassType);
|
|
FMeshPassProcessor* PassMeshProcessor = CreateFunction(Scene, nullptr, &DrawListContext);
|
|
|
|
if (PassMeshProcessor != nullptr)
|
|
{
|
|
for (const FMeshInfoAndIndex& MeshAndInfo : MeshBatches)
|
|
{
|
|
FPrimitiveSceneInfo* SceneInfo = SceneInfos[MeshAndInfo.InfoIndex];
|
|
FStaticMeshBatch& Mesh = SceneInfo->StaticMeshes[MeshAndInfo.MeshIndex];
|
|
|
|
FStaticMeshBatchRelevance& MeshRelevance = SceneInfo->StaticMeshRelevances[MeshAndInfo.MeshIndex];
|
|
|
|
check(!MeshRelevance.CommandInfosMask.Get(PassType));
|
|
|
|
uint64 BatchElementMask = ~0ull;
|
|
// NOTE: AddMeshBatch calls FCachedPassMeshDrawListContext::FinalizeCommand
|
|
PassMeshProcessor->AddMeshBatch(Mesh, BatchElementMask, SceneInfo->Proxy);
|
|
|
|
FCachedMeshDrawCommandInfo CommandInfo = DrawListContext.GetCommandInfoAndReset();
|
|
if (CommandInfo.CommandIndex != -1 || CommandInfo.StateBucketId != -1)
|
|
{
|
|
static_assert(sizeof(MeshRelevance.CommandInfosMask) * 8 >= EMeshPass::Num, "CommandInfosMask is too small to contain all mesh passes.");
|
|
MeshRelevance.CommandInfosMask.Set(PassType);
|
|
MeshRelevance.CommandInfosBase++;
|
|
|
|
int CommandInfoIndex = MeshAndInfo.MeshIndex * EMeshPass::Num + PassType;
|
|
check(SceneInfo->StaticMeshCommandInfos[CommandInfoIndex].MeshPass == EMeshPass::Num);
|
|
SceneInfo->StaticMeshCommandInfos[CommandInfoIndex] = CommandInfo;
|
|
}
|
|
}
|
|
|
|
PassMeshProcessor->~FMeshPassProcessor();
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int LocalIndex = (Index * BATCH_SIZE); LocalIndex < LocalNum; LocalIndex++)
|
|
{
|
|
FPrimitiveSceneInfo* SceneInfo = SceneInfos[LocalIndex];
|
|
int PrefixSum = 0;
|
|
for (int32 MeshIndex = 0; MeshIndex < SceneInfo->StaticMeshes.Num(); MeshIndex++)
|
|
{
|
|
FStaticMeshBatchRelevance& MeshRelevance = SceneInfo->StaticMeshRelevances[MeshIndex];
|
|
if (MeshRelevance.CommandInfosBase > 0)
|
|
{
|
|
EMeshPass::Type PassType = EMeshPass::DepthPass;
|
|
int NewPrefixSum = PrefixSum;
|
|
for (;;)
|
|
{
|
|
PassType = MeshRelevance.CommandInfosMask.SkipEmpty(PassType);
|
|
if (PassType == EMeshPass::Num)
|
|
{
|
|
break;
|
|
}
|
|
|
|
int CommandInfoIndex = MeshIndex * EMeshPass::Num + PassType;
|
|
checkSlow(CommandInfoIndex >= NewPrefixSum);
|
|
SceneInfo->StaticMeshCommandInfos[NewPrefixSum] = SceneInfo->StaticMeshCommandInfos[CommandInfoIndex];
|
|
NewPrefixSum++;
|
|
PassType = EMeshPass::Type(PassType + 1);
|
|
}
|
|
|
|
#if DO_GUARD_SLOW
|
|
int NumBits = MeshRelevance.CommandInfosMask.GetNum();
|
|
check(PrefixSum + NumBits == NewPrefixSum);
|
|
int LastPass = -1;
|
|
for (int32 TestIndex = PrefixSum; TestIndex < NewPrefixSum; TestIndex++)
|
|
{
|
|
int MeshPass = SceneInfo->StaticMeshCommandInfos[TestIndex].MeshPass;
|
|
check(MeshPass > LastPass);
|
|
LastPass = MeshPass;
|
|
}
|
|
#endif
|
|
MeshRelevance.CommandInfosBase = PrefixSum;
|
|
PrefixSum = NewPrefixSum;
|
|
}
|
|
}
|
|
SceneInfo->StaticMeshCommandInfos.SetNum(PrefixSum, true);
|
|
}
|
|
};
|
|
|
|
bool bAnyLooseParameterBuffers = false;
|
|
if (GMeshDrawCommandsCacheMultithreaded && FApp::ShouldUseThreadingForPerformance())
|
|
{
|
|
TArray<FCachedPassMeshDrawListContextDeferred, TMemStackAllocator<>> DrawListContexts;
|
|
DrawListContexts.Reserve(NumBatches);
|
|
for(int32 ContextIndex = 0; ContextIndex < NumBatches; ++ContextIndex)
|
|
{
|
|
DrawListContexts.Emplace(*Scene);
|
|
}
|
|
|
|
ParallelForTemplate(
|
|
NumBatches,
|
|
[&DrawListContexts, &DoWorkLambda](int32 Index)
|
|
{
|
|
FTaskTagScope Scope(ETaskTag::EParallelRenderingThread);
|
|
DoWorkLambda(DrawListContexts[Index], Index);
|
|
},
|
|
EParallelForFlags::PumpRenderingThread | EParallelForFlags::Unbalanced
|
|
);
|
|
|
|
if (NumBatches > 0)
|
|
{
|
|
SCOPED_NAMED_EVENT(DeferredFinalizeMeshDrawCommands, FColor::Emerald);
|
|
|
|
for (int32 Index = 0; Index < NumBatches; ++Index)
|
|
{
|
|
FCachedPassMeshDrawListContextDeferred& DrawListContext = DrawListContexts[Index];
|
|
const int32 Start = Index * BATCH_SIZE;
|
|
const int32 End = FMath::Min((Index * BATCH_SIZE) + BATCH_SIZE, SceneInfos.Num());
|
|
DrawListContext.DeferredFinalizeMeshDrawCommands(SceneInfos, Start, End);
|
|
bAnyLooseParameterBuffers |= DrawListContext.HasAnyLooseParameterBuffers();
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FCachedPassMeshDrawListContextImmediate DrawListContext(*Scene);
|
|
for (int Idx = 0; Idx < NumBatches; Idx++)
|
|
{
|
|
DoWorkLambda(DrawListContext, Idx);
|
|
}
|
|
bAnyLooseParameterBuffers = DrawListContext.HasAnyLooseParameterBuffers();
|
|
}
|
|
|
|
#if DO_GUARD_SLOW
|
|
{
|
|
static int32 LogCount = 0;
|
|
if (bAnyLooseParameterBuffers && (LogCount++ % 1000) == 0)
|
|
{
|
|
UE_LOG(LogRenderer, Warning, TEXT("One or more Cached Mesh Draw commands use loose parameters. This causes overhead and will break dynamic instancing, potentially reducing performance further. Use Uniform Buffers instead."));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (!FParallelMeshDrawCommandPass::IsOnDemandShaderCreationEnabled())
|
|
{
|
|
FGraphicsMinimalPipelineStateId::InitializePersistentIds();
|
|
}
|
|
}
|
|
|
|
void FPrimitiveSceneInfo::RemoveCachedMeshDrawCommands()
|
|
{
|
|
checkSlow(IsInRenderingThread());
|
|
|
|
for (int32 CommandIndex = 0; CommandIndex < StaticMeshCommandInfos.Num(); ++CommandIndex)
|
|
{
|
|
const FCachedMeshDrawCommandInfo& CachedCommand = StaticMeshCommandInfos[CommandIndex];
|
|
|
|
if (CachedCommand.StateBucketId != INDEX_NONE)
|
|
{
|
|
EMeshPass::Type PassIndex = CachedCommand.MeshPass;
|
|
FGraphicsMinimalPipelineStateId CachedPipelineId;
|
|
|
|
{
|
|
auto& ElementKVP = Scene->CachedMeshDrawCommandStateBuckets[PassIndex].GetByElementId(CachedCommand.StateBucketId);
|
|
CachedPipelineId = ElementKVP.Key.CachedPipelineId;
|
|
|
|
FMeshDrawCommandCount& StateBucketCount = ElementKVP.Value;
|
|
check(StateBucketCount.Num > 0);
|
|
StateBucketCount.Num--;
|
|
if (StateBucketCount.Num == 0)
|
|
{
|
|
Scene->CachedMeshDrawCommandStateBuckets[PassIndex].RemoveByElementId(CachedCommand.StateBucketId);
|
|
}
|
|
}
|
|
|
|
FGraphicsMinimalPipelineStateId::RemovePersistentId(CachedPipelineId);
|
|
}
|
|
else if (CachedCommand.CommandIndex >= 0)
|
|
{
|
|
FCachedPassMeshDrawList& PassDrawList = Scene->CachedDrawLists[CachedCommand.MeshPass];
|
|
FGraphicsMinimalPipelineStateId CachedPipelineId = PassDrawList.MeshDrawCommands[CachedCommand.CommandIndex].CachedPipelineId;
|
|
|
|
PassDrawList.MeshDrawCommands.RemoveAt(CachedCommand.CommandIndex);
|
|
FGraphicsMinimalPipelineStateId::RemovePersistentId(CachedPipelineId);
|
|
|
|
// Track the lowest index that might be free for faster AddAtLowestFreeIndex
|
|
PassDrawList.LowestFreeIndexSearchStart = FMath::Min(PassDrawList.LowestFreeIndexSearchStart, CachedCommand.CommandIndex);
|
|
}
|
|
|
|
}
|
|
|
|
for (int32 MeshIndex = 0; MeshIndex < StaticMeshRelevances.Num(); ++MeshIndex)
|
|
{
|
|
FStaticMeshBatchRelevance& MeshRelevance = StaticMeshRelevances[MeshIndex];
|
|
|
|
MeshRelevance.CommandInfosMask.Reset();
|
|
}
|
|
|
|
StaticMeshCommandInfos.Empty();
|
|
}
|
|
|
|
static void BuildNaniteDrawCommands(FRHICommandListImmediate& RHICmdList, FScene* Scene, FPrimitiveSceneInfo* PrimitiveSceneInfo, FNaniteDrawListContext& DrawListContext);
|
|
|
|
void FPrimitiveSceneInfo::CacheNaniteDrawCommands(FRHICommandListImmediate& RHICmdList, FScene* Scene, const TArrayView<FPrimitiveSceneInfo*>& SceneInfos)
|
|
{
|
|
SCOPED_NAMED_EVENT(FPrimitiveSceneInfo_CacheNaniteDrawCommands, FColor::Emerald);
|
|
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(FPrimitiveSceneInfo_CacheNaniteDrawCommands);
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_CacheNaniteDrawCommands);
|
|
|
|
FMemMark Mark(FMemStack::Get());
|
|
FMaterialRenderProxy::UpdateDeferredCachedUniformExpressions();
|
|
|
|
const bool bNaniteEnabled = DoesPlatformSupportNanite(GMaxRHIShaderPlatform);
|
|
if (bNaniteEnabled)
|
|
{
|
|
TArray<FNaniteDrawListContext, TInlineAllocator<1>> DrawListContexts;
|
|
|
|
if (GNaniteDrawCommandCacheMultithreaded && FApp::ShouldUseThreadingForPerformance())
|
|
{
|
|
ParallelForWithTaskContext(
|
|
DrawListContexts,
|
|
SceneInfos.Num(),
|
|
[&RHICmdList, Scene, &SceneInfos](FNaniteDrawListContext& Context, int32 Index)
|
|
{
|
|
FMemMark Mark(FMemStack::Get());
|
|
FTaskTagScope Scope(ETaskTag::EParallelRenderingThread);
|
|
BuildNaniteDrawCommands(RHICmdList, Scene, SceneInfos[Index], Context);
|
|
}
|
|
);
|
|
}
|
|
else
|
|
{
|
|
FNaniteDrawListContext& DrawListContext = DrawListContexts.AddDefaulted_GetRef();
|
|
for (FPrimitiveSceneInfo* PrimitiveSceneInfo : SceneInfos)
|
|
{
|
|
BuildNaniteDrawCommands(RHICmdList, Scene, PrimitiveSceneInfo, DrawListContext);
|
|
}
|
|
}
|
|
|
|
if (DrawListContexts.Num() > 0)
|
|
{
|
|
SCOPED_NAMED_EVENT(NaniteDrawListApply, FColor::Emerald);
|
|
for (FNaniteDrawListContext& Context : DrawListContexts)
|
|
{
|
|
Context.Apply(*Scene);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void BuildNaniteDrawCommands(FRHICommandListImmediate& RHICmdList, FScene* Scene, FPrimitiveSceneInfo* PrimitiveSceneInfo, FNaniteDrawListContext& DrawListContext)
|
|
{
|
|
FPrimitiveSceneProxy* Proxy = PrimitiveSceneInfo->Proxy;
|
|
if (Proxy->IsNaniteMesh())
|
|
{
|
|
Nanite::FSceneProxyBase* NaniteProxy = static_cast<Nanite::FSceneProxyBase*>(Proxy);
|
|
{
|
|
FNaniteDrawListContext::FPrimitiveSceneInfoScope PrimInfoScope(DrawListContext, *PrimitiveSceneInfo);
|
|
|
|
auto PassBody = [PrimitiveSceneInfo, NaniteProxy, &DrawListContext](ENaniteMeshPass::Type MeshPass, FMeshPassProcessor* const NaniteMeshProcessor)
|
|
{
|
|
FNaniteDrawListContext::FMeshPassScope MeshPassScope(DrawListContext, MeshPass);
|
|
|
|
int32 StaticMeshesCount = PrimitiveSceneInfo->StaticMeshes.Num();
|
|
for (int32 MeshIndex = 0; MeshIndex < StaticMeshesCount; ++MeshIndex)
|
|
{
|
|
FStaticMeshBatchRelevance& MeshRelevance = PrimitiveSceneInfo->StaticMeshRelevances[MeshIndex];
|
|
FStaticMeshBatch& Mesh = PrimitiveSceneInfo->StaticMeshes[MeshIndex];
|
|
|
|
if (MeshRelevance.bSupportsNaniteRendering && Mesh.bUseForMaterial)
|
|
{
|
|
uint64 BatchElementMask = ~0ull;
|
|
NaniteMeshProcessor->AddMeshBatch(Mesh, BatchElementMask, NaniteProxy);
|
|
}
|
|
}
|
|
|
|
TArray<Nanite::FSceneProxyBase::FMaterialSection>& NaniteMaterialSections = NaniteProxy->GetMaterialSections();
|
|
for (int32 MaterialSectionIndex = 0; MaterialSectionIndex < NaniteMaterialSections.Num(); ++MaterialSectionIndex)
|
|
{
|
|
Nanite::FSceneProxyBase::FMaterialSection& MaterialSection = NaniteMaterialSections[MaterialSectionIndex];
|
|
check(MaterialSection.RasterMaterialProxy != nullptr);
|
|
|
|
FNaniteRasterPipeline RasterPipeline{};
|
|
RasterPipeline.RasterMaterial = MaterialSection.RasterMaterialProxy;
|
|
RasterPipeline.bIsTwoSided = !!MaterialSection.MaterialRelevance.bTwoSided;
|
|
|
|
DrawListContext.DeferredPipelines[MeshPass].Add(
|
|
FNaniteDrawListContext::FDeferredPipeline{
|
|
PrimitiveSceneInfo,
|
|
RasterPipeline,
|
|
uint8(MaterialSectionIndex)
|
|
}
|
|
);
|
|
}
|
|
};
|
|
|
|
// ENaniteMeshPass::BasePass
|
|
{
|
|
FMeshPassProcessor* NaniteMeshProcessor = CreateNaniteMeshProcessor(Scene, nullptr, &DrawListContext);
|
|
PassBody(ENaniteMeshPass::BasePass, NaniteMeshProcessor);
|
|
NaniteMeshProcessor->~FMeshPassProcessor();
|
|
}
|
|
|
|
// ENaniteMeshPass::LumenCardCapture
|
|
if (Lumen::HasPrimitiveNaniteMeshBatches(Proxy) && DoesPlatformSupportLumenGI(GetFeatureLevelShaderPlatform(Scene->GetFeatureLevel())))
|
|
{
|
|
FMeshPassProcessor* NaniteMeshProcessor = CreateLumenCardNaniteMeshProcessor(Scene, nullptr, &DrawListContext);
|
|
PassBody(ENaniteMeshPass::LumenCardCapture, NaniteMeshProcessor);
|
|
NaniteMeshProcessor->~FMeshPassProcessor();
|
|
}
|
|
|
|
static_assert(ENaniteMeshPass::Num == 2, "Change BuildNaniteDrawCommands() to account for more Nanite mesh passes");
|
|
}
|
|
}
|
|
}
|
|
|
|
void FPrimitiveSceneInfo::RemoveCachedNaniteDrawCommands()
|
|
{
|
|
checkSlow(IsInRenderingThread());
|
|
|
|
if (!Proxy->IsNaniteMesh())
|
|
{
|
|
return;
|
|
}
|
|
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_RemoveCachedNaniteDrawCommands);
|
|
|
|
for (int32 NaniteMeshPassIndex = 0; NaniteMeshPassIndex < ENaniteMeshPass::Num; ++NaniteMeshPassIndex)
|
|
{
|
|
FNaniteMaterialCommands& ShadingCommands = Scene->NaniteMaterials[NaniteMeshPassIndex];
|
|
FNaniteRasterPipelines& RasterPipelines = Scene->NaniteRasterPipelines[NaniteMeshPassIndex];
|
|
|
|
TArray<FNaniteCommandInfo>& NanitePassCommandInfo = NaniteCommandInfos[NaniteMeshPassIndex];
|
|
for (int32 CommandIndex = 0; CommandIndex < NanitePassCommandInfo.Num(); ++CommandIndex)
|
|
{
|
|
const FNaniteCommandInfo& CommandInfo = NanitePassCommandInfo[CommandIndex];
|
|
ShadingCommands.Unregister(CommandInfo);
|
|
}
|
|
|
|
TArray<FNaniteRasterBin>& NanitePassRasterBins = NaniteRasterBins[NaniteMeshPassIndex];
|
|
for (int32 RasterBinIndex = 0; RasterBinIndex < NanitePassRasterBins.Num(); ++RasterBinIndex)
|
|
{
|
|
const FNaniteRasterBin& RasterBin = NanitePassRasterBins[RasterBinIndex];
|
|
RasterPipelines.Unregister(RasterBin);
|
|
}
|
|
|
|
NanitePassRasterBins.Reset();
|
|
NanitePassCommandInfo.Reset();
|
|
NaniteMaterialSlots[NaniteMeshPassIndex].Reset();
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
NaniteHitProxyIds.Reset();
|
|
#endif
|
|
}
|
|
|
|
#if RHI_RAYTRACING
|
|
void FScene::RefreshRayTracingMeshCommandCache()
|
|
{
|
|
// Get rid of all existing cached commands
|
|
CachedRayTracingMeshCommands.Empty(CachedRayTracingMeshCommands.Num());
|
|
|
|
// Re-cache all current primitives
|
|
FPrimitiveSceneInfo::CacheRayTracingPrimitives(this, Primitives);
|
|
}
|
|
|
|
void FScene::RefreshRayTracingInstances()
|
|
{
|
|
// Re-cache all current primitives
|
|
FPrimitiveSceneInfo::UpdateCachedRayTracingInstances(this, Primitives);
|
|
}
|
|
|
|
void FScene::UpdateRayTracedLights()
|
|
{
|
|
// Whether a light can use ray traced shadows depends on CVars that may be changed at runtime.
|
|
// It is not enough to check the light shadow mode when light is added to the scene. This must be done during rendering.
|
|
|
|
bHasRayTracedLights = false;
|
|
|
|
if (!IsRayTracingEnabled())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// We currently don't need a full list of RT lights, only whether there are any RT lights at all.
|
|
for (auto LightIt = Lights.CreateConstIterator(); LightIt; ++LightIt)
|
|
{
|
|
const FLightSceneInfoCompact& LightSceneInfoCompact = *LightIt;
|
|
if (ShouldRenderRayTracingShadowsForLight(LightSceneInfoCompact))
|
|
{
|
|
bHasRayTracedLights = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void FPrimitiveSceneInfo::UpdateCachedRayTracingInstances(FScene* Scene, const TArrayView<FPrimitiveSceneInfo*>& SceneInfos)
|
|
{
|
|
if (IsRayTracingEnabled())
|
|
{
|
|
checkf(GRHISupportsMultithreadedShaderCreation, TEXT("Raytracing code needs the ability to create shaders from task threads."));
|
|
|
|
for (FPrimitiveSceneInfo* SceneInfo : SceneInfos)
|
|
{
|
|
// Write group id
|
|
const int32 RayTracingGroupId = SceneInfo->Proxy->GetRayTracingGroupId();
|
|
if (RayTracingGroupId != -1)
|
|
{
|
|
Scene->PrimitiveRayTracingGroupIds[SceneInfo->GetIndex()] = Scene->PrimitiveRayTracingGroups.FindId(RayTracingGroupId);
|
|
}
|
|
|
|
FRayTracingInstance CachedRayTracingInstance;
|
|
ERayTracingPrimitiveFlags& Flags = Scene->PrimitiveRayTracingFlags[SceneInfo->GetIndex()];
|
|
|
|
// Cache the coarse mesh streaming handle
|
|
SceneInfo->CoarseMeshStreamingHandle = SceneInfo->Proxy->GetCoarseMeshStreamingHandle();
|
|
|
|
// Write flags
|
|
Flags = SceneInfo->Proxy->GetCachedRayTracingInstance(CachedRayTracingInstance);
|
|
UpdateCachedRayTracingInstance(SceneInfo, CachedRayTracingInstance, Flags);
|
|
}
|
|
}
|
|
}
|
|
|
|
struct DeferredMeshLODCommandIndex
|
|
{
|
|
FPrimitiveSceneInfo* SceneInfo;
|
|
int8 MeshLODIndex;
|
|
int32 CommandIndex;
|
|
};
|
|
|
|
template<class T>
|
|
class FCacheRayTracingPrimitivesContext
|
|
{
|
|
public:
|
|
FCacheRayTracingPrimitivesContext(FScene* Scene)
|
|
: CommandContext(Commands)
|
|
, PassDrawRenderState(Scene->UniformBuffers.ViewUniformBuffer)
|
|
, RayTracingMeshProcessor(&CommandContext, Scene, nullptr, PassDrawRenderState, Scene->CachedRayTracingMeshCommandsMode)
|
|
{ }
|
|
|
|
FTempRayTracingMeshCommandStorage Commands;
|
|
FCachedRayTracingMeshCommandContext<T> CommandContext;
|
|
FMeshPassProcessorRenderState PassDrawRenderState;
|
|
FRayTracingMeshProcessor RayTracingMeshProcessor;
|
|
TArray<DeferredMeshLODCommandIndex> DeferredMeshLODCommandIndices;
|
|
};
|
|
|
|
template<bool bDeferLODCommandIndices, class T>
|
|
void CacheRayTracingPrimitive(
|
|
FScene* Scene,
|
|
FPrimitiveSceneInfo* SceneInfo,
|
|
T& Commands,
|
|
FCachedRayTracingMeshCommandContext<T>& CommandContext,
|
|
FRayTracingMeshProcessor& RayTracingMeshProcessor,
|
|
TArray<DeferredMeshLODCommandIndex>* DeferredMeshLODCommandIndices,
|
|
FRayTracingInstance& CachedRayTracingInstance,
|
|
ERayTracingPrimitiveFlags& Flags)
|
|
{
|
|
if (SceneInfo->GetRayTracingGeometryNum() > 0 && SceneInfo->StaticMeshes.Num() > 0)
|
|
{
|
|
int32 MaxLOD = -1;
|
|
for (const FStaticMeshBatch& Mesh : SceneInfo->StaticMeshes)
|
|
{
|
|
MaxLOD = MaxLOD < Mesh.LODIndex ? Mesh.LODIndex : MaxLOD;
|
|
}
|
|
|
|
SceneInfo->CachedRayTracingMeshCommandIndicesPerLOD.Empty(MaxLOD + 1);
|
|
SceneInfo->CachedRayTracingMeshCommandIndicesPerLOD.AddDefaulted(MaxLOD + 1);
|
|
|
|
SceneInfo->CachedRayTracingMeshCommandsHashPerLOD.Empty(MaxLOD + 1);
|
|
SceneInfo->CachedRayTracingMeshCommandsHashPerLOD.AddZeroed(MaxLOD + 1);
|
|
|
|
for (const FStaticMeshBatch& Mesh : SceneInfo->StaticMeshes)
|
|
{
|
|
// Why do we pass a full mask here when the dynamic case only uses a mask of 1?
|
|
// Also note that the code below assumes only a single command was generated per batch (see SupportsCachingMeshDrawCommands(...))
|
|
const uint64 BatchElementMask = ~0ull;
|
|
RayTracingMeshProcessor.AddMeshBatch(Mesh, BatchElementMask, SceneInfo->Proxy);
|
|
|
|
if (CommandContext.CommandIndex >= 0)
|
|
{
|
|
uint64& Hash = SceneInfo->CachedRayTracingMeshCommandsHashPerLOD[Mesh.LODIndex];
|
|
Hash <<= 1;
|
|
Hash ^= Commands[CommandContext.CommandIndex].ShaderBindings.GetDynamicInstancingHash();
|
|
|
|
if (bDeferLODCommandIndices)
|
|
{
|
|
DeferredMeshLODCommandIndices->Add({ SceneInfo,Mesh.LODIndex, CommandContext.CommandIndex });
|
|
}
|
|
else
|
|
{
|
|
SceneInfo->CachedRayTracingMeshCommandIndicesPerLOD[Mesh.LODIndex].Add(CommandContext.CommandIndex);
|
|
}
|
|
|
|
CommandContext.CommandIndex = -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// This path is mutually exclusive with the old path (used by normal static meshes) and is only used by Nanite proxies now.
|
|
// TODO: move normal static meshes to this path, but needs testing to not break FN
|
|
|
|
// Write group id
|
|
const int32 RayTracingGroupId = SceneInfo->Proxy->GetRayTracingGroupId();
|
|
if (RayTracingGroupId != -1)
|
|
{
|
|
Scene->PrimitiveRayTracingGroupIds[SceneInfo->GetIndex()] = Scene->PrimitiveRayTracingGroups.FindId(RayTracingGroupId);
|
|
}
|
|
|
|
// Write flags
|
|
Flags = SceneInfo->Proxy->GetCachedRayTracingInstance(CachedRayTracingInstance);
|
|
|
|
// Cache the coarse mesh streaming handle
|
|
SceneInfo->CoarseMeshStreamingHandle = SceneInfo->Proxy->GetCoarseMeshStreamingHandle();
|
|
|
|
if (EnumHasAnyFlags(Flags, ERayTracingPrimitiveFlags::CacheMeshCommands))
|
|
{
|
|
// TODO: LOD w/ screen size support. Probably needs another array parallel to OutRayTracingInstances
|
|
// We assume it is exactly 1 LOD now (true for Nanite proxies)
|
|
SceneInfo->CachedRayTracingMeshCommandIndicesPerLOD.Empty(1);
|
|
SceneInfo->CachedRayTracingMeshCommandIndicesPerLOD.AddDefaulted(1);
|
|
|
|
SceneInfo->CachedRayTracingMeshCommandsHashPerLOD.Empty(1);
|
|
SceneInfo->CachedRayTracingMeshCommandsHashPerLOD.AddZeroed(1);
|
|
|
|
for (const FMeshBatch& Mesh : CachedRayTracingInstance.Materials)
|
|
{
|
|
// Why do we pass a full mask here when the dynamic case only uses a mask of 1?
|
|
// Also note that the code below assumes only a single command was generated per batch (see SupportsCachingMeshDrawCommands(...))
|
|
const uint64 BatchElementMask = ~0ull;
|
|
RayTracingMeshProcessor.AddMeshBatch(Mesh, BatchElementMask, SceneInfo->Proxy);
|
|
|
|
// The material section must emit a command. Otherwise, it should have been excluded earlier
|
|
check(CommandContext.CommandIndex >= 0);
|
|
|
|
uint64& Hash = SceneInfo->CachedRayTracingMeshCommandsHashPerLOD[Mesh.LODIndex];
|
|
Hash <<= 1;
|
|
Hash ^= Commands[CommandContext.CommandIndex].ShaderBindings.GetDynamicInstancingHash();
|
|
|
|
if (bDeferLODCommandIndices)
|
|
{
|
|
DeferredMeshLODCommandIndices->Add({ SceneInfo,Mesh.LODIndex, CommandContext.CommandIndex });
|
|
}
|
|
else
|
|
{
|
|
SceneInfo->CachedRayTracingMeshCommandIndicesPerLOD[Mesh.LODIndex].Add(CommandContext.CommandIndex);
|
|
}
|
|
|
|
CommandContext.CommandIndex = -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
void FPrimitiveSceneInfo::CacheRayTracingPrimitives(FScene* Scene, const TArrayView<FPrimitiveSceneInfo*>& SceneInfos)
|
|
{
|
|
if (IsRayTracingEnabled())
|
|
{
|
|
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(FPrimitiveSceneInfo_CacheRayTracingPrimitives)
|
|
SCOPED_NAMED_EVENT(FPrimitiveSceneInfo_CacheRayTracingPrimitives, FColor::Emerald);
|
|
|
|
checkf(GRHISupportsMultithreadedShaderCreation, TEXT("Raytracing code needs the ability to create shaders from task threads."));
|
|
|
|
FCachedRayTracingMeshCommandStorage& CachedRayTracingMeshCommands = Scene->CachedRayTracingMeshCommands;
|
|
|
|
if (GRayTracingPrimitiveCacheMultithreaded && FApp::ShouldUseThreadingForPerformance())
|
|
{
|
|
TArray<FCacheRayTracingPrimitivesContext<FTempRayTracingMeshCommandStorage>> Contexts;
|
|
ParallelForWithTaskContext(
|
|
Contexts,
|
|
SceneInfos.Num(),
|
|
[Scene](int32 ContextIndex, int32 NumContexts) { return Scene; },
|
|
[Scene, &SceneInfos](FCacheRayTracingPrimitivesContext<FTempRayTracingMeshCommandStorage>& Context, int32 Index)
|
|
{
|
|
FMemMark Mark(FMemStack::Get());
|
|
FOptionalTaskTagScope Scope(ETaskTag::EParallelRenderingThread);
|
|
|
|
FPrimitiveSceneInfo* SceneInfo = SceneInfos[Index];
|
|
FRayTracingInstance CachedInstance;
|
|
ERayTracingPrimitiveFlags& Flags = Scene->PrimitiveRayTracingFlags[SceneInfo->GetIndex()];
|
|
CacheRayTracingPrimitive<true>(Scene, SceneInfo, Context.Commands, Context.CommandContext, Context.RayTracingMeshProcessor, &Context.DeferredMeshLODCommandIndices, CachedInstance, Flags);
|
|
UpdateCachedRayTracingInstance(SceneInfo, CachedInstance, Flags);
|
|
}
|
|
);
|
|
|
|
if (Contexts.Num() > 0)
|
|
{
|
|
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(FPrimitiveSceneInfo_CacheRayTracingPrimitives_Merge)
|
|
SCOPED_NAMED_EVENT(FPrimitiveSceneInfo_CacheRayTracingPrimitives_Merge, FColor::Emerald);
|
|
|
|
// copy commands generated by multiple threads to the sparse array in FScene
|
|
// and set each mesh LOD command index
|
|
for (const auto& Context : Contexts)
|
|
{
|
|
for (const DeferredMeshLODCommandIndex& Entry : Context.DeferredMeshLODCommandIndices)
|
|
{
|
|
int32 CommandIndex = CachedRayTracingMeshCommands.Add(Context.Commands[Entry.CommandIndex]);
|
|
Entry.SceneInfo->CachedRayTracingMeshCommandIndicesPerLOD[Entry.MeshLODIndex].Add(CommandIndex);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FCachedRayTracingMeshCommandContext CommandContext(CachedRayTracingMeshCommands);
|
|
FMeshPassProcessorRenderState PassDrawRenderState(Scene->UniformBuffers.ViewUniformBuffer);
|
|
FRayTracingMeshProcessor RayTracingMeshProcessor(&CommandContext, Scene, nullptr, PassDrawRenderState, Scene->CachedRayTracingMeshCommandsMode);
|
|
|
|
for (FPrimitiveSceneInfo* SceneInfo : SceneInfos)
|
|
{
|
|
FRayTracingInstance CachedRayTracingInstance;
|
|
ERayTracingPrimitiveFlags& Flags = Scene->PrimitiveRayTracingFlags[SceneInfo->GetIndex()];
|
|
CacheRayTracingPrimitive<false>(Scene, SceneInfo, CachedRayTracingMeshCommands, CommandContext, RayTracingMeshProcessor, nullptr, CachedRayTracingInstance, Flags);
|
|
UpdateCachedRayTracingInstance(SceneInfo, CachedRayTracingInstance, Flags);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FPrimitiveSceneInfo::UpdateCachedRayTracingInstance(FPrimitiveSceneInfo* SceneInfo, const FRayTracingInstance& CachedRayTracingInstance, const ERayTracingPrimitiveFlags Flags)
|
|
{
|
|
if (EnumHasAnyFlags(Flags, ERayTracingPrimitiveFlags::CacheInstances))
|
|
{
|
|
checkf(CachedRayTracingInstance.InstanceTransforms.IsEmpty() && CachedRayTracingInstance.InstanceTransformsView.IsEmpty(),
|
|
TEXT("Primitives with ERayTracingPrimitiveFlags::CacheInstances get instances transforms from GPUScene"));
|
|
|
|
// TODO: allocate from FRayTracingScene & do better low-level caching
|
|
SceneInfo->CachedRayTracingInstance.NumTransforms = CachedRayTracingInstance.NumTransforms;
|
|
SceneInfo->CachedRayTracingInstance.BaseInstanceSceneDataOffset = SceneInfo->GetInstanceSceneDataOffset();
|
|
|
|
SceneInfo->CachedRayTracingInstanceWorldBounds.Empty();
|
|
SceneInfo->CachedRayTracingInstanceWorldBounds.AddUninitialized(CachedRayTracingInstance.NumTransforms);
|
|
|
|
SceneInfo->UpdateCachedRayTracingInstanceWorldBounds(SceneInfo->Proxy->GetLocalToWorld());
|
|
|
|
SceneInfo->CachedRayTracingInstance.GeometryRHI = CachedRayTracingInstance.Geometry->RayTracingGeometryRHI;
|
|
|
|
// At this point (in AddToScene()) PrimitiveIndex has been set
|
|
check(SceneInfo->GetIndex() != INDEX_NONE);
|
|
SceneInfo->CachedRayTracingInstance.DefaultUserData = (uint32)SceneInfo->GetIndex();
|
|
SceneInfo->CachedRayTracingInstance.Mask = CachedRayTracingInstance.Mask; // When no cached command is found, InstanceMask == 0 and the instance is effectively filtered out
|
|
|
|
SceneInfo->CachedRayTracingInstance.bApplyLocalBoundsTransform = CachedRayTracingInstance.bApplyLocalBoundsTransform;
|
|
|
|
if (CachedRayTracingInstance.bForceOpaque)
|
|
{
|
|
SceneInfo->CachedRayTracingInstance.Flags |= ERayTracingInstanceFlags::ForceOpaque;
|
|
}
|
|
|
|
if (CachedRayTracingInstance.bDoubleSided)
|
|
{
|
|
SceneInfo->CachedRayTracingInstance.Flags |= ERayTracingInstanceFlags::TriangleCullDisable;
|
|
}
|
|
}
|
|
}
|
|
|
|
void FPrimitiveSceneInfo::RemoveCachedRayTracingPrimitives()
|
|
{
|
|
if (IsRayTracingEnabled())
|
|
{
|
|
for (auto& CachedRayTracingMeshCommandIndices : CachedRayTracingMeshCommandIndicesPerLOD)
|
|
{
|
|
for (auto CommandIndex : CachedRayTracingMeshCommandIndices)
|
|
{
|
|
if (CommandIndex >= 0)
|
|
{
|
|
Scene->CachedRayTracingMeshCommands.RemoveAt(CommandIndex);
|
|
}
|
|
}
|
|
}
|
|
|
|
CachedRayTracingMeshCommandIndicesPerLOD.Empty();
|
|
|
|
CachedRayTracingMeshCommandsHashPerLOD.Empty();
|
|
}
|
|
}
|
|
|
|
void FPrimitiveSceneInfo::UpdateCachedRayTracingInstanceWorldBounds(const FMatrix& NewPrimitiveLocalToWorld)
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_UpdateCachedRayTracingInstanceWorldBounds);
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(UpdateCachedRayTracingInstanceWorldBounds);
|
|
|
|
TConstArrayView<FPrimitiveInstance> InstanceSceneData = Proxy->GetInstanceSceneData();
|
|
|
|
SmallestRayTracingInstanceWorldBoundsIndex = 0;
|
|
|
|
for (int32 Index = 0; Index < CachedRayTracingInstanceWorldBounds.Num(); Index++)
|
|
{
|
|
const FRenderBounds& LocalBoundingBox = Proxy->GetInstanceLocalBounds(Index);
|
|
|
|
FMatrix LocalTransform = InstanceSceneData[Index].LocalToPrimitive.ToMatrix();
|
|
|
|
CachedRayTracingInstanceWorldBounds[Index] = LocalBoundingBox.TransformBy(LocalTransform * NewPrimitiveLocalToWorld).ToBoxSphereBounds();
|
|
SmallestRayTracingInstanceWorldBoundsIndex = CachedRayTracingInstanceWorldBounds[Index].SphereRadius < CachedRayTracingInstanceWorldBounds[SmallestRayTracingInstanceWorldBoundsIndex].SphereRadius ? Index : SmallestRayTracingInstanceWorldBoundsIndex;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void FPrimitiveSceneInfo::AddStaticMeshes(FRHICommandListImmediate& RHICmdList, FScene* Scene, const TArrayView<FPrimitiveSceneInfo*>& SceneInfos, bool bAddToStaticDrawLists)
|
|
{
|
|
LLM_SCOPE(ELLMTag::StaticMesh);
|
|
|
|
{
|
|
ParallelForTemplate(SceneInfos.Num(), [Scene, &SceneInfos](int32 Index)
|
|
{
|
|
FOptionalTaskTagScope Scope(ETaskTag::EParallelRenderingThread);
|
|
SCOPED_NAMED_EVENT(FPrimitiveSceneInfo_AddStaticMeshes_DrawStaticElements, FColor::Magenta);
|
|
FPrimitiveSceneInfo* SceneInfo = SceneInfos[Index];
|
|
// Cache the primitive's static mesh elements.
|
|
FBatchingSPDI BatchingSPDI(SceneInfo);
|
|
BatchingSPDI.SetHitProxy(SceneInfo->DefaultDynamicHitProxy);
|
|
SceneInfo->Proxy->DrawStaticElements(&BatchingSPDI);
|
|
SceneInfo->StaticMeshes.Shrink();
|
|
SceneInfo->StaticMeshRelevances.Shrink();
|
|
|
|
check(SceneInfo->StaticMeshRelevances.Num() == SceneInfo->StaticMeshes.Num());
|
|
});
|
|
}
|
|
|
|
{
|
|
const ERHIFeatureLevel::Type FeatureLevel = Scene->GetFeatureLevel();
|
|
|
|
SCOPED_NAMED_EVENT(FPrimitiveSceneInfo_AddStaticMeshes_UpdateSceneArrays, FColor::Blue);
|
|
for (FPrimitiveSceneInfo* SceneInfo : SceneInfos)
|
|
{
|
|
// Allocate OIT index buffer where needed
|
|
const bool bAllocateSortedTriangles = OIT::IsEnabled(EOITSortingType::SortedTriangles, GMaxRHIShaderPlatform) && SceneInfo->Proxy->SupportsSortedTriangles();
|
|
|
|
for (int32 MeshIndex = 0; MeshIndex < SceneInfo->StaticMeshes.Num(); MeshIndex++)
|
|
{
|
|
FStaticMeshBatchRelevance& MeshRelevance = SceneInfo->StaticMeshRelevances[MeshIndex];
|
|
FStaticMeshBatch& Mesh = SceneInfo->StaticMeshes[MeshIndex];
|
|
|
|
// Add the static mesh to the scene's static mesh list.
|
|
FSparseArrayAllocationInfo SceneArrayAllocation = Scene->StaticMeshes.AddUninitialized();
|
|
Scene->StaticMeshes[SceneArrayAllocation.Index] = &Mesh;
|
|
Mesh.Id = SceneArrayAllocation.Index;
|
|
MeshRelevance.Id = SceneArrayAllocation.Index;
|
|
|
|
if (bAllocateSortedTriangles && OIT::IsCompatible(Mesh, FeatureLevel))
|
|
{
|
|
FSortedTriangleData Allocation = Scene->OITSceneData.Allocate(Mesh.Elements[0].IndexBuffer, EPrimitiveType(Mesh.Type), Mesh.Elements[0].FirstIndex, Mesh.Elements[0].NumPrimitives);
|
|
OIT::ConvertSortedIndexToDynamicIndex(&Allocation, &Mesh.Elements[0].DynamicIndexBuffer);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bAddToStaticDrawLists)
|
|
{
|
|
CacheMeshDrawCommands(RHICmdList, Scene, SceneInfos);
|
|
CacheNaniteDrawCommands(RHICmdList, Scene, SceneInfos);
|
|
#if RHI_RAYTRACING
|
|
CacheRayTracingPrimitives(Scene, SceneInfos);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
static void OnVirtualTextureDestroyed(const FVirtualTextureProducerHandle& InHandle, void* Baton)
|
|
{
|
|
FPrimitiveSceneInfo* PrimitiveSceneInfo = static_cast<FPrimitiveSceneInfo*>(Baton);
|
|
|
|
// Update the main uniform buffer
|
|
PrimitiveSceneInfo->UpdateStaticLightingBuffer();
|
|
|
|
// Also need to update lightmap data inside GPUScene, if that's enabled
|
|
PrimitiveSceneInfo->Scene->GPUScene.AddPrimitiveToUpdate(PrimitiveSceneInfo->GetIndex(), EPrimitiveDirtyState::ChangedStaticLighting);
|
|
}
|
|
|
|
static void GetRuntimeVirtualTextureLODRange(TArray<class FStaticMeshBatchRelevance> const& MeshRelevances, int8& OutMinLOD, int8& OutMaxLOD)
|
|
{
|
|
OutMinLOD = MAX_int8;
|
|
OutMaxLOD = 0;
|
|
|
|
for (int32 MeshIndex = 0; MeshIndex < MeshRelevances.Num(); ++MeshIndex)
|
|
{
|
|
const FStaticMeshBatchRelevance& MeshRelevance = MeshRelevances[MeshIndex];
|
|
if (MeshRelevance.bRenderToVirtualTexture)
|
|
{
|
|
OutMinLOD = FMath::Min(OutMinLOD, MeshRelevance.LODIndex);
|
|
OutMaxLOD = FMath::Max(OutMaxLOD, MeshRelevance.LODIndex);
|
|
}
|
|
}
|
|
|
|
check(OutMinLOD <= OutMaxLOD);
|
|
}
|
|
|
|
int32 FPrimitiveSceneInfo::UpdateStaticLightingBuffer()
|
|
{
|
|
checkSlow(IsInRenderingThread());
|
|
|
|
if (bRegisteredVirtualTextureProducerCallback)
|
|
{
|
|
// Remove any previous VT callbacks
|
|
FVirtualTextureSystem::Get().RemoveAllProducerDestroyedCallbacks(this);
|
|
bRegisteredVirtualTextureProducerCallback = false;
|
|
}
|
|
|
|
FPrimitiveSceneProxy::FLCIArray LCIs;
|
|
Proxy->GetLCIs(LCIs);
|
|
for (int32 i = 0; i < LCIs.Num(); ++i)
|
|
{
|
|
FLightCacheInterface* LCI = LCIs[i];
|
|
|
|
if (LCI)
|
|
{
|
|
LCI->CreatePrecomputedLightingUniformBuffer_RenderingThread(Scene->GetFeatureLevel());
|
|
|
|
// If lightmap is using virtual texture, need to set a callback to update our uniform buffers if VT is destroyed,
|
|
// since we cache VT parameters inside these uniform buffers
|
|
FVirtualTextureProducerHandle VTProducerHandle;
|
|
if (LCI->GetVirtualTextureLightmapProducer(Scene->GetFeatureLevel(), VTProducerHandle))
|
|
{
|
|
FVirtualTextureSystem::Get().AddProducerDestroyedCallback(VTProducerHandle, &OnVirtualTextureDestroyed, this);
|
|
bRegisteredVirtualTextureProducerCallback = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return LCIs.Num();
|
|
}
|
|
|
|
void FPrimitiveSceneInfo::AllocateGPUSceneInstances(FScene* Scene, const TArrayView<FPrimitiveSceneInfo*>& SceneInfos)
|
|
{
|
|
if (Scene->GPUScene.IsEnabled())
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_UpdateGPUSceneTime);
|
|
for (FPrimitiveSceneInfo* SceneInfo : SceneInfos)
|
|
{
|
|
check
|
|
(
|
|
SceneInfo->InstanceSceneDataOffset == INDEX_NONE &&
|
|
SceneInfo->NumInstanceSceneDataEntries == 0 &&
|
|
SceneInfo->InstancePayloadDataOffset == INDEX_NONE &&
|
|
SceneInfo->InstancePayloadDataStride == 0
|
|
);
|
|
|
|
if (SceneInfo->Proxy->SupportsInstanceDataBuffer())
|
|
{
|
|
const TConstArrayView<FPrimitiveInstance> InstanceSceneData = SceneInfo->Proxy->GetInstanceSceneData();
|
|
|
|
SceneInfo->NumInstanceSceneDataEntries = InstanceSceneData.Num();
|
|
if (SceneInfo->NumInstanceSceneDataEntries > 0)
|
|
{
|
|
SceneInfo->InstanceSceneDataOffset = Scene->GPUScene.AllocateInstanceSceneDataSlots(SceneInfo->NumInstanceSceneDataEntries);
|
|
|
|
SceneInfo->InstancePayloadDataStride = SceneInfo->Proxy->GetPayloadDataStride(); // Returns number of float4 optional data values
|
|
if (SceneInfo->InstancePayloadDataStride > 0)
|
|
{
|
|
const uint32 TotalFloat4Count = SceneInfo->NumInstanceSceneDataEntries * SceneInfo->InstancePayloadDataStride;
|
|
SceneInfo->InstancePayloadDataOffset = Scene->GPUScene.AllocateInstancePayloadDataSlots(TotalFloat4Count);
|
|
}
|
|
|
|
if (GGPUSceneInstanceBVH)
|
|
{
|
|
// TODO: Replace Instance BVH FBounds with FRenderBounds
|
|
for (int32 InstanceIndex = 0; InstanceIndex < SceneInfo->NumInstanceSceneDataEntries; ++InstanceIndex)
|
|
{
|
|
const FPrimitiveInstance& PrimitiveInstance = InstanceSceneData[InstanceIndex];
|
|
FRenderBounds WorldBounds = SceneInfo->Proxy->GetInstanceLocalBounds(InstanceIndex);
|
|
WorldBounds.TransformBy(PrimitiveInstance.ComputeLocalToWorld(SceneInfo->Proxy->GetLocalToWorld()));
|
|
Scene->InstanceBVH.Add(FBounds3f({ WorldBounds.GetMin(), WorldBounds.GetMax() }), SceneInfo->InstanceSceneDataOffset + InstanceIndex);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Allocate a single 'dummy/fallback' instance for the primitive that gets automatically populated with the data from the primitive
|
|
SceneInfo->InstanceSceneDataOffset = Scene->GPUScene.AllocateInstanceSceneDataSlots(1);
|
|
SceneInfo->NumInstanceSceneDataEntries = 1;
|
|
|
|
// TODO: Hook up for dummy instances?
|
|
SceneInfo->InstancePayloadDataOffset = INDEX_NONE;
|
|
SceneInfo->InstancePayloadDataStride = 0;
|
|
}
|
|
|
|
// Force a primitive update in the GPU scene,
|
|
// NOTE: does not set Added as this is handled elsewhere.
|
|
Scene->GPUScene.AddPrimitiveToUpdate(SceneInfo->PackedIndex, EPrimitiveDirtyState::ChangedAll);
|
|
|
|
// Force a primitive update in the Lumen scene
|
|
if (Scene->LumenSceneData)
|
|
{
|
|
Scene->LumenSceneData->UpdatePrimitiveInstanceOffset(SceneInfo->PackedIndex);
|
|
}
|
|
}
|
|
|
|
OnGPUSceneInstancesAllocated.Broadcast();
|
|
}
|
|
}
|
|
|
|
void FPrimitiveSceneInfo::ReallocateGPUSceneInstances(FScene* Scene, const TArrayView<FPrimitiveSceneInfo*>& SceneInfos)
|
|
{
|
|
SCOPED_NAMED_EVENT(ReallocateGPUSceneInstances, FColor::Emerald);
|
|
|
|
// Free each scene info.
|
|
for (FPrimitiveSceneInfo* SceneInfo : SceneInfos)
|
|
{
|
|
SceneInfo->FreeGPUSceneInstances();
|
|
}
|
|
|
|
// Allocate them all.
|
|
AllocateGPUSceneInstances(Scene, SceneInfos);
|
|
}
|
|
|
|
void FPrimitiveSceneInfo::FreeGPUSceneInstances()
|
|
{
|
|
if (!Scene->GPUScene.IsEnabled())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Release all instance data slots associated with this primitive.
|
|
if (InstanceSceneDataOffset != INDEX_NONE)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_UpdateGPUSceneTime);
|
|
|
|
check(Proxy->SupportsInstanceDataBuffer() || NumInstanceSceneDataEntries == 1);
|
|
if (GGPUSceneInstanceBVH)
|
|
{
|
|
for (int32 InstanceIndex = 0; InstanceIndex < NumInstanceSceneDataEntries; InstanceIndex++)
|
|
{
|
|
Scene->InstanceBVH.Remove(InstanceSceneDataOffset + InstanceIndex);
|
|
}
|
|
}
|
|
|
|
// Release all instance payload data slots associated with this primitive.
|
|
if (InstancePayloadDataOffset != INDEX_NONE)
|
|
{
|
|
check(InstancePayloadDataStride > 0);
|
|
|
|
const uint32 TotalFloat4Count = NumInstanceSceneDataEntries * InstancePayloadDataStride;
|
|
Scene->GPUScene.FreeInstancePayloadDataSlots(InstancePayloadDataOffset, TotalFloat4Count);
|
|
InstancePayloadDataOffset = INDEX_NONE;
|
|
InstancePayloadDataStride = 0;
|
|
}
|
|
|
|
Scene->GPUScene.FreeInstanceSceneDataSlots(InstanceSceneDataOffset, NumInstanceSceneDataEntries);
|
|
InstanceSceneDataOffset = INDEX_NONE;
|
|
NumInstanceSceneDataEntries = 0;
|
|
|
|
OnGPUSceneInstancesFreed.Broadcast();
|
|
}
|
|
}
|
|
|
|
void FPrimitiveSceneInfo::AddToScene(FRHICommandListImmediate& RHICmdList, FScene* Scene, const TArrayView<FPrimitiveSceneInfo*>& SceneInfos, bool bUpdateStaticDrawLists, bool bAddToStaticDrawLists, bool bAsyncCreateLPIs)
|
|
{
|
|
check(IsInRenderingThread());
|
|
|
|
{
|
|
SCOPED_NAMED_EVENT(FPrimitiveSceneInfo_AddToScene_IndirectLightingCacheUniformBuffer, FColor::Turquoise);
|
|
for (FPrimitiveSceneInfo* SceneInfo : SceneInfos)
|
|
{
|
|
FPrimitiveSceneProxy* Proxy = SceneInfo->Proxy;
|
|
// Create an indirect lighting cache uniform buffer if we attaching a primitive that may require it, as it may be stored inside a cached mesh command.
|
|
if (IsIndirectLightingCacheAllowed(Scene->GetFeatureLevel())
|
|
&& Proxy->WillEverBeLit()
|
|
&& ((Proxy->HasStaticLighting() && Proxy->NeedsUnbuiltPreviewLighting()) || (Proxy->IsMovable() && Proxy->GetIndirectLightingCacheQuality() != ILCQ_Off) || Proxy->GetLightmapType() == ELightmapType::ForceVolumetric))
|
|
{
|
|
if (!SceneInfo->IndirectLightingCacheUniformBuffer)
|
|
{
|
|
FIndirectLightingCacheUniformParameters Parameters;
|
|
|
|
GetIndirectLightingCacheParameters(
|
|
Scene->GetFeatureLevel(),
|
|
Parameters,
|
|
nullptr,
|
|
nullptr,
|
|
FVector(0.0f, 0.0f, 0.0f),
|
|
0,
|
|
nullptr);
|
|
|
|
SceneInfo->IndirectLightingCacheUniformBuffer = TUniformBufferRef<FIndirectLightingCacheUniformParameters>::CreateUniformBufferImmediate(Parameters, UniformBuffer_MultiFrame, EUniformBufferValidation::None);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
SCOPED_NAMED_EVENT(FPrimitiveSceneInfo_AddToScene_IndirectLightingCacheAllocation, FColor::Orange);
|
|
for (FPrimitiveSceneInfo* SceneInfo : SceneInfos)
|
|
{
|
|
FPrimitiveSceneProxy* Proxy = SceneInfo->Proxy;
|
|
// If we are attaching a primitive that should be statically lit but has unbuilt lighting,
|
|
// Allocate space in the indirect lighting cache so that it can be used for previewing indirect lighting
|
|
if (Proxy->HasStaticLighting()
|
|
&& Proxy->NeedsUnbuiltPreviewLighting()
|
|
&& IsIndirectLightingCacheAllowed(Scene->GetFeatureLevel()))
|
|
{
|
|
FIndirectLightingCacheAllocation* PrimitiveAllocation = Scene->IndirectLightingCache.FindPrimitiveAllocation(SceneInfo->PrimitiveComponentId);
|
|
|
|
if (PrimitiveAllocation)
|
|
{
|
|
SceneInfo->IndirectLightingCacheAllocation = PrimitiveAllocation;
|
|
PrimitiveAllocation->SetDirty();
|
|
}
|
|
else
|
|
{
|
|
PrimitiveAllocation = Scene->IndirectLightingCache.AllocatePrimitive(SceneInfo, true);
|
|
PrimitiveAllocation->SetDirty();
|
|
SceneInfo->IndirectLightingCacheAllocation = PrimitiveAllocation;
|
|
}
|
|
}
|
|
SceneInfo->MarkIndirectLightingCacheBufferDirty();
|
|
}
|
|
}
|
|
|
|
{
|
|
SCOPED_NAMED_EVENT(FPrimitiveSceneInfo_AddToScene_LightmapDataOffset, FColor::Green);
|
|
for (FPrimitiveSceneInfo* SceneInfo : SceneInfos)
|
|
{
|
|
const bool bAllowStaticLighting = FReadOnlyCVARCache::Get().bAllowStaticLighting;
|
|
if (bAllowStaticLighting)
|
|
{
|
|
SceneInfo->NumLightmapDataEntries = SceneInfo->UpdateStaticLightingBuffer();
|
|
if (SceneInfo->NumLightmapDataEntries > 0 && UseGPUScene(GMaxRHIShaderPlatform, Scene->GetFeatureLevel()))
|
|
{
|
|
SceneInfo->LightmapDataOffset = Scene->GPUScene.LightmapDataAllocator.Allocate(SceneInfo->NumLightmapDataEntries);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
{
|
|
SCOPED_NAMED_EVENT(FPrimitiveSceneInfo_AddToScene_ReflectionCaptures, FColor::Yellow);
|
|
for (FPrimitiveSceneInfo* SceneInfo : SceneInfos)
|
|
{
|
|
// Cache the nearest reflection proxy if needed
|
|
if (SceneInfo->NeedsReflectionCaptureUpdate())
|
|
{
|
|
SceneInfo->CacheReflectionCaptures();
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
SCOPED_NAMED_EVENT(FPrimitiveSceneInfo_AddToScene_AddStaticMeshes, FColor::Magenta);
|
|
if (bUpdateStaticDrawLists)
|
|
{
|
|
AddStaticMeshes(RHICmdList, Scene, SceneInfos, bAddToStaticDrawLists);
|
|
}
|
|
}
|
|
|
|
{
|
|
SCOPED_NAMED_EVENT(FPrimitiveSceneInfo_AddToScene_AddToPrimitiveOctree, FColor::Red);
|
|
for (FPrimitiveSceneInfo* SceneInfo : SceneInfos)
|
|
{
|
|
// create potential storage for our compact info
|
|
FPrimitiveSceneInfoCompact CompactPrimitiveSceneInfo(SceneInfo);
|
|
|
|
// Add the primitive to the octree.
|
|
check(!SceneInfo->OctreeId.IsValidId());
|
|
Scene->PrimitiveOctree.AddElement(CompactPrimitiveSceneInfo);
|
|
check(SceneInfo->OctreeId.IsValidId());
|
|
}
|
|
}
|
|
|
|
{
|
|
SCOPED_NAMED_EVENT(FPrimitiveSceneInfo_AddToScene_UpdateBounds, FColor::Cyan);
|
|
for (FPrimitiveSceneInfo* SceneInfo : SceneInfos)
|
|
{
|
|
FPrimitiveSceneProxy* Proxy = SceneInfo->Proxy;
|
|
int32 PackedIndex = SceneInfo->PackedIndex;
|
|
|
|
if (Proxy->CastsDynamicIndirectShadow())
|
|
{
|
|
Scene->DynamicIndirectCasterPrimitives.Add(SceneInfo);
|
|
}
|
|
|
|
Scene->PrimitiveSceneProxies[PackedIndex] = Proxy;
|
|
Scene->PrimitiveTransforms[PackedIndex] = Proxy->GetLocalToWorld();
|
|
|
|
// Set bounds.
|
|
FPrimitiveBounds& PrimitiveBounds = Scene->PrimitiveBounds[PackedIndex];
|
|
FBoxSphereBounds BoxSphereBounds = Proxy->GetBounds();
|
|
PrimitiveBounds.BoxSphereBounds = BoxSphereBounds;
|
|
PrimitiveBounds.MinDrawDistance = Proxy->GetMinDrawDistance();
|
|
PrimitiveBounds.MaxDrawDistance = Proxy->GetMaxDrawDistance();
|
|
PrimitiveBounds.MaxCullDistance = PrimitiveBounds.MaxDrawDistance;
|
|
|
|
Scene->PrimitiveFlagsCompact[PackedIndex] = FPrimitiveFlagsCompact(Proxy);
|
|
|
|
// Store precomputed visibility ID.
|
|
int32 VisibilityBitIndex = Proxy->GetVisibilityId();
|
|
FPrimitiveVisibilityId& VisibilityId = Scene->PrimitiveVisibilityIds[PackedIndex];
|
|
VisibilityId.ByteIndex = VisibilityBitIndex / 8;
|
|
VisibilityId.BitMask = (1 << (VisibilityBitIndex & 0x7));
|
|
|
|
// Store occlusion flags.
|
|
uint8 OcclusionFlags = EOcclusionFlags::None;
|
|
if (Proxy->CanBeOccluded())
|
|
{
|
|
OcclusionFlags |= EOcclusionFlags::CanBeOccluded;
|
|
}
|
|
if (Proxy->HasSubprimitiveOcclusionQueries())
|
|
{
|
|
OcclusionFlags |= EOcclusionFlags::HasSubprimitiveQueries;
|
|
}
|
|
if (Proxy->AllowApproximateOcclusion()
|
|
// Allow approximate occlusion if attached, even if the parent does not have bLightAttachmentsAsGroup enabled
|
|
|| SceneInfo->LightingAttachmentRoot.IsValid())
|
|
{
|
|
OcclusionFlags |= EOcclusionFlags::AllowApproximateOcclusion;
|
|
}
|
|
if (VisibilityBitIndex >= 0)
|
|
{
|
|
OcclusionFlags |= EOcclusionFlags::HasPrecomputedVisibility;
|
|
}
|
|
Scene->PrimitiveOcclusionFlags[PackedIndex] = OcclusionFlags;
|
|
|
|
// Store occlusion bounds.
|
|
FBoxSphereBounds OcclusionBounds = BoxSphereBounds;
|
|
if (Proxy->HasCustomOcclusionBounds())
|
|
{
|
|
OcclusionBounds = Proxy->GetCustomOcclusionBounds();
|
|
}
|
|
OcclusionBounds.BoxExtent.X = OcclusionBounds.BoxExtent.X + OCCLUSION_SLOP;
|
|
OcclusionBounds.BoxExtent.Y = OcclusionBounds.BoxExtent.Y + OCCLUSION_SLOP;
|
|
OcclusionBounds.BoxExtent.Z = OcclusionBounds.BoxExtent.Z + OCCLUSION_SLOP;
|
|
OcclusionBounds.SphereRadius = OcclusionBounds.SphereRadius + OCCLUSION_SLOP;
|
|
Scene->PrimitiveOcclusionBounds[PackedIndex] = OcclusionBounds;
|
|
|
|
// Store the component.
|
|
Scene->PrimitiveComponentIds[PackedIndex] = SceneInfo->PrimitiveComponentId;
|
|
|
|
#if RHI_RAYTRACING
|
|
// Set group id
|
|
const int32 RayTracingGroupId = SceneInfo->Proxy->GetRayTracingGroupId();
|
|
if (RayTracingGroupId != -1)
|
|
{
|
|
Scene->PrimitiveRayTracingGroupIds[PackedIndex] = Scene->PrimitiveRayTracingGroups.FindId(RayTracingGroupId);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
{
|
|
SCOPED_NAMED_EVENT(FPrimitiveSceneInfo_AddToScene_UpdateVirtualTexture, FColor::Emerald);
|
|
for (FPrimitiveSceneInfo* SceneInfo : SceneInfos)
|
|
{
|
|
FPrimitiveSceneProxy* Proxy = SceneInfo->Proxy;
|
|
// Store the runtime virtual texture flags.
|
|
SceneInfo->UpdateRuntimeVirtualTextureFlags();
|
|
Scene->PrimitiveVirtualTextureFlags[SceneInfo->PackedIndex] = SceneInfo->RuntimeVirtualTextureFlags;
|
|
|
|
// Store the runtime virtual texture Lod info.
|
|
if (SceneInfo->RuntimeVirtualTextureFlags.bRenderToVirtualTexture)
|
|
{
|
|
int8 MinLod, MaxLod;
|
|
GetRuntimeVirtualTextureLODRange(SceneInfo->StaticMeshRelevances, MinLod, MaxLod);
|
|
|
|
FPrimitiveVirtualTextureLodInfo& LodInfo = Scene->PrimitiveVirtualTextureLod[SceneInfo->PackedIndex];
|
|
LodInfo.MinLod = FMath::Clamp((int32)MinLod, 0, 15);
|
|
LodInfo.MaxLod = FMath::Clamp((int32)MaxLod, 0, 15);
|
|
LodInfo.LodBias = FMath::Clamp(Proxy->GetVirtualTextureLodBias() + FPrimitiveVirtualTextureLodInfo::LodBiasOffset, 0, 15);
|
|
LodInfo.CullMethod = Proxy->GetVirtualTextureMinCoverage() == 0 ? 0 : 1;
|
|
LodInfo.CullValue = LodInfo.CullMethod == 0 ? Proxy->GetVirtualTextureCullMips() : Proxy->GetVirtualTextureMinCoverage();
|
|
}
|
|
}
|
|
}
|
|
|
|
// Find lights that affect the primitive in the light octree.
|
|
for (FPrimitiveSceneInfo* SceneInfo : SceneInfos)
|
|
{
|
|
Scene->CreateLightPrimitiveInteractionsForPrimitive(SceneInfo, bAsyncCreateLPIs);
|
|
|
|
FPrimitiveSceneProxy* Proxy = SceneInfo->Proxy;
|
|
INC_MEMORY_STAT_BY(STAT_PrimitiveInfoMemory, sizeof(*SceneInfo) + SceneInfo->StaticMeshes.GetAllocatedSize() + SceneInfo->StaticMeshRelevances.GetAllocatedSize() + Proxy->GetMemoryFootprint());
|
|
}
|
|
|
|
{
|
|
SCOPED_NAMED_EVENT(FPrimitiveSceneInfo_AddToScene_LevelNotifyPrimitives, FColor::Blue);
|
|
for (FPrimitiveSceneInfo* SceneInfo : SceneInfos)
|
|
{
|
|
if (SceneInfo->Proxy->ShouldNotifyOnWorldAddRemove())
|
|
{
|
|
TArray<FPrimitiveSceneInfo*>& LevelNotifyPrimitives = Scene->PrimitivesNeedingLevelUpdateNotification.FindOrAdd(SceneInfo->Proxy->GetLevelName());
|
|
SceneInfo->LevelUpdateNotificationIndex = LevelNotifyPrimitives.Num();
|
|
LevelNotifyPrimitives.Add(SceneInfo);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FPrimitiveSceneInfo::RemoveStaticMeshes()
|
|
{
|
|
// Deallocate potential OIT dynamic index buffer
|
|
if (OIT::IsEnabled(EOITSortingType::SortedTriangles, GMaxRHIShaderPlatform))
|
|
{
|
|
for (int32 MeshIndex = 0; MeshIndex < StaticMeshes.Num(); MeshIndex++)
|
|
{
|
|
FStaticMeshBatch& Mesh = StaticMeshes[MeshIndex];
|
|
if (Mesh.Elements.Num() > 0 && Mesh.Elements[0].DynamicIndexBuffer.IsValid())
|
|
{
|
|
Scene->OITSceneData.Deallocate(Mesh.Elements[0].DynamicIndexBuffer.IndexBuffer);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove static meshes from the scene.
|
|
StaticMeshes.Empty();
|
|
StaticMeshRelevances.Empty();
|
|
RemoveCachedMeshDrawCommands();
|
|
RemoveCachedNaniteDrawCommands();
|
|
#if RHI_RAYTRACING
|
|
RemoveCachedRayTracingPrimitives();
|
|
#endif
|
|
}
|
|
|
|
void FPrimitiveSceneInfo::RemoveFromScene(bool bUpdateStaticDrawLists)
|
|
{
|
|
check(IsInRenderingThread());
|
|
|
|
// implicit linked list. The destruction will update this "head" pointer to the next item in the list.
|
|
while (LightList)
|
|
{
|
|
FLightPrimitiveInteraction::Destroy(LightList);
|
|
}
|
|
|
|
// Remove the primitive from the octree.
|
|
check(OctreeId.IsValidId());
|
|
check(Scene->PrimitiveOctree.GetElementById(OctreeId).PrimitiveSceneInfo == this);
|
|
Scene->PrimitiveOctree.RemoveElement(OctreeId);
|
|
OctreeId = FOctreeElementId2();
|
|
|
|
if (LightmapDataOffset != INDEX_NONE && UseGPUScene(GMaxRHIShaderPlatform, Scene->GetFeatureLevel()))
|
|
{
|
|
Scene->GPUScene.LightmapDataAllocator.Free(LightmapDataOffset, NumLightmapDataEntries);
|
|
}
|
|
|
|
if (Proxy->CastsDynamicIndirectShadow())
|
|
{
|
|
Scene->DynamicIndirectCasterPrimitives.RemoveSingleSwap(this);
|
|
}
|
|
|
|
IndirectLightingCacheAllocation = NULL;
|
|
|
|
if (Proxy->IsOftenMoving())
|
|
{
|
|
MarkIndirectLightingCacheBufferDirty();
|
|
}
|
|
|
|
DEC_MEMORY_STAT_BY(STAT_PrimitiveInfoMemory, sizeof(*this) + StaticMeshes.GetAllocatedSize() + StaticMeshRelevances.GetAllocatedSize() + Proxy->GetMemoryFootprint());
|
|
|
|
if (bUpdateStaticDrawLists)
|
|
{
|
|
if (IsIndexValid()) // PackedIndex
|
|
{
|
|
Scene->PrimitivesNeedingStaticMeshUpdate[PackedIndex] = false;
|
|
}
|
|
|
|
if (bNeedsStaticMeshUpdateWithoutVisibilityCheck)
|
|
{
|
|
Scene->PrimitivesNeedingStaticMeshUpdateWithoutVisibilityCheck.Remove(this);
|
|
|
|
bNeedsStaticMeshUpdateWithoutVisibilityCheck = false;
|
|
}
|
|
|
|
// IndirectLightingCacheUniformBuffer may be cached inside cached mesh draw commands, so we
|
|
// can't delete it unless we also update cached mesh command.
|
|
IndirectLightingCacheUniformBuffer.SafeRelease();
|
|
|
|
RemoveStaticMeshes();
|
|
}
|
|
|
|
if (bRegisteredVirtualTextureProducerCallback)
|
|
{
|
|
FVirtualTextureSystem::Get().RemoveAllProducerDestroyedCallbacks(this);
|
|
bRegisteredVirtualTextureProducerCallback = false;
|
|
}
|
|
|
|
if (Proxy->ShouldNotifyOnWorldAddRemove())
|
|
{
|
|
TArray<FPrimitiveSceneInfo*>* LevelNotifyPrimitives = Scene->PrimitivesNeedingLevelUpdateNotification.Find(Proxy->GetLevelName());
|
|
if (LevelNotifyPrimitives != nullptr)
|
|
{
|
|
checkSlow(LevelUpdateNotificationIndex != INDEX_NONE);
|
|
LevelNotifyPrimitives->RemoveAtSwap(LevelUpdateNotificationIndex, 1, false);
|
|
if (LevelNotifyPrimitives->Num() == 0)
|
|
{
|
|
Scene->PrimitivesNeedingLevelUpdateNotification.Remove(Proxy->GetLevelName());
|
|
}
|
|
else if (LevelUpdateNotificationIndex < LevelNotifyPrimitives->Num())
|
|
{
|
|
// Update swapped element's LevelUpdateNotificationIndex
|
|
((*LevelNotifyPrimitives)[LevelUpdateNotificationIndex])->LevelUpdateNotificationIndex = LevelUpdateNotificationIndex;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FPrimitiveSceneInfo::UpdateRuntimeVirtualTextureFlags()
|
|
{
|
|
RuntimeVirtualTextureFlags.bRenderToVirtualTexture = false;
|
|
RuntimeVirtualTextureFlags.RuntimeVirtualTextureMask = 0;
|
|
|
|
if (Proxy->WritesVirtualTexture())
|
|
{
|
|
if (Proxy->IsNaniteMesh())
|
|
{
|
|
UE_LOG(LogRenderer, Warning, TEXT("Rendering a nanite mesh to a runtime virtual texture isn't yet supported. Please disable this option on primitive component : %s"), *Proxy->GetOwnerName().ToString());
|
|
}
|
|
else if (StaticMeshes.Num() == 0)
|
|
{
|
|
UE_LOG(LogRenderer, Warning, TEXT("Rendering a primitive in a runtime virtual texture implies that there is a mesh to render. Please disable this option on primitive component : %s"), *Proxy->GetOwnerName().ToString());
|
|
}
|
|
else
|
|
{
|
|
RuntimeVirtualTextureFlags.bRenderToVirtualTexture = true;
|
|
|
|
// Performance assumption: The arrays of runtime virtual textures are small (less that 5?) so that O(n^2) scan isn't expensive
|
|
for (TSparseArray<FRuntimeVirtualTextureSceneProxy*>::TConstIterator It(Scene->RuntimeVirtualTextures); It; ++It)
|
|
{
|
|
int32 SceneIndex = It.GetIndex();
|
|
if (SceneIndex < FPrimitiveVirtualTextureFlags::RuntimeVirtualTexture_BitCount)
|
|
{
|
|
URuntimeVirtualTexture* SceneVirtualTexture = (*It)->VirtualTexture;
|
|
if (Proxy->WritesVirtualTexture(SceneVirtualTexture))
|
|
{
|
|
RuntimeVirtualTextureFlags.RuntimeVirtualTextureMask |= 1 << SceneIndex;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FPrimitiveSceneInfo::NeedsUpdateStaticMeshes()
|
|
{
|
|
return Scene->PrimitivesNeedingStaticMeshUpdate[PackedIndex];
|
|
}
|
|
|
|
void FPrimitiveSceneInfo::UpdateStaticMeshes(FRHICommandListImmediate& RHICmdList, FScene* Scene, const TArrayView<FPrimitiveSceneInfo*>& SceneInfos, EUpdateStaticMeshFlags UpdateFlags, bool bReAddToDrawLists)
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_FPrimitiveSceneInfo_UpdateStaticMeshes);
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FPrimitiveSceneInfo_UpdateStaticMeshes);
|
|
|
|
const bool bUpdateRayTracingCommands = EnumHasAnyFlags(UpdateFlags, EUpdateStaticMeshFlags::RayTracingCommands) || !IsRayTracingEnabled();
|
|
const bool bUpdateAllCommands = EnumHasAnyFlags(UpdateFlags, EUpdateStaticMeshFlags::RasterCommands) && bUpdateRayTracingCommands;
|
|
|
|
const bool bNeedsStaticMeshUpdate = !(bReAddToDrawLists && bUpdateAllCommands);
|
|
|
|
for (int32 Index = 0; Index < SceneInfos.Num(); Index++)
|
|
{
|
|
FPrimitiveSceneInfo* SceneInfo = SceneInfos[Index];
|
|
Scene->PrimitivesNeedingStaticMeshUpdate[SceneInfo->PackedIndex] = bNeedsStaticMeshUpdate;
|
|
|
|
if (!bNeedsStaticMeshUpdate && SceneInfo->bNeedsStaticMeshUpdateWithoutVisibilityCheck)
|
|
{
|
|
Scene->PrimitivesNeedingStaticMeshUpdateWithoutVisibilityCheck.Remove(SceneInfo);
|
|
|
|
SceneInfo->bNeedsStaticMeshUpdateWithoutVisibilityCheck = false;
|
|
}
|
|
|
|
if (EnumHasAnyFlags(UpdateFlags, EUpdateStaticMeshFlags::RasterCommands))
|
|
{
|
|
SceneInfo->RemoveCachedMeshDrawCommands();
|
|
SceneInfo->RemoveCachedNaniteDrawCommands();
|
|
}
|
|
|
|
#if RHI_RAYTRACING
|
|
if (EnumHasAnyFlags(UpdateFlags, EUpdateStaticMeshFlags::RayTracingCommands))
|
|
{
|
|
SceneInfo->RemoveCachedRayTracingPrimitives();
|
|
}
|
|
#endif
|
|
|
|
if (SceneInfo->Proxy && SceneInfo->Proxy->IsNaniteMesh())
|
|
{
|
|
// Make sure material table indirections are kept in sync with GPU Scene and cached Nanite MDCs
|
|
SceneInfo->RequestGPUSceneUpdate(EPrimitiveDirtyState::ChangedOther);
|
|
}
|
|
}
|
|
|
|
if (bReAddToDrawLists)
|
|
{
|
|
if (EnumHasAnyFlags(UpdateFlags, EUpdateStaticMeshFlags::RasterCommands))
|
|
{
|
|
CacheMeshDrawCommands(RHICmdList, Scene, SceneInfos);
|
|
CacheNaniteDrawCommands(RHICmdList, Scene, SceneInfos);
|
|
}
|
|
|
|
#if RHI_RAYTRACING
|
|
if (EnumHasAnyFlags(UpdateFlags, EUpdateStaticMeshFlags::RayTracingCommands))
|
|
{
|
|
CacheRayTracingPrimitives(Scene, SceneInfos);
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
#if RHI_RAYTRACING
|
|
void FPrimitiveSceneInfo::UpdateCachedRaytracingData(FScene* Scene, const TArrayView<FPrimitiveSceneInfo*>& SceneInfos)
|
|
{
|
|
if (SceneInfos.Num() > 0)
|
|
{
|
|
for (int32 Index = 0; Index < SceneInfos.Num(); Index++)
|
|
{
|
|
FPrimitiveSceneInfo* SceneInfo = SceneInfos[Index];
|
|
// should have been marked dirty by calling UpdateCachedRayTracingState on the scene before
|
|
// scene info is being updated here
|
|
check(SceneInfo->bCachedRaytracingDataDirty);
|
|
SceneInfo->RemoveCachedRayTracingPrimitives();
|
|
SceneInfo->bCachedRaytracingDataDirty = false;
|
|
}
|
|
|
|
CacheRayTracingPrimitives(Scene, SceneInfos);
|
|
}
|
|
}
|
|
#endif //RHI_RAYTRACING
|
|
|
|
void FPrimitiveSceneInfo::UpdateUniformBuffer(FRHICommandListImmediate& RHICmdList)
|
|
{
|
|
checkSlow(bNeedsUniformBufferUpdate);
|
|
bNeedsUniformBufferUpdate = false;
|
|
Proxy->UpdateUniformBuffer();
|
|
// TODO: Figure out when and why this is called
|
|
Scene->GPUScene.AddPrimitiveToUpdate(PackedIndex, EPrimitiveDirtyState::ChangedAll);
|
|
}
|
|
|
|
void FPrimitiveSceneInfo::BeginDeferredUpdateStaticMeshes()
|
|
{
|
|
// Set a flag which causes InitViews to update the static meshes the next time the primitive is visible.
|
|
if (IsIndexValid()) // PackedIndex
|
|
{
|
|
Scene->PrimitivesNeedingStaticMeshUpdate[PackedIndex] = true;
|
|
}
|
|
}
|
|
|
|
void FPrimitiveSceneInfo::BeginDeferredUpdateStaticMeshesWithoutVisibilityCheck()
|
|
{
|
|
if (NeedsUpdateStaticMeshes() && !bNeedsStaticMeshUpdateWithoutVisibilityCheck)
|
|
{
|
|
bNeedsStaticMeshUpdateWithoutVisibilityCheck = true;
|
|
|
|
Scene->PrimitivesNeedingStaticMeshUpdateWithoutVisibilityCheck.Add(this);
|
|
}
|
|
}
|
|
|
|
void FPrimitiveSceneInfo::FlushRuntimeVirtualTexture()
|
|
{
|
|
if (RuntimeVirtualTextureFlags.bRenderToVirtualTexture)
|
|
{
|
|
uint32 RuntimeVirtualTextureIndex = 0;
|
|
uint32 Mask = RuntimeVirtualTextureFlags.RuntimeVirtualTextureMask;
|
|
while (Mask != 0)
|
|
{
|
|
if (Mask & 1)
|
|
{
|
|
Scene->RuntimeVirtualTextures[RuntimeVirtualTextureIndex]->Dirty(Proxy->GetBounds());
|
|
}
|
|
Mask >>= 1;
|
|
RuntimeVirtualTextureIndex++;
|
|
}
|
|
}
|
|
}
|
|
|
|
void FPrimitiveSceneInfo::LinkLODParentComponent()
|
|
{
|
|
if (LODParentComponentId.IsValid())
|
|
{
|
|
Scene->SceneLODHierarchy.AddChildNode(LODParentComponentId, this);
|
|
}
|
|
}
|
|
|
|
void FPrimitiveSceneInfo::UnlinkLODParentComponent()
|
|
{
|
|
if(LODParentComponentId.IsValid())
|
|
{
|
|
Scene->SceneLODHierarchy.RemoveChildNode(LODParentComponentId, this);
|
|
}
|
|
}
|
|
|
|
void FPrimitiveSceneInfo::LinkAttachmentGroup()
|
|
{
|
|
// Add the primitive to its attachment group.
|
|
if (LightingAttachmentRoot.IsValid())
|
|
{
|
|
FAttachmentGroupSceneInfo* AttachmentGroup = Scene->AttachmentGroups.Find(LightingAttachmentRoot);
|
|
|
|
if (!AttachmentGroup)
|
|
{
|
|
// If this is the first primitive attached that uses this attachment parent, create a new attachment group.
|
|
AttachmentGroup = &Scene->AttachmentGroups.Add(LightingAttachmentRoot, FAttachmentGroupSceneInfo());
|
|
}
|
|
|
|
AttachmentGroup->Primitives.Add(this);
|
|
}
|
|
else if (Proxy->LightAttachmentsAsGroup())
|
|
{
|
|
FAttachmentGroupSceneInfo* AttachmentGroup = Scene->AttachmentGroups.Find(PrimitiveComponentId);
|
|
|
|
if (!AttachmentGroup)
|
|
{
|
|
// Create an empty attachment group
|
|
AttachmentGroup = &Scene->AttachmentGroups.Add(PrimitiveComponentId, FAttachmentGroupSceneInfo());
|
|
}
|
|
|
|
AttachmentGroup->ParentSceneInfo = this;
|
|
}
|
|
}
|
|
|
|
void FPrimitiveSceneInfo::UnlinkAttachmentGroup()
|
|
{
|
|
// Remove the primitive from its attachment group.
|
|
if (LightingAttachmentRoot.IsValid())
|
|
{
|
|
FAttachmentGroupSceneInfo& AttachmentGroup = Scene->AttachmentGroups.FindChecked(LightingAttachmentRoot);
|
|
AttachmentGroup.Primitives.RemoveSwap(this);
|
|
|
|
if (AttachmentGroup.Primitives.Num() == 0 && AttachmentGroup.ParentSceneInfo == nullptr)
|
|
{
|
|
// If this was the last primitive attached that uses this attachment group and the root has left the building, free the group.
|
|
Scene->AttachmentGroups.Remove(LightingAttachmentRoot);
|
|
}
|
|
}
|
|
else if (Proxy->LightAttachmentsAsGroup())
|
|
{
|
|
FAttachmentGroupSceneInfo* AttachmentGroup = Scene->AttachmentGroups.Find(PrimitiveComponentId);
|
|
|
|
if (AttachmentGroup)
|
|
{
|
|
AttachmentGroup->ParentSceneInfo = NULL;
|
|
if (AttachmentGroup->Primitives.Num() == 0)
|
|
{
|
|
// If this was the owner and the group is empty, remove it (otherwise the above will remove when the last attached goes).
|
|
Scene->AttachmentGroups.Remove(PrimitiveComponentId);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FPrimitiveSceneInfo::RequestGPUSceneUpdate(EPrimitiveDirtyState PrimitiveDirtyState)
|
|
{
|
|
if (Scene && IsIndexValid())
|
|
{
|
|
Scene->GPUScene.AddPrimitiveToUpdate(GetIndex(), PrimitiveDirtyState);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void FPrimitiveSceneInfo::GatherLightingAttachmentGroupPrimitives(TArray<FPrimitiveSceneInfo*, SceneRenderingAllocator>& OutChildSceneInfos)
|
|
{
|
|
#if ENABLE_NAN_DIAGNOSTIC
|
|
// local function that returns full name of object
|
|
auto GetObjectName = [](const UPrimitiveComponent* InPrimitive)->FString
|
|
{
|
|
return (InPrimitive) ? InPrimitive->GetFullName() : FString(TEXT("Unknown Object"));
|
|
};
|
|
|
|
// verify that the current object has a valid bbox before adding it
|
|
const float& BoundsRadius = this->Proxy->GetBounds().SphereRadius;
|
|
if (ensureMsgf(!FMath::IsNaN(BoundsRadius) && FMath::IsFinite(BoundsRadius),
|
|
TEXT("%s had an ill-formed bbox and was skipped during shadow setup, contact DavidH."), *GetObjectName(this->ComponentForDebuggingOnly)))
|
|
{
|
|
OutChildSceneInfos.Add(this);
|
|
}
|
|
else
|
|
{
|
|
// return, leaving the TArray empty
|
|
return;
|
|
}
|
|
|
|
#else
|
|
// add self at the head of this queue
|
|
OutChildSceneInfos.Add(this);
|
|
#endif
|
|
|
|
if (!LightingAttachmentRoot.IsValid() && Proxy->LightAttachmentsAsGroup())
|
|
{
|
|
const FAttachmentGroupSceneInfo* AttachmentGroup = Scene->AttachmentGroups.Find(PrimitiveComponentId);
|
|
|
|
if (AttachmentGroup)
|
|
{
|
|
|
|
for (int32 ChildIndex = 0, ChildIndexMax = AttachmentGroup->Primitives.Num(); ChildIndex < ChildIndexMax; ChildIndex++)
|
|
{
|
|
FPrimitiveSceneInfo* ShadowChild = AttachmentGroup->Primitives[ChildIndex];
|
|
#if ENABLE_NAN_DIAGNOSTIC
|
|
// Only enqueue objects with valid bounds using the normality of the SphereRaduis as criteria.
|
|
|
|
const float& ShadowChildBoundsRadius = ShadowChild->Proxy->GetBounds().SphereRadius;
|
|
|
|
if (ensureMsgf(!FMath::IsNaN(ShadowChildBoundsRadius) && FMath::IsFinite(ShadowChildBoundsRadius),
|
|
TEXT("%s had an ill-formed bbox and was skipped during shadow setup, contact DavidH."), *GetObjectName(ShadowChild->ComponentForDebuggingOnly)))
|
|
{
|
|
checkSlow(!OutChildSceneInfos.Contains(ShadowChild))
|
|
OutChildSceneInfos.Add(ShadowChild);
|
|
}
|
|
#else
|
|
// enqueue all objects.
|
|
checkSlow(!OutChildSceneInfos.Contains(ShadowChild))
|
|
OutChildSceneInfos.Add(ShadowChild);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FPrimitiveSceneInfo::GatherLightingAttachmentGroupPrimitives(TArray<const FPrimitiveSceneInfo*, SceneRenderingAllocator>& OutChildSceneInfos) const
|
|
{
|
|
OutChildSceneInfos.Add(this);
|
|
|
|
if (!LightingAttachmentRoot.IsValid() && Proxy->LightAttachmentsAsGroup())
|
|
{
|
|
const FAttachmentGroupSceneInfo* AttachmentGroup = Scene->AttachmentGroups.Find(PrimitiveComponentId);
|
|
|
|
if (AttachmentGroup)
|
|
{
|
|
for (int32 ChildIndex = 0, ChildIndexMax = AttachmentGroup->Primitives.Num(); ChildIndex < ChildIndexMax; ChildIndex++)
|
|
{
|
|
const FPrimitiveSceneInfo* ShadowChild = AttachmentGroup->Primitives[ChildIndex];
|
|
|
|
checkSlow(!OutChildSceneInfos.Contains(ShadowChild))
|
|
OutChildSceneInfos.Add(ShadowChild);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
FBoxSphereBounds FPrimitiveSceneInfo::GetAttachmentGroupBounds() const
|
|
{
|
|
FBoxSphereBounds Bounds = Proxy->GetBounds();
|
|
|
|
if (!LightingAttachmentRoot.IsValid() && Proxy->LightAttachmentsAsGroup())
|
|
{
|
|
const FAttachmentGroupSceneInfo* AttachmentGroup = Scene->AttachmentGroups.Find(PrimitiveComponentId);
|
|
|
|
if (AttachmentGroup)
|
|
{
|
|
for (int32 ChildIndex = 0; ChildIndex < AttachmentGroup->Primitives.Num(); ChildIndex++)
|
|
{
|
|
FPrimitiveSceneInfo* AttachmentChild = AttachmentGroup->Primitives[ChildIndex];
|
|
Bounds = Bounds + AttachmentChild->Proxy->GetBounds();
|
|
}
|
|
}
|
|
}
|
|
|
|
return Bounds;
|
|
}
|
|
|
|
uint32 FPrimitiveSceneInfo::GetMemoryFootprint()
|
|
{
|
|
return( sizeof( *this ) + HitProxies.GetAllocatedSize() + StaticMeshes.GetAllocatedSize() + StaticMeshRelevances.GetAllocatedSize() );
|
|
}
|
|
|
|
void FPrimitiveSceneInfo::ApplyWorldOffset(FVector InOffset)
|
|
{
|
|
Proxy->ApplyWorldOffset(InOffset);
|
|
}
|
|
|
|
void FPrimitiveSceneInfo::UpdateIndirectLightingCacheBuffer(
|
|
const FIndirectLightingCache* LightingCache,
|
|
const FIndirectLightingCacheAllocation* LightingAllocation,
|
|
FVector VolumetricLightmapLookupPosition,
|
|
uint32 SceneFrameNumber,
|
|
FVolumetricLightmapSceneData* VolumetricLightmapSceneData)
|
|
{
|
|
FIndirectLightingCacheUniformParameters Parameters;
|
|
|
|
GetIndirectLightingCacheParameters(
|
|
Scene->GetFeatureLevel(),
|
|
Parameters,
|
|
LightingCache,
|
|
LightingAllocation,
|
|
VolumetricLightmapLookupPosition,
|
|
SceneFrameNumber,
|
|
VolumetricLightmapSceneData);
|
|
|
|
if (IndirectLightingCacheUniformBuffer)
|
|
{
|
|
IndirectLightingCacheUniformBuffer.UpdateUniformBufferImmediate(Parameters);
|
|
}
|
|
}
|
|
|
|
void FPrimitiveSceneInfo::UpdateIndirectLightingCacheBuffer()
|
|
{
|
|
if (bIndirectLightingCacheBufferDirty)
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_UpdateIndirectLightingCacheBuffer);
|
|
|
|
if (Scene->GetFeatureLevel() < ERHIFeatureLevel::SM5
|
|
&& Scene->VolumetricLightmapSceneData.HasData()
|
|
&& (Proxy->IsMovable() || Proxy->NeedsUnbuiltPreviewLighting() || Proxy->GetLightmapType() == ELightmapType::ForceVolumetric)
|
|
&& Proxy->WillEverBeLit())
|
|
{
|
|
UpdateIndirectLightingCacheBuffer(
|
|
nullptr,
|
|
nullptr,
|
|
Proxy->GetBounds().Origin,
|
|
Scene->GetFrameNumber(),
|
|
&Scene->VolumetricLightmapSceneData);
|
|
}
|
|
// The update is invalid if the lighting cache allocation was not in a functional state.
|
|
else if (IndirectLightingCacheAllocation && (Scene->IndirectLightingCache.IsInitialized() && IndirectLightingCacheAllocation->bHasEverUpdatedSingleSample))
|
|
{
|
|
UpdateIndirectLightingCacheBuffer(
|
|
&Scene->IndirectLightingCache,
|
|
IndirectLightingCacheAllocation,
|
|
FVector(0, 0, 0),
|
|
0,
|
|
nullptr);
|
|
}
|
|
else
|
|
{
|
|
// Fallback to the global empty buffer parameters
|
|
UpdateIndirectLightingCacheBuffer(nullptr, nullptr, FVector(0.0f, 0.0f, 0.0f), 0, nullptr);
|
|
}
|
|
|
|
bIndirectLightingCacheBufferDirty = false;
|
|
}
|
|
}
|
|
|
|
void FPrimitiveSceneInfo::GetStaticMeshesLODRange(int8& OutMinLOD, int8& OutMaxLOD) const
|
|
{
|
|
OutMinLOD = MAX_int8;
|
|
OutMaxLOD = 0;
|
|
|
|
for (int32 MeshIndex = 0; MeshIndex < StaticMeshRelevances.Num(); ++MeshIndex)
|
|
{
|
|
const FStaticMeshBatchRelevance& MeshRelevance = StaticMeshRelevances[MeshIndex];
|
|
OutMinLOD = FMath::Min(OutMinLOD, MeshRelevance.LODIndex);
|
|
OutMaxLOD = FMath::Max(OutMaxLOD, MeshRelevance.LODIndex);
|
|
}
|
|
}
|
|
|
|
const FMeshBatch* FPrimitiveSceneInfo::GetMeshBatch(int8 InLODIndex) const
|
|
{
|
|
if (StaticMeshes.IsValidIndex(InLODIndex))
|
|
{
|
|
return &StaticMeshes[InLODIndex];
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
bool FPrimitiveSceneInfo::NeedsReflectionCaptureUpdate() const
|
|
{
|
|
return bNeedsCachedReflectionCaptureUpdate &&
|
|
// For mobile, the per-object reflection is used for everything
|
|
(Scene->GetShadingPath() == EShadingPath::Mobile || IsForwardShadingEnabled(Scene->GetShaderPlatform()));
|
|
}
|
|
|
|
void FPrimitiveSceneInfo::CacheReflectionCaptures()
|
|
{
|
|
// do not use Scene->PrimitiveBounds here, as it may be not initialized yet
|
|
FBoxSphereBounds BoxSphereBounds = Proxy->GetBounds();
|
|
|
|
CachedReflectionCaptureProxy = Scene->FindClosestReflectionCapture(BoxSphereBounds.Origin);
|
|
CachedPlanarReflectionProxy = Scene->FindClosestPlanarReflection(BoxSphereBounds);
|
|
if (Scene->GetShadingPath() == EShadingPath::Mobile)
|
|
{
|
|
// mobile HQ reflections
|
|
Scene->FindClosestReflectionCaptures(BoxSphereBounds.Origin, CachedReflectionCaptureProxies);
|
|
}
|
|
|
|
bNeedsCachedReflectionCaptureUpdate = false;
|
|
}
|
|
|
|
void FPrimitiveSceneInfo::RemoveCachedReflectionCaptures()
|
|
{
|
|
CachedReflectionCaptureProxy = nullptr;
|
|
CachedPlanarReflectionProxy = nullptr;
|
|
FMemory::Memzero(CachedReflectionCaptureProxies);
|
|
bNeedsCachedReflectionCaptureUpdate = true;
|
|
}
|
|
|
|
void FPrimitiveSceneInfo::UpdateComponentLastRenderTime(float CurrentWorldTime, bool bUpdateLastRenderTimeOnScreen) const
|
|
{
|
|
ComponentForDebuggingOnly->LastRenderTime = CurrentWorldTime;
|
|
if (bUpdateLastRenderTimeOnScreen)
|
|
{
|
|
ComponentForDebuggingOnly->LastRenderTimeOnScreen = CurrentWorldTime;
|
|
}
|
|
if (OwnerLastRenderTime)
|
|
{
|
|
*OwnerLastRenderTime = CurrentWorldTime; // Sets OwningActor->LastRenderTime
|
|
}
|
|
}
|
|
|
|
void FPrimitiveOctreeSemantics::SetOctreeNodeIndex(const FPrimitiveSceneInfoCompact& Element, FOctreeElementId2 Id)
|
|
{
|
|
// When a Primitive is removed from the renderer, it's index will be invalidated. Only update if the primitive still
|
|
// has a valid index.
|
|
if (Element.PrimitiveSceneInfo->IsIndexValid())
|
|
{
|
|
Element.PrimitiveSceneInfo->Scene->PrimitiveOctreeIndex[Element.PrimitiveSceneInfo->GetIndex()] = Id.GetNodeIndex();
|
|
}
|
|
}
|
|
|
|
FString FPrimitiveSceneInfo::GetFullnameForDebuggingOnly() const
|
|
{
|
|
// This is not correct to access component from rendering thread, but this is for debugging only
|
|
if (ComponentForDebuggingOnly)
|
|
{
|
|
return ComponentForDebuggingOnly->GetFullGroupName(false);
|
|
}
|
|
return FString(TEXT("Unknown Object"));
|
|
}
|