You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
- Renamed the command to access the tool from DrawPrimitiveDebugger.Open to PrimitiveDebugger.Open - Fixed access violations and crashes that occurred when opening and using the tool. - Fixed issue where the debugger table would not populate until filter text was entered when using it in-game. - Fixed an issue causing the server to crash if the console command cheat DrawPrimitiveDebugger.Open is used - Added support for skeletal meshes and other primitives. - Added a details panel for the currently selected row to declutter the table view and provide more in depth information such as the materials and textures used and the number of available LODs. The data displayed will refresh automatically and display the active location, LOD, and triangle count of the primitive. - The details panel can force LOD levels, force disable nanite, display bounds, and show skeletal bones. Bounds and bone displays only work in development or debug builds. - Note that any changes to primitives made by the debugger, as well as pinned and hidden entries, will be reset when the debugger is closed. - Fixed communication between the game and render threads when handling frame captures. - Enhanced search functionality. You can now search by primitive name, primitive class, actor name, actor class, material name, and texture used. #rb daniele.vettorel [FYI] elizabeth.bunner, nicolas.mercier, bryce.lumpkin #tests FNTEST-128602 [CL 32064534 by zach harris in ue5-main branch]
2432 lines
86 KiB
C++
2432 lines
86 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 "RayTracingGeometry.h"
|
|
#include "Components/ComponentInterfaces.h"
|
|
|
|
#include "RendererModule.h"
|
|
#include "HAL/LowLevelMemTracker.h"
|
|
#include "RayTracing/RayTracingMaterialHitShaders.h"
|
|
#include "RayTracing/RayTracingInstanceMask.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 "Nanite/NaniteRayTracing.h"
|
|
#include "Nanite/NaniteShading.h"
|
|
#include "Rendering/NaniteResources.h"
|
|
#include "NaniteSceneProxy.h"
|
|
#include "Lumen/LumenSceneData.h"
|
|
#include "Lumen/LumenSceneCardCapture.h"
|
|
#include "RayTracingDefinitions.h"
|
|
#include "RenderCore.h"
|
|
#include "Materials/MaterialRenderProxy.h"
|
|
#include "StaticMeshBatch.h"
|
|
#include "PrimitiveSceneDesc.h"
|
|
#include "BasePassRendering.h" // TODO: Remove with later refactor (moving Nanite shading into its own files)
|
|
#include "InstanceDataSceneProxy.h"
|
|
|
|
|
|
extern int32 GGPUSceneInstanceClearList;
|
|
|
|
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 GMeshDrawCommandsBatchSize = 12;
|
|
static FAutoConsoleVariableRef CVarDrawCommandsCacheMultithreadedBatchSize(
|
|
TEXT("r.MeshDrawCommands.BatchSize"),
|
|
GMeshDrawCommandsBatchSize,
|
|
TEXT("Batch size of cache mesh draw commands when multithreading of draw command caching is enabled"),
|
|
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);
|
|
bool bUseForWaterInfoTextureDepth = Mesh.bUseForWaterInfoTextureDepth;
|
|
bool bUseForLumenSceneCapture = Mesh.bUseForLumenSurfaceCacheCapture;
|
|
|
|
FStaticMeshBatchRelevance* StaticMeshRelevance = new(PrimitiveSceneInfo->StaticMeshRelevances) FStaticMeshBatchRelevance(
|
|
*StaticMesh,
|
|
ScreenSize,
|
|
bSupportsCachingMeshDrawCommands,
|
|
bUseSkyMaterial,
|
|
bUseSingleLayerWaterMaterial,
|
|
bUseAnisotropy,
|
|
bSupportsNaniteRendering,
|
|
bSupportsGPUScene,
|
|
bUseForWaterInfoTextureDepth,
|
|
bUseForLumenSceneCapture,
|
|
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())
|
|
, bIsAlwaysVisible(Proxy->IsAlwaysVisible())
|
|
, 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();
|
|
}
|
|
|
|
struct FPrimitiveSceneInfoAdapter
|
|
{
|
|
void CreateHitProxies()
|
|
{
|
|
if (PrimitiveComponentInterface)
|
|
{
|
|
// Support for legacy path for proxy creation, if not handled it'll internally invoke the IPrimitiveComponentInterface path
|
|
if (UPrimitiveComponent* PrimitiveComponent = PrimitiveComponentInterface->GetUObject<UPrimitiveComponent>())
|
|
{
|
|
DefaultHitProxy = SceneProxy->CreateHitProxies(PrimitiveComponent, HitProxies);
|
|
}
|
|
else
|
|
{
|
|
// For all other implementers
|
|
DefaultHitProxy = SceneProxy->CreateHitProxies(PrimitiveComponentInterface, HitProxies);
|
|
}
|
|
}
|
|
}
|
|
|
|
FPrimitiveSceneInfoAdapter(UPrimitiveComponent* InComponent)
|
|
{
|
|
SceneProxy = InComponent->SceneProxy;
|
|
SceneData = &InComponent->SceneData;
|
|
ComponentId = SceneData->PrimitiveSceneId;
|
|
check(InComponent->GetSceneData().RegistrationSerialNumber != -1);
|
|
RegistrationSerialNumber = InComponent->GetSceneData().RegistrationSerialNumber;
|
|
Component = InComponent;
|
|
PrimitiveComponentInterface = InComponent->GetPrimitiveComponentInterface();
|
|
PrimitiveDesc = nullptr;
|
|
|
|
// This validates the UPrimitiveComponent has properly initialized its OwnerLastRenderTimePtr
|
|
check(InComponent->SceneData.OwnerLastRenderTimePtr == FActorLastRenderTime::GetPtr(InComponent->GetOwner()));
|
|
Mobility = InComponent->Mobility;
|
|
|
|
const UPrimitiveComponent* SearchParentComponent = InComponent->GetLightingAttachmentRoot();
|
|
|
|
if (SearchParentComponent && SearchParentComponent != InComponent)
|
|
{
|
|
LightingAttachmentComponentId = SearchParentComponent->GetPrimitiveSceneId();
|
|
}
|
|
|
|
// set LOD parent info if exists
|
|
UPrimitiveComponent* LODParent = InComponent->GetLODParentPrimitive();
|
|
if (LODParent)
|
|
{
|
|
LODParentComponentId = LODParent->GetPrimitiveSceneId();
|
|
}
|
|
|
|
if (GIsEditor)
|
|
{
|
|
CreateHitProxies();
|
|
}
|
|
|
|
}
|
|
|
|
FPrimitiveSceneInfoAdapter(FPrimitiveSceneDesc* InPrimitiveSceneDesc)
|
|
{
|
|
check(InPrimitiveSceneDesc);
|
|
|
|
Component = nullptr;
|
|
PrimitiveComponentInterface = InPrimitiveSceneDesc->GetPrimitiveComponentInterface();
|
|
SceneData = &InPrimitiveSceneDesc->GetSceneData();
|
|
PrimitiveDesc = InPrimitiveSceneDesc;
|
|
SceneProxy = InPrimitiveSceneDesc->GetSceneProxy();
|
|
check(SceneProxy);
|
|
ComponentId = InPrimitiveSceneDesc->GetPrimitiveSceneId();
|
|
RegistrationSerialNumber = InPrimitiveSceneDesc->GetRegistrationSerialNumber();
|
|
LODParentComponentId = InPrimitiveSceneDesc->GetLODParentId();
|
|
LightingAttachmentComponentId = InPrimitiveSceneDesc->GetLightingAttachmentId();
|
|
Mobility = InPrimitiveSceneDesc->GetMobility();
|
|
|
|
if (GIsEditor && PrimitiveComponentInterface)
|
|
{
|
|
CreateHitProxies();
|
|
}
|
|
|
|
}
|
|
|
|
FPrimitiveSceneProxy* SceneProxy;
|
|
FPrimitiveComponentId ComponentId;
|
|
int32 RegistrationSerialNumber;
|
|
FPrimitiveComponentId LODParentComponentId;
|
|
FPrimitiveComponentId LightingAttachmentComponentId;
|
|
EComponentMobility::Type Mobility;
|
|
|
|
// mutable so that hit proxies can be moved to final destination
|
|
mutable TArray<TRefCountPtr<HHitProxy> > HitProxies;
|
|
HHitProxy* DefaultHitProxy = nullptr;
|
|
|
|
FPrimitiveSceneInfoData* SceneData;
|
|
UPrimitiveComponent* Component;
|
|
IPrimitiveComponent* PrimitiveComponentInterface;
|
|
FPrimitiveSceneDesc* PrimitiveDesc;
|
|
};
|
|
|
|
FPrimitiveSceneInfo::FPrimitiveSceneInfo(const FPrimitiveSceneInfoAdapter& InAdapter, FScene* InScene):
|
|
Proxy(InAdapter.SceneProxy),
|
|
PrimitiveComponentId(InAdapter.ComponentId),
|
|
RegistrationSerialNumber(InAdapter.RegistrationSerialNumber),
|
|
IndirectLightingCacheAllocation(NULL),
|
|
CachedPlanarReflectionProxy(NULL),
|
|
CachedReflectionCaptureProxy(NULL),
|
|
DefaultDynamicHitProxy(NULL),
|
|
LastRenderTime(-FLT_MAX),
|
|
LightList(NULL),
|
|
Scene(InScene),
|
|
NumMobileDynamicLocalLights(0),
|
|
GpuLodInstanceRadius(0),
|
|
PackedIndex(INDEX_NONE),
|
|
PersistentIndex(FPersistentPrimitiveIndex{ INDEX_NONE }),
|
|
PrimitiveComponentInterfaceForDebuggingOnly(InAdapter.PrimitiveComponentInterface),
|
|
SceneData(InAdapter.SceneData),
|
|
bNeedsUniformBufferUpdate(false),
|
|
bIndirectLightingCacheBufferDirty(false),
|
|
bRegisteredVirtualTextureProducerCallback(false),
|
|
bRegisteredWithVelocityData(false),
|
|
bCacheShadowAsStatic((InAdapter.Mobility != EComponentMobility::Movable && InAdapter.SceneProxy->GetShadowCacheInvalidationBehavior() != EShadowCacheInvalidationBehavior::Always) || InAdapter.SceneProxy->GetShadowCacheInvalidationBehavior() == EShadowCacheInvalidationBehavior::Static),
|
|
bNaniteRasterBinsRenderCustomDepth(false),
|
|
bPendingAddToScene(false),
|
|
bPendingAddStaticMeshes(false),
|
|
bPendingFlushVirtualTexture(false),
|
|
bNeedsCachedReflectionCaptureUpdate(true),
|
|
bShouldRenderInMainPass(InAdapter.SceneProxy->ShouldRenderInMainPass()),
|
|
bVisibleInRealTimeSkyCapture(InAdapter.SceneProxy->IsVisibleInRealTimeSkyCaptures()),
|
|
#if RHI_RAYTRACING
|
|
bDrawInGame(Proxy->IsDrawnInGame()),
|
|
bRayTracingFarField(Proxy->IsRayTracingFarField()),
|
|
bIsVisibleInSceneCaptures(!InAdapter.SceneProxy->IsHiddenInSceneCapture()),
|
|
bIsVisibleInSceneCapturesOnly(InAdapter.SceneProxy->IsVisibleInSceneCaptureOnly()),
|
|
bIsRayTracingRelevant(InAdapter.SceneProxy->IsRayTracingRelevant()),
|
|
bIsRayTracingStaticRelevant(InAdapter.SceneProxy->IsRayTracingStaticRelevant()),
|
|
bIsVisibleInRayTracing(InAdapter.SceneProxy->IsVisibleInRayTracing()),
|
|
bCachedRaytracingDataDirty(true),
|
|
bCachedRayTracingInstanceMaskAndFlagsDirty(true),
|
|
bCachedRayTracingInstanceAnySegmentsDecal(false),
|
|
bCachedRayTracingInstanceAllSegmentsDecal(false),
|
|
CoarseMeshStreamingHandle(InAdapter.SceneProxy->GetCoarseMeshStreamingHandle()),
|
|
#endif
|
|
// We want the unsynchronized access here, as the responsibility passes to the primitive scene info.
|
|
InstanceSceneDataBuffersInternal(InAdapter.SceneProxy->GetInstanceSceneDataBuffers(FPrimitiveSceneProxy::EInstanceBufferAccessFlags::UnsynchronizedAndUnsafe)),
|
|
InstanceDataUpdateTaskInfo(InAdapter.SceneProxy->GetInstanceDataUpdateTaskInfo()),
|
|
LevelUpdateNotificationIndex(INDEX_NONE),
|
|
InstanceSceneDataOffset(INDEX_NONE),
|
|
NumInstanceSceneDataEntries(0),
|
|
InstancePayloadDataOffset(INDEX_NONE),
|
|
InstancePayloadDataStride(0),
|
|
LightmapDataOffset(INDEX_NONE),
|
|
NumLightmapDataEntries(0)
|
|
{
|
|
check(PrimitiveComponentId.IsValid());
|
|
check(Proxy);
|
|
check(SceneData);
|
|
|
|
LightingAttachmentRoot = InAdapter.LightingAttachmentComponentId;
|
|
|
|
// 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 = InAdapter.DefaultHitProxy;
|
|
HitProxies = MoveTemp(InAdapter.HitProxies);
|
|
|
|
if( DefaultDynamicHitProxy )
|
|
{
|
|
check(HitProxies.Contains(DefaultDynamicHitProxy));
|
|
DefaultDynamicHitProxyId = DefaultDynamicHitProxy->Id;
|
|
}
|
|
}
|
|
|
|
LODParentComponentId = InAdapter.LODParentComponentId;
|
|
|
|
FMemory::Memzero(CachedReflectionCaptureProxies);
|
|
|
|
#if RHI_RAYTRACING
|
|
// Cache static ray tracing geometries in SceneInfo to avoid having to access SceneProxy later
|
|
StaticRayTracingGeometries = InAdapter.SceneProxy->GetStaticRayTracingGeometries();
|
|
CachedRayTracingGeometry = nullptr;
|
|
#endif
|
|
|
|
if (FInstanceCullingContext::IsGPUCullingEnabled())
|
|
{
|
|
GpuLodInstanceRadius = InAdapter.SceneProxy->GetGpuLodInstanceRadius();
|
|
}
|
|
}
|
|
|
|
FPrimitiveSceneInfo::FPrimitiveSceneInfo(UPrimitiveComponent* InPrimitive,FScene* InScene)
|
|
: FPrimitiveSceneInfo(FPrimitiveSceneInfoAdapter(InPrimitive), InScene)
|
|
{
|
|
}
|
|
|
|
FPrimitiveSceneInfo::FPrimitiveSceneInfo(FPrimitiveSceneDesc* InPrimitiveSceneDesc,FScene* InScene)
|
|
: FPrimitiveSceneInfo(FPrimitiveSceneInfoAdapter(InPrimitiveSceneDesc), InScene)
|
|
{
|
|
}
|
|
|
|
FPrimitiveSceneInfo::~FPrimitiveSceneInfo()
|
|
{
|
|
check(!OctreeId.IsValidId());
|
|
for (int32 PassIndex = 0; PassIndex < EMeshPass::Num; PassIndex++)
|
|
{
|
|
check(StaticMeshCommandInfos.Num() == 0);
|
|
}
|
|
}
|
|
|
|
#if RHI_RAYTRACING
|
|
bool FPrimitiveSceneInfo::IsCachedRayTracingGeometryValid() const
|
|
{
|
|
if (CachedRayTracingGeometry)
|
|
{
|
|
// TODO: Doesn't take Nanite Ray Tracing into account
|
|
check(CachedRayTracingGeometry->GetRHI() == CachedRayTracingInstance.GeometryRHI);
|
|
check(!CachedRayTracingGeometry->GetRequiresBuild() && !CachedRayTracingGeometry->HasPendingBuildRequest());
|
|
|
|
return CachedRayTracingGeometry->IsValid();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
FRayTracingGeometry* FPrimitiveSceneInfo::GetStaticRayTracingGeometry(int8 LODIndex) const
|
|
{
|
|
if (LODIndex < StaticRayTracingGeometries.Num())
|
|
{
|
|
return StaticRayTracingGeometries[LODIndex];
|
|
}
|
|
else
|
|
{
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
FRayTracingGeometry* FPrimitiveSceneInfo::GetValidStaticRayTracingGeometry(int8& InOutLODIndex) const
|
|
{
|
|
// TODO: Move HasPendingBuildRequest() / BoostBuildPriority() out of this function
|
|
|
|
for (; InOutLODIndex < StaticRayTracingGeometries.Num(); ++InOutLODIndex)
|
|
{
|
|
if (StaticRayTracingGeometries[InOutLODIndex]->HasPendingBuildRequest())
|
|
{
|
|
ensure(StaticRayTracingGeometries[InOutLODIndex]->IsValid());
|
|
StaticRayTracingGeometries[InOutLODIndex]->BoostBuildPriority();
|
|
}
|
|
else if (StaticRayTracingGeometries[InOutLODIndex]->IsValid())
|
|
{
|
|
return StaticRayTracingGeometries[InOutLODIndex];
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
FRHIRayTracingGeometry* FPrimitiveSceneInfo::GetStaticRayTracingGeometryInstance(int LodLevel) const
|
|
{
|
|
if (StaticRayTracingGeometries.Num() > LodLevel)
|
|
{
|
|
// TODO: Select different LOD, when build is still pending for this LOD?
|
|
if (StaticRayTracingGeometries[LodLevel]->HasPendingBuildRequest())
|
|
{
|
|
StaticRayTracingGeometries[LodLevel]->BoostBuildPriority();
|
|
return nullptr;
|
|
}
|
|
else if (StaticRayTracingGeometries[LodLevel]->IsValid())
|
|
{
|
|
return StaticRayTracingGeometries[LodLevel]->GetRHI();
|
|
}
|
|
else
|
|
{
|
|
return nullptr;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return nullptr;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
void FPrimitiveSceneInfo::CacheMeshDrawCommands(FScene* Scene, TArrayView<FPrimitiveSceneInfo*> SceneInfos)
|
|
{
|
|
SCOPED_NAMED_EVENT(FPrimitiveSceneInfo_CacheMeshDrawCommands, FColor::Emerald);
|
|
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(FPrimitiveSceneInfo_CacheMeshDrawCommands);
|
|
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_CacheMeshDrawCommands);
|
|
|
|
// This reduce stuttering in editor by improving balancing of all the
|
|
// shadermap processing. Keep it as it is for runtime as the requirements are different.
|
|
const int BATCH_SIZE = WITH_EDITOR ? 1 : GMeshDrawCommandsBatchSize;
|
|
const int NumBatches = (SceneInfos.Num() + BATCH_SIZE - 1) / BATCH_SIZE;
|
|
|
|
auto DoWorkLambda = [Scene, SceneInfos, BATCH_SIZE](FCachedPassMeshDrawListContext& DrawListContext, int32 Index)
|
|
{
|
|
SCOPED_NAMED_EVENT(FPrimitiveSceneInfo_CacheMeshDrawCommand, FColor::Green);
|
|
|
|
struct FMeshInfoAndIndex
|
|
{
|
|
int32 InfoIndex;
|
|
int32 MeshIndex;
|
|
};
|
|
|
|
TArray<FMeshInfoAndIndex, SceneRenderingAllocator> 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 = GetFeatureLevelShadingPath(Scene->GetFeatureLevel());
|
|
EMeshPass::Type PassType = (EMeshPass::Type)PassIndex;
|
|
|
|
if ((FPassProcessorManager::GetPassFlags(ShadingPath, PassType) & EMeshPassFlags::CachedMeshCommands) != EMeshPassFlags::None)
|
|
{
|
|
FCachedPassMeshDrawListContext::FMeshPassScope MeshPassScope(DrawListContext, PassType);
|
|
|
|
FMeshPassProcessor* PassMeshProcessor = FPassProcessorManager::CreateMeshPassProcessor(ShadingPath, PassType, Scene->GetFeatureLevel(), 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;
|
|
FCachedMeshDrawCommandInfo& CurrentCommandInfo = SceneInfo->StaticMeshCommandInfos[CommandInfoIndex];
|
|
checkf(CurrentCommandInfo.MeshPass == EMeshPass::Num,
|
|
TEXT("SceneInfo->StaticMeshCommandInfos[%d] is not expected to be initialized yet. MeshPass is %d, but expected EMeshPass::Num (%d)."),
|
|
CommandInfoIndex, (int32)EMeshPass::Num, CurrentCommandInfo.MeshPass);
|
|
CurrentCommandInfo = CommandInfo;
|
|
}
|
|
}
|
|
|
|
delete PassMeshProcessor;
|
|
}
|
|
}
|
|
}
|
|
|
|
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, EAllowShrinking::No);
|
|
SceneInfo->StaticMeshCommandInfos.Shrink();
|
|
}
|
|
};
|
|
|
|
bool bAnyLooseParameterBuffers = false;
|
|
if (GMeshDrawCommandsCacheMultithreaded && FApp::ShouldUseThreadingForPerformance())
|
|
{
|
|
TArray<FCachedPassMeshDrawListContextDeferred, SceneRenderingAllocator> DrawListContexts;
|
|
DrawListContexts.Reserve(NumBatches);
|
|
for(int32 ContextIndex = 0; ContextIndex < NumBatches; ++ContextIndex)
|
|
{
|
|
DrawListContexts.Emplace(*Scene);
|
|
}
|
|
|
|
ParallelForTemplate(
|
|
NumBatches,
|
|
[&DrawListContexts, &DoWorkLambda](int32 Index)
|
|
{
|
|
FOptionalTaskTagScope Scope(ETaskTag::EParallelRenderingThread);
|
|
DoWorkLambda(DrawListContexts[Index], Index);
|
|
},
|
|
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 BuildNaniteMaterialBins(FScene* Scene, FPrimitiveSceneInfo* PrimitiveSceneInfo, FNaniteDrawListContext& DrawListContext);
|
|
|
|
void FPrimitiveSceneInfo::CacheNaniteMaterialBins(FScene* Scene, const TArrayView<FPrimitiveSceneInfo*>& SceneInfos)
|
|
{
|
|
SCOPED_NAMED_EVENT(FPrimitiveSceneInfo_CacheNaniteMaterialBins, FColor::Emerald);
|
|
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(FPrimitiveSceneInfo_CacheNaniteMaterialBins);
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_CacheNaniteMaterialBins);
|
|
|
|
const bool bNaniteEnabled = DoesPlatformSupportNanite(GMaxRHIShaderPlatform);
|
|
if (bNaniteEnabled)
|
|
{
|
|
TArray<FNaniteDrawListContext, TInlineAllocator<1>> DrawListContexts;
|
|
|
|
if (GNaniteDrawCommandCacheMultithreaded && FApp::ShouldUseThreadingForPerformance())
|
|
{
|
|
ParallelForWithTaskContext(
|
|
DrawListContexts,
|
|
SceneInfos.Num(),
|
|
[Scene, &SceneInfos](FNaniteDrawListContext& Context, int32 Index)
|
|
{
|
|
FOptionalTaskTagScope Scope(ETaskTag::EParallelRenderingThread);
|
|
BuildNaniteMaterialBins(Scene, SceneInfos[Index], Context);
|
|
}
|
|
);
|
|
}
|
|
else
|
|
{
|
|
FNaniteDrawListContext& DrawListContext = DrawListContexts.AddDefaulted_GetRef();
|
|
for (FPrimitiveSceneInfo* PrimitiveSceneInfo : SceneInfos)
|
|
{
|
|
BuildNaniteMaterialBins(Scene, PrimitiveSceneInfo, DrawListContext);
|
|
}
|
|
}
|
|
|
|
FPrimitiveViewRelevance& CombinedPrimitiveRelevance = Scene->NaniteShadingPipelines[ENaniteMeshPass::BasePass].CombinedRelevance;
|
|
CombinedPrimitiveRelevance = FPrimitiveViewRelevance();
|
|
|
|
if (DrawListContexts.Num() > 0)
|
|
{
|
|
SCOPED_NAMED_EVENT(NaniteDrawListApply, FColor::Emerald);
|
|
|
|
FMaterialRelevance CombinedMaterialRelevance;
|
|
|
|
CombinedPrimitiveRelevance.bDrawRelevance = true;
|
|
CombinedPrimitiveRelevance.bStaticRelevance = true;
|
|
CombinedPrimitiveRelevance.bRenderInMainPass = true;
|
|
CombinedPrimitiveRelevance.bShadowRelevance = true;
|
|
|
|
// Nanite::GetSupportsCustomDepthRendering() && ShouldRenderCustomDepth();
|
|
CombinedPrimitiveRelevance.bRenderCustomDepth = false; // TODO: Unsupported in fast path
|
|
|
|
// GetLightingChannelMask() != GetDefaultLightingChannelMask();
|
|
CombinedPrimitiveRelevance.bUsesLightingChannels = false; // TODO: Unsupported in fast path
|
|
|
|
for (FNaniteDrawListContext& Context : DrawListContexts)
|
|
{
|
|
Context.Apply(*Scene);
|
|
|
|
// Update combined material relevance
|
|
CombinedMaterialRelevance |= Context.CombinedRelevance;
|
|
}
|
|
|
|
// Apply combined material relevance to combined primitive view relevance
|
|
CombinedMaterialRelevance.SetPrimitiveViewRelevance(CombinedPrimitiveRelevance);
|
|
}
|
|
|
|
if (UseNaniteComputeMaterials())
|
|
{
|
|
Scene->NaniteShadingPipelines[ENaniteMeshPass::BasePass].bBuildCommands = true;
|
|
Scene->NaniteShadingPipelines[ENaniteMeshPass::LumenCardCapture].bBuildCommands = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
void BuildNaniteMaterialBins(FScene* Scene, FPrimitiveSceneInfo* PrimitiveSceneInfo, FNaniteDrawListContext& DrawListContext)
|
|
{
|
|
const bool bUseComputeMaterials = UseNaniteComputeMaterials();
|
|
|
|
FPrimitiveSceneProxy* Proxy = PrimitiveSceneInfo->Proxy;
|
|
if (Proxy->IsNaniteMesh())
|
|
{
|
|
Nanite::FSceneProxyBase* NaniteProxy = static_cast<Nanite::FSceneProxyBase*>(Proxy);
|
|
{
|
|
FNaniteDrawListContext::FPrimitiveSceneInfoScope PrimInfoScope(DrawListContext, *PrimitiveSceneInfo);
|
|
|
|
auto PassBody = [Scene, PrimitiveSceneInfo, NaniteProxy, &DrawListContext, bUseComputeMaterials](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();
|
|
if (NaniteMaterialSections.Num() > 0)
|
|
{
|
|
FNaniteDrawListContext::FDeferredPipelines& PipelinesCommand = DrawListContext.DeferredPipelines[MeshPass].Emplace_GetRef();
|
|
PipelinesCommand.PrimitiveSceneInfo = PrimitiveSceneInfo;
|
|
|
|
DrawListContext.CombinedRelevance |= NaniteProxy->GetCombinedMaterialRelevance();
|
|
|
|
for (int32 MaterialSectionIndex = 0; MaterialSectionIndex < NaniteMaterialSections.Num(); ++MaterialSectionIndex)
|
|
{
|
|
Nanite::FSceneProxyBase::FMaterialSection& MaterialSection = NaniteMaterialSections[MaterialSectionIndex];
|
|
check(MaterialSection.RasterMaterialProxy != nullptr);
|
|
check(MaterialSection.ShadingMaterialProxy != nullptr);
|
|
|
|
FNaniteRasterPipeline& RasterPipeline = PipelinesCommand.RasterPipelines.Emplace_GetRef();
|
|
RasterPipeline.RasterMaterial = MaterialSection.RasterMaterialProxy;
|
|
RasterPipeline.bIsTwoSided = !!MaterialSection.MaterialRelevance.bTwoSided;
|
|
RasterPipeline.bSplineMesh = NaniteProxy->IsSplineMesh();
|
|
RasterPipeline.bPerPixelEval = MaterialSection.MaterialRelevance.bMasked ||
|
|
MaterialSection.MaterialRelevance.bUsesPixelDepthOffset;
|
|
|
|
RasterPipeline.DisplacementScaling = MaterialSection.DisplacementScaling;
|
|
|
|
float WPODisableDistance;
|
|
RasterPipeline.bWPODisableDistance =
|
|
MaterialSection.MaterialRelevance.bUsesWorldPositionOffset &&
|
|
!MaterialSection.bAlwaysEvaluateWPO &&
|
|
NaniteProxy->GetInstanceWorldPositionOffsetDisableDistance(WPODisableDistance);
|
|
|
|
if (bUseComputeMaterials)
|
|
{
|
|
FNaniteShadingPipeline& ShadingPipeline = PipelinesCommand.ShadingPipelines.Emplace_GetRef();
|
|
|
|
// TODO: Refactor this out
|
|
bool bLoaded = false;
|
|
if (MeshPass == ENaniteMeshPass::BasePass)
|
|
{
|
|
bLoaded = LoadBasePassPipeline(*Scene, NaniteProxy, MaterialSection, ShadingPipeline);
|
|
}
|
|
else if (MeshPass == ENaniteMeshPass::LumenCardCapture)
|
|
{
|
|
bLoaded = LoadLumenCardPipeline(*Scene, NaniteProxy, MaterialSection, ShadingPipeline);
|
|
}
|
|
|
|
check(bLoaded);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// ENaniteMeshPass::BasePass
|
|
{
|
|
FMeshPassProcessor* NaniteMeshProcessor = CreateNaniteMeshProcessor(Scene->GetFeatureLevel(), Scene, nullptr, &DrawListContext);
|
|
PassBody(ENaniteMeshPass::BasePass, NaniteMeshProcessor);
|
|
delete NaniteMeshProcessor;
|
|
}
|
|
|
|
// ENaniteMeshPass::LumenCardCapture
|
|
if (LumenScene::HasPrimitiveNaniteMeshBatches(Proxy) && DoesPlatformSupportLumenGI(GetFeatureLevelShaderPlatform(Scene->GetFeatureLevel())))
|
|
{
|
|
FMeshPassProcessor* NaniteMeshProcessor = CreateLumenCardNaniteMeshProcessor(Scene->GetFeatureLevel(), Scene, nullptr, &DrawListContext);
|
|
PassBody(ENaniteMeshPass::LumenCardCapture, NaniteMeshProcessor);
|
|
delete NaniteMeshProcessor;
|
|
}
|
|
|
|
static_assert(ENaniteMeshPass::Num == 2, "Change BuildNaniteMaterialBins() to account for more Nanite mesh passes");
|
|
}
|
|
}
|
|
}
|
|
|
|
void FPrimitiveSceneInfo::RemoveCachedNaniteMaterialBins()
|
|
{
|
|
checkSlow(IsInRenderingThread());
|
|
|
|
if (!Proxy->IsNaniteMesh())
|
|
{
|
|
return;
|
|
}
|
|
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_RemoveCachedNaniteMaterialBins);
|
|
|
|
for (int32 NaniteMeshPassIndex = 0; NaniteMeshPassIndex < ENaniteMeshPass::Num; ++NaniteMeshPassIndex)
|
|
{
|
|
FNaniteMaterialCommands& ShadingCommands = Scene->NaniteMaterials[NaniteMeshPassIndex];
|
|
FNaniteRasterPipelines& RasterPipelines = Scene->NaniteRasterPipelines[NaniteMeshPassIndex];
|
|
FNaniteShadingPipelines& ShadingPipelines = Scene->NaniteShadingPipelines[NaniteMeshPassIndex];
|
|
FNaniteVisibility& Visibility = Scene->NaniteVisibility[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];
|
|
if (NaniteMeshPassIndex == ENaniteMeshPass::BasePass && bNaniteRasterBinsRenderCustomDepth)
|
|
{
|
|
// need to unregister these bins for custom pass first
|
|
RasterPipelines.UnregisterBinForCustomPass(RasterBin.BinIndex);
|
|
}
|
|
RasterPipelines.Unregister(RasterBin);
|
|
}
|
|
|
|
TArray<FNaniteShadingBin>& NanitePassShadingBins = NaniteShadingBins[NaniteMeshPassIndex];
|
|
for (int32 ShadingBinIndex = 0; ShadingBinIndex < NanitePassShadingBins.Num(); ++ShadingBinIndex)
|
|
{
|
|
const FNaniteShadingBin& ShadingBin = NanitePassShadingBins[ShadingBinIndex];
|
|
ShadingPipelines.Unregister(ShadingBin);
|
|
}
|
|
|
|
// Need to rebuild the shading commands list
|
|
ShadingPipelines.bBuildCommands = true;
|
|
|
|
Visibility.RemoveReferences(this);
|
|
|
|
NanitePassRasterBins.Reset();
|
|
NanitePassShadingBins.Reset();
|
|
NanitePassCommandInfo.Reset();
|
|
NaniteMaterialSlots[NaniteMeshPassIndex].Reset();
|
|
}
|
|
|
|
bNaniteRasterBinsRenderCustomDepth = false;
|
|
}
|
|
|
|
#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 (const FLightSceneInfoCompact& LightSceneInfoCompact : Lights)
|
|
{
|
|
if (ShouldRenderRayTracingShadowsForLight(LightSceneInfoCompact))
|
|
{
|
|
bHasRayTracedLights = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void FPrimitiveSceneInfo::UpdateCachedRayTracingInstances(FScene* Scene, const TArrayView<FPrimitiveSceneInfo*>& SceneInfos)
|
|
{
|
|
if (IsRayTracingEnabled())
|
|
{
|
|
SCOPED_NAMED_EVENT(FPrimitiveSceneInfo_UpdateCachedRayTracingInstances, FColor::Turquoise);
|
|
checkf(GRHISupportsMultithreadedShaderCreation, TEXT("Raytracing code needs the ability to create shaders from task threads."));
|
|
|
|
for (FPrimitiveSceneInfo* SceneInfo : SceneInfos)
|
|
{
|
|
const int32 PrimitiveIndex = SceneInfo->GetIndex();
|
|
|
|
#if DO_CHECK
|
|
{
|
|
Experimental::FHashElementId SceneRayTracingGroupId;
|
|
const int32 RayTracingGroupId = SceneInfo->Proxy->GetRayTracingGroupId();
|
|
if (RayTracingGroupId != -1)
|
|
{
|
|
SceneRayTracingGroupId = Scene->PrimitiveRayTracingGroups.FindId(RayTracingGroupId);
|
|
}
|
|
|
|
check(Scene->PrimitiveRayTracingGroupIds[PrimitiveIndex] == SceneRayTracingGroupId);
|
|
check(SceneInfo->CoarseMeshStreamingHandle == SceneInfo->Proxy->GetCoarseMeshStreamingHandle());
|
|
}
|
|
#endif
|
|
|
|
FRayTracingInstance CachedRayTracingInstance;
|
|
ERayTracingPrimitiveFlags& Flags = Scene->PrimitiveRayTracingFlags[PrimitiveIndex];
|
|
|
|
// 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)
|
|
, RayTracingMeshProcessor(&CommandContext, Scene, nullptr, Scene->CachedRayTracingMeshCommandsMode)
|
|
{ }
|
|
|
|
FTempRayTracingMeshCommandStorage Commands;
|
|
FCachedRayTracingMeshCommandContext<T> CommandContext;
|
|
FRayTracingMeshProcessor RayTracingMeshProcessor;
|
|
TArray<DeferredMeshLODCommandIndex> DeferredMeshLODCommandIndices;
|
|
};
|
|
|
|
template<bool bDeferLODCommandIndices, class T>
|
|
void CacheRayTracingMeshBatch(
|
|
const FMeshBatch& MeshBatch,
|
|
FPrimitiveSceneInfo* SceneInfo,
|
|
T& Commands,
|
|
FCachedRayTracingMeshCommandContext<T>& CommandContext,
|
|
FRayTracingMeshProcessor& RayTracingMeshProcessor,
|
|
TArray<DeferredMeshLODCommandIndex>* DeferredMeshLODCommandIndices,
|
|
bool bMustEmitCommand)
|
|
{
|
|
// 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(MeshBatch, BatchElementMask, SceneInfo->Proxy);
|
|
|
|
check(!bMustEmitCommand || CommandContext.CommandIndex >= 0);
|
|
|
|
if (bMustEmitCommand || CommandContext.CommandIndex >= 0)
|
|
{
|
|
uint64& Hash = SceneInfo->CachedRayTracingMeshCommandsHashPerLOD[MeshBatch.LODIndex];
|
|
|
|
// We want the hash to change if either the shader or the binding contents change. This is used by the autoinstance feature.
|
|
const FRHIShader* Shader = Commands[CommandContext.CommandIndex].MaterialShader;
|
|
|
|
// TODO: It would be better to use 64 bits for both of these to reduce the chance of hash collisions
|
|
// but GetDynamicInstancingHash is currently a public function, so changing the return type would be an API change
|
|
uint32 ShaderHash = Shader != nullptr ? GetTypeHash(Shader->GetHash()) : 0;
|
|
uint32 ShaderBindingsHash = Commands[CommandContext.CommandIndex].ShaderBindings.GetDynamicInstancingHash();
|
|
|
|
Hash <<= 1; // TODO: It would probably be better to use some kind of proper 64 bit mix here?
|
|
Hash ^= (uint64(ShaderBindingsHash) << 32) | uint64(ShaderHash);
|
|
|
|
if (bDeferLODCommandIndices)
|
|
{
|
|
DeferredMeshLODCommandIndices->Add({ SceneInfo, MeshBatch.LODIndex, CommandContext.CommandIndex });
|
|
}
|
|
else
|
|
{
|
|
SceneInfo->CachedRayTracingMeshCommandIndicesPerLOD[MeshBatch.LODIndex].Add(CommandContext.CommandIndex);
|
|
}
|
|
|
|
CommandContext.CommandIndex = -1;
|
|
}
|
|
}
|
|
|
|
template<bool bDeferLODCommandIndices, class T>
|
|
void CacheRayTracingPrimitive(
|
|
FScene* Scene,
|
|
FPrimitiveSceneInfo* SceneInfo,
|
|
T& Commands,
|
|
FCachedRayTracingMeshCommandContext<T>& CommandContext,
|
|
FRayTracingMeshProcessor& RayTracingMeshProcessor,
|
|
TArray<DeferredMeshLODCommandIndex>* DeferredMeshLODCommandIndices,
|
|
FRayTracingInstance& OutCachedRayTracingInstance,
|
|
ERayTracingPrimitiveFlags& OutFlags)
|
|
{
|
|
#if DO_CHECK
|
|
{
|
|
Experimental::FHashElementId SceneRayTracingGroupId;
|
|
const int32 RayTracingGroupId = SceneInfo->Proxy->GetRayTracingGroupId();
|
|
if (RayTracingGroupId != -1)
|
|
{
|
|
SceneRayTracingGroupId = Scene->PrimitiveRayTracingGroups.FindId(RayTracingGroupId);
|
|
}
|
|
|
|
check(Scene->PrimitiveRayTracingGroupIds[SceneInfo->GetIndex()] == SceneRayTracingGroupId);
|
|
check(SceneInfo->CoarseMeshStreamingHandle == SceneInfo->Proxy->GetCoarseMeshStreamingHandle());
|
|
}
|
|
#endif
|
|
|
|
// Write flags
|
|
OutFlags = SceneInfo->Proxy->GetCachedRayTracingInstance(OutCachedRayTracingInstance);
|
|
|
|
// the following flags cause ray tracing mesh command caching to be disabled
|
|
static const ERayTracingPrimitiveFlags DisableCacheMeshCommandsFlags = ERayTracingPrimitiveFlags::Dynamic
|
|
| ERayTracingPrimitiveFlags::Exclude
|
|
| ERayTracingPrimitiveFlags::Skip
|
|
| ERayTracingPrimitiveFlags::UnsupportedProxyType;
|
|
|
|
if (!EnumHasAnyFlags(OutFlags, DisableCacheMeshCommandsFlags))
|
|
{
|
|
// Cache ray tracing mesh commands in FPrimitiveSceneInfo
|
|
|
|
int32 MaxLOD = -1;
|
|
|
|
if (OutCachedRayTracingInstance.Materials.Num() > 0)
|
|
{
|
|
// 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)
|
|
MaxLOD = 0;
|
|
}
|
|
else
|
|
{
|
|
for (const FStaticMeshBatch& Mesh : SceneInfo->StaticMeshes)
|
|
{
|
|
MaxLOD = MaxLOD < Mesh.LODIndex ? Mesh.LODIndex : MaxLOD;
|
|
}
|
|
}
|
|
|
|
SceneInfo->CachedRayTracingMeshCommandIndicesPerLOD.Empty(MaxLOD + 1);
|
|
SceneInfo->CachedRayTracingMeshCommandIndicesPerLOD.AddDefaulted(MaxLOD + 1); // should be initialzied to -1?
|
|
|
|
SceneInfo->CachedRayTracingMeshCommandsHashPerLOD.Empty(MaxLOD + 1);
|
|
SceneInfo->CachedRayTracingMeshCommandsHashPerLOD.AddZeroed(MaxLOD + 1);
|
|
|
|
if (OutCachedRayTracingInstance.Materials.Num() > 0)
|
|
{
|
|
// The material section must emit a command. Otherwise, it should have been excluded earlier
|
|
const bool bMustEmitCommand = true;
|
|
|
|
for (const FMeshBatch& Mesh : OutCachedRayTracingInstance.Materials)
|
|
{
|
|
CacheRayTracingMeshBatch<bDeferLODCommandIndices>(Mesh, SceneInfo, Commands, CommandContext, RayTracingMeshProcessor, DeferredMeshLODCommandIndices, bMustEmitCommand);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const bool bMustEmitCommand = false;
|
|
|
|
for (const FStaticMeshBatch& Mesh : SceneInfo->StaticMeshes)
|
|
{
|
|
CacheRayTracingMeshBatch<bDeferLODCommandIndices>(Mesh, SceneInfo, Commands, CommandContext, RayTracingMeshProcessor, DeferredMeshLODCommandIndices, bMustEmitCommand);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FPrimitiveSceneInfo::CacheRayTracingPrimitives(FScene* Scene, const TArrayView<FPrimitiveSceneInfo*>& SceneInfos)
|
|
{
|
|
if (IsRayTracingEnabled(Scene->GetShaderPlatform()))
|
|
{
|
|
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)
|
|
{
|
|
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);
|
|
SceneInfo->bCachedRaytracingDataDirty = false;
|
|
}
|
|
);
|
|
|
|
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);
|
|
FRayTracingMeshProcessor RayTracingMeshProcessor(&CommandContext, Scene, nullptr, 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);
|
|
SceneInfo->bCachedRaytracingDataDirty = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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"));
|
|
|
|
FPrimitiveSceneProxy* SceneProxy = SceneInfo->Proxy;
|
|
|
|
// TODO: allocate from FRayTracingScene & do better low-level caching
|
|
SceneInfo->CachedRayTracingInstance.NumTransforms = CachedRayTracingInstance.NumTransforms;
|
|
SceneInfo->CachedRayTracingInstance.BaseInstanceSceneDataOffset = SceneInfo->GetInstanceSceneDataOffset();
|
|
|
|
SceneInfo->CachedRayTracingGeometry = CachedRayTracingInstance.Geometry;
|
|
|
|
if (Nanite::GetRayTracingMode() != Nanite::ERayTracingMode::Fallback && SceneProxy->IsNaniteMesh())
|
|
{
|
|
SceneInfo->CachedRayTracingInstance.GeometryRHI = Nanite::GRayTracingManager.GetRayTracingGeometry(SceneInfo);
|
|
|
|
// nanite ray tracing geometry might not be ready yet
|
|
// if not ready, this pointer will be patched as soon as it is
|
|
}
|
|
else
|
|
{
|
|
checkf(CachedRayTracingInstance.Geometry, TEXT("Cached ray tracing instances must have valid geometries.")); // unless using nanite ray tracing
|
|
|
|
SceneInfo->CachedRayTracingInstance.GeometryRHI = CachedRayTracingInstance.Geometry->GetRHI();
|
|
}
|
|
|
|
// At this point (in AddToScene()) PrimitiveIndex has been set
|
|
check(SceneInfo->GetPersistentIndex().IsValid());
|
|
SceneInfo->CachedRayTracingInstance.DefaultUserData = (uint32)SceneInfo->GetPersistentIndex().Index;
|
|
|
|
SceneInfo->CachedRayTracingInstance.bApplyLocalBoundsTransform = CachedRayTracingInstance.bApplyLocalBoundsTransform;
|
|
|
|
SceneInfo->CachedRayTracingInstance.Flags = ERayTracingInstanceFlags::None;
|
|
|
|
FRayTracingMaskAndFlags InstanceMaskAndFlags;
|
|
|
|
// TODO: Check CachedRayTracingInstance.bInstanceMaskAndFlagsDirty?
|
|
|
|
if (CachedRayTracingInstance.GetMaterials().IsEmpty())
|
|
{
|
|
// If the material list is empty, explicitly set the mask to 0 so it will not be added in the raytracing scene
|
|
InstanceMaskAndFlags.Mask = 0;
|
|
}
|
|
else
|
|
{
|
|
InstanceMaskAndFlags = BuildRayTracingInstanceMaskAndFlags(CachedRayTracingInstance, *SceneProxy, nullptr);
|
|
}
|
|
|
|
SceneInfo->CachedRayTracingInstance.Mask = InstanceMaskAndFlags.Mask; // When no cached command is found, InstanceMask == 0 and the instance is effectively filtered out
|
|
|
|
SceneInfo->CachedRayTracingInstance.Flags = ERayTracingInstanceFlags::None;
|
|
|
|
if (InstanceMaskAndFlags.bForceOpaque)
|
|
{
|
|
SceneInfo->CachedRayTracingInstance.Flags |= ERayTracingInstanceFlags::ForceOpaque;
|
|
}
|
|
|
|
if (InstanceMaskAndFlags.bDoubleSided)
|
|
{
|
|
SceneInfo->CachedRayTracingInstance.Flags |= ERayTracingInstanceFlags::TriangleCullDisable;
|
|
}
|
|
|
|
SceneInfo->bCachedRayTracingInstanceAnySegmentsDecal = InstanceMaskAndFlags.bAnySegmentsDecal;
|
|
SceneInfo->bCachedRayTracingInstanceAllSegmentsDecal = InstanceMaskAndFlags.bAllSegmentsDecal;
|
|
|
|
SceneInfo->bCachedRayTracingInstanceMaskAndFlagsDirty = false;
|
|
}
|
|
else
|
|
{
|
|
SceneInfo->CachedRayTracingInstance.Mask = 0xFF;
|
|
SceneInfo->CachedRayTracingInstance.Flags = ERayTracingInstanceFlags::None;
|
|
SceneInfo->bCachedRayTracingInstanceAnySegmentsDecal = false;
|
|
SceneInfo->bCachedRayTracingInstanceAllSegmentsDecal = false;
|
|
|
|
SceneInfo->bCachedRayTracingInstanceMaskAndFlagsDirty = true;
|
|
}
|
|
}
|
|
|
|
void FPrimitiveSceneInfo::RemoveCachedRayTracingPrimitives()
|
|
{
|
|
if (IsRayTracingEnabled())
|
|
{
|
|
for (auto& CachedRayTracingMeshCommandIndices : CachedRayTracingMeshCommandIndicesPerLOD)
|
|
{
|
|
for (auto CommandIndex : CachedRayTracingMeshCommandIndices)
|
|
{
|
|
if (CommandIndex >= 0)
|
|
{
|
|
Scene->CachedRayTracingMeshCommands.RemoveAt(CommandIndex);
|
|
}
|
|
}
|
|
}
|
|
|
|
CachedRayTracingMeshCommandIndicesPerLOD.Empty();
|
|
|
|
CachedRayTracingMeshCommandsHashPerLOD.Empty();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void FPrimitiveSceneInfo::AddStaticMeshes(FRHICommandListBase& RHICmdList, FScene* Scene, TArrayView<FPrimitiveSceneInfo*> SceneInfos, bool bCacheMeshDrawCommands)
|
|
{
|
|
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();
|
|
SceneInfo->bPendingAddStaticMeshes = false;
|
|
|
|
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::IsSortedTrianglesEnabled(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))
|
|
{
|
|
Scene->OITSceneData.Allocate(RHICmdList, EPrimitiveType(Mesh.Type), Mesh.Elements[0], Mesh.Elements[0].DynamicIndexBuffer);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bCacheMeshDrawCommands)
|
|
{
|
|
CacheMeshDrawCommands(Scene, SceneInfos);
|
|
CacheNaniteMaterialBins(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->GetPersistentIndex(), 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
|
|
);
|
|
|
|
// Note: this will return 1 instance for primitives without the instance data buffer.
|
|
FInstanceDataBufferHeader InstanceDataHeader = SceneInfo->GetInstanceDataHeader();
|
|
SceneInfo->NumInstanceSceneDataEntries = InstanceDataHeader.NumInstances;
|
|
if (SceneInfo->NumInstanceSceneDataEntries > 0)
|
|
{
|
|
SceneInfo->InstanceSceneDataOffset = Scene->GPUScene.AllocateInstanceSceneDataSlots(SceneInfo->NumInstanceSceneDataEntries);
|
|
SceneInfo->InstancePayloadDataStride = InstanceDataHeader.PayloadDataStride;
|
|
if (SceneInfo->InstancePayloadDataStride > 0)
|
|
{
|
|
const uint32 TotalFloat4Count = SceneInfo->NumInstanceSceneDataEntries * SceneInfo->InstancePayloadDataStride;
|
|
SceneInfo->InstancePayloadDataOffset = Scene->GPUScene.AllocateInstancePayloadDataSlots(TotalFloat4Count);
|
|
}
|
|
}
|
|
|
|
// Force a primitive update in the GPU scene,
|
|
// NOTE: does not set Added as this is handled elsewhere.
|
|
Scene->GPUScene.AddPrimitiveToUpdate(SceneInfo->GetPersistentIndex(), EPrimitiveDirtyState::ChangedAll);
|
|
|
|
// Force a primitive update in the Lumen scene(s)
|
|
for (FLumenSceneDataIterator LumenSceneData = Scene->GetLumenSceneDataIterator(); LumenSceneData; ++LumenSceneData)
|
|
{
|
|
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);
|
|
|
|
// 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(FScene* Scene, TArrayView<FPrimitiveSceneInfo*> SceneInfos)
|
|
{
|
|
check(IsInRenderingThread());
|
|
SCOPED_NAMED_EVENT(FPrimitiveSceneInfo_AddToScene, FColor::Turquoise);
|
|
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
|
|
SceneInfo->bPendingAddToScene = false;
|
|
}
|
|
}
|
|
|
|
{
|
|
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 = IsStaticLightingAllowed();
|
|
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_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
|
|
|
|
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::UpdateVirtualTextures(FScene* Scene, TArrayView<FPrimitiveSceneInfo*> SceneInfos)
|
|
{
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
|
|
void FPrimitiveSceneInfo::RemoveStaticMeshes()
|
|
{
|
|
// Deallocate potential OIT dynamic index buffer
|
|
if (OIT::IsSortedTrianglesEnabled(GMaxRHIShaderPlatform))
|
|
{
|
|
for (int32 MeshIndex = 0; MeshIndex < StaticMeshes.Num(); MeshIndex++)
|
|
{
|
|
FStaticMeshBatch& Mesh = StaticMeshes[MeshIndex];
|
|
if (Mesh.Elements.Num() > 0)
|
|
{
|
|
Scene->OITSceneData.Deallocate(Mesh.Elements[0]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove static meshes from the scene.
|
|
StaticMeshes.Empty();
|
|
StaticMeshRelevances.Empty();
|
|
RemoveCachedMeshDrawCommands();
|
|
RemoveCachedNaniteMaterialBins();
|
|
#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;
|
|
}
|
|
|
|
// 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, EAllowShrinking::No);
|
|
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)
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FPrimitiveSceneInfo::UpdateStaticMeshes(FScene* Scene, 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 (EnumHasAnyFlags(UpdateFlags, EUpdateStaticMeshFlags::RasterCommands))
|
|
{
|
|
SceneInfo->RemoveCachedMeshDrawCommands();
|
|
SceneInfo->RemoveCachedNaniteMaterialBins();
|
|
}
|
|
|
|
#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(Scene, SceneInfos);
|
|
CacheNaniteMaterialBins(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();
|
|
}
|
|
|
|
CacheRayTracingPrimitives(Scene, SceneInfos);
|
|
}
|
|
}
|
|
#endif //RHI_RAYTRACING
|
|
|
|
void FPrimitiveSceneInfo::RequestStaticMeshUpdate()
|
|
{
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
bool FPrimitiveSceneInfo::RequestUniformBufferUpdate()
|
|
{
|
|
if (IsIndexValid()) // PackedIndex
|
|
{
|
|
Scene->PrimitivesNeedingUniformBufferUpdate[PackedIndex] = true;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
const FInstanceSceneDataBuffers *FPrimitiveSceneInfo::GetInstanceSceneDataBuffers() const
|
|
{
|
|
if (!HasInstanceDataBuffers())
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
if (InstanceDataUpdateTaskInfo)
|
|
{
|
|
InstanceDataUpdateTaskInfo->WaitForUpdateCompletion();
|
|
}
|
|
return InstanceSceneDataBuffersInternal;
|
|
}
|
|
|
|
FInstanceDataBufferHeader FPrimitiveSceneInfo::GetInstanceDataHeader() const
|
|
{
|
|
if (!HasInstanceDataBuffers())
|
|
{
|
|
return FInstanceDataBufferHeader::SinglePrimitiveHeader;
|
|
}
|
|
|
|
if (InstanceDataUpdateTaskInfo)
|
|
{
|
|
return InstanceDataUpdateTaskInfo->GetHeader();
|
|
}
|
|
|
|
return InstanceSceneDataBuffersInternal->GetHeader();
|
|
}
|
|
|
|
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(GetPersistentIndex(), PrimitiveDirtyState);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void FPrimitiveSceneInfo::RefreshNaniteRasterBins()
|
|
{
|
|
const bool bShouldRenderCustomDepth = Proxy->ShouldRenderCustomDepth();
|
|
if (bShouldRenderCustomDepth == bNaniteRasterBinsRenderCustomDepth)
|
|
{
|
|
// nothing to do
|
|
return;
|
|
}
|
|
|
|
TArray<FNaniteRasterBin>& NanitePassRasterBins = NaniteRasterBins[ENaniteMeshPass::BasePass];
|
|
FNaniteRasterPipelines& RasterPipelines = Scene->NaniteRasterPipelines[ENaniteMeshPass::BasePass];
|
|
for (const FNaniteRasterBin& RasterBin : NanitePassRasterBins)
|
|
{
|
|
if (bShouldRenderCustomDepth)
|
|
{
|
|
RasterPipelines.RegisterBinForCustomPass(RasterBin.BinIndex);
|
|
}
|
|
else
|
|
{
|
|
RasterPipelines.UnregisterBinForCustomPass(RasterBin.BinIndex);
|
|
}
|
|
}
|
|
|
|
bNaniteRasterBinsRenderCustomDepth = bShouldRenderCustomDepth;
|
|
}
|
|
|
|
void FPrimitiveSceneInfo::GatherLightingAttachmentGroupPrimitives(TArray<FPrimitiveSceneInfo*, SceneRenderingAllocator>& OutChildSceneInfos)
|
|
{
|
|
#if ENABLE_NAN_DIAGNOSTIC
|
|
// local function that returns full name of object
|
|
auto GetObjectName = [](const UObject* 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->PrimitiveComponentInterfaceForDebuggingOnly->GetUObject())))
|
|
{
|
|
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->PrimitiveComponentInterfaceForDebuggingOnly->GetUObject())))
|
|
{
|
|
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(FRHICommandListBase& RHICmdList, FVector InOffset)
|
|
{
|
|
Proxy->ApplyWorldOffset(RHICmdList, InOffset);
|
|
}
|
|
|
|
void FPrimitiveSceneInfo::UpdateIndirectLightingCacheBuffer(
|
|
FRHICommandListBase& RHICmdList,
|
|
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(RHICmdList, Parameters);
|
|
}
|
|
}
|
|
|
|
void FPrimitiveSceneInfo::UpdateIndirectLightingCacheBuffer(FRHICommandListBase& RHICmdList)
|
|
{
|
|
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(
|
|
RHICmdList,
|
|
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(
|
|
RHICmdList,
|
|
&Scene->IndirectLightingCache,
|
|
IndirectLightingCacheAllocation,
|
|
FVector(0, 0, 0),
|
|
0,
|
|
nullptr);
|
|
}
|
|
else
|
|
{
|
|
// Fallback to the global empty buffer parameters
|
|
UpdateIndirectLightingCacheBuffer(RHICmdList, 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
|
|
{
|
|
SceneData->SetLastRenderTime(CurrentWorldTime, bUpdateLastRenderTimeOnScreen);
|
|
}
|
|
|
|
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
|
|
{
|
|
if (PrimitiveComponentInterfaceForDebuggingOnly)
|
|
{
|
|
return PrimitiveComponentInterfaceForDebuggingOnly->GetUObject()->GetFullGroupName(false);
|
|
}
|
|
|
|
return FString(TEXT("Unknown Object"));
|
|
}
|
|
|
|
FString FPrimitiveSceneInfo::GetOwnerActorNameOrLabelForDebuggingOnly() const
|
|
{
|
|
if (PrimitiveComponentInterfaceForDebuggingOnly)
|
|
{
|
|
return PrimitiveComponentInterfaceForDebuggingOnly->GetOwnerName();
|
|
}
|
|
|
|
return FString(TEXT("Unknown Object"));
|
|
}
|
|
|
|
UPrimitiveComponent* FPrimitiveSceneInfo::GetComponentForDebugOnly() const
|
|
{
|
|
return Cast<UPrimitiveComponent>(PrimitiveComponentInterfaceForDebuggingOnly->GetUObject());
|
|
}
|
|
|
|
IPrimitiveComponent* FPrimitiveSceneInfo::GetComponentInterfaceForDebugOnly() const
|
|
{
|
|
return PrimitiveComponentInterfaceForDebuggingOnly;
|
|
} |