// Copyright Epic Games, Inc. All Rights Reserved. /*============================================================================= ValidationRHICommon.h: Public Validation RHI definitions. =============================================================================*/ #pragma once #include "Experimental/ConcurrentLinearAllocator.h" #include "PixelFormat.h" #include "RHIPipeline.h" #include "RHIStrings.h" #include "RHIAccess.h" #include "RHIBreadcrumbs.h" #include "Templates/TypeHash.h" #if ENABLE_RHI_VALIDATION extern RHI_API bool GRHIValidationEnabled; #else const bool GRHIValidationEnabled = false; #endif #if ENABLE_RHI_VALIDATION class FRHIShader; class FRHIShaderResourceView; class FRHIUniformBuffer; class FRHIViewableResource; class FRHIUnorderedAccessView; class FRHITexture; struct FValidationCommandList; class FValidationComputeContext; class FValidationContext; class FValidationRHI; struct FRHITextureCreateDesc; struct FRHITransitionInfo; struct FRHIViewDesc; class FRayTracingPipelineStateInitializer; class FRHIRayTracingPipelineState; struct FRayTracingShaderBindingTableInitializer; struct FRayTracingLocalShaderBindings; enum class ERayTracingShaderBindingTableLifetime : uint8; enum class ERayTracingBindingType : uint8; enum class ERayTracingShaderBindingMode : uint8; enum class ERayTracingHitGroupIndexingMode : uint8; namespace RHIValidation { struct FStaticUniformBuffers { TArray Bindings; bool bInSetPipelineStateCall{}; void Reset(); void ValidateSetShaderUniformBuffer(FRHIUniformBuffer* UniformBuffer); }; struct FStageBoundUniformBuffers { FStageBoundUniformBuffers(); void Reset(); void Bind(uint32 Index, FRHIUniformBuffer* UniformBuffer); TArray Buffers; }; struct FBoundUniformBuffers { void Reset(); FStageBoundUniformBuffers& Get(EShaderFrequency Stage) { return StageBindings[Stage]; } FStageBoundUniformBuffers StageBindings[SF_NumFrequencies]; }; class FTracker; class FResource; class FTextureResource; struct FOperation; struct FSubresourceState; struct FSubresourceRange; struct FResourceIdentity; enum class ELoggingMode { None, Manual, Automatic }; enum class EResourcePlane { // Common plane index. Used for all resources Common = 0, // Additional plane indices for depth stencil resources Stencil = 1, Htile = 0, // @todo: do we need to track htile resources? // Additional plane indices for color render targets Cmask = 0, // @todo: do we need to track cmask resources? Fmask = 0, // @todo: do we need to track fmask resources? Max = 2 }; struct FSubresourceIndex { static constexpr int32 kWholeResource = -1; int32 MipIndex; int32 ArraySlice; int32 PlaneIndex; constexpr FSubresourceIndex() : MipIndex(kWholeResource) , ArraySlice(kWholeResource) , PlaneIndex(kWholeResource) {} constexpr FSubresourceIndex(int32 InMipIndex, int32 InArraySlice, int32 InPlaneIndex) : MipIndex(InMipIndex) , ArraySlice(InArraySlice) , PlaneIndex(InPlaneIndex) {} inline bool IsWholeResource() const { return MipIndex == kWholeResource && ArraySlice == kWholeResource && PlaneIndex == kWholeResource; } }; struct FState { ERHIAccess Access; ERHIPipeline Pipelines; FState() = default; FState(ERHIAccess InAccess, ERHIPipeline InPipelines) : Access(InAccess) , Pipelines(InPipelines) {} inline bool operator == (const FState& RHS) const { return Access == RHS.Access && Pipelines == RHS.Pipelines; } inline bool operator != (const FState& RHS) const { return !(*this == RHS); } inline FString ToString() const { return FString::Printf(TEXT("Access: %s, Pipelines: %s"), *GetRHIAccessName(Access), *GetRHIPipelineName(Pipelines)); } }; struct FSubresourceState { struct FPipelineState { FPipelineState() { Current.Access = ERHIAccess::Unknown; Current.Pipelines = ERHIPipeline::Graphics; Previous = Current; } FState Previous; FState Current; EResourceTransitionFlags Flags = EResourceTransitionFlags::None; // True when a BeginTransition has been issued, and false when the transition has been ended. bool bTransitioning = false; // True when a transition with EResourceTransitionFlags::IgnoreAfterState happened, another transition with EResourceTransitionFlags::IgnoreAfterState needs to happen before a regular one bool bIgnoringAfterState = false; // True when the resource has been used within a Begin/EndUAVOverlap region. bool bUsedWithAllUAVsOverlap = false; // True if the calling code explicitly enabled overlapping on this UAV. bool bExplicitAllowUAVOverlap = false; bool bUsedWithExplicitUAVsOverlap = false; // Pointer to the previous create/begin transition backtraces if logging is enabled for this resource. void* CreateTransitionBacktrace = nullptr; void* BeginTransitionBacktrace = nullptr; }; TRHIPipelineArray LastTransitionFences{InPlace, 0}; TRHIPipelineArray States; void BeginTransition (FResource* Resource, FSubresourceIndex const& SubresourceIndex, const FState& CurrentStateFromRHI, const FState& TargetState, EResourceTransitionFlags NewFlags, ERHITransitionCreateFlags CreateFlags, ERHIPipeline Pipeline, const TRHIPipelineArray& PipelineMaxAwaitedFenceValues, void* CreateTrace); void EndTransition (FResource* Resource, FSubresourceIndex const& SubresourceIndex, const FState& CurrentStateFromRHI, const FState& TargetState, EResourceTransitionFlags NewFlags, ERHIPipeline Pipeline, uint64 PipelineFenceValue, void* CreateTrace); void Assert (FResource* Resource, FSubresourceIndex const& SubresourceIndex, const FState& RequiredState, bool bAllowAllUAVsOverlap); void AssertTracked (FResource* Resource, FSubresourceIndex const& SubresourceIndex, const FState& RequiredState, ERHIPipeline ExecutingPipeline); void SpecificUAVOverlap(FResource* Resource, FSubresourceIndex const& SubresourceIndex, ERHIPipeline Pipeline, bool bAllow); }; struct FSubresourceRange { uint32 MipIndex, NumMips; uint32 ArraySlice, NumArraySlices; uint32 PlaneIndex, NumPlanes; FSubresourceRange() = default; FSubresourceRange(uint32 InMipIndex, uint32 InNumMips, uint32 InArraySlice, uint32 InNumArraySlices, uint32 InPlaneIndex, uint32 InNumPlanes) : MipIndex(InMipIndex) , NumMips(InNumMips) , ArraySlice(InArraySlice) , NumArraySlices(InNumArraySlices) , PlaneIndex(InPlaneIndex) , NumPlanes(InNumPlanes) {} inline bool operator == (FSubresourceRange const& RHS) const { return MipIndex == RHS.MipIndex && NumMips == RHS.NumMips && ArraySlice == RHS.ArraySlice && NumArraySlices == RHS.NumArraySlices && PlaneIndex == RHS.PlaneIndex && NumPlanes == RHS.NumPlanes; } inline bool operator != (FSubresourceRange const& RHS) const { return !(*this == RHS); } inline bool IsWholeResource(FResource& Resource) const; }; inline uint32 GetTypeHash(const FSubresourceRange& Range) { uint32 Hash = HashCombineFast(Range.MipIndex, Range.NumMips); Hash = HashCombineFast(Hash, Range.ArraySlice); Hash = HashCombineFast(Hash, Range.NumArraySlices); Hash = HashCombineFast(Hash, Range.PlaneIndex); Hash = HashCombineFast(Hash, Range.NumPlanes); return Hash; } struct FResourceIdentity { FResource* Resource; FSubresourceRange SubresourceRange; FResourceIdentity() = default; inline bool operator == (FResourceIdentity const& RHS) const { return Resource == RHS.Resource && SubresourceRange == RHS.SubresourceRange; } inline bool operator != (FResourceIdentity const& RHS) const { return !(*this == RHS); } }; inline uint32 GetTypeHash(const FResourceIdentity& ResourceIdentiy) { uint32 Hash = PointerHash(ResourceIdentiy.Resource); Hash = HashCombineFast(Hash, GetTypeHash(ResourceIdentiy.SubresourceRange)); return Hash; } struct FViewIdentity : public FResourceIdentity { uint32 Stride = 0; RHI_API FViewIdentity(FRHIViewableResource* Resource, FRHIViewDesc const& ViewDesc); }; struct FTransientState { FTransientState() = default; enum class EStatus : uint8 { None, Acquired, Discarded }; FTransientState(ERHIAccess InitialAccess) : bTransient(InitialAccess == ERHIAccess::Discard) {} void* AcquireBacktrace = nullptr; int32 NumAcquiredSubresources = 0; bool bTransient = false; EStatus Status = EStatus::None; FORCEINLINE bool IsAcquired() const { return Status == EStatus::Acquired; } FORCEINLINE bool IsDiscarded() const { return Status == EStatus::Discarded; } void Acquire(FResource* Resource, void* CreateTrace, ERHIPipeline ExecutingPipeline); void Discard(FResource* Resource, void* CreateTrace, ERHIPipeline DiscardPipelines, ERHIPipeline ExecutingPipeline); static void AliasingOverlap(FResource* ResourceBefore, FResource* ResourceAfter, void* CreateTrace); }; class FResource { friend FTracker; friend FTextureResource; friend FOperation; friend FSubresourceState; friend FSubresourceRange; friend FTransientState; friend FValidationRHI; protected: uint32 NumMips = 0; uint32 NumArraySlices = 0; uint32 NumPlanes = 0; FTransientState TransientState; FState TrackedState{ ERHIAccess::Unknown, ERHIPipeline::None }; private: FString DebugName; FSubresourceState WholeResourceState; TArray SubresourceStates; mutable FThreadSafeCounter NumOpRefs; inline void EnumerateSubresources(FSubresourceRange const& SubresourceRange, TFunctionRef Callback, bool bBeginTransition = false); public: ~FResource() { checkf(NumOpRefs.GetValue() == 0, TEXT("RHI validation resource '%s' is being deleted, but it is still queued in the replay command stream!"), *DebugName); } ELoggingMode LoggingMode = ELoggingMode::None; RHI_API void SetDebugName(const TCHAR* Name, const TCHAR* Suffix = nullptr); inline const TCHAR* GetDebugName() const { return DebugName.Len() ? *DebugName : nullptr; } inline bool IsBarrierTrackingInitialized() const { return NumMips > 0 && NumArraySlices > 0; } inline void AddOpRef() const { NumOpRefs.Increment(); } inline void ReleaseOpRef() const { const int32 RefCount = NumOpRefs.Decrement(); check(RefCount >= 0); } inline FState GetTrackedState() const { return TrackedState; } inline uint32 GetNumSubresources() const { return NumMips * NumArraySlices * NumPlanes; } inline FSubresourceRange GetWholeResourceRange() { checkSlow(NumMips > 0 && NumArraySlices > 0 && NumPlanes > 0); FSubresourceRange SubresourceRange; SubresourceRange.MipIndex = 0; SubresourceRange.ArraySlice = 0; SubresourceRange.PlaneIndex = 0; SubresourceRange.NumMips = NumMips; SubresourceRange.NumArraySlices = NumArraySlices; SubresourceRange.NumPlanes = NumPlanes; return SubresourceRange; } inline FResourceIdentity GetWholeResourceIdentity() { FResourceIdentity Identity; Identity.Resource = this; Identity.SubresourceRange = GetWholeResourceRange(); return Identity; } void InitTransient(const TCHAR* InDebugName); protected: void InitBarrierTracking(int32 InNumMips, int32 InNumArraySlices, int32 InNumPlanes, ERHIAccess InResourceState, const TCHAR* InDebugName); }; inline bool FSubresourceRange::IsWholeResource(FResource& Resource) const { return MipIndex == 0 && ArraySlice == 0 && PlaneIndex == 0 && NumMips == Resource.NumMips && NumArraySlices == Resource.NumArraySlices && NumPlanes == Resource.NumPlanes; } class FBufferResource : public FResource { public: inline void InitBarrierTracking(ERHIAccess InResourceState, FName InDebugName) { const FString LocalDebugName = InDebugName.ToString(); FResource::InitBarrierTracking(1, 1, 1, InResourceState, *LocalDebugName); } }; class FAccelerationStructureResource : public FBufferResource { public: }; class FTextureResource { private: // Don't use inheritance here. Because FRHITextureReferences exist, we have to // call through a virtual to get the real underlying tracker resource from an FRHITexture*. FResource PRIVATE_TrackerResource; int32 GetNumPlanesFromFormat(EPixelFormat Format); public: FTextureResource() = default; RHI_API FTextureResource(FRHITextureCreateDesc const& CreateDesc); virtual ~FTextureResource() {} virtual FResource* GetTrackerResource() { return &PRIVATE_TrackerResource; } RHI_API void InitBarrierTracking(FRHITextureCreateDesc const& CreateDesc); inline bool IsBarrierTrackingInitialized() const { // @todo: clean up const_cast once FRHITextureReference is removed and // we don't need to keep a separate PRIVATE_TrackerResource object. return const_cast(this)->GetTrackerResource()->IsBarrierTrackingInitialized(); } RHI_API void InitBarrierTracking (int32 InNumMips, int32 InNumArraySlices, EPixelFormat PixelFormat, ETextureCreateFlags Flags, ERHIAccess InResourceState, const TCHAR* InDebugName); RHI_API void CheckValidationLayout(int32 InNumMips, int32 InNumArraySlices, EPixelFormat PixelFormat); RHI_API FResourceIdentity GetViewIdentity(uint32 InMipIndex, uint32 InNumMips, uint32 InArraySlice, uint32 InNumArraySlices, uint32 InPlaneIndex, uint32 InNumPlanes); RHI_API FResourceIdentity GetTransitionIdentity(const FRHITransitionInfo& Info); inline FResourceIdentity GetWholeResourceIdentity() { return GetTrackerResource()->GetWholeResourceIdentity(); } inline FResourceIdentity GetWholeResourceIdentitySRV() { FResourceIdentity Identity = GetWholeResourceIdentity(); // When binding a whole texture for shader read (SRV), we only use the first plane. // Other planes like stencil require a separate view to access for read in the shader. Identity.SubresourceRange.NumPlanes = 1; return Identity; } }; class FRayTracingPipelineState { public: RHI_API FRayTracingPipelineState(const FRayTracingPipelineStateInitializer& Initializer); RHI_API FRHIRayTracingShader* GetShader(ERayTracingBindingType BindingType, uint32 Index) const; private: // Cache the RHIShaders per binding type so they can be retrieved during SetBindingsOnShaderBindingTable to find all the used resources for a certain RHIShader TArray MissShaders; TArray HitGroupShaders; TArray CallableShaders; }; struct FUAVBinding { FRHIUnorderedAccessView* UAV = nullptr; uint32 Slot = 0; inline bool operator == (FUAVBinding const& RHS) const { return UAV == RHS.UAV && Slot == RHS.Slot; } inline bool operator != (FUAVBinding const& RHS) const { return !(*this == RHS); } }; inline uint32 GetTypeHash(const FUAVBinding& UAVBinding) { uint32 Hash = PointerHash(UAVBinding.UAV); Hash = HashCombineFast(Hash, UAVBinding.Slot); return Hash; } class FShaderBindingTable { public: RHI_API FShaderBindingTable(const FRayTracingShaderBindingTableInitializer& InInitializer); RHI_API void Clear(); RHI_API void SetBindingsOnShaderBindingTable(FRayTracingPipelineState* RayTracingPipelineState, uint32 NumBindings, const FRayTracingLocalShaderBindings* Bindings, ERayTracingBindingType BindingType); RHI_API void Commit(); RHI_API void ValidateStateForDispatch(RHIValidation::FTracker* Tracker) const; void AddSRV(const FResourceIdentity& ResourceIdentity, uint32 WorkerIndex) { WorkerData[WorkerIndex].SRVs.Add(ResourceIdentity); } void AddUAV(FRHIUnorderedAccessView* UAV, uint32 InSlot, uint32 WorkerIndex) { WorkerData[WorkerIndex].UAVs.Add({ UAV, InSlot }); } private: ERayTracingShaderBindingTableLifetime LifeTime; ERayTracingShaderBindingMode ShaderBindingMode; ERayTracingHitGroupIndexingMode HitGroupIndexingMode; bool bIsDirty = true; struct FWorkerThreadData { TSet SRVs; TSet UAVs; }; static constexpr uint32 MaxBindingWorkers = 5; // RHI thread + 4 parallel workers. FWorkerThreadData WorkerData[MaxBindingWorkers]; }; struct FFence { bool bSignaled = false; ERHIPipeline SrcPipe = ERHIPipeline::None; ERHIPipeline DstPipe = ERHIPipeline::None; uint64 FenceValue = 0; }; enum class EOpType { BeginTransition , EndTransition , SetTrackedAccess , AliasingOverlap , AcquireTransient , DiscardTransient , InitTransient , Assert , Rename , Signal , Wait , AllUAVsOverlap , SpecificUAVOverlap #if WITH_RHI_BREADCRUMBS , BeginBreadcrumbGPU , EndBreadcrumbGPU , SetBreadcrumbRange #endif }; struct FUniformBufferResource { uint64 AllocatedFrameID = 0; bool bContainsNullContents = false; EUniformBufferUsage UniformBufferUsage; void* AllocatedCallstack; void InitLifetimeTracking(uint64 FrameID, const void* Contents, EUniformBufferUsage Usage); void UpdateAllocation(uint64 FrameID); void ValidateLifeTime(); }; struct FOpQueueState; struct FOperation { EOpType Type; union { struct { FResourceIdentity Identity; FState PreviousState; FState NextState; EResourceTransitionFlags Flags; ERHITransitionCreateFlags CreateFlags; void* CreateBacktrace; } Data_BeginTransition; struct { FResourceIdentity Identity; FState PreviousState; FState NextState; EResourceTransitionFlags Flags; void* CreateBacktrace; } Data_EndTransition; struct { FResource* Resource; FState State; } Data_SetTrackedAccess; struct { FResource* ResourceBefore; FResource* ResourceAfter; void* CreateBacktrace; } Data_AliasingOverlap; struct { FResource* Resource; void* CreateBacktrace; } Data_AcquireTransient; struct { FResource* Resource; TCHAR* DebugName; } Data_InitTransient; struct { FResourceIdentity Identity; FState RequiredState; } Data_Assert; struct { FResource* Resource; TCHAR* DebugName; const TCHAR* Suffix; } Data_Rename; struct { FFence* Fence; } Data_Signal; struct { FFence* Fence; } Data_Wait; struct { bool bAllow; } Data_AllUAVsOverlap; struct { FResourceIdentity Identity; bool bAllow; } Data_SpecificUAVOverlap; #if WITH_RHI_BREADCRUMBS struct { FRHIBreadcrumbNode* Breadcrumb; } Data_Breadcrumb; struct { FRHIBreadcrumbRange Range; } Data_BreadcrumbRange; #endif // WITH_RHI_BREADCRUMBS }; // Returns true if the operation is complete RHI_API bool Replay(FOpQueueState& Queue) const; static inline FOperation BeginTransitionResource(FResourceIdentity Identity, FState PreviousState, FState NextState, EResourceTransitionFlags Flags, ERHITransitionCreateFlags CreateFlags, void* CreateBacktrace) { for (ERHIPipeline Pipeline : MakeFlagsRange(PreviousState.Pipelines)) { Identity.Resource->AddOpRef(); } FOperation Op; Op.Type = EOpType::BeginTransition; Op.Data_BeginTransition.Identity = Identity; Op.Data_BeginTransition.PreviousState = PreviousState; Op.Data_BeginTransition.NextState = NextState; Op.Data_BeginTransition.Flags = Flags; Op.Data_BeginTransition.CreateFlags = CreateFlags; Op.Data_BeginTransition.CreateBacktrace = CreateBacktrace; return MoveTemp(Op); } static inline FOperation EndTransitionResource(FResourceIdentity Identity, FState PreviousState, FState NextState, EResourceTransitionFlags Flags, void* CreateBacktrace) { for (ERHIPipeline Pipeline : MakeFlagsRange(NextState.Pipelines)) { Identity.Resource->AddOpRef(); } FOperation Op; Op.Type = EOpType::EndTransition; Op.Data_EndTransition.Identity = Identity; Op.Data_EndTransition.PreviousState = PreviousState; Op.Data_EndTransition.NextState = NextState; Op.Data_EndTransition.Flags = Flags; Op.Data_EndTransition.CreateBacktrace = CreateBacktrace; return MoveTemp(Op); } static inline FOperation SetTrackedAccess(FResource* Resource, FState State) { Resource->AddOpRef(); FOperation Op; Op.Type = EOpType::SetTrackedAccess; Op.Data_SetTrackedAccess.Resource = Resource; Op.Data_SetTrackedAccess.State = State; return MoveTemp(Op); } static inline FOperation AliasingOverlap(FResource* ResourceBefore, FResource* ResourceAfter, void* CreateBacktrace) { ResourceBefore->AddOpRef(); ResourceAfter->AddOpRef(); FOperation Op; Op.Type = EOpType::AliasingOverlap; Op.Data_AliasingOverlap.ResourceBefore = ResourceBefore; Op.Data_AliasingOverlap.ResourceAfter = ResourceAfter; Op.Data_AliasingOverlap.CreateBacktrace = CreateBacktrace; return Op; } static inline FOperation AcquireTransientResource(FResource* Resource, void* CreateBacktrace) { Resource->AddOpRef(); FOperation Op; Op.Type = EOpType::AcquireTransient; Op.Data_AcquireTransient.Resource = Resource; Op.Data_AcquireTransient.CreateBacktrace = CreateBacktrace; return MoveTemp(Op); } static inline FOperation InitTransient(FResource* Resource, const TCHAR* DebugName) { Resource->AddOpRef(); FOperation Op; Op.Type = EOpType::InitTransient; Op.Data_InitTransient.Resource = Resource; AllocStringCopy(Op.Data_InitTransient.DebugName, DebugName); return MoveTemp(Op); } static inline FOperation Assert(FResourceIdentity Identity, FState RequiredState) { Identity.Resource->AddOpRef(); FOperation Op; Op.Type = EOpType::Assert; Op.Data_Assert.Identity = Identity; Op.Data_Assert.RequiredState = RequiredState; return MoveTemp(Op); } static inline FOperation Rename(FResource* Resource, const TCHAR* NewName, const TCHAR* Suffix = nullptr) { Resource->AddOpRef(); FOperation Op; Op.Type = EOpType::Rename; Op.Data_Rename.Resource = Resource; AllocStringCopy(Op.Data_Rename.DebugName, NewName); Op.Data_Rename.Suffix = Suffix; return MoveTemp(Op); } static inline FOperation Signal(FFence* Fence) { FOperation Op; Op.Type = EOpType::Signal; Op.Data_Signal.Fence = Fence; return MoveTemp(Op); } static inline FOperation Wait(FFence* Fence) { FOperation Op; Op.Type = EOpType::Wait; Op.Data_Wait.Fence = Fence; return MoveTemp(Op); } static inline FOperation AllUAVsOverlap(bool bAllow) { FOperation Op; Op.Type = EOpType::AllUAVsOverlap; Op.Data_AllUAVsOverlap.bAllow = bAllow; return MoveTemp(Op); } static inline FOperation SpecificUAVOverlap(FResourceIdentity Identity, bool bAllow) { Identity.Resource->AddOpRef(); FOperation Op; Op.Type = EOpType::SpecificUAVOverlap; Op.Data_SpecificUAVOverlap.Identity = Identity; Op.Data_SpecificUAVOverlap.bAllow = bAllow; return MoveTemp(Op); } #if WITH_RHI_BREADCRUMBS static inline FOperation BeginBreadcrumbGPU(FRHIBreadcrumbNode* Breadcrumb) { check(Breadcrumb && Breadcrumb != FRHIBreadcrumbNode::Sentinel); FOperation Op; Op.Type = EOpType::BeginBreadcrumbGPU; Op.Data_Breadcrumb.Breadcrumb = Breadcrumb; return Op; } static inline FOperation EndBreadcrumbGPU(FRHIBreadcrumbNode* Breadcrumb) { check(Breadcrumb && Breadcrumb != FRHIBreadcrumbNode::Sentinel); FOperation Op; Op.Type = EOpType::EndBreadcrumbGPU; Op.Data_Breadcrumb.Breadcrumb = Breadcrumb; return Op; } static FOperation SetBreadcrumbRange(FRHIBreadcrumbRange const& BreadcrumbRange) { check(BreadcrumbRange.First != FRHIBreadcrumbNode::Sentinel); check(BreadcrumbRange.Last != FRHIBreadcrumbNode::Sentinel); FOperation Op; Op.Type = EOpType::SetBreadcrumbRange; Op.Data_BreadcrumbRange.Range = BreadcrumbRange; return Op; } #endif // WITH_RHI_BREADCRUMBS private: static inline void AllocStringCopy(TCHAR*& OutString, const TCHAR* InString) { int32 Len = FCString::Strlen(InString); OutString = new TCHAR[Len + 1]; FMemory::Memcpy(OutString, InString, Len * sizeof(TCHAR)); OutString[Len] = 0; } }; struct FTransitionResource { TRHIPipelineArray> PendingSignals; TRHIPipelineArray> PendingWaits; TArray PendingAliases; TArray PendingAliasingOverlaps; TArray PendingOperationsBegin; TArray PendingOperationsEnd; }; enum class EUAVMode { Graphics, Compute, Num }; struct FOpQueueState { ERHIPipeline const Pipeline; uint64 FenceValue = 0; TRHIPipelineArray MaxAwaitedFenceValues{InPlace, 0}; #if WITH_RHI_BREADCRUMBS struct { FRHIBreadcrumbRange Range {}; FRHIBreadcrumbNode* Current = nullptr; } Breadcrumbs; #endif bool bAllowAllUAVsOverlap = false; struct FOpsList : public TArray { int32 ReplayPos = 0; FOpsList(FOpsList&&) = default; FOpsList(TArray&& Other) : TArray(MoveTemp(Other)) {} }; TArray Ops; FOpQueueState(ERHIPipeline Pipeline) : Pipeline(Pipeline) {} void AppendOps(FValidationCommandList* CommandList); // Returns true if progress was made bool Execute(); }; class FTracker { struct FUAVTracker { private: TArray UAVs; public: FUAVTracker() { UAVs.Reserve(GRHIGlobals.MinGuaranteedSimultaneousUAVs); } inline FRHIUnorderedAccessView*& operator[](int32 Slot) { if (Slot >= UAVs.Num()) { UAVs.SetNumZeroed(Slot + 1); } return UAVs[Slot]; } inline void Reset() { UAVs.SetNum(0, EAllowShrinking::No); } void DrawOrDispatch(FTracker* BarrierTracker, const FState& RequiredState); }; public: FTracker(ERHIPipeline InPipeline) : Pipeline(InPipeline) {} RHI_API void AddOp(const FOperation& Op); void AddOps(TArray const& List) { for (const FOperation& Op : List) { AddOp(Op); } } TArray Finalize() { return MoveTemp(CurrentList); } #if WITH_RHI_BREADCRUMBS void BeginBreadcrumbGPU(FRHIBreadcrumbNode* Breadcrumb) { AddOp(FOperation::BeginBreadcrumbGPU(Breadcrumb)); } void EndBreadcrumbGPU(FRHIBreadcrumbNode* Breadcrumb) { AddOp(FOperation::EndBreadcrumbGPU(Breadcrumb)); } #endif void SetTrackedAccess(FResource* Resource, ERHIAccess Access, ERHIPipeline Pipelines) { AddOp(FOperation::SetTrackedAccess(Resource, FState(Access, Pipelines))); } void Rename(FResource* Resource, const TCHAR* NewName, const TCHAR* Suffix = nullptr) { AddOp(FOperation::Rename(Resource, NewName, Suffix)); } void Assert(FResourceIdentity Identity, ERHIAccess RequiredAccess) { AddOp(FOperation::Assert(Identity, FState(RequiredAccess, Pipeline))); } void AssertUAV(FRHIUnorderedAccessView* UAV, EUAVMode Mode, int32 Slot) { checkSlow(Mode == EUAVMode::Compute || Pipeline == ERHIPipeline::Graphics); UAVTrackers[int32(Mode)][Slot] = UAV; } void AssertUAV(FRHIUnorderedAccessView* UAV, ERHIAccess Access, int32 Slot) { checkSlow(!(Access & ~ERHIAccess::UAVMask)); AssertUAV(UAV, Access == ERHIAccess::UAVGraphics ? EUAVMode::Graphics : EUAVMode::Compute, Slot); } void TransitionResource(FResourceIdentity Identity, FState PreviousState, FState NextState, EResourceTransitionFlags Flags) { // This function exists due to the implicit transitions that RHI functions make (e.g. RHICopyToResolveTarget). // It should be removed when we eventually remove all implicit transitions from the RHI. AddOp(FOperation::BeginTransitionResource(Identity, PreviousState, NextState, Flags, ERHITransitionCreateFlags::None, nullptr)); AddOp(FOperation::EndTransitionResource(Identity, PreviousState, NextState, Flags, nullptr)); } void AllUAVsOverlap(bool bAllow) { AddOp(FOperation::AllUAVsOverlap(bAllow)); } void SpecificUAVOverlap(FResourceIdentity Identity, bool bAllow) { AddOp(FOperation::SpecificUAVOverlap(Identity, bAllow)); } void Dispatch() { UAVTrackers[int32(EUAVMode::Compute)].DrawOrDispatch(this, FState(ERHIAccess::UAVCompute, Pipeline)); } void Draw() { checkSlow(Pipeline == ERHIPipeline::Graphics); UAVTrackers[int32(EUAVMode::Graphics)].DrawOrDispatch(this, FState(ERHIAccess::UAVGraphics, Pipeline)); } void ResetUAVState(EUAVMode Mode) { UAVTrackers[int32(Mode)].Reset(); } void ResetAllUAVState() { for (int32 Index = 0; Index < UE_ARRAY_COUNT(UAVTrackers); ++Index) { ResetUAVState(EUAVMode(Index)); } } static FOpQueueState& GetQueue(ERHIPipeline Pipeline); static void SubmitValidationOps(ERHIPipeline Pipeline, TArray&& Ops); private: const ERHIPipeline Pipeline; TArray CurrentList; FUAVTracker UAVTrackers[int32(EUAVMode::Num)]; friend FOperation; static RHI_API FOpQueueState OpQueues[int32(ERHIPipeline::Num)]; }; extern RHI_API void* CaptureBacktrace(); /** Validates that the SRV is conform to what the shader expects */ extern RHI_API void ValidateShaderResourceView(const FRHIShader* RHIShaderBase, uint32 BindIndex, const FRHIShaderResourceView* SRV); extern RHI_API void ValidateShaderResourceView(const FRHIShader* RHIShaderBase, uint32 BindIndex, const FRHITexture* Texture); /** Validates that the UAV conforms to what the shader expects */ extern RHI_API void ValidateUnorderedAccessView(const FRHIShader* RHIShaderBase, uint32 BindIndex, const FRHIUnorderedAccessView* SRV); /** Validates that the UB conforms to what the shader expects */ extern RHI_API void ValidateUniformBuffer(const FRHIShader* RHIShaderBase, uint32 BindIndex, FRHIUniformBuffer* SRV); } #endif // ENABLE_RHI_VALIDATION