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