// Copyright Epic Games, Inc. All Rights Reserved. /*============================================================================= MeshPassProcessor.cpp: =============================================================================*/ #include "MeshPassProcessor.h" #include "SceneUtils.h" #include "SceneRendering.h" #include "Logging/LogMacros.h" #include "RendererModule.h" #include "SceneCore.h" #include "ScenePrivate.h" #include "SceneInterface.h" #include "MeshPassProcessor.inl" #include "PipelineStateCache.h" #include "RayTracing/RayTracingMaterialHitShaders.h" #include "Hash/CityHash.h" #include "ComponentRecreateRenderStateContext.h" #include "RenderCore.h" #include "UnrealEngine.h" #include "SceneUniformBuffer.h" #include "MeshDrawCommandStats.h" #include "VariableRateShadingImageManager.h" FRWLock FGraphicsMinimalPipelineStateId::PersistentIdTableLock; FGraphicsMinimalPipelineStateId::PersistentTableType FGraphicsMinimalPipelineStateId::PersistentIdTable; #if MESH_DRAW_COMMAND_DEBUG_DATA std::atomic FGraphicsMinimalPipelineStateId::DebugSaltAllocationIndex; std::atomic FGraphicsMinimalPipelineStateId::LocalPipelineIdTableSize(0); std::atomic FGraphicsMinimalPipelineStateId::CurrentLocalPipelineIdTableSize(0); #endif //MESH_DRAW_COMMAND_DEBUG_DATA bool FGraphicsMinimalPipelineStateId::NeedsShaderInitialisation = true; bool FGraphicsMinimalPipelineStateId::bIsIdTableFrozen = false; std::atomic FGraphicsMinimalPipelineStateId::ReffedItemCount(0); const FMeshDrawCommandSortKey FMeshDrawCommandSortKey::Default = { {0} }; int32 GEmitMeshDrawEvent = 0; static FAutoConsoleVariableRef CVarEmitMeshDrawEvent( TEXT("r.EmitMeshDrawEvents"), GEmitMeshDrawEvent, TEXT("Emits a GPU event around each drawing policy draw call. /n") TEXT("Useful for seeing stats about each draw call, however it greatly distorts total time and time per draw call."), ECVF_RenderThreadSafe ); static TAutoConsoleVariable CVarSafeStateLookup( TEXT("r.SafeStateLookup"), 1, TEXT("Forces new-style safe state lookup for easy runtime perf comparison\n"), ECVF_Scalability | ECVF_RenderThreadSafe); int32 GSkipDrawOnPSOPrecaching = 0; static FAutoConsoleVariableRef CVarSkipDrawOnPSOPrecaching( TEXT("r.SkipDrawOnPSOPrecaching"), GSkipDrawOnPSOPrecaching, TEXT("Skips mesh draw call when the PSO is still compiling (default 0)."), ECVF_RenderThreadSafe ); static bool GSkipUnloadedShaders = false; static FAutoConsoleVariableRef CVarAllowSkipUnloadedShaders( TEXT("r.SkipUnloadedShaders"), GSkipUnloadedShaders, TEXT("Skip the draw call when the shaders are not fully preloaded. Useful for debugging"), ECVF_Default ); #if WITH_EDITORONLY_DATA int32 GNaniteIsolateInvalidCoarseMesh = 0; static FAutoConsoleVariableRef CVarNaniteIsolateInvalidCoarseMesh( TEXT("r.Nanite.IsolateInvalidCoarseMesh"), GNaniteIsolateInvalidCoarseMesh, TEXT("Debug mode to render only non-Nanite proxies that incorrectly reference coarse static mesh assets."), FConsoleVariableDelegate::CreateLambda([](IConsoleVariable* InVariable) { // Needed to force a recache of all the static mesh draw commands FGlobalComponentRecreateRenderStateContext Context; }) ); #endif inline void SetTextureParameter(FRHIBatchedShaderParameters& BatchedParameters, const FShaderResourceParameterInfo& Parameter, FRHITexture* TextureRHI) { #if PLATFORM_SUPPORTS_BINDLESS_RENDERING if (Parameter.Type == EShaderParameterType::BindlessSRV) { check(Parameter.BufferIndex == 0); BatchedParameters.SetBindlessTexture(Parameter.BaseIndex, TextureRHI); } else #endif { BatchedParameters.SetShaderTexture(Parameter.BaseIndex, TextureRHI); } } inline void SetSrvParameter(FRHIBatchedShaderParameters& BatchedParameters, const FShaderResourceParameterInfo& Parameter, FRHIShaderResourceView* SrvRHI) { #if PLATFORM_SUPPORTS_BINDLESS_RENDERING if (Parameter.Type == EShaderParameterType::BindlessSRV) { check(Parameter.BufferIndex == 0); BatchedParameters.SetBindlessResourceView(Parameter.BaseIndex, SrvRHI); } else #endif { BatchedParameters.SetShaderResourceViewParameter(Parameter.BaseIndex, SrvRHI); } } inline void SetSamplerParameter(FRHIBatchedShaderParameters& BatchedParameters, const FShaderResourceParameterInfo& Parameter, FRHISamplerState* SamplerStateRHI) { #if PLATFORM_SUPPORTS_BINDLESS_RENDERING if (Parameter.Type == EShaderParameterType::BindlessSampler) { BatchedParameters.SetBindlessSampler(Parameter.BaseIndex, SamplerStateRHI); } else #endif { BatchedParameters.SetShaderSampler(Parameter.BaseIndex, SamplerStateRHI); } } inline void SetLooseParameters(FRHIBatchedShaderParameters& BatchedParameters, const FShaderParameterMapInfo& ParameterMapInfo, const uint8* LooseDataStart) { for (const FShaderLooseParameterBufferInfo& LooseParameterBuffer : ParameterMapInfo.LooseParameterBuffers) { for (const FShaderLooseParameterInfo& Parameter : LooseParameterBuffer.Parameters) { BatchedParameters.SetShaderParameter( LooseParameterBuffer.BaseIndex, Parameter.BaseIndex, Parameter.Size, LooseDataStart ); LooseDataStart += Parameter.Size; } } } void FReadOnlyMeshDrawSingleShaderBindings::SetShaderBindings( FRHIBatchedShaderParameters& BatchedParameters, const FReadOnlyMeshDrawSingleShaderBindings& RESTRICT SingleShaderBindings, FShaderBindingState& RESTRICT ShaderBindingState) { FRHIUniformBuffer* const* RESTRICT UniformBufferBindings = SingleShaderBindings.GetUniformBufferStart(); const FShaderUniformBufferParameterInfo* RESTRICT UniformBufferParameters = SingleShaderBindings.ParameterMapInfo.UniformBuffers.GetData(); const int32 NumUniformBuffers = SingleShaderBindings.ParameterMapInfo.UniformBuffers.Num(); for (int32 UniformBufferIndex = 0; UniformBufferIndex < NumUniformBuffers; UniformBufferIndex++) { FShaderUniformBufferParameterInfo Parameter = UniformBufferParameters[UniformBufferIndex]; checkSlow(Parameter.BaseIndex < UE_ARRAY_COUNT(ShaderBindingState.UniformBuffers)); FRHIUniformBuffer* UniformBuffer = UniformBufferBindings[UniformBufferIndex]; if (UniformBuffer != ShaderBindingState.UniformBuffers[Parameter.BaseIndex]) { BatchedParameters.SetShaderUniformBuffer(Parameter.BaseIndex, UniformBuffer); ShaderBindingState.UniformBuffers[Parameter.BaseIndex] = UniformBuffer; ShaderBindingState.MaxUniformBufferUsed = FMath::Max((int32)Parameter.BaseIndex, ShaderBindingState.MaxUniformBufferUsed); } } FRHISamplerState* const* RESTRICT SamplerBindings = SingleShaderBindings.GetSamplerStart(); const FShaderResourceParameterInfo* RESTRICT TextureSamplerParameters = SingleShaderBindings.ParameterMapInfo.TextureSamplers.GetData(); const int32 NumTextureSamplers = SingleShaderBindings.ParameterMapInfo.TextureSamplers.Num(); for (int32 SamplerIndex = 0; SamplerIndex < NumTextureSamplers; SamplerIndex++) { FShaderResourceParameterInfo Parameter = TextureSamplerParameters[SamplerIndex]; FRHISamplerState* Sampler = (FRHISamplerState*)SamplerBindings[SamplerIndex]; SetSamplerParameter(BatchedParameters, Parameter, Sampler); } const uint8* RESTRICT SRVType = SingleShaderBindings.GetSRVTypeStart(); FRHIResource* const* RESTRICT SRVBindings = SingleShaderBindings.GetSRVStart(); const FShaderResourceParameterInfo* RESTRICT SRVParameters = SingleShaderBindings.ParameterMapInfo.SRVs.GetData(); const uint32 NumSRVs = SingleShaderBindings.ParameterMapInfo.SRVs.Num(); for (uint32 SRVIndex = 0; SRVIndex < NumSRVs; SRVIndex++) { FShaderResourceParameterInfo Parameter = SRVParameters[SRVIndex]; uint32 TypeByteIndex = SRVIndex / 8; uint32 TypeBitIndex = SRVIndex % 8; if (SRVType[TypeByteIndex] & (1 << TypeBitIndex)) { FRHIShaderResourceView* SRV = (FRHIShaderResourceView*)SRVBindings[SRVIndex]; SetSrvParameter(BatchedParameters, Parameter, SRV); } else { FRHITexture* Texture = (FRHITexture*)SRVBindings[SRVIndex]; SetTextureParameter(BatchedParameters, Parameter, Texture); } } SetLooseParameters(BatchedParameters, SingleShaderBindings.ParameterMapInfo, SingleShaderBindings.GetLooseDataStart()); } void FReadOnlyMeshDrawSingleShaderBindings::SetShaderBindings( FRHIBatchedShaderParameters& BatchedParameters, const FReadOnlyMeshDrawSingleShaderBindings& RESTRICT SingleShaderBindings) { FRHIUniformBuffer* const* RESTRICT UniformBufferBindings = SingleShaderBindings.GetUniformBufferStart(); const FShaderUniformBufferParameterInfo* RESTRICT UniformBufferParameters = SingleShaderBindings.ParameterMapInfo.UniformBuffers.GetData(); const int32 NumUniformBuffers = SingleShaderBindings.ParameterMapInfo.UniformBuffers.Num(); for (int32 UniformBufferIndex = 0; UniformBufferIndex < NumUniformBuffers; UniformBufferIndex++) { FShaderUniformBufferParameterInfo Parameter = UniformBufferParameters[UniformBufferIndex]; FRHIUniformBuffer* UniformBuffer = UniformBufferBindings[UniformBufferIndex]; if (UniformBuffer) { BatchedParameters.SetShaderUniformBuffer(Parameter.BaseIndex, UniformBuffer); } } FRHISamplerState* const* RESTRICT SamplerBindings = SingleShaderBindings.GetSamplerStart(); const FShaderResourceParameterInfo* RESTRICT TextureSamplerParameters = SingleShaderBindings.ParameterMapInfo.TextureSamplers.GetData(); const int32 NumTextureSamplers = SingleShaderBindings.ParameterMapInfo.TextureSamplers.Num(); for (int32 SamplerIndex = 0; SamplerIndex < NumTextureSamplers; SamplerIndex++) { FShaderResourceParameterInfo Parameter = TextureSamplerParameters[SamplerIndex]; FRHISamplerState* Sampler = (FRHISamplerState*)SamplerBindings[SamplerIndex]; if (Sampler) { SetSamplerParameter(BatchedParameters, Parameter, Sampler); } } const uint8* RESTRICT SRVType = SingleShaderBindings.GetSRVTypeStart(); FRHIResource* const* RESTRICT SRVBindings = SingleShaderBindings.GetSRVStart(); const FShaderResourceParameterInfo* RESTRICT SRVParameters = SingleShaderBindings.ParameterMapInfo.SRVs.GetData(); const uint32 NumSRVs = SingleShaderBindings.ParameterMapInfo.SRVs.Num(); for (uint32 SRVIndex = 0; SRVIndex < NumSRVs; SRVIndex++) { FShaderResourceParameterInfo Parameter = SRVParameters[SRVIndex]; uint32 TypeByteIndex = SRVIndex / 8; uint32 TypeBitIndex = SRVIndex % 8; if (SRVType[TypeByteIndex] & (1 << TypeBitIndex)) { FRHIShaderResourceView* SRV = (FRHIShaderResourceView*)SRVBindings[SRVIndex]; if (SRV) { SetSrvParameter(BatchedParameters, Parameter, SRV); } } else { FRHITexture* Texture = (FRHITexture*)SRVBindings[SRVIndex]; if (Texture) { SetTextureParameter(BatchedParameters, Parameter, Texture); } } } SetLooseParameters(BatchedParameters, SingleShaderBindings.ParameterMapInfo, SingleShaderBindings.GetLooseDataStart()); } #if RHI_RAYTRACING FRayTracingLocalShaderBindings* FMeshDrawShaderBindings::SetRayTracingShaderBindings( FRayTracingLocalShaderBindingWriter* BindingWriter, uint32 ShaderIndexInPipeline, uint32 RecordIndex, uint32 UserData, ERayTracingLocalShaderBindingType BindingType) const { check(ShaderLayouts.Num() == 1); FReadOnlyMeshDrawSingleShaderBindings SingleShaderBindings(ShaderLayouts[0], GetData()); FRHIUniformBuffer* const* RESTRICT UniformBufferBindings = SingleShaderBindings.GetUniformBufferStart(); const FShaderUniformBufferParameterInfo* RESTRICT UniformBufferParameters = SingleShaderBindings.ParameterMapInfo.UniformBuffers.GetData(); const int32 NumUniformBufferParameters = SingleShaderBindings.ParameterMapInfo.UniformBuffers.Num(); checkf(SingleShaderBindings.ParameterMapInfo.TextureSamplers.Num() == 0, TEXT("Texture sampler parameters are not supported for ray tracing. UniformBuffers must be used for all resource binding.")); checkf(SingleShaderBindings.ParameterMapInfo.SRVs.Num() == 0, TEXT("SRV parameters are not supported for ray tracing. UniformBuffers must be used for all resource binding.")); // Measure parameter memory requirements int32 MaxUniformBufferIndex = -1; for (int32 UniformBufferIndex = 0; UniformBufferIndex < NumUniformBufferParameters; UniformBufferIndex++) { FShaderUniformBufferParameterInfo Parameter = UniformBufferParameters[UniformBufferIndex]; const FRHIUniformBuffer* UniformBuffer = UniformBufferBindings[UniformBufferIndex]; MaxUniformBufferIndex = FMath::Max((int32)Parameter.BaseIndex, MaxUniformBufferIndex); } const uint32 NumUniformBuffersToSet = MaxUniformBufferIndex + 1; const TMemoryImageArray& LooseParameterBuffers = SingleShaderBindings.ParameterMapInfo.LooseParameterBuffers; uint32 LooseParameterDataSize = 0; if (LooseParameterBuffers.Num()) { check(LooseParameterBuffers.Num() <= 1); const FShaderLooseParameterBufferInfo& LooseParameterBuffer = SingleShaderBindings.ParameterMapInfo.LooseParameterBuffers[0]; check(LooseParameterBuffer.BaseIndex == 0); for (int32 LooseParameterIndex = 0; LooseParameterIndex < LooseParameterBuffer.Parameters.Num(); LooseParameterIndex++) { FShaderLooseParameterInfo LooseParameter = LooseParameterBuffer.Parameters[LooseParameterIndex]; LooseParameterDataSize = FMath::Max(LooseParameterDataSize, LooseParameter.BaseIndex + LooseParameter.Size); } } // Vulkan places loose parameter data in the shader record which does not add to UB count checkf(MaxUniformBufferIndex + 1 == NumUniformBufferParameters + (GRHIGlobals.RayTracing.SupportsLooseParamsInShaderRecord ? 0 : LooseParameterBuffers.Num()), TEXT("Highest index of a uniform buffer was %d, but there were %d uniform buffer parameters and %d loose parameters"), MaxUniformBufferIndex, NumUniformBufferParameters, LooseParameterBuffers.Num()); // Allocate and fill bindings FRayTracingLocalShaderBindings& Bindings = BindingWriter->AddWithInlineParameters(NumUniformBuffersToSet, LooseParameterDataSize); Bindings.RecordIndex = RecordIndex; Bindings.ShaderIndexInPipeline = ShaderIndexInPipeline; Bindings.UserData = UserData; // UserData could be used to store material ID or any other kind of per-material constant. This can be retrieved in hit shaders via GetHitGroupUserData(). Bindings.BindingType = BindingType; for (int32 UniformBufferIndex = 0; UniformBufferIndex < NumUniformBufferParameters; UniformBufferIndex++) { FShaderUniformBufferParameterInfo Parameter = UniformBufferParameters[UniformBufferIndex]; const FRHIUniformBuffer* UniformBuffer = UniformBufferBindings[UniformBufferIndex]; Bindings.UniformBuffers[Parameter.BaseIndex] = const_cast(UniformBuffer); } if (LooseParameterBuffers.Num()) { const FShaderLooseParameterBufferInfo& LooseParameterBuffer = SingleShaderBindings.ParameterMapInfo.LooseParameterBuffers[0]; const uint8* LooseDataOffset = SingleShaderBindings.GetLooseDataStart(); #if DO_CHECK // Memzero the loose parameter data because it's used for validation on persistent SBT entries to make sure binding data is always the same FMemory::Memzero(Bindings.LooseParameterData, LooseParameterDataSize); #endif // DO_CHECK for (int32 LooseParameterIndex = 0; LooseParameterIndex < LooseParameterBuffer.Parameters.Num(); LooseParameterIndex++) { FShaderLooseParameterInfo LooseParameter = LooseParameterBuffer.Parameters[LooseParameterIndex]; FMemory::Memcpy(Bindings.LooseParameterData + LooseParameter.BaseIndex, LooseDataOffset, LooseParameter.Size); LooseDataOffset += LooseParameter.Size; } } return &Bindings; } FRayTracingLocalShaderBindings* FMeshDrawShaderBindings::SetRayTracingShaderBindingsForHitGroup( FRayTracingLocalShaderBindingWriter* BindingWriter, uint32 RecordIndex, const FRHIRayTracingGeometry* Geometry, uint32 GeometrySegmentIndex, uint32 HitGroupIndexInPipeline, ERayTracingLocalShaderBindingType BindingType) const { FRayTracingLocalShaderBindings* Bindings = SetRayTracingShaderBindings(BindingWriter, HitGroupIndexInPipeline, RecordIndex, 0, BindingType); Bindings->Geometry = Geometry; Bindings->SegmentIndex = GeometrySegmentIndex; return Bindings; } void FMeshDrawShaderBindings::SetRayTracingShaderBindingsForMissShader( FRHICommandList& RHICmdList, FRHIShaderBindingTable* SBT, uint32 RecordIndex, FRayTracingPipelineState* PipelineState, uint32 ShaderIndexInPipeline) const { check(ShaderLayouts.Num() == 1); FReadOnlyMeshDrawSingleShaderBindings SingleShaderBindings(ShaderLayouts[0], GetData()); FShaderBindingState BindingState; const int32 MaxUniformBuffers = UE_ARRAY_COUNT(BindingState.UniformBuffers); FRHIUniformBuffer* const* RESTRICT UniformBufferBindings = SingleShaderBindings.GetUniformBufferStart(); const FShaderUniformBufferParameterInfo* RESTRICT UniformBufferParameters = SingleShaderBindings.ParameterMapInfo.UniformBuffers.GetData(); const int32 NumUniformBufferParameters = SingleShaderBindings.ParameterMapInfo.UniformBuffers.Num(); checkf(SingleShaderBindings.ParameterMapInfo.TextureSamplers.Num() == 0, TEXT("Texture sampler parameters are not supported for ray tracing. UniformBuffers must be used for all resource binding.")); checkf(SingleShaderBindings.ParameterMapInfo.SRVs.Num() == 0, TEXT("SRV parameters are not supported for ray tracing. UniformBuffers must be used for all resource binding.")); // Measure parameter memory requirements int32 MaxUniformBufferUsed = -1; for (int32 UniformBufferIndex = 0; UniformBufferIndex < NumUniformBufferParameters; UniformBufferIndex++) { FShaderUniformBufferParameterInfo Parameter = UniformBufferParameters[UniformBufferIndex]; checkSlow(Parameter.BaseIndex < MaxUniformBuffers); FRHIUniformBuffer* UniformBuffer = UniformBufferBindings[UniformBufferIndex]; if (Parameter.BaseIndex < MaxUniformBuffers) { BindingState.UniformBuffers[Parameter.BaseIndex] = UniformBuffer; MaxUniformBufferUsed = FMath::Max((int32)Parameter.BaseIndex, MaxUniformBufferUsed); } } checkf(SingleShaderBindings.ParameterMapInfo.TextureSamplers.Num() == 0, TEXT("Texture sampler parameters are not supported for ray tracing. UniformBuffers must be used for all resource binding.")); checkf(SingleShaderBindings.ParameterMapInfo.SRVs.Num() == 0, TEXT("SRV parameters are not supported for ray tracing. UniformBuffers must be used for all resource binding.")); checkf(SingleShaderBindings.ParameterMapInfo.LooseParameterBuffers.Num() == 0, TEXT("Ray tracing miss shaders may not have loose parameters")); uint32 NumUniformBuffersToSet = MaxUniformBufferUsed + 1; const uint32 UserData = 0; // UserData could be used to store material ID or any other kind of per-material constant. This can be retrieved in hit shaders via GetHitGroupUserData(). RHICmdList.SetRayTracingMissShader( SBT, RecordIndex, PipelineState, ShaderIndexInPipeline, NumUniformBuffersToSet, BindingState.UniformBuffers, UserData); } #endif // RHI_RAYTRACING FGraphicsMinimalPipelineStateId FGraphicsMinimalPipelineStateId::GetPersistentId(const FGraphicsMinimalPipelineStateInitializer& InPipelineState) { Experimental::FHashElementId TableId; #if MESH_DRAW_COMMAND_DEBUG_DATA int32 DebugSalt; #endif auto hash = PersistentIdTable.ComputeHash(InPipelineState); { FRWScopeLock Lock(PersistentIdTableLock, SLT_ReadOnly); #if UE_BUILD_DEBUG FGraphicsMinimalPipelineStateInitializer PipelineStateDebug = FGraphicsMinimalPipelineStateInitializer(InPipelineState); check(GetTypeHash(PipelineStateDebug) == GetTypeHash(InPipelineState)); check(PipelineStateDebug == InPipelineState); #endif TableId = PersistentIdTable.FindIdByHash(hash, InPipelineState); if (!TableId.IsValid()) { Lock.ReleaseReadOnlyLockAndAcquireWriteLock_USE_WITH_CAUTION(); TableId = PersistentIdTable.FindOrAddIdByHash(hash, InPipelineState, FRefCountedGraphicsMinimalPipelineState()); } FRefCountedGraphicsMinimalPipelineState& Value = PersistentIdTable.GetByElementId(TableId).Value; // Needs to be atomic, so condition is only true on first increment if (Value.RefNum.fetch_add(1) == 0) { if (!NeedsShaderInitialisation) { NeedsShaderInitialisation = true; } ReffedItemCount++; } #if MESH_DRAW_COMMAND_DEBUG_DATA DebugSalt = Value.DebugSalt; #endif } checkf(TableId.GetIndex() < (MAX_uint32 >> 2), TEXT("Persistent FGraphicsMinimalPipelineStateId table overflow!")); FGraphicsMinimalPipelineStateId Ret; Ret.bValid = 1; Ret.bComesFromLocalPipelineStateSet = 0; Ret.SetElementIndex = TableId.GetIndex(); #if MESH_DRAW_COMMAND_DEBUG_DATA Ret.DebugSalt = DebugSalt; #endif return Ret; } void FGraphicsMinimalPipelineStateId::InitializePersistentIds() { TRACE_CPUPROFILER_EVENT_SCOPE(InitializePersistentMdcIds); FRWScopeLock WriteLock(PersistentIdTableLock, SLT_Write); if (NeedsShaderInitialisation) { for (TPair& Element : PersistentIdTable) { Element.Key.BoundShaderState.LazilyInitShaders(); } NeedsShaderInitialisation = false; } } void FGraphicsMinimalPipelineStateId::RemovePersistentId(FGraphicsMinimalPipelineStateId Id) { check(!Id.bComesFromLocalPipelineStateSet && Id.bValid); { FRWScopeLock WriteLock(PersistentIdTableLock, SLT_Write); FRefCountedGraphicsMinimalPipelineState& RefCountedStateInitializer = PersistentIdTable.GetByElementId(Id.SetElementIndex).Value; check(RefCountedStateInitializer.RefNum > 0); --RefCountedStateInitializer.RefNum; if (RefCountedStateInitializer.RefNum == 0) { ReffedItemCount--; if (bIsIdTableFrozen == false) { PersistentIdTable.RemoveByElementId(Id.SetElementIndex); } } } } void FGraphicsMinimalPipelineStateId::FreezeIdTable(bool bFreeze) { if (bFreeze != bIsIdTableFrozen) { FRWScopeLock WriteLock(PersistentIdTableLock, SLT_Write); bIsIdTableFrozen = bFreeze; // When set back to false, do a pass through the table and clean up any zero ref items. Zero ref items usually don't happen // in practice given the context of how this function is used during calls to FPrimitiveSceneInfo::UpdateStaticMeshes where // multiple scene renderers are running (i.e. nDisplay multi-view). That function removes and re-creates draw commands for a // set of render proxies, which we expect to produce the same set of pipeline cache IDs, given that no game logic can run // to change any state in the render proxies between renders of the same scene. We could assert, but cleaning up the zero // reference items isn't an unreasonable option either (in case someone tries to use this function for other purposes)... if (bFreeze == false) { // We can detect whether we need to scan the table for zero ref items, by checking whether the number of items with // non-zero ref count equals the number of items in the table. if (PersistentIdTable.Num() != ReffedItemCount) { // There's a bug in our reference counting if the reffed item count is more than the items actually in the table check(PersistentIdTable.Num() > ReffedItemCount); // Remove zero reffed items for (auto PersistentIdIterator = PersistentIdTable.begin(); PersistentIdIterator != PersistentIdTable.end(); ++PersistentIdIterator) { if (PersistentIdTable.GetByElementId(PersistentIdIterator.GetElementId()).Value.RefNum == 0) { PersistentIdTable.RemoveByElementId(PersistentIdIterator.GetElementId()); } } } } } } FGraphicsMinimalPipelineStateId FGraphicsMinimalPipelineStateId::GetPipelineStateId(const FGraphicsMinimalPipelineStateInitializer& InPipelineState, FGraphicsMinimalPipelineStateSet& InOutPassSet, bool& InNeedsShaderInitialisation) { FGraphicsMinimalPipelineStateId Ret; Ret.bValid = 1; Ret.bComesFromLocalPipelineStateSet = 1; #if UE_BUILD_DEBUG FGraphicsMinimalPipelineStateInitializer PipelineStateDebug = FGraphicsMinimalPipelineStateInitializer(InPipelineState); check(GetTypeHash(PipelineStateDebug) == GetTypeHash(InPipelineState)); check(PipelineStateDebug == InPipelineState); #endif Experimental::FHashElementId TableIndex = InOutPassSet.FindOrAddId(InPipelineState); #if UE_BUILD_DEBUG check(InOutPassSet.GetByElementId(TableIndex) == InPipelineState); #endif InNeedsShaderInitialisation = InNeedsShaderInitialisation || InPipelineState.BoundShaderState.NeedsShaderInitialisation(); checkf(TableIndex.GetIndex() < (MAX_uint32 >> 2), TEXT("One frame FGraphicsMinimalPipelineStateId table overflow!")); Ret.SetElementIndex = TableIndex.GetIndex(); #if MESH_DRAW_COMMAND_DEBUG_DATA Ret.DebugSalt = 0; // Salt is ignored for pipelines from local pipeline state set, just initialize to zero #endif return Ret; } void FGraphicsMinimalPipelineStateId::ResetLocalPipelineIdTableSize() { #if MESH_DRAW_COMMAND_DEBUG_DATA int32 CapturedPipelineIdTableSize; do { CapturedPipelineIdTableSize = CurrentLocalPipelineIdTableSize; }while (!CurrentLocalPipelineIdTableSize.compare_exchange_strong(CapturedPipelineIdTableSize, 0)); LocalPipelineIdTableSize = CapturedPipelineIdTableSize; #endif //MESH_DRAW_COMMAND_DEBUG_DATA } void FGraphicsMinimalPipelineStateId::AddSizeToLocalPipelineIdTableSize(SIZE_T Size) { #if MESH_DRAW_COMMAND_DEBUG_DATA CurrentLocalPipelineIdTableSize += int32(Size); #endif } FMeshDrawShaderBindings::~FMeshDrawShaderBindings() { Release(); } void FMeshDrawShaderBindings::Initialize(const FMeshProcessorShaders& Shaders) { const int32 NumShaderFrequencies = (Shaders.VertexShader.IsValid() ? 1 : 0) + (Shaders.PixelShader.IsValid() ? 1 : 0) + (Shaders.GeometryShader.IsValid() ? 1 : 0) + (Shaders.ComputeShader.IsValid() ? 1 : 0) + (Shaders.WorkGraphShader.IsValid() ? 1 : 0) #if RHI_RAYTRACING + (Shaders.RayTracingShader.IsValid() ? 1 : 0) #endif ; ShaderLayouts.Empty(NumShaderFrequencies); int32 ShaderBindingDataSize = 0; if (Shaders.VertexShader.IsValid()) { ShaderLayouts.Add(FMeshDrawShaderBindingsLayout(Shaders.VertexShader)); ShaderBindingDataSize += ShaderLayouts.Last().GetDataSizeBytes(); check(ShaderFrequencyBits < (1 << SF_Vertex)); ShaderFrequencyBits |= (1 << SF_Vertex); } if (Shaders.PixelShader.IsValid()) { ShaderLayouts.Add(FMeshDrawShaderBindingsLayout(Shaders.PixelShader)); ShaderBindingDataSize += ShaderLayouts.Last().GetDataSizeBytes(); check(ShaderFrequencyBits < (1 << SF_Pixel)); ShaderFrequencyBits |= (1 << SF_Pixel); } if (Shaders.GeometryShader.IsValid()) { ShaderLayouts.Add(FMeshDrawShaderBindingsLayout(Shaders.GeometryShader)); ShaderBindingDataSize += ShaderLayouts.Last().GetDataSizeBytes(); check(ShaderFrequencyBits < (1 << SF_Geometry)); ShaderFrequencyBits |= (1 << SF_Geometry); } if (Shaders.ComputeShader.IsValid()) { ShaderLayouts.Add(FMeshDrawShaderBindingsLayout(Shaders.ComputeShader)); ShaderBindingDataSize += ShaderLayouts.Last().GetDataSizeBytes(); check(ShaderFrequencyBits < (1 << SF_Compute)); ShaderFrequencyBits |= (1 << SF_Compute); } if (Shaders.WorkGraphShader.IsValid()) { ShaderLayouts.Add(FMeshDrawShaderBindingsLayout(Shaders.WorkGraphShader)); ShaderBindingDataSize += ShaderLayouts.Last().GetDataSizeBytes(); check(ShaderFrequencyBits < (1 << SF_WorkGraphComputeNode)); ShaderFrequencyBits |= (1 << SF_WorkGraphComputeNode); } #if RHI_RAYTRACING if (Shaders.RayTracingShader.IsValid()) { ShaderLayouts.Add(FMeshDrawShaderBindingsLayout(Shaders.RayTracingShader)); ShaderBindingDataSize += ShaderLayouts.Last().GetDataSizeBytes(); const EShaderFrequency Frequency = Shaders.RayTracingShader->GetFrequency(); check(ShaderFrequencyBits < (1 << Frequency)); ShaderFrequencyBits |= (1 << Frequency); } #endif checkSlow(ShaderLayouts.Num() == NumShaderFrequencies); if (ShaderBindingDataSize > 0) { AllocateZeroed(ShaderBindingDataSize); } } void FMeshDrawShaderBindings::Initialize(const TShaderRef& Shader) { const int32 NumShaderFrequencies = (Shader.IsValid() ? 1 : 0); ShaderLayouts.Empty(NumShaderFrequencies); int32 ShaderBindingDataSize = 0; if (Shader.IsValid()) { ShaderLayouts.Add(FMeshDrawShaderBindingsLayout(Shader)); ShaderBindingDataSize += ShaderLayouts.Last().GetDataSizeBytes(); const EShaderFrequency Frequency = Shader->GetFrequency(); check(ShaderFrequencyBits < (1 << Frequency)); ShaderFrequencyBits |= (1 << Frequency); } checkSlow(ShaderLayouts.Num() == NumShaderFrequencies); if (ShaderBindingDataSize > 0) { AllocateZeroed(ShaderBindingDataSize); } } void FMeshDrawShaderBindings::Finalize(const FMeshProcessorShaders* ShadersForDebugging) { #if VALIDATE_MESH_COMMAND_BINDINGS if (!ShadersForDebugging) { return; } const uint8* ShaderBindingDataPtr = GetData(); uint32 ShaderFrequencyBitIndex = ~0; for (int32 ShaderBindingsIndex = 0; ShaderBindingsIndex < ShaderLayouts.Num(); ShaderBindingsIndex++) { EShaderFrequency Frequency = SF_NumFrequencies; while (true) { ShaderFrequencyBitIndex++; if ((ShaderFrequencyBits & (1 << ShaderFrequencyBitIndex)) != 0) { Frequency = EShaderFrequency(ShaderFrequencyBitIndex); break; } } check(Frequency < SF_NumFrequencies); const FMeshDrawShaderBindingsLayout& ShaderLayout = ShaderLayouts[ShaderBindingsIndex]; TShaderRef Shader = ShadersForDebugging->GetShader(Frequency); check(Shader.IsValid()); const FVertexFactoryType* VFType = Shader.GetVertexFactoryType(); FReadOnlyMeshDrawSingleShaderBindings SingleShaderBindings(ShaderLayout, ShaderBindingDataPtr); FRHIUniformBuffer* const* UniformBufferBindings = SingleShaderBindings.GetUniformBufferStart(); for (int32 BindingIndex = 0; BindingIndex < ShaderLayout.ParameterMapInfo.UniformBuffers.Num(); BindingIndex++) { FShaderUniformBufferParameterInfo ParameterInfo = ShaderLayout.ParameterMapInfo.UniformBuffers[BindingIndex]; FRHIUniformBuffer* UniformBufferValue = UniformBufferBindings[BindingIndex]; if (!UniformBufferValue) { // Search the automatically bound uniform buffers for more context if available const FShaderParametersMetadata* AutomaticallyBoundUniformBufferStruct = Shader->FindAutomaticallyBoundUniformBufferStruct(ParameterInfo.BaseIndex); if (AutomaticallyBoundUniformBufferStruct) { ensureMsgf( UniformBufferValue || EnumHasAnyFlags(AutomaticallyBoundUniformBufferStruct->GetBindingFlags(), EUniformBufferBindingFlags::Static) || EnumHasAnyFlags(AutomaticallyBoundUniformBufferStruct->GetUsageFlags(), FShaderParametersMetadata::EUsageFlags::ManuallyBoundByPass), TEXT("Shader %s with vertex factory %s never set automatically bound uniform buffer at BaseIndex %i. Expected buffer of type %s. This can cause GPU hangs, depending on how the shader uses it."), Shader.GetType()->GetName(), VFType ? VFType->GetName() : TEXT("nullptr"), ParameterInfo.BaseIndex, AutomaticallyBoundUniformBufferStruct->GetStructTypeName()); } else { ensureMsgf(UniformBufferValue, TEXT("Shader %s with vertex factory %s never set uniform buffer at BaseIndex %i. This can cause GPU hangs, depending on how the shader uses it."), VFType ? VFType->GetName() : TEXT("nullptr"), Shader.GetType()->GetName(), ParameterInfo.BaseIndex); } } } FRHISamplerState* const* SamplerBindings = SingleShaderBindings.GetSamplerStart(); for (int32 BindingIndex = 0; BindingIndex < ShaderLayout.ParameterMapInfo.TextureSamplers.Num(); BindingIndex++) { FShaderResourceParameterInfo ParameterInfo = ShaderLayout.ParameterMapInfo.TextureSamplers[BindingIndex]; const FRHISamplerState* SamplerValue = SamplerBindings[BindingIndex]; ensureMsgf(SamplerValue, TEXT("Shader %s with vertex factory %s never set sampler at BaseIndex %u. This can cause GPU hangs, depending on how the shader uses it."), Shader.GetType()->GetName(), VFType ? VFType->GetName() : TEXT("nullptr"), ParameterInfo.BaseIndex); } const uint8* RESTRICT SRVType = SingleShaderBindings.GetSRVTypeStart(); FRHIResource* const* RESTRICT SRVBindings = SingleShaderBindings.GetSRVStart(); const FShaderResourceParameterInfo* RESTRICT SRVParameters = SingleShaderBindings.ParameterMapInfo.SRVs.GetData(); const uint32 NumSRVs = SingleShaderBindings.ParameterMapInfo.SRVs.Num(); for (uint32 SRVIndex = 0; SRVIndex < NumSRVs; SRVIndex++) { FShaderResourceParameterInfo Parameter = SRVParameters[SRVIndex]; uint32 TypeByteIndex = SRVIndex / 8; uint32 TypeBitIndex = SRVIndex % 8; if (SRVType[TypeByteIndex] & (1 << TypeBitIndex)) { FRHIShaderResourceView* SRV = (FRHIShaderResourceView*)SRVBindings[SRVIndex]; ensureMsgf(SRV, TEXT("Shader %s with vertex factory %s never set SRV at BaseIndex %u. This can cause GPU hangs, depending on how the shader uses it."), Shader.GetType()->GetName(), VFType ? VFType->GetName() : TEXT("nullptr"), Parameter.BaseIndex); } else { FRHITexture* Texture = (FRHITexture*)SRVBindings[SRVIndex]; ensureMsgf(Texture, TEXT("Shader %s with vertex factory %s never set texture at BaseIndex %u. This can cause GPU hangs, depending on how the shader uses it."), Shader.GetType()->GetName(), VFType ? VFType->GetName() : TEXT("nullptr"), Parameter.BaseIndex); } } ShaderBindingDataPtr += ShaderLayout.GetDataSizeBytes(); } #endif } void FMeshDrawShaderBindings::CopyFrom(const FMeshDrawShaderBindings& Other) { Release(); ShaderLayouts = Other.ShaderLayouts; ShaderFrequencyBits = Other.ShaderFrequencyBits; Allocate(Other.Size); if (Other.UsesInlineStorage()) { Data = Other.Data; } else { FPlatformMemory::Memcpy(GetData(), Other.GetData(), Size); } #if VALIDATE_UNIFORM_BUFFER_LIFETIME uint8* ShaderBindingDataPtr = GetData(); for (int32 ShaderBindingsIndex = 0; ShaderBindingsIndex < ShaderLayouts.Num(); ShaderBindingsIndex++) { FMeshDrawSingleShaderBindings SingleShaderBindings(ShaderLayouts[ShaderBindingsIndex], ShaderBindingDataPtr); const FRHIUniformBuffer** RESTRICT UniformBufferBindings = SingleShaderBindings.GetUniformBufferStart(); const int32 NumUniformBuffers = SingleShaderBindings.ParameterMapInfo.UniformBuffers.Num(); for (int32 UniformBufferIndex = 0; UniformBufferIndex < NumUniformBuffers; UniformBufferIndex++) { const FRHIUniformBuffer* UniformBuffer = UniformBufferBindings[UniformBufferIndex]; if (UniformBuffer) { UniformBuffer->NumMeshCommandReferencesForDebugging++; } } ShaderBindingDataPtr += ShaderLayouts[ShaderBindingsIndex].GetDataSizeBytes(); } #endif } void FMeshDrawShaderBindings::Release() { #if VALIDATE_UNIFORM_BUFFER_LIFETIME uint8* ShaderBindingDataPtr = GetData(); for (int32 ShaderBindingsIndex = 0; ShaderBindingsIndex < ShaderLayouts.Num(); ShaderBindingsIndex++) { FMeshDrawSingleShaderBindings SingleShaderBindings(ShaderLayouts[ShaderBindingsIndex], ShaderBindingDataPtr); const FRHIUniformBuffer** RESTRICT UniformBufferBindings = SingleShaderBindings.GetUniformBufferStart(); const int32 NumUniformBuffers = SingleShaderBindings.ParameterMapInfo.UniformBuffers.Num(); for (int32 UniformBufferIndex = 0; UniformBufferIndex < NumUniformBuffers; UniformBufferIndex++) { if (const FRHIUniformBuffer* UniformBuffer = UniformBufferBindings[UniformBufferIndex]) { const int32 NumMeshCommandReferencesForDebugging = --UniformBuffer->NumMeshCommandReferencesForDebugging; check(NumMeshCommandReferencesForDebugging >= 0); } } ShaderBindingDataPtr += ShaderLayouts[ShaderBindingsIndex].GetDataSizeBytes(); } #endif if (Size > sizeof(FData)) { delete[] Data.GetHeapData(); } Size = 0; Data.SetHeapData(nullptr); } bool FMinimalBoundShaderStateInput::AllowSkipUnloadedShaders() const { return GSkipUnloadedShaders; } void FGraphicsMinimalPipelineStateInitializer::SetupBoundShaderState(FRHIVertexDeclaration* VertexDeclaration, const FMeshProcessorShaders& Shaders) { BoundShaderState = FMinimalBoundShaderStateInput(); BoundShaderState.VertexDeclarationRHI = VertexDeclaration; checkf(Shaders.VertexShader.IsValid(), TEXT("Can't render without a vertex shader")); if(Shaders.VertexShader.IsValid()) { checkSlow(Shaders.VertexShader->GetFrequency() == SF_Vertex); BoundShaderState.VertexShaderResource = Shaders.VertexShader.GetResource(); BoundShaderState.VertexShaderIndex = Shaders.VertexShader->GetResourceIndex(); check(BoundShaderState.VertexShaderResource->IsValidShaderIndex(BoundShaderState.VertexShaderIndex)); } if (Shaders.PixelShader.IsValid()) { checkSlow(Shaders.PixelShader->GetFrequency() == SF_Pixel); BoundShaderState.PixelShaderResource = Shaders.PixelShader.GetResource(); BoundShaderState.PixelShaderIndex = Shaders.PixelShader->GetResourceIndex(); check(BoundShaderState.PixelShaderResource->IsValidShaderIndex(BoundShaderState.PixelShaderIndex)); } #if PLATFORM_SUPPORTS_GEOMETRY_SHADERS if (Shaders.GeometryShader.IsValid()) { checkSlow(Shaders.GeometryShader->GetFrequency() == SF_Geometry); BoundShaderState.GeometryShaderResource = Shaders.GeometryShader.GetResource(); BoundShaderState.GeometryShaderIndex = Shaders.GeometryShader->GetResourceIndex(); check(BoundShaderState.GeometryShaderResource->IsValidShaderIndex(BoundShaderState.GeometryShaderIndex)); } #endif // PLATFORM_SUPPORTS_GEOMETRY_SHADERS } void FGraphicsMinimalPipelineStateInitializer::ComputeStatePrecachePSOHash() { if(StatePrecachePSOHash == 0) { StatePrecachePSOHash = AsGraphicsPipelineStateInitializer().StatePrecachePSOHash; check(StatePrecachePSOHash); } else { checkSlow(StatePrecachePSOHash == AsGraphicsPipelineStateInitializer().StatePrecachePSOHash); } } void FMeshDrawCommand::SetDrawParametersAndFinalize( const FMeshBatch& MeshBatch, int32 BatchElementIndex, FGraphicsMinimalPipelineStateId PipelineId, const FMeshProcessorShaders* ShadersForDebugging) { const FMeshBatchElement& BatchElement = MeshBatch.Elements[BatchElementIndex]; check(!BatchElement.IndexBuffer || (BatchElement.IndexBuffer && BatchElement.IndexBuffer->IsInitialized() && BatchElement.IndexBuffer->IndexBufferRHI)); IndexBuffer = BatchElement.IndexBuffer ? BatchElement.IndexBuffer->IndexBufferRHI.GetReference() : nullptr; FirstIndex = BatchElement.FirstIndex; NumPrimitives = BatchElement.NumPrimitives; NumInstances = BatchElement.NumInstances; // If the mesh batch has a valid dynamic index buffer, use it instead if (BatchElement.DynamicIndexBuffer.IsValid() && BatchElement.DynamicIndexBuffer.IndexBuffer->IsInitialized()) { check(!BatchElement.DynamicIndexBuffer.IndexBuffer || (BatchElement.DynamicIndexBuffer.IndexBuffer && BatchElement.DynamicIndexBuffer.IndexBuffer->IsInitialized() && BatchElement.DynamicIndexBuffer.IndexBuffer->IndexBufferRHI)); IndexBuffer = BatchElement.DynamicIndexBuffer.IndexBuffer ? BatchElement.DynamicIndexBuffer.IndexBuffer->IndexBufferRHI.GetReference() : nullptr; FirstIndex = BatchElement.DynamicIndexBuffer.FirstIndex; PrimitiveType = EPrimitiveType(BatchElement.DynamicIndexBuffer.PrimitiveType); } if (NumPrimitives > 0) { VertexParams.BaseVertexIndex = BatchElement.BaseVertexIndex; VertexParams.NumVertices = BatchElement.MaxVertexIndex - BatchElement.MinVertexIndex + 1; checkf(!BatchElement.IndirectArgsBuffer, TEXT("FMeshBatchElement::NumPrimitives must be set to 0 when a IndirectArgsBuffer is used")); } else { checkf(BatchElement.IndirectArgsBuffer, TEXT("It is only valid to set BatchElement.NumPrimitives == 0 when a IndirectArgsBuffer is used")); IndirectArgs.Buffer = BatchElement.IndirectArgsBuffer; IndirectArgs.Offset = BatchElement.IndirectArgsOffset; } Finalize(PipelineId, ShadersForDebugging); } void FMeshDrawShaderBindings::SetOnCommandList(FRHICommandList& RHICmdList, const FBoundShaderStateInput& Shaders, FShaderBindingState* StateCacheShaderBindings) const { const uint8* ShaderBindingDataPtr = GetData(); uint32 ShaderFrequencyBitIndex = ~0; for (int32 ShaderBindingsIndex = 0; ShaderBindingsIndex < ShaderLayouts.Num(); ShaderBindingsIndex++) { EShaderFrequency Frequency = SF_NumFrequencies; while (true) { ShaderFrequencyBitIndex++; if ((ShaderFrequencyBits & (1 << ShaderFrequencyBitIndex)) != 0) { Frequency = EShaderFrequency(ShaderFrequencyBitIndex); break; } } check(Frequency < SF_NumFrequencies); FReadOnlyMeshDrawSingleShaderBindings SingleShaderBindings(ShaderLayouts[ShaderBindingsIndex], ShaderBindingDataPtr); FShaderBindingState& ShaderBindingState = StateCacheShaderBindings[Frequency]; if (Frequency == SF_Vertex) { FRHIBatchedShaderParameters& BatchedParameters = RHICmdList.GetScratchShaderParameters(); FReadOnlyMeshDrawSingleShaderBindings::SetShaderBindings(BatchedParameters, SingleShaderBindings, ShaderBindingState); RHICmdList.SetBatchedShaderParameters(Shaders.VertexShaderRHI, BatchedParameters); } else if (Frequency == SF_Pixel) { FRHIBatchedShaderParameters& BatchedParameters = RHICmdList.GetScratchShaderParameters(); FReadOnlyMeshDrawSingleShaderBindings::SetShaderBindings(BatchedParameters, SingleShaderBindings, ShaderBindingState); RHICmdList.SetBatchedShaderParameters(Shaders.PixelShaderRHI, BatchedParameters); } else if (Frequency == SF_Geometry) { FRHIBatchedShaderParameters& BatchedParameters = RHICmdList.GetScratchShaderParameters(); FReadOnlyMeshDrawSingleShaderBindings::SetShaderBindings(BatchedParameters, SingleShaderBindings, ShaderBindingState); RHICmdList.SetBatchedShaderParameters(Shaders.GetGeometryShader(), BatchedParameters); } else { checkf(0, TEXT("Unknown shader frequency")); } ShaderBindingDataPtr += ShaderLayouts[ShaderBindingsIndex].GetDataSizeBytes(); } } void FMeshDrawShaderBindings::SetParameters(FRHIBatchedShaderParameters& BatchedParameters, class FShaderBindingState* StateCacheShaderBindings) const { check(ShaderLayouts.Num() == 1); FReadOnlyMeshDrawSingleShaderBindings SingleShaderBindings(ShaderLayouts[0], GetData()); if (StateCacheShaderBindings != nullptr) { FReadOnlyMeshDrawSingleShaderBindings::SetShaderBindings(BatchedParameters, SingleShaderBindings, *StateCacheShaderBindings); } else { FReadOnlyMeshDrawSingleShaderBindings::SetShaderBindings(BatchedParameters, SingleShaderBindings); } } void FMeshDrawShaderBindings::SetOnCommandList(FRHIComputeCommandList& RHICmdList, FRHIComputeShader* Shader, FShaderBindingState* StateCacheShaderBindings) const { FRHIBatchedShaderParameters& BatchedParameters = RHICmdList.GetScratchShaderParameters(); SetParameters(BatchedParameters, StateCacheShaderBindings); RHICmdList.SetBatchedShaderParameters(Shader, BatchedParameters); } bool FMeshDrawShaderBindings::MatchesForDynamicInstancing(const FMeshDrawShaderBindings& Rhs) const { if (ShaderFrequencyBits != Rhs.ShaderFrequencyBits) { return false; } if (ShaderLayouts.Num() != Rhs.ShaderLayouts.Num()) { return false; } for (int Index = 0; Index < ShaderLayouts.Num(); Index++) { if (!(ShaderLayouts[Index] == Rhs.ShaderLayouts[Index])) { return false; } } const uint8* ShaderBindingDataPtr = GetData(); const uint8* OtherShaderBindingDataPtr = Rhs.GetData(); for (int32 ShaderBindingsIndex = 0; ShaderBindingsIndex < ShaderLayouts.Num(); ShaderBindingsIndex++) { FReadOnlyMeshDrawSingleShaderBindings SingleShaderBindings(ShaderLayouts[ShaderBindingsIndex], ShaderBindingDataPtr); FReadOnlyMeshDrawSingleShaderBindings OtherSingleShaderBindings(Rhs.ShaderLayouts[ShaderBindingsIndex], OtherShaderBindingDataPtr); if (SingleShaderBindings.ParameterMapInfo.LooseParameterBuffers.Num()) { const uint8* LooseBindings = SingleShaderBindings.GetLooseDataStart(); const uint8* OtherLooseBindings = OtherSingleShaderBindings.GetLooseDataStart(); const uint32 LooseLength = SingleShaderBindings.GetLooseDataSizeBytes(); const uint32 OtherLength = OtherSingleShaderBindings.GetLooseDataSizeBytes(); if (LooseLength != OtherLength) { return false; } if (memcmp(LooseBindings, OtherLooseBindings, LooseLength) != 0) { return false; } } FRHISamplerState* const* SamplerBindings = SingleShaderBindings.GetSamplerStart(); FRHISamplerState* const* OtherSamplerBindings = OtherSingleShaderBindings.GetSamplerStart(); for (int32 SamplerIndex = 0; SamplerIndex < SingleShaderBindings.ParameterMapInfo.TextureSamplers.Num(); SamplerIndex++) { const FRHIResource* Sampler = SamplerBindings[SamplerIndex]; const FRHIResource* OtherSampler = OtherSamplerBindings[SamplerIndex]; if (Sampler != OtherSampler) { return false; } } FRHIResource* const* SrvBindings = SingleShaderBindings.GetSRVStart(); FRHIResource* const* OtherSrvBindings = OtherSingleShaderBindings.GetSRVStart(); for (int32 SrvIndex = 0; SrvIndex < SingleShaderBindings.ParameterMapInfo.SRVs.Num(); SrvIndex++) { const FRHIResource* Srv = SrvBindings[SrvIndex]; const FRHIResource* OtherSrv = OtherSrvBindings[SrvIndex]; if (Srv != OtherSrv) { return false; } } FRHIUniformBuffer* const* UniformBufferBindings = SingleShaderBindings.GetUniformBufferStart(); FRHIUniformBuffer* const* OtherUniformBufferBindings = OtherSingleShaderBindings.GetUniformBufferStart(); for (int32 UniformBufferIndex = 0; UniformBufferIndex < SingleShaderBindings.ParameterMapInfo.UniformBuffers.Num(); UniformBufferIndex++) { const FRHIUniformBuffer* UniformBuffer = UniformBufferBindings[UniformBufferIndex]; const FRHIUniformBuffer* OtherUniformBuffer = OtherUniformBufferBindings[UniformBufferIndex]; if (UniformBuffer != OtherUniformBuffer) { return false; } } ShaderBindingDataPtr += ShaderLayouts[ShaderBindingsIndex].GetDataSizeBytes(); OtherShaderBindingDataPtr += Rhs.ShaderLayouts[ShaderBindingsIndex].GetDataSizeBytes(); } return true; } uint32 FMeshDrawShaderBindings::GetDynamicInstancingHash() const { //add and initialize any leftover padding within the struct to avoid unstable keys struct FHashKey { uint32 LooseParametersHash = 0; uint32 UniformBufferHash = 0; uint16 Size; uint16 Frequencies; static inline uint32 PointerHash(const void* Key) { #if PLATFORM_64BITS // Ignoring the lower 4 bits since they are likely zero anyway. // Higher bits are more significant in 64 bit builds. return reinterpret_cast(Key) >> 4; #else return reinterpret_cast(Key); #endif }; static inline uint32 HashCombine(uint32 A, uint32 B) { return A ^ (B + 0x9e3779b9 + (A << 6) + (A >> 2)); } } HashKey; HashKey.Size = Size; HashKey.Frequencies = ShaderFrequencyBits; const uint8* ShaderBindingDataPtr = GetData(); for (int32 ShaderBindingsIndex = 0; ShaderBindingsIndex < ShaderLayouts.Num(); ShaderBindingsIndex++) { FReadOnlyMeshDrawSingleShaderBindings SingleShaderBindings(ShaderLayouts[ShaderBindingsIndex], ShaderBindingDataPtr); if (SingleShaderBindings.ParameterMapInfo.LooseParameterBuffers.Num()) { const uint8* LooseBindings = SingleShaderBindings.GetLooseDataStart(); uint32 Length = SingleShaderBindings.GetLooseDataSizeBytes(); HashKey.LooseParametersHash = uint32(CityHash64((const char*)LooseBindings, Length)); } FRHISamplerState* const* SamplerBindings = SingleShaderBindings.GetSamplerStart(); for (int32 SamplerIndex = 0; SamplerIndex < SingleShaderBindings.ParameterMapInfo.TextureSamplers.Num(); SamplerIndex++) { const FRHIResource* Sampler = SamplerBindings[SamplerIndex]; HashKey.LooseParametersHash = FHashKey::HashCombine(FHashKey::PointerHash(Sampler), HashKey.LooseParametersHash); } FRHIResource* const* SrvBindings = SingleShaderBindings.GetSRVStart(); for (int32 SrvIndex = 0; SrvIndex < SingleShaderBindings.ParameterMapInfo.SRVs.Num(); SrvIndex++) { const FRHIResource* Srv = SrvBindings[SrvIndex]; HashKey.LooseParametersHash = FHashKey::HashCombine(FHashKey::PointerHash(Srv), HashKey.LooseParametersHash); } FRHIUniformBuffer* const* UniformBufferBindings = SingleShaderBindings.GetUniformBufferStart(); for (int32 UniformBufferIndex = 0; UniformBufferIndex < SingleShaderBindings.ParameterMapInfo.UniformBuffers.Num(); UniformBufferIndex++) { const FRHIUniformBuffer* UniformBuffer = UniformBufferBindings[UniformBufferIndex]; HashKey.UniformBufferHash = FHashKey::HashCombine(FHashKey::PointerHash(UniformBuffer), HashKey.UniformBufferHash); } ShaderBindingDataPtr += ShaderLayouts[ShaderBindingsIndex].GetDataSizeBytes(); } return uint32(CityHash64((char*)&HashKey, sizeof(FHashKey))); } static EPSOPrecacheResult RetrieveAndCachePSOPrecacheResult( const FGraphicsMinimalPipelineStateInitializer& MeshPipelineState, const FGraphicsPipelineStateInitializer& GraphicsPSOInit, bool bAllowSkipDrawCommand) { if (!PipelineStateCache::IsPSOPrecachingEnabled()) { return EPSOPrecacheResult::Unknown; } EPSOPrecacheResult PSOPrecacheResult = MeshPipelineState.PSOPrecacheState; bool bShouldCheckPrecacheResult = false; // If PSO precache validation is on, we need to check the state for stats tracking purposes. #if PSO_PRECACHING_VALIDATE if (PSOCollectorStats::IsPrecachingValidationEnabled() && PSOPrecacheResult == EPSOPrecacheResult::Unknown) { bShouldCheckPrecacheResult = true; } #endif // If we are skipping draws when the PSO is being precached but is not ready, we // need to keep checking the state until it's not marked active anymore. if (bAllowSkipDrawCommand && GSkipDrawOnPSOPrecaching) { if (PSOPrecacheResult == EPSOPrecacheResult::Unknown || PSOPrecacheResult == EPSOPrecacheResult::Active) { bShouldCheckPrecacheResult = true; } } if (bShouldCheckPrecacheResult) { // Cache the state so that it's only checked again if necessary. PSOPrecacheResult = PipelineStateCache::CheckPipelineStateInCache(GraphicsPSOInit); MeshPipelineState.PSOPrecacheState = PSOPrecacheResult; } return PSOPrecacheResult; } bool FMeshDrawCommand::SubmitDrawBegin( const FMeshDrawCommand& RESTRICT MeshDrawCommand, const FGraphicsMinimalPipelineStateSet& GraphicsMinimalPipelineStateSet, const FMeshDrawCommandSceneArgs& SceneArgs, uint32 InstanceFactor, FRHICommandList& RHICmdList, FMeshDrawCommandStateCache& RESTRICT StateCache, bool bAllowSkipDrawCommand) { checkSlow(MeshDrawCommand.CachedPipelineId.IsValid()); // GPUCULL_TODO: Can't do this check as the VFs are created with GMaxRHIFeatureLevel (so may support PrimitiveIdStreamIndex even for preview platforms) // Want to be sure that we supply GPU-scene instance data if required. // checkSlow(MeshDrawCommand.PrimitiveIdStreamIndex == -1 || ScenePrimitiveIdsBuffer != nullptr); const FGraphicsMinimalPipelineStateInitializer& MeshPipelineState = MeshDrawCommand.CachedPipelineId.GetPipelineState(GraphicsMinimalPipelineStateSet); if (MeshDrawCommand.CachedPipelineId.GetId() != StateCache.PipelineId) { FGraphicsPipelineStateInitializer GraphicsPSOInit = MeshPipelineState.AsGraphicsPipelineStateInitializer(); RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit); EPSOPrecacheResult PSOPrecacheResult = RetrieveAndCachePSOPrecacheResult(MeshPipelineState, GraphicsPSOInit, bAllowSkipDrawCommand); #if MESH_DRAW_COMMAND_DEBUG_DATA && MESH_DRAW_COMMAND_STATS // Try and skip draw if the shaders are not completly loaded yet. if (!MeshPipelineState.BoundShaderState.IsShaderAllLoaded()) { UE_LOG(LogRenderer, Log, TEXT("Missing Preload shaders. Material Friendly: %s Material Proxy: %s Static: %s"), *MeshDrawCommand.DebugData.Material->GetFriendlyName(), *MeshDrawCommand.DebugData.MaterialRenderProxy->GetMaterialName(), *MeshDrawCommand.StatsData.CategoryName.ToString()); MeshPipelineState.BoundShaderState.ForceShaderReload(); GraphicsPSOInit.BoundShaderState = MeshPipelineState.BoundShaderState.AsBoundShaderState(); } #endif // MESH_DRAW_COMMAND_DEBUG_DATA #if PSO_PRECACHING_VALIDATE #if MESH_DRAW_COMMAND_DEBUG_DATA PSOCollectorStats::CheckFullPipelineStateInCache(GraphicsPSOInit, PSOPrecacheResult, MeshDrawCommand.DebugData.MaterialRenderProxy, MeshDrawCommand.DebugData.VertexFactoryType, MeshDrawCommand.DebugData.PrimitiveSceneProxyIfNotUsingStateBuckets, MeshDrawCommand.DebugData.PSOCollectorIndex); #else PSOCollectorStats::CheckFullPipelineStateInCache(GraphicsPSOInit, PSOPrecacheResult, nullptr, nullptr, nullptr, INDEX_NONE); #endif // MESH_DRAW_COMMAND_DEBUG_DATA #endif // PSO_PRECACHING_VALIDATE // Try and skip draw if the PSO is not precached yet. if (bAllowSkipDrawCommand && GSkipDrawOnPSOPrecaching && PSOPrecacheResult == EPSOPrecacheResult::Active) { return false; } // We can set the new StencilRef here to avoid the set below SetGraphicsPipelineStateCheckApply(RHICmdList, GraphicsPSOInit, MeshDrawCommand.StencilRef); StateCache.SetPipelineState(MeshDrawCommand.CachedPipelineId.GetId()); StateCache.StencilRef = MeshDrawCommand.StencilRef; } if (MeshDrawCommand.StencilRef != StateCache.StencilRef) { RHICmdList.SetStencilRef(MeshDrawCommand.StencilRef); StateCache.StencilRef = MeshDrawCommand.StencilRef; } // Platforms that use global UB binding don't need to set PrimitiveIdStream const int8 PrimitiveIdStreamIndex = (IsUniformBufferStaticSlotValid(SceneArgs.BatchedPrimitiveSlot) ? -1 : MeshDrawCommand.PrimitiveIdStreamIndex); for (int32 VertexBindingIndex = 0; VertexBindingIndex < MeshDrawCommand.VertexStreams.Num(); VertexBindingIndex++) { const FVertexInputStream& Stream = MeshDrawCommand.VertexStreams[VertexBindingIndex]; if (PrimitiveIdStreamIndex != -1 && Stream.StreamIndex == PrimitiveIdStreamIndex) { RHICmdList.SetStreamSource(Stream.StreamIndex, SceneArgs.PrimitiveIdsBuffer, SceneArgs.PrimitiveIdOffset); StateCache.VertexStreams[Stream.StreamIndex] = Stream; } else if (StateCache.VertexStreams[Stream.StreamIndex] != Stream) { Stream.SetOnRHICommandList(RHICmdList); StateCache.VertexStreams[Stream.StreamIndex] = Stream; } } MeshDrawCommand.ShaderBindings.SetOnCommandList(RHICmdList, MeshPipelineState.BoundShaderState.AsBoundShaderState(), StateCache.ShaderBindings); return true; } void FMeshDrawCommand::SubmitDrawEnd(const FMeshDrawCommand& MeshDrawCommand, const FMeshDrawCommandSceneArgs& SceneArgs, uint32 InstanceFactor, FRHICommandList& RHICmdList) { const bool bDoOverrideArgs = SceneArgs.IndirectArgsBuffer != nullptr && MeshDrawCommand.PrimitiveIdStreamIndex >= 0; if (IsUniformBufferStaticSlotValid(SceneArgs.BatchedPrimitiveSlot)) { RHICmdList.SetUniformBufferDynamicOffset(SceneArgs.BatchedPrimitiveSlot, SceneArgs.PrimitiveIdOffset); } if (SceneArgs.RootConstants.IsSet()) { RHICmdList.SetShaderRootConstants(SceneArgs.RootConstants.GetValue()); } if (MeshDrawCommand.IndexBuffer) { if (MeshDrawCommand.NumPrimitives > 0 && !bDoOverrideArgs) { RHICmdList.DrawIndexedPrimitive( MeshDrawCommand.IndexBuffer, MeshDrawCommand.VertexParams.BaseVertexIndex, 0, MeshDrawCommand.VertexParams.NumVertices, MeshDrawCommand.FirstIndex, MeshDrawCommand.NumPrimitives, MeshDrawCommand.NumInstances * InstanceFactor ); } else { RHICmdList.DrawIndexedPrimitiveIndirect( MeshDrawCommand.IndexBuffer, bDoOverrideArgs ? SceneArgs.IndirectArgsBuffer : MeshDrawCommand.IndirectArgs.Buffer, bDoOverrideArgs ? SceneArgs.IndirectArgsByteOffset : MeshDrawCommand.IndirectArgs.Offset ); } } else { if (MeshDrawCommand.NumPrimitives > 0 && !bDoOverrideArgs) { RHICmdList.DrawPrimitive( MeshDrawCommand.VertexParams.BaseVertexIndex + MeshDrawCommand.FirstIndex, MeshDrawCommand.NumPrimitives, MeshDrawCommand.NumInstances * InstanceFactor); } else { RHICmdList.DrawPrimitiveIndirect( bDoOverrideArgs ? SceneArgs.IndirectArgsBuffer : MeshDrawCommand.IndirectArgs.Buffer, bDoOverrideArgs ? SceneArgs.IndirectArgsByteOffset : MeshDrawCommand.IndirectArgs.Offset ); } } } bool FMeshDrawCommand::SubmitDrawIndirectBegin( const FMeshDrawCommand& RESTRICT MeshDrawCommand, const FGraphicsMinimalPipelineStateSet& GraphicsMinimalPipelineStateSet, const FMeshDrawCommandSceneArgs& SceneArgs, uint32 InstanceFactor, FRHICommandList& RHICmdList, FMeshDrawCommandStateCache& RESTRICT StateCache, bool bAllowSkipDrawCommand) { return SubmitDrawBegin( MeshDrawCommand, GraphicsMinimalPipelineStateSet, SceneArgs, InstanceFactor, RHICmdList, StateCache, bAllowSkipDrawCommand ); } void FMeshDrawCommand::SubmitDrawIndirectEnd( const FMeshDrawCommand& MeshDrawCommand, const FMeshDrawCommandSceneArgs& SceneArgs, uint32 InstanceFactor, FRHICommandList& RHICmdList) { FRHIBuffer* IndirectArgsBuffer = nullptr; uint32 IndirectArgsOffset = 0; if (MeshDrawCommand.NumPrimitives == 0) { IndirectArgsBuffer = MeshDrawCommand.IndirectArgs.Buffer; IndirectArgsOffset = MeshDrawCommand.IndirectArgs.Offset; } if (SceneArgs.IndirectArgsBuffer != nullptr) { IndirectArgsBuffer = SceneArgs.IndirectArgsBuffer; IndirectArgsOffset = SceneArgs.IndirectArgsByteOffset; } if (IsUniformBufferStaticSlotValid(SceneArgs.BatchedPrimitiveSlot)) { RHICmdList.SetUniformBufferDynamicOffset(SceneArgs.BatchedPrimitiveSlot, SceneArgs.PrimitiveIdOffset); } if (IndirectArgsBuffer != nullptr) { if (MeshDrawCommand.IndexBuffer) { RHICmdList.DrawIndexedPrimitiveIndirect( MeshDrawCommand.IndexBuffer, IndirectArgsBuffer, IndirectArgsOffset ); } else { RHICmdList.DrawPrimitiveIndirect( IndirectArgsBuffer, IndirectArgsOffset ); } } else if (MeshDrawCommand.NumPrimitives > 0) { if (MeshDrawCommand.IndexBuffer) { RHICmdList.DrawIndexedPrimitive( MeshDrawCommand.IndexBuffer, MeshDrawCommand.VertexParams.BaseVertexIndex, 0, MeshDrawCommand.VertexParams.NumVertices, MeshDrawCommand.FirstIndex, MeshDrawCommand.NumPrimitives, MeshDrawCommand.NumInstances * InstanceFactor ); } else { RHICmdList.DrawPrimitive( MeshDrawCommand.VertexParams.BaseVertexIndex + MeshDrawCommand.FirstIndex, MeshDrawCommand.NumPrimitives, MeshDrawCommand.NumInstances * InstanceFactor ); } } } bool FMeshDrawCommand::SubmitDraw( const FMeshDrawCommand& RESTRICT MeshDrawCommand, const FGraphicsMinimalPipelineStateSet& GraphicsMinimalPipelineStateSet, const FMeshDrawCommandSceneArgs& SceneArgs, uint32 InstanceFactor, FRHICommandList& RHICmdList, FMeshDrawCommandStateCache& RESTRICT StateCache) { #if WANTS_DRAW_MESH_EVENTS RHI_BREADCRUMB_EVENT_CONDITIONAL_F(RHICmdList, GShowMaterialDrawEvents != 0, "MaterialDrawEvent", "%s %s (%u instances)" , MeshDrawCommand.DebugData.MaterialRenderProxy->GetMaterialName() , MeshDrawCommand.DebugData.ResourceName , MeshDrawCommand.NumInstances * InstanceFactor ); #endif bool bAllowSkipDrawCommand = true; if (SubmitDrawBegin(MeshDrawCommand, GraphicsMinimalPipelineStateSet, SceneArgs, InstanceFactor, RHICmdList, StateCache, bAllowSkipDrawCommand)) { SubmitDrawEnd(MeshDrawCommand, SceneArgs, InstanceFactor, RHICmdList); return true; } return false; } void ApplyTargetsInfo(FGraphicsPipelineStateInitializer& GraphicsPSOInit, const FGraphicsPipelineRenderTargetsInfo& RenderTargetsInfo) { GraphicsPSOInit.RenderTargetsEnabled = RenderTargetsInfo.RenderTargetsEnabled; GraphicsPSOInit.RenderTargetFormats = RenderTargetsInfo.RenderTargetFormats; GraphicsPSOInit.RenderTargetFlags = RenderTargetsInfo.RenderTargetFlags; GraphicsPSOInit.NumSamples = RenderTargetsInfo.NumSamples; GraphicsPSOInit.DepthStencilTargetFormat = RenderTargetsInfo.DepthStencilTargetFormat; GraphicsPSOInit.DepthStencilTargetFlag = RenderTargetsInfo.DepthStencilTargetFlag; GraphicsPSOInit.DepthTargetLoadAction = RenderTargetsInfo.DepthTargetLoadAction; GraphicsPSOInit.DepthTargetStoreAction = RenderTargetsInfo.DepthTargetStoreAction; GraphicsPSOInit.StencilTargetLoadAction = RenderTargetsInfo.StencilTargetLoadAction; GraphicsPSOInit.StencilTargetStoreAction = RenderTargetsInfo.StencilTargetStoreAction; GraphicsPSOInit.DepthStencilAccess = RenderTargetsInfo.DepthStencilAccess; GraphicsPSOInit.MultiViewCount = RenderTargetsInfo.MultiViewCount; GraphicsPSOInit.bHasFragmentDensityAttachment = RenderTargetsInfo.bHasFragmentDensityAttachment; } ESubpassHint GetSubpassHint(const FStaticShaderPlatform Platform, bool bIsUsingGBuffers, bool bMultiViewRendering, uint32 NumSamples) { ESubpassHint SubpassHint = ESubpassHint::None; if (IsMobilePlatform(Platform)) { if (bIsUsingGBuffers) { SubpassHint = ESubpassHint::DeferredShadingSubpass; } else { SubpassHint = IsMobileTonemapSubpassEnabledInline(Platform, bMultiViewRendering, NumSamples) ? ESubpassHint::CustomResolveSubpass : ESubpassHint::DepthReadSubpass; } } return SubpassHint; } uint64 FMeshDrawCommand::GetPipelineStateSortingKey(FRHICommandList& RHICmdList, const FGraphicsPipelineRenderTargetsInfo& RenderTargetsInfo) const { // Default fallback sort key uint64 SortKey = CachedPipelineId.GetId(); if (GRHISupportsPipelineStateSortKey) { FGraphicsMinimalPipelineStateSet PipelineStateSet; FGraphicsPipelineStateInitializer GraphicsPSOInit = CachedPipelineId.GetPipelineState(PipelineStateSet).AsGraphicsPipelineStateInitializer(); ApplyTargetsInfo(GraphicsPSOInit, RenderTargetsInfo); // PSO is retrieved here already. This is currently only used by Nanite::DrawLumenMeshCards - can this also used the version without command list? const FGraphicsPipelineState* PipelineState = PipelineStateCache::GetAndOrCreateGraphicsPipelineState(RHICmdList, GraphicsPSOInit, EApplyRendertargetOption::DoNothing); if (PipelineState) { const uint64 StateSortKey = PipelineStateCache::RetrieveGraphicsPipelineStateSortKey(PipelineState); if (StateSortKey != 0) // 0 on the first occurrence (prior to caching), so these commands will fall back on shader id for sorting. { SortKey = StateSortKey; } } } return SortKey; } uint64 FMeshDrawCommand::GetPipelineStateSortingKey(const FGraphicsPipelineRenderTargetsInfo& RenderTargetsInfo) const { // Default fallback sort key uint64 SortKey = CachedPipelineId.GetId(); if (GRHISupportsPipelineStateSortKey) { FGraphicsMinimalPipelineStateSet PipelineStateSet; FGraphicsPipelineStateInitializer GraphicsPSOInit = CachedPipelineId.GetPipelineState(PipelineStateSet).AsGraphicsPipelineStateInitializer(); ApplyTargetsInfo(GraphicsPSOInit, RenderTargetsInfo); const FGraphicsPipelineState* PipelineState = PipelineStateCache::FindGraphicsPipelineState(GraphicsPSOInit, false /* bVerifyUse */); if (PipelineState) { const uint64 StateSortKey = PipelineStateCache::RetrieveGraphicsPipelineStateSortKey(PipelineState); if (StateSortKey != 0) // 0 on the first occurrence (prior to caching), so these commands will fall back on shader id for sorting. { SortKey = StateSortKey; } } } return SortKey; } #if MESH_DRAW_COMMAND_DEBUG_DATA void FMeshDrawCommand::SetDebugData(const FPrimitiveSceneProxy* PrimitiveSceneProxy, const FMaterial* Material, const FMaterialRenderProxy* MaterialRenderProxy, const FMeshProcessorShaders& UntypedShaders, const FVertexFactory* VertexFactory, const FMeshBatch& MeshBatch, int32 PSOCollectorIndex) { DebugData.PrimitiveSceneProxyIfNotUsingStateBuckets = PrimitiveSceneProxy; DebugData.Material = Material; DebugData.MaterialRenderProxy = MaterialRenderProxy; DebugData.VertexShader = UntypedShaders.VertexShader; DebugData.PixelShader = UntypedShaders.PixelShader; DebugData.LODIndex = MeshBatch.LODIndex; DebugData.SegmentIndex = MeshBatch.SegmentIndex; DebugData.ResourceName = PrimitiveSceneProxy ? PrimitiveSceneProxy->GetResourceName() : FName(); #if PSO_PRECACHING_VALIDATE DebugData.VertexFactory = VertexFactory; DebugData.VertexFactoryType = VertexFactory->GetType(); DebugData.PSOCollectorIndex = PSOCollectorIndex; #endif } #endif #if MESH_DRAW_COMMAND_STATS void FMeshDrawCommand::SetStatsData(const FPrimitiveSceneProxy* PrimitiveSceneProxy) { StatsData.CategoryName = PrimitiveSceneProxy ? PrimitiveSceneProxy->GetMeshDrawCommandStatsCategory() : FName(); } void FMeshDrawCommand::GetStatsData(FVisibleMeshDrawCommandStatsData& OutVisibleStatsData) const { OutVisibleStatsData.StatsData = StatsData; OutVisibleStatsData.PrimitiveCount = NumPrimitives; #if MESH_DRAW_COMMAND_DEBUG_DATA OutVisibleStatsData.LODIndex = DebugData.LODIndex; OutVisibleStatsData.SegmentIndex = DebugData.SegmentIndex; OutVisibleStatsData.ResourceName = DebugData.ResourceName; OutVisibleStatsData.MaterialName = DebugData.MaterialRenderProxy->GetMaterialName(); #endif } #endif // MESH_DRAW_COMMAND_STATS void SubmitMeshDrawCommands( const FMeshCommandOneFrameArray& VisibleMeshDrawCommands, const FGraphicsMinimalPipelineStateSet& GraphicsMinimalPipelineStateSet, const FMeshDrawCommandSceneArgs& SceneArgs, uint32 PrimitiveIdBufferStride, bool bDynamicInstancing, uint32 InstanceFactor, FRHICommandList& RHICmdList) { SubmitMeshDrawCommandsRange(VisibleMeshDrawCommands, GraphicsMinimalPipelineStateSet, SceneArgs, PrimitiveIdBufferStride, bDynamicInstancing, 0, VisibleMeshDrawCommands.Num(), InstanceFactor, RHICmdList); } void SubmitMeshDrawCommandsRange( const FMeshCommandOneFrameArray& VisibleMeshDrawCommands, const FGraphicsMinimalPipelineStateSet& GraphicsMinimalPipelineStateSet, const FMeshDrawCommandSceneArgs& InSceneArgs, uint32 PrimitiveIdBufferStride, bool bDynamicInstancing, int32 StartIndex, int32 NumMeshDrawCommands, uint32 InstanceFactor, FRHICommandList& RHICmdList) { // GPUCULL_TODO: workaround for the fact that DrawDynamicMeshPassPrivate et al. don't work with GPU-Scene instancing // we don't support dynamic instancing for this path since we require one primitive per draw command // This is because the stride on the instance data buffer is set to 0 so only the first will ever be fetched. checkSlow(!bDynamicInstancing); bDynamicInstancing = false; //-V763 FMeshDrawCommandStateCache StateCache; FMeshDrawCommandSceneArgs LocalSceneArgs = InSceneArgs; INC_DWORD_STAT_BY(STAT_MeshDrawCalls, NumMeshDrawCommands); for (int32 DrawCommandIndex = StartIndex; DrawCommandIndex < StartIndex + NumMeshDrawCommands; DrawCommandIndex++) { SCOPED_CONDITIONAL_DRAW_EVENTF(RHICmdList, MeshEvent, GEmitMeshDrawEvent != 0, TEXT("Mesh Draw")); const FVisibleMeshDrawCommand& VisibleMeshDrawCommand = VisibleMeshDrawCommands[DrawCommandIndex]; LocalSceneArgs.PrimitiveIdOffset = InSceneArgs.PrimitiveIdOffset + (bDynamicInstancing ? VisibleMeshDrawCommand.PrimitiveIdBufferOffset : DrawCommandIndex) * PrimitiveIdBufferStride; checkSlow(!bDynamicInstancing || VisibleMeshDrawCommand.PrimitiveIdBufferOffset >= 0); FMeshDrawCommand::SubmitDraw(*VisibleMeshDrawCommand.MeshDrawCommand, GraphicsMinimalPipelineStateSet, LocalSceneArgs, InstanceFactor, RHICmdList, StateCache); } } void ApplyViewOverridesToMeshDrawCommands(const FSceneView& View, FMeshCommandOneFrameArray& VisibleMeshDrawCommands, FDynamicMeshDrawCommandStorage& DynamicMeshDrawCommandStorage, FGraphicsMinimalPipelineStateSet& GraphicsMinimalPipelineStateSet, bool& InNeedsShaderInitialisation) { if (View.bReverseCulling || View.bRenderSceneTwoSided) { const FMeshCommandOneFrameArray& PassVisibleMeshDrawCommands = VisibleMeshDrawCommands; FMeshCommandOneFrameArray ViewOverriddenMeshCommands; ViewOverriddenMeshCommands.Empty(PassVisibleMeshDrawCommands.Num()); for (int32 MeshCommandIndex = 0; MeshCommandIndex < PassVisibleMeshDrawCommands.Num(); MeshCommandIndex++) { DynamicMeshDrawCommandStorage.MeshDrawCommands.Add(1); FMeshDrawCommand& NewMeshCommand = DynamicMeshDrawCommandStorage.MeshDrawCommands[DynamicMeshDrawCommandStorage.MeshDrawCommands.Num() - 1]; const FVisibleMeshDrawCommand& VisibleMeshDrawCommand = PassVisibleMeshDrawCommands[MeshCommandIndex]; const FMeshDrawCommand& MeshCommand = *VisibleMeshDrawCommand.MeshDrawCommand; NewMeshCommand = MeshCommand; const ERasterizerCullMode LocalCullMode = View.bRenderSceneTwoSided ? CM_None : View.bReverseCulling ? FMeshPassProcessor::InverseCullMode(VisibleMeshDrawCommand.MeshCullMode) : VisibleMeshDrawCommand.MeshCullMode; FGraphicsMinimalPipelineStateInitializer PipelineState = MeshCommand.CachedPipelineId.GetPipelineState(GraphicsMinimalPipelineStateSet); PipelineState.RasterizerState = GetStaticRasterizerState(VisibleMeshDrawCommand.MeshFillMode, LocalCullMode); const FGraphicsMinimalPipelineStateId PipelineId = FGraphicsMinimalPipelineStateId::GetPipelineStateId(PipelineState, GraphicsMinimalPipelineStateSet, InNeedsShaderInitialisation); NewMeshCommand.Finalize(PipelineId, nullptr); FVisibleMeshDrawCommand NewVisibleMeshDrawCommand; NewVisibleMeshDrawCommand.Setup( &NewMeshCommand, VisibleMeshDrawCommand.PrimitiveIdInfo, VisibleMeshDrawCommand.StateBucketId, VisibleMeshDrawCommand.MeshFillMode, VisibleMeshDrawCommand.MeshCullMode, VisibleMeshDrawCommand.Flags, VisibleMeshDrawCommand.SortKey, VisibleMeshDrawCommand.CullingPayload, VisibleMeshDrawCommand.CullingPayloadFlags, VisibleMeshDrawCommand.RunArray, VisibleMeshDrawCommand.NumRuns); ViewOverriddenMeshCommands.Add(NewVisibleMeshDrawCommand); } // Replace VisibleMeshDrawCommands Swap(VisibleMeshDrawCommands, ViewOverriddenMeshCommands); } } class FDynamicBatchedPrimitiveLayout : public FRenderResource { public: void InitRHI(FRHICommandListBase& RHICmdList) override { // This Layout fully replicates BatchedPrimitive UB // we replace RDG_SRV with a regular SRV to be able to update UB inside RDG passes const static FName BatchedPrimitiveSlotName = "BatchedPrimitive"; FRHIUniformBufferLayoutInitializer Initializer(TEXT("FBatchedPrimitiveParameters"), 16u); EnumAddFlags(Initializer.Flags, ERHIUniformBufferFlags::UniformView); EnumAddFlags(Initializer.Flags, ERHIUniformBufferFlags::NoEmulatedUniformBuffer); // implicit for UniformView Initializer.Resources.Add({0, UBMT_RDG_BUFFER_SRV}); Initializer.StaticSlot = FUniformBufferStaticSlotRegistry::Get().FindSlotByName(BatchedPrimitiveSlotName); Initializer.BindingFlags = EUniformBufferBindingFlags::StaticAndShader; Initializer.ComputeHash(); // set view source to a regular SRV after hash computation, to make sure hash matches BatchedPrimitive layout Initializer.Resources[0].MemberType = UBMT_SRV; LayoutRHI = RHICreateUniformBufferLayout(Initializer); } void ReleaseRHI() override { LayoutRHI = nullptr; } FUniformBufferLayoutRHIRef LayoutRHI; }; TGlobalResource GDynamicBatchedPrimitiveLayout; void DrawDynamicMeshPassPrivate( const FSceneView& View, FRHICommandList& RHICmdList, FMeshCommandOneFrameArray& VisibleMeshDrawCommands, FDynamicMeshDrawCommandStorage& DynamicMeshDrawCommandStorage, FGraphicsMinimalPipelineStateSet& GraphicsMinimalPipelineStateSet, bool& InNeedsShaderInitialisation, uint32 InstanceFactor) { if (VisibleMeshDrawCommands.Num() > 0) { // GPUCULL_TODO: workaround for the fact that DrawDynamicMeshPassPrivate et al. don't work with GPU-Scene instancing // we don't support dynamic instancing for this path since we require one primitive per draw command // This is because the stride on the instance data buffer is set to 0 so only the first will ever be fetched. const bool bDynamicInstancing = false; const ERHIFeatureLevel::Type FeatureLevel = View.GetFeatureLevel(); const EShaderPlatform ShaderPlatform = View.GetShaderPlatform(); const bool bUseGPUScene = UseGPUScene(ShaderPlatform, FeatureLevel); ApplyViewOverridesToMeshDrawCommands(View, VisibleMeshDrawCommands, DynamicMeshDrawCommandStorage, GraphicsMinimalPipelineStateSet, InNeedsShaderInitialisation); check(View.bIsViewInfo); const FViewInfo* ViewInfo = static_cast(&View); FMeshDrawCommandSceneArgs SceneArgs; SortAndMergeDynamicPassMeshDrawCommands(View, RHICmdList, VisibleMeshDrawCommands, DynamicMeshDrawCommandStorage, SceneArgs.PrimitiveIdsBuffer, InstanceFactor, &ViewInfo->DynamicPrimitiveCollector); if (bUseGPUScene && PlatformGPUSceneUsesUniformBufferView(ShaderPlatform)) { FShaderResourceViewRHIRef BatchedPrimitiveSRV = RHICmdList.CreateShaderResourceView(SceneArgs.PrimitiveIdsBuffer, FRHIViewDesc::CreateBufferSRV().SetTypeFromBuffer(SceneArgs.PrimitiveIdsBuffer)); SceneArgs.PrimitiveIdsBuffer = nullptr; FRHIShaderResourceView* SRV = BatchedPrimitiveSRV.GetReference(); FUniformBufferRHIRef UBRef = RHICreateUniformBuffer(&SRV, GDynamicBatchedPrimitiveLayout.LayoutRHI, UniformBuffer_SingleFrame, EUniformBufferValidation::None); SceneArgs.BatchedPrimitiveSlot = FInstanceCullingContext::GetStaticUniformBufferSlot(ShaderPlatform); RHICmdList.SetStaticUniformBuffer(SceneArgs.BatchedPrimitiveSlot, UBRef); } const uint32 PrimitiveIdBufferStride = FInstanceCullingContext::GetInstanceIdBufferStride(ShaderPlatform); SubmitMeshDrawCommandsRange(VisibleMeshDrawCommands, GraphicsMinimalPipelineStateSet, SceneArgs, PrimitiveIdBufferStride, bDynamicInstancing, 0, VisibleMeshDrawCommands.Num(), InstanceFactor, RHICmdList); } } FMeshDrawCommandSortKey CalculateMeshStaticSortKey(const FMeshMaterialShader* VertexShader, const FMeshMaterialShader* PixelShader) { FMeshDrawCommandSortKey SortKey; SortKey.Generic.VertexShaderHash = VertexShader ? VertexShader->GetSortKey() : 0; SortKey.Generic.PixelShaderHash = PixelShader ? PixelShader->GetSortKey() : 0; return SortKey; } FMeshPassProcessor::FMeshPassProcessor(EMeshPass::Type InMeshPassType, const FScene* InScene, ERHIFeatureLevel::Type InFeatureLevel, const FSceneView* InViewIfDynamicMeshCommand, FMeshPassDrawListContext* InDrawListContext) : IPSOCollector(FPassProcessorManager::GetPSOCollectorIndex(GetFeatureLevelShadingPath(InFeatureLevel), InMeshPassType)) , MeshPassType(InMeshPassType) , Scene(InScene) , FeatureLevel(InFeatureLevel) , ViewIfDynamicMeshCommand(InViewIfDynamicMeshCommand) , DrawListContext(InDrawListContext) { } FMeshPassProcessor::FMeshPassProcessor(const TCHAR* InMeshPassName, const FScene* InScene, ERHIFeatureLevel::Type InFeatureLevel, const FSceneView* InViewIfDynamicMeshCommand, FMeshPassDrawListContext* InDrawListContext) : IPSOCollector(FPSOCollectorCreateManager::GetIndex(GetFeatureLevelShadingPath(InFeatureLevel), InMeshPassName)) , MeshPassType(EMeshPass::Num) , Scene(InScene) , FeatureLevel(InFeatureLevel) , ViewIfDynamicMeshCommand(InViewIfDynamicMeshCommand) , DrawListContext(InDrawListContext) { } FMeshPassProcessor::FMeshDrawingPolicyOverrideSettings FMeshPassProcessor::ComputeMeshOverrideSettings(const FPSOPrecacheParams& PrecachePSOParams) { FMeshDrawingPolicyOverrideSettings OverrideSettings; OverrideSettings.MeshPrimitiveType = PT_TriangleList; OverrideSettings.MeshOverrideFlags |= PrecachePSOParams.bDisableBackFaceCulling ? EDrawingPolicyOverrideFlags::TwoSided : EDrawingPolicyOverrideFlags::None; OverrideSettings.MeshOverrideFlags |= PrecachePSOParams.bReverseCulling ? EDrawingPolicyOverrideFlags::ReverseCullMode : EDrawingPolicyOverrideFlags::None; return OverrideSettings; } FMeshPassProcessor::FMeshDrawingPolicyOverrideSettings FMeshPassProcessor::ComputeMeshOverrideSettings(const FMeshBatch& Mesh) { FMeshDrawingPolicyOverrideSettings OverrideSettings; OverrideSettings.MeshPrimitiveType = (EPrimitiveType)Mesh.Type; OverrideSettings.MeshOverrideFlags |= Mesh.bDisableBackfaceCulling ? EDrawingPolicyOverrideFlags::TwoSided : EDrawingPolicyOverrideFlags::None; OverrideSettings.MeshOverrideFlags |= Mesh.bDitheredLODTransition ? EDrawingPolicyOverrideFlags::DitheredLODTransition : EDrawingPolicyOverrideFlags::None; OverrideSettings.MeshOverrideFlags |= Mesh.bWireframe ? EDrawingPolicyOverrideFlags::Wireframe : EDrawingPolicyOverrideFlags::None; OverrideSettings.MeshOverrideFlags |= Mesh.ReverseCulling ? EDrawingPolicyOverrideFlags::ReverseCullMode : EDrawingPolicyOverrideFlags::None; return OverrideSettings; } ERasterizerFillMode FMeshPassProcessor::ComputeMeshFillMode(const FMaterial& InMaterialResource, const FMeshDrawingPolicyOverrideSettings& InOverrideSettings) { const bool bMaterialResourceIsTwoSided = InMaterialResource.IsTwoSided(); const bool bIsWireframeMaterial = InMaterialResource.IsWireframe() || !!(InOverrideSettings.MeshOverrideFlags & EDrawingPolicyOverrideFlags::Wireframe); return bIsWireframeMaterial ? FM_Wireframe : FM_Solid; } ERasterizerCullMode FMeshPassProcessor::ComputeMeshCullMode(const FMaterial& InMaterialResource, const FMeshDrawingPolicyOverrideSettings& InOverrideSettings) { const bool bMaterialResourceIsTwoSided = InMaterialResource.IsTwoSided(); const bool bInTwoSidedOverride = !!(InOverrideSettings.MeshOverrideFlags & EDrawingPolicyOverrideFlags::TwoSided); const bool bInReverseCullModeOverride = !!(InOverrideSettings.MeshOverrideFlags & EDrawingPolicyOverrideFlags::ReverseCullMode); const bool bIsTwoSided = (bMaterialResourceIsTwoSided || bInTwoSidedOverride); const bool bMeshRenderTwoSided = bIsTwoSided || bInTwoSidedOverride; return bMeshRenderTwoSided ? CM_None : (bInReverseCullModeOverride ? CM_CCW : CM_CW); } FMeshDrawCommandPrimitiveIdInfo FMeshPassProcessor::GetDrawCommandPrimitiveId( const FPrimitiveSceneInfo* RESTRICT PrimitiveSceneInfo, const FMeshBatchElement& BatchElement) const { FMeshDrawCommandPrimitiveIdInfo PrimitiveIdInfo = FMeshDrawCommandPrimitiveIdInfo(0, FPersistentPrimitiveIndex { 0 }, -1); if (UseGPUScene(GMaxRHIShaderPlatform, FeatureLevel)) { if (BatchElement.PrimitiveIdMode == PrimID_FromPrimitiveSceneInfo) { ensureMsgf(BatchElement.PrimitiveUniformBufferResource == nullptr, TEXT("PrimitiveUniformBufferResource should not be setup when PrimitiveIdMode == PrimID_FromPrimitiveSceneInfo")); check(PrimitiveSceneInfo); PrimitiveIdInfo.DrawPrimitiveId = PrimitiveSceneInfo->GetPersistentIndex().Index; PrimitiveIdInfo.InstanceSceneDataOffset = PrimitiveSceneInfo->GetInstanceSceneDataOffset(); PrimitiveIdInfo.bIsDynamicPrimitive = 0U; } else if (BatchElement.PrimitiveIdMode == PrimID_DynamicPrimitiveShaderData && ViewIfDynamicMeshCommand != nullptr) { // Mark using GPrimIDDynamicFlag (top bit) as we defer this to later. PrimitiveIdInfo.DrawPrimitiveId = BatchElement.DynamicPrimitiveIndex | GPrimIDDynamicFlag; PrimitiveIdInfo.InstanceSceneDataOffset = BatchElement.DynamicPrimitiveInstanceSceneDataOffset; PrimitiveIdInfo.bIsDynamicPrimitive = 1U; } else { check(BatchElement.PrimitiveIdMode == PrimID_ForceZero); } } PrimitiveIdInfo.ScenePrimitiveId = PrimitiveSceneInfo ? PrimitiveSceneInfo->GetIndex() : -1; return PrimitiveIdInfo; } bool FMeshPassProcessor::ShouldSkipMeshDrawCommand(const FMeshBatch& RESTRICT MeshBatch, const FPrimitiveSceneProxy* RESTRICT PrimitiveSceneProxy) const { bool bSkipMeshDrawCommand = false; #if WITH_EDITORONLY_DATA // Support debug mode to render only non-Nanite proxies that incorrectly reference coarse mesh static mesh assets. if (GNaniteIsolateInvalidCoarseMesh != 0) { // Skip everything by default bSkipMeshDrawCommand = true; const bool bNaniteProxy = PrimitiveSceneProxy != nullptr && PrimitiveSceneProxy->IsNaniteMesh(); if (!bNaniteProxy && MeshBatch.VertexFactory != nullptr) { // Only skip if the referenced static mesh is not a generated Nanite coarse mesh const bool bIsCoarseProxy = MeshBatch.VertexFactory->IsCoarseProxyMesh(); if (bIsCoarseProxy) { bSkipMeshDrawCommand = false; } } } #endif return bSkipMeshDrawCommand; } bool FMeshPassProcessor::PipelineVariableRateShadingEnabled() const { return GVRSImageManager.IsPipelineVRSEnabled(); } bool FMeshPassProcessor::HardwareVariableRateShadingSupportedByScene() const { if (Scene) { return HardwareVariableRateShadingSupportedByPlatform(Scene->GetShaderPlatform()); } else if (ViewIfDynamicMeshCommand) { // When applying changes to a material in the editor, we take this pathway return HardwareVariableRateShadingSupportedByPlatform(ViewIfDynamicMeshCommand->GetShaderPlatform()); } else { return false; } } FCachedPassMeshDrawListContext::FCachedPassMeshDrawListContext(FScene& InScene) : Scene(InScene) , bUseGPUScene(UseGPUScene(GMaxRHIShaderPlatform, GMaxRHIFeatureLevel)) { } FMeshDrawCommand& FCachedPassMeshDrawListContext::AddCommand(FMeshDrawCommand& Initializer, uint32 NumElements) { checkf(CurrMeshPass < EMeshPass::Num, TEXT("BeginMeshPass() must be called before adding commands to this context")); ensureMsgf(CommandInfo.CommandIndex == -1 && CommandInfo.StateBucketId == -1, TEXT("GetCommandInfoAndReset() wasn't called since the last command was added")); if (NumElements == 1) { return Initializer; } else { MeshDrawCommandForStateBucketing = Initializer; return MeshDrawCommandForStateBucketing; } } void FCachedPassMeshDrawListContext::BeginMeshPass(EMeshPass::Type MeshPass) { checkf(CurrMeshPass == EMeshPass::Num, TEXT("BeginMeshPass() was called without a matching EndMeshPass()")); check(MeshPass < EMeshPass::Num); CurrMeshPass = MeshPass; } void FCachedPassMeshDrawListContext::EndMeshPass() { checkf(CurrMeshPass < EMeshPass::Num, TEXT("EndMeshPass() was called without matching BeginMeshPass()")); CurrMeshPass = EMeshPass::Num; } FCachedMeshDrawCommandInfo FCachedPassMeshDrawListContext::GetCommandInfoAndReset() { FCachedMeshDrawCommandInfo Ret = CommandInfo; CommandInfo.CommandIndex = -1; CommandInfo.StateBucketId = -1; return Ret; } void FCachedPassMeshDrawListContext::FinalizeCommandCommon( const FMeshBatch& MeshBatch, int32 BatchElementIndex, ERasterizerFillMode MeshFillMode, ERasterizerCullMode MeshCullMode, FMeshDrawCommandSortKey SortKey, EFVisibleMeshDrawCommandFlags Flags, const FGraphicsMinimalPipelineStateInitializer& PipelineState, const FMeshProcessorShaders* ShadersForDebugging, FMeshDrawCommand& MeshDrawCommand) { FGraphicsMinimalPipelineStateId PipelineId = FGraphicsMinimalPipelineStateId::GetPersistentId(PipelineState); MeshDrawCommand.SetDrawParametersAndFinalize(MeshBatch, BatchElementIndex, PipelineId, ShadersForDebugging); CommandInfo = FCachedMeshDrawCommandInfo(CurrMeshPass); CommandInfo.SortKey = SortKey; CommandInfo.CullingPayload = CreateCullingPayload(MeshBatch, MeshBatch.Elements[BatchElementIndex]); CommandInfo.MeshFillMode = MeshFillMode; CommandInfo.MeshCullMode = MeshCullMode; CommandInfo.Flags = Flags; #if MESH_DRAW_COMMAND_DEBUG_DATA if (bUseGPUScene) { MeshDrawCommand.ClearDebugPrimitiveSceneProxy(); //When using State Buckets multiple PrimitiveSceneProxies use the same MeshDrawCommand, so The PrimitiveSceneProxy pointer can't be stored. } #endif #if DO_GUARD_SLOW if (bUseGPUScene) { FMeshDrawCommand MeshDrawCommandDebug = FMeshDrawCommand(MeshDrawCommand); check(MeshDrawCommandDebug.ShaderBindings.GetDynamicInstancingHash() == MeshDrawCommand.ShaderBindings.GetDynamicInstancingHash()); check(MeshDrawCommandDebug.GetDynamicInstancingHash() == MeshDrawCommand.GetDynamicInstancingHash()); } if (Scene.GetShadingPath() == EShadingPath::Deferred) { ensureMsgf(MeshDrawCommand.VertexStreams.GetAllocatedSize() == 0, TEXT("Cached Mesh Draw command overflows VertexStreams. VertexStream inline size should be tweaked.")); if ( CurrMeshPass == EMeshPass::BasePass || CurrMeshPass == EMeshPass::DepthPass || CurrMeshPass == EMeshPass::SecondStageDepthPass || CurrMeshPass == EMeshPass::OnePassPointLightShadowDepth || CurrMeshPass == EMeshPass::CSMShadowDepth || CurrMeshPass == EMeshPass::VSMShadowDepth) { TArray> ShaderFrequencies; MeshDrawCommand.ShaderBindings.GetShaderFrequencies(ShaderFrequencies); int32 DataOffset = 0; for (int32 i = 0; i < ShaderFrequencies.Num(); i++) { FMeshDrawSingleShaderBindings SingleShaderBindings = MeshDrawCommand.ShaderBindings.GetSingleShaderBindings(ShaderFrequencies[i], DataOffset); if (SingleShaderBindings.GetParameterMapInfo().LooseParameterBuffers.Num() != 0) { bAnyLooseParameterBuffers = true; } ensureMsgf(SingleShaderBindings.GetParameterMapInfo().SRVs.Num() == 0, TEXT("Cached Mesh Draw command uses individual SRVs. This will break dynamic instancing in performance critical pass. Use Uniform Buffers instead.")); ensureMsgf(SingleShaderBindings.GetParameterMapInfo().TextureSamplers.Num() == 0, TEXT("Cached Mesh Draw command uses individual Texture Samplers. This will break dynamic instancing in performance critical pass. Use Uniform Buffers instead.")); } } } #endif } void FCachedPassMeshDrawListContextImmediate::FinalizeCommand( const FMeshBatch& MeshBatch, int32 BatchElementIndex, const FMeshDrawCommandPrimitiveIdInfo& IdInfo, ERasterizerFillMode MeshFillMode, ERasterizerCullMode MeshCullMode, FMeshDrawCommandSortKey SortKey, EFVisibleMeshDrawCommandFlags Flags, const FGraphicsMinimalPipelineStateInitializer& PipelineState, const FMeshProcessorShaders* ShadersForDebugging, FMeshDrawCommand& MeshDrawCommand) { // disabling this by default as it incurs a high cost in perf captures due to sheer volume. Recommendation is to re-enable locally if you need to profile this particular code. // QUICK_SCOPE_CYCLE_COUNTER(STAT_FinalizeCachedMeshDrawCommand); FinalizeCommandCommon( MeshBatch, BatchElementIndex, MeshFillMode, MeshCullMode, SortKey, Flags, PipelineState, ShadersForDebugging, MeshDrawCommand ); if (bUseGPUScene) { Experimental::FHashElementId SetId; FStateBucketMap& BucketMap = Scene.CachedMeshDrawCommandStateBuckets[CurrMeshPass]; auto hash = BucketMap.ComputeHash(MeshDrawCommand); { SetId = BucketMap.FindOrAddIdByHash(hash, MeshDrawCommand, FMeshDrawCommandCount()); FMeshDrawCommandCount& DrawCount = BucketMap.GetByElementId(SetId).Value; DrawCount.Num++; } CommandInfo.StateBucketId = SetId.GetIndex(); } else { // Only one FMeshDrawCommand supported per FStaticMesh in a pass // Allocate at lowest free index so that 'r.DoLazyStaticMeshUpdate' can shrink the TSparseArray more effectively FCachedPassMeshDrawList& CachedDrawLists = Scene.CachedDrawLists[CurrMeshPass]; CommandInfo.CommandIndex = CachedDrawLists.MeshDrawCommands.EmplaceAtLowestFreeIndex(CachedDrawLists.LowestFreeIndexSearchStart, MeshDrawCommand); } } void FCachedPassMeshDrawListContextDeferred::FinalizeCommand( const FMeshBatch& MeshBatch, int32 BatchElementIndex, const FMeshDrawCommandPrimitiveIdInfo& IdInfo, ERasterizerFillMode MeshFillMode, ERasterizerCullMode MeshCullMode, FMeshDrawCommandSortKey SortKey, EFVisibleMeshDrawCommandFlags Flags, const FGraphicsMinimalPipelineStateInitializer& PipelineState, const FMeshProcessorShaders* ShadersForDebugging, FMeshDrawCommand& MeshDrawCommand) { // disabling this by default as it incurs a high cost in perf captures due to sheer volume. Recommendation is to re-enable locally if you need to profile this particular code. // QUICK_SCOPE_CYCLE_COUNTER(STAT_FinalizeCachedMeshDrawCommand); FinalizeCommandCommon( MeshBatch, BatchElementIndex, MeshFillMode, MeshCullMode, SortKey, Flags, PipelineState, ShadersForDebugging, MeshDrawCommand ); const int32 Index = DeferredCommands.Add(MeshDrawCommand); if (bUseGPUScene) { // Cache the hash here to make the deferred finalize less expensive DeferredCommandHashes.Add(FStateBucketMap::ComputeHash(MeshDrawCommand)); CommandInfo.StateBucketId = Index; } else { CommandInfo.CommandIndex = Index; } } void FCachedPassMeshDrawListContextDeferred::DeferredFinalizeMeshDrawCommands(const TArrayView& SceneInfos, int32 Start, int32 End) { if (bUseGPUScene) { for (int32 SceneInfoIndex = Start; SceneInfoIndex < End; ++SceneInfoIndex) { FPrimitiveSceneInfo* SceneInfo = SceneInfos[SceneInfoIndex]; for (auto& CmdInfo : SceneInfo->StaticMeshCommandInfos) { check(CmdInfo.MeshPass < EMeshPass::Num); FStateBucketMap& BucketMap = Scene.CachedMeshDrawCommandStateBuckets[CmdInfo.MeshPass]; int32 DeferredIndex = CmdInfo.StateBucketId; check(DeferredIndex >= 0 && DeferredIndex < DeferredCommands.Num()); check(CmdInfo.CommandIndex == -1); FMeshDrawCommand& Command = DeferredCommands[DeferredIndex]; const Experimental::FHashType CommandHash = DeferredCommandHashes[DeferredIndex]; Experimental::FHashElementId SetId = BucketMap.FindOrAddIdByHash(CommandHash, MoveTemp(Command), FMeshDrawCommandCount()); FMeshDrawCommandCount& DrawCount = BucketMap.GetByElementId(SetId).Value; DrawCount.Num++; CmdInfo.StateBucketId = SetId.GetIndex(); } } } else { for (int32 SceneInfoIndex = Start; SceneInfoIndex < End; ++SceneInfoIndex) { FPrimitiveSceneInfo* SceneInfo = SceneInfos[SceneInfoIndex]; for (auto& CmdInfo : SceneInfo->StaticMeshCommandInfos) { check(CmdInfo.MeshPass < EMeshPass::Num); FCachedPassMeshDrawList& CachedDrawLists = Scene.CachedDrawLists[CmdInfo.MeshPass]; check(CmdInfo.CommandIndex >= 0 && CmdInfo.CommandIndex < DeferredCommands.Num()); check(CmdInfo.StateBucketId == -1); FMeshDrawCommand& Command = DeferredCommands[CmdInfo.CommandIndex]; CmdInfo.CommandIndex = CachedDrawLists.MeshDrawCommands.EmplaceAtLowestFreeIndex(CachedDrawLists.LowestFreeIndexSearchStart, MoveTemp(Command)); } } } DeferredCommands.Reset(); DeferredCommandHashes.Reset(); } PassProcessorCreateFunction FPassProcessorManager::JumpTable[(int32)EShadingPath::Num][EMeshPass::Num] = {}; DeprecatedPassProcessorCreateFunction FPassProcessorManager::DeprecatedJumpTable[(int32)EShadingPath::Num][EMeshPass::Num] = {}; EMeshPassFlags FPassProcessorManager::Flags[(int32)EShadingPath::Num][EMeshPass::Num] = {}; int32 FPassProcessorManager::PSOCollectorIndex[(int32)EShadingPath::Num][EMeshPass::Num] = {}; static_assert(EMeshPass::Num <= FPSOCollectorCreateManager::MaxPSOCollectorCount); void FPassProcessorManager::SetPassFlags(EShadingPath ShadingPath, EMeshPass::Type PassType, EMeshPassFlags NewFlags) { check(IsInGameThread()); FGlobalComponentRecreateRenderStateContext Context; if (JumpTable[(uint32)ShadingPath][PassType]) { Flags[(uint32)ShadingPath][PassType] = NewFlags; } } void AddRenderTargetInfo( EPixelFormat PixelFormat, ETextureCreateFlags CreateFlags, FGraphicsPipelineRenderTargetsInfo& RenderTargetsInfo) { RenderTargetsInfo.RenderTargetFormats[RenderTargetsInfo.RenderTargetsEnabled] = PixelFormat; RenderTargetsInfo.RenderTargetFlags[RenderTargetsInfo.RenderTargetsEnabled] = CreateFlags; RenderTargetsInfo.RenderTargetsEnabled++; } void SetupDepthStencilInfo( EPixelFormat DepthStencilFormat, ETextureCreateFlags DepthStencilCreateFlags, ERenderTargetLoadAction DepthTargetLoadAction, ERenderTargetLoadAction StencilTargetLoadAction, FExclusiveDepthStencil DepthStencilAccess, FGraphicsPipelineRenderTargetsInfo& RenderTargetsInfo) { // Setup depth stencil state RenderTargetsInfo.DepthStencilTargetFormat = DepthStencilFormat; RenderTargetsInfo.DepthStencilTargetFlag = DepthStencilCreateFlags; RenderTargetsInfo.DepthTargetLoadAction = DepthTargetLoadAction; RenderTargetsInfo.StencilTargetLoadAction = StencilTargetLoadAction; RenderTargetsInfo.DepthStencilAccess = DepthStencilAccess; const ERenderTargetStoreAction StoreAction = EnumHasAnyFlags(RenderTargetsInfo.DepthStencilTargetFlag, TexCreate_Memoryless) ? ERenderTargetStoreAction::ENoAction : ERenderTargetStoreAction::EStore; RenderTargetsInfo.DepthTargetStoreAction = RenderTargetsInfo.DepthStencilAccess.IsUsingDepth() ? StoreAction : ERenderTargetStoreAction::ENoAction; RenderTargetsInfo.StencilTargetStoreAction = RenderTargetsInfo.DepthStencilAccess.IsUsingStencil() ? StoreAction : ERenderTargetStoreAction::ENoAction; } void SetupGBufferRenderTargetInfo(const FSceneTexturesConfig& SceneTexturesConfig, FGraphicsPipelineRenderTargetsInfo& RenderTargetsInfo, bool bSetupDepthStencil, EGBufferLayout Layout) { SceneTexturesConfig.GetGBufferRenderTargetsInfo(RenderTargetsInfo, Layout); if (bSetupDepthStencil) { SetupDepthStencilInfo(PF_DepthStencil, SceneTexturesConfig.DepthCreateFlags, ERenderTargetLoadAction::ELoad, ERenderTargetLoadAction::ELoad, FExclusiveDepthStencil::DepthRead_StencilWrite, RenderTargetsInfo); } } #if PSO_PRECACHING_VALIDATE FGraphicsMinimalPipelineStateInitializer PSOCollectorStats::GetShadersOnlyInitializer(const FGraphicsMinimalPipelineStateInitializer& Initializer) { FGraphicsMinimalPipelineStateInitializer ShadersOnlyInitializer; ShadersOnlyInitializer.BoundShaderState = Initializer.BoundShaderState; ShadersOnlyInitializer.BoundShaderState.VertexDeclarationRHI = nullptr; ShadersOnlyInitializer.ComputeStatePrecachePSOHash(); return ShadersOnlyInitializer; } FGraphicsMinimalPipelineStateInitializer PSOCollectorStats::PatchMinimalPipelineStateToCheck(const FGraphicsMinimalPipelineStateInitializer& Initializer) { FGraphicsMinimalPipelineStateInitializer PatchedInitializer = Initializer; //PatchedInitializer.DepthStencilState = nullptr; //PatchedInitializer.RasterizerState = nullptr; //PatchedInitializer.BlendState = nullptr; //PatchedInitializer.PrimitiveType = PT_TriangleList; //PatchedInitializer.ImmutableSamplerState = FImmutableSamplerState(); //PatchedInitializer.BoundShaderState.VertexDeclarationRHI = nullptr; //PatchedInitializer.UniqueEntry = false; // Recompute the hash when disabling certain states for checks. // PatchedInitializer.StatePrecachePSOHash = 0; PatchedInitializer.ComputeStatePrecachePSOHash(); return PatchedInitializer; } uint64 PSOCollectorStats::GetPSOPrecacheHash(const FGraphicsMinimalPipelineStateInitializer& GraphicsPSOInitializer) { return GraphicsPSOInitializer.StatePrecachePSOHash; } void PSOCollectorStats::CheckShaderOnlyStateInCache( const FGraphicsMinimalPipelineStateInitializer& Initializer, const FMaterial& Material, const FVertexFactoryType* VFType, const FPrimitiveSceneProxy* PrimitiveSceneProxy, int32 PSOCollectorIndex) { FGraphicsMinimalPipelineStateInitializer ShadersOnlyInitializer = GetShadersOnlyInitializer(Initializer); EPSOPrecacheResult Result = EPSOPrecacheResult::Unknown; bool bStatsUpdated = PSOCollectorStats::GetShadersOnlyPSOPrecacheStatsCollector().CheckStateInCacheByHash(EPSOPrecacheType::MeshPass, ShadersOnlyInitializer.StatePrecachePSOHash, PSOCollectorIndex, VFType, Result); if (IsFullPrecachingValidationEnabled() && bStatsUpdated && Result != EPSOPrecacheResult::Unknown && Result != EPSOPrecacheResult::Complete) { FGraphicsPipelineStateInitializer GraphicsPSOInitializer = ShadersOnlyInitializer.AsGraphicsPipelineStateInitializer(); LogPSOMissInfo(GraphicsPSOInitializer, EPSOPrecacheMissType::ShadersOnly, Result, &Material, VFType, PrimitiveSceneProxy, EPSOPrecacheType::MeshPass, PSOCollectorIndex, ShadersOnlyInitializer.StatePrecachePSOHash); } } void PSOCollectorStats::CheckMinimalPipelineStateInCache( const FGraphicsMinimalPipelineStateInitializer& Initializer, const FMaterial& Material, const FVertexFactoryType* VFType, const FPrimitiveSceneProxy* PrimitiveSceneProxy, int32 PSOCollectorIndex) { FGraphicsMinimalPipelineStateInitializer PatchedMinimalInitializer = PSOCollectorStats::PatchMinimalPipelineStateToCheck(Initializer); EPSOPrecacheResult Result = EPSOPrecacheResult::Unknown; bool bStatsUpdated = PSOCollectorStats::GetMinimalPSOPrecacheStatsCollector().CheckStateInCacheByHash(EPSOPrecacheType::MeshPass, PatchedMinimalInitializer.StatePrecachePSOHash, PSOCollectorIndex, VFType, Result); if (IsFullPrecachingValidationEnabled() && bStatsUpdated && Result != EPSOPrecacheResult::Unknown && Result != EPSOPrecacheResult::Complete) { FGraphicsMinimalPipelineStateInitializer ShadersOnlyInitializer = GetShadersOnlyInitializer(Initializer); bool bShaderOnlyPrecached = PSOCollectorStats::GetShadersOnlyPSOPrecacheStatsCollector().IsPrecached(ShadersOnlyInitializer.StatePrecachePSOHash); if (bShaderOnlyPrecached) { check(Result == EPSOPrecacheResult::Missed); FGraphicsPipelineStateInitializer GraphicsPSOInitializer = PatchedMinimalInitializer.AsGraphicsPipelineStateInitializer(); LogPSOMissInfo(GraphicsPSOInitializer, EPSOPrecacheMissType::MinimalPSOState, Result, &Material, VFType, PrimitiveSceneProxy, EPSOPrecacheType::MeshPass, PSOCollectorIndex, ShadersOnlyInitializer.StatePrecachePSOHash); } } } #endif // PSO_PRECACHING_VALIDATE