You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
#rb zach.bethel #preflight 612f0d3779d62b0001b43362 #ROBOMERGE-SOURCE: CL 17383153 via CL 17383415 #ROBOMERGE-BOT: STARSHIP (Main -> Release-Engine-Test) (v865-17346139) [CL 17383421 by charles derousiers in ue5-release-engine-test branch]
1839 lines
61 KiB
C++
1839 lines
61 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "RenderGraphValidation.h"
|
|
#include "RenderGraphPrivate.h"
|
|
#include "Misc/FileHelper.h"
|
|
#include "Misc/Paths.h"
|
|
#include "MultiGPU.h"
|
|
|
|
#if RDG_ENABLE_DEBUG
|
|
|
|
namespace
|
|
{
|
|
template <typename TFunction>
|
|
void EnumerateSubresources(const FRHITransitionInfo& Transition, uint32 NumMips, uint32 NumArraySlices, uint32 NumPlaneSlices, TFunction Function)
|
|
{
|
|
uint32 MinMipIndex = 0;
|
|
uint32 MaxMipIndex = NumMips;
|
|
uint32 MinArraySlice = 0;
|
|
uint32 MaxArraySlice = NumArraySlices;
|
|
uint32 MinPlaneSlice = 0;
|
|
uint32 MaxPlaneSlice = NumPlaneSlices;
|
|
|
|
if (!Transition.IsAllMips())
|
|
{
|
|
MinMipIndex = Transition.MipIndex;
|
|
MaxMipIndex = MinMipIndex + 1;
|
|
}
|
|
|
|
if (!Transition.IsAllArraySlices())
|
|
{
|
|
MinArraySlice = Transition.ArraySlice;
|
|
MaxArraySlice = MinArraySlice + 1;
|
|
}
|
|
|
|
if (!Transition.IsAllPlaneSlices())
|
|
{
|
|
MinPlaneSlice = Transition.PlaneSlice;
|
|
MaxPlaneSlice = MinPlaneSlice + 1;
|
|
}
|
|
|
|
for (uint32 PlaneSlice = MinPlaneSlice; PlaneSlice < MaxPlaneSlice; ++PlaneSlice)
|
|
{
|
|
for (uint32 ArraySlice = MinArraySlice; ArraySlice < MaxArraySlice; ++ArraySlice)
|
|
{
|
|
for (uint32 MipIndex = MinMipIndex; MipIndex < MaxMipIndex; ++MipIndex)
|
|
{
|
|
Function(FRDGTextureSubresource(MipIndex, ArraySlice, PlaneSlice));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const ERHIAccess AccessMaskCopy = ERHIAccess::CopySrc | ERHIAccess::CopyDest | ERHIAccess::CPURead;
|
|
const ERHIAccess AccessMaskCompute = ERHIAccess::SRVCompute | ERHIAccess::UAVCompute;
|
|
const ERHIAccess AccessMaskRaster = ERHIAccess::ResolveSrc | ERHIAccess::ResolveDst | ERHIAccess::DSVRead | ERHIAccess::DSVWrite | ERHIAccess::RTV | ERHIAccess::SRVGraphics | ERHIAccess::UAVGraphics | ERHIAccess::Present | ERHIAccess::VertexOrIndexBuffer;
|
|
const ERHIAccess AccessMaskComputeOrRaster = ERHIAccess::IndirectArgs;
|
|
|
|
/** Validates that only one builder instance exists at any time. This is currently a requirement for state tracking and allocation lifetimes. */
|
|
bool GRDGBuilderActive = false;
|
|
|
|
} //! namespace
|
|
|
|
struct FRDGResourceDebugData
|
|
{
|
|
/** Boolean to track at runtime whether a resource is actually used by the lambda of a pass or not, to detect unnecessary resource dependencies on passes. */
|
|
bool bIsActuallyUsedByPass = false;
|
|
|
|
/** Boolean to track at pass execution whether the underlying RHI resource is allowed to be accessed. */
|
|
bool bAllowRHIAccess = false;
|
|
};
|
|
|
|
void FRDGResource::MarkResourceAsUsed()
|
|
{
|
|
ValidateRHIAccess();
|
|
|
|
GetDebugData().bIsActuallyUsedByPass = true;
|
|
}
|
|
|
|
void FRDGResource::ValidateRHIAccess() const
|
|
{
|
|
check(DebugData);
|
|
checkf(DebugData->bAllowRHIAccess || GRDGAllowRHIAccess,
|
|
TEXT("Accessing the RHI resource of %s at this time is not allowed. If you hit this check in pass, ")
|
|
TEXT("that is due to this resource not being referenced in the parameters of your pass."),
|
|
Name);
|
|
}
|
|
|
|
FRDGResourceDebugData& FRDGResource::GetDebugData() const
|
|
{
|
|
check(DebugData);
|
|
return *DebugData;
|
|
}
|
|
|
|
struct FRDGParentResourceDebugData
|
|
{
|
|
/** Pointer towards the pass that is the first to produce it, for even more convenient error message. */
|
|
const FRDGPass* FirstProducer = nullptr;
|
|
|
|
/** Count the number of times it has been used by a pass (without culling). */
|
|
uint32 PassAccessCount = 0;
|
|
|
|
/** Tracks whether this resource was clobbered by the builder prior to use. */
|
|
bool bHasBeenClobbered = false;
|
|
|
|
/** Tracks which pass performed a finalize operation on the resource. */
|
|
FRDGPassHandle FinalizePass;
|
|
};
|
|
|
|
FRDGParentResourceDebugData& FRDGParentResource::GetParentDebugData() const
|
|
{
|
|
check(ParentDebugData);
|
|
return *ParentDebugData;
|
|
}
|
|
|
|
struct FRDGTextureDebugData
|
|
{
|
|
/** Tracks whether a UAV has ever been allocated to catch when TexCreate_UAV was unneeded. */
|
|
bool bHasNeededUAV = false;
|
|
|
|
/** Tracks whether has ever been bound as a render target to catch when TexCreate_RenderTargetable was unneeded. */
|
|
bool bHasBeenBoundAsRenderTarget = false;
|
|
};
|
|
|
|
FRDGTextureDebugData& FRDGTexture::GetTextureDebugData() const
|
|
{
|
|
check(TextureDebugData);
|
|
return *TextureDebugData;
|
|
}
|
|
|
|
struct FRDGBufferDebugData
|
|
{
|
|
/** Tracks state changes in order of execution. */
|
|
TArray<TPair<FRDGPassHandle, FRDGSubresourceState>, FRDGArrayAllocator> States;
|
|
};
|
|
|
|
FRDGBufferDebugData& FRDGBuffer::GetBufferDebugData() const
|
|
{
|
|
check(BufferDebugData);
|
|
return *BufferDebugData;
|
|
}
|
|
|
|
void FRDGUniformBuffer::MarkResourceAsUsed()
|
|
{
|
|
FRDGResource::MarkResourceAsUsed();
|
|
|
|
// Individual resources can't be culled from a uniform buffer, so we have to mark them all as used.
|
|
ParameterStruct.Enumerate([](FRDGParameter Parameter)
|
|
{
|
|
if (FRDGResourceRef Resource = Parameter.GetAsResource())
|
|
{
|
|
Resource->MarkResourceAsUsed();
|
|
}
|
|
});
|
|
}
|
|
|
|
FRDGUserValidation::FRDGUserValidation(FRDGAllocator& InAllocator, bool bInParallelExecuteEnabled)
|
|
: Allocator(InAllocator)
|
|
, bParallelExecuteEnabled(bInParallelExecuteEnabled)
|
|
{
|
|
checkf(!GRDGBuilderActive, TEXT("Another FRDGBuilder already exists on the stack. Only one builder can be created at a time. This builder instance should be merged into the parent one."));
|
|
GRDGBuilderActive = true;
|
|
}
|
|
|
|
FRDGUserValidation::~FRDGUserValidation()
|
|
{
|
|
checkf(bHasExecuted, TEXT("Render graph execution is required to ensure consistency with immediate mode."));
|
|
}
|
|
|
|
void FRDGUserValidation::ExecuteGuard(const TCHAR* Operation, const TCHAR* ResourceName)
|
|
{
|
|
checkf(!bHasExecuted, TEXT("Render graph operation '%s' with resource '%s' must be performed prior to graph execution."), Operation, ResourceName);
|
|
}
|
|
|
|
void FRDGUserValidation::ValidateCreateResource(FRDGResourceRef Resource)
|
|
{
|
|
check(Resource);
|
|
Resource->DebugData = Allocator.Alloc<FRDGResourceDebugData>();
|
|
|
|
bool bAlreadyInSet = false;
|
|
ResourceMap.Emplace(Resource, &bAlreadyInSet);
|
|
check(!bAlreadyInSet);
|
|
}
|
|
|
|
void FRDGUserValidation::ValidateCreateParentResource(FRDGParentResourceRef Resource)
|
|
{
|
|
ValidateCreateResource(Resource);
|
|
Resource->ParentDebugData = Allocator.Alloc<FRDGParentResourceDebugData>();
|
|
}
|
|
|
|
void FRDGUserValidation::ValidateCreateTexture(FRDGTextureRef Texture)
|
|
{
|
|
ValidateCreateParentResource(Texture);
|
|
Texture->TextureDebugData = Allocator.Alloc<FRDGTextureDebugData>();
|
|
if (GRDGDebug)
|
|
{
|
|
TrackedTextures.Add(Texture);
|
|
}
|
|
}
|
|
|
|
void FRDGUserValidation::ValidateCreateBuffer(FRDGBufferRef Buffer)
|
|
{
|
|
ValidateCreateParentResource(Buffer);
|
|
Buffer->BufferDebugData = Allocator.Alloc<FRDGBufferDebugData>();
|
|
if (GRDGDebug)
|
|
{
|
|
TrackedBuffers.Add(Buffer);
|
|
}
|
|
}
|
|
|
|
void FRDGUserValidation::ValidateCreateSRV(FRDGTextureSRVRef SRV)
|
|
{
|
|
ValidateCreateResource(SRV);
|
|
}
|
|
|
|
void FRDGUserValidation::ValidateCreateSRV(FRDGBufferSRVRef SRV)
|
|
{
|
|
ValidateCreateResource(SRV);
|
|
}
|
|
|
|
void FRDGUserValidation::ValidateCreateUAV(FRDGTextureUAVRef UAV)
|
|
{
|
|
ValidateCreateResource(UAV);
|
|
}
|
|
|
|
void FRDGUserValidation::ValidateCreateUAV(FRDGBufferUAVRef UAV)
|
|
{
|
|
ValidateCreateResource(UAV);
|
|
}
|
|
|
|
void FRDGUserValidation::ValidateCreateUniformBuffer(FRDGUniformBufferRef UniformBuffer)
|
|
{
|
|
ValidateCreateResource(UniformBuffer);
|
|
}
|
|
|
|
void FRDGUserValidation::ValidateRegisterExternalTexture(
|
|
const TRefCountPtr<IPooledRenderTarget>& ExternalPooledTexture,
|
|
const TCHAR* Name,
|
|
ERenderTargetTexture RenderTargetTexture,
|
|
ERDGTextureFlags Flags)
|
|
{
|
|
checkf(Name, TEXT("Attempted to register external texture with NULL name."));
|
|
checkf(ExternalPooledTexture.IsValid(), TEXT("Attempted to register NULL external texture."));
|
|
checkf(ExternalPooledTexture->IsCompatibleWithRDG(), TEXT("Pooled render target %s is not a compatible type for RDG."), Name);
|
|
ExecuteGuard(TEXT("RegisterExternalTexture"), Name);
|
|
}
|
|
|
|
void FRDGUserValidation::ValidateRegisterExternalBuffer(const TRefCountPtr<FRDGPooledBuffer>& ExternalPooledBuffer, const TCHAR* Name, ERDGBufferFlags Flags)
|
|
{
|
|
checkf(Name, TEXT("Attempted to register external buffer with NULL name."));
|
|
checkf(ExternalPooledBuffer.IsValid(), TEXT("Attempted to register NULL external buffer."));
|
|
ExecuteGuard(TEXT("RegisterExternalBuffer"), Name);
|
|
}
|
|
|
|
void FRDGUserValidation::ValidateRegisterExternalTexture(FRDGTextureRef Texture)
|
|
{
|
|
ValidateCreateTexture(Texture);
|
|
}
|
|
|
|
void FRDGUserValidation::ValidateRegisterExternalBuffer(FRDGBufferRef Buffer)
|
|
{
|
|
ValidateCreateBuffer(Buffer);
|
|
}
|
|
|
|
void FRDGUserValidation::ValidateCreateTexture(const FRDGTextureDesc& Desc, const TCHAR* Name, ERDGTextureFlags Flags)
|
|
{
|
|
checkf(Name, TEXT("Creating a texture requires a valid debug name."));
|
|
ExecuteGuard(TEXT("CreateTexture"), Name);
|
|
|
|
// Validate the pixel format.
|
|
checkf(Desc.Format != PF_Unknown, TEXT("Illegal to create texture %s with an invalid pixel format."), Name);
|
|
checkf(Desc.Format < PF_MAX, TEXT("Illegal to create texture %s with invalid FPooledRenderTargetDesc::Format."), Name);
|
|
checkf(GPixelFormats[Desc.Format].Supported,
|
|
TEXT("Failed to create texture %s with pixel format %s because it is not supported."), Name, GPixelFormats[Desc.Format].Name);
|
|
checkf(Desc.IsValid(), TEXT("Texture %s was created with an invalid descriptor."), Name);
|
|
|
|
// Can't create back buffer textures
|
|
checkf(!EnumHasAnyFlags(Desc.Flags, ETextureCreateFlags::Presentable), TEXT("Illegal to create texture %s with presentable flag."), Name);
|
|
|
|
const bool bCanHaveUAV = EnumHasAnyFlags(Desc.Flags, TexCreate_UAV);
|
|
const bool bIsMSAA = Desc.NumSamples > 1;
|
|
|
|
// D3D11 doesn't allow creating a UAV on MSAA texture.
|
|
const bool bIsUAVForMSAATexture = bIsMSAA && bCanHaveUAV;
|
|
checkf(!bIsUAVForMSAATexture, TEXT("TexCreate_UAV is not allowed on MSAA texture %s."), Name);
|
|
|
|
checkf(!EnumHasAnyFlags(Flags, ERDGTextureFlags::ReadOnly), TEXT("Cannot create texture %s with the ReadOnly flag. Only registered textures can use this flag."), Name);
|
|
}
|
|
|
|
void FRDGUserValidation::ValidateCreateBuffer(const FRDGBufferDesc& Desc, const TCHAR* Name, ERDGBufferFlags Flags)
|
|
{
|
|
checkf(Name, TEXT("Creating a buffer requires a valid debug name."));
|
|
ExecuteGuard(TEXT("CreateBuffer"), Name);
|
|
|
|
checkf(Desc.GetTotalNumBytes() > 0, TEXT("Creating buffer '%s' is zero bytes in size."), Name);
|
|
|
|
const bool bIsByteAddress = (Desc.Usage & BUF_ByteAddressBuffer) == BUF_ByteAddressBuffer;
|
|
|
|
if (bIsByteAddress && Desc.UnderlyingType == FRDGBufferDesc::EUnderlyingType::StructuredBuffer)
|
|
{
|
|
checkf(Desc.BytesPerElement == 4, TEXT("Creating buffer '%s' as a structured buffer that is also byte addressable, BytesPerElement must be 4! Instead it is %d"), Name, Desc.BytesPerElement);
|
|
}
|
|
|
|
checkf(!EnumHasAnyFlags(Flags, ERDGBufferFlags::ReadOnly), TEXT("Cannot create buffer %s with the ReadOnly flag. Only registered buffers can use this flag."), Name);
|
|
}
|
|
|
|
void FRDGUserValidation::ValidateCreateSRV(const FRDGTextureSRVDesc& Desc)
|
|
{
|
|
FRDGTextureRef Texture = Desc.Texture;
|
|
checkf(Texture, TEXT("Texture SRV created with a null texture."));
|
|
ExecuteGuard(TEXT("CreateSRV"), Texture->Name);
|
|
checkf(Texture->Desc.Flags & TexCreate_ShaderResource, TEXT("Attempted to create SRV from texture %s which was not created with TexCreate_ShaderResource"), Desc.Texture->Name);
|
|
|
|
// Validate the pixel format if overridden by the SRV's descriptor.
|
|
if (Desc.Format == PF_X24_G8)
|
|
{
|
|
// PF_X24_G8 is a bit of mess in the RHI, used to read the stencil, but have varying BlockBytes.
|
|
checkf(Texture->Desc.Format == PF_DepthStencil, TEXT("PF_X24_G8 is only to read stencil from a PF_DepthStencil texture"));
|
|
}
|
|
else if (Desc.Format != PF_Unknown)
|
|
{
|
|
checkf(Desc.Format < PF_MAX, TEXT("Illegal to create SRV for texture %s with invalid FPooledRenderTargetDesc::Format."), Texture->Name);
|
|
checkf(GPixelFormats[Desc.Format].Supported, TEXT("Failed to create SRV for texture %s with pixel format %s because it is not supported."),
|
|
Texture->Name, GPixelFormats[Desc.Format].Name);
|
|
|
|
EPixelFormat ResourcePixelFormat = Texture->Desc.Format;
|
|
|
|
checkf(
|
|
GPixelFormats[Desc.Format].BlockBytes == GPixelFormats[ResourcePixelFormat].BlockBytes &&
|
|
GPixelFormats[Desc.Format].BlockSizeX == GPixelFormats[ResourcePixelFormat].BlockSizeX &&
|
|
GPixelFormats[Desc.Format].BlockSizeY == GPixelFormats[ResourcePixelFormat].BlockSizeY &&
|
|
GPixelFormats[Desc.Format].BlockSizeZ == GPixelFormats[ResourcePixelFormat].BlockSizeZ,
|
|
TEXT("Failed to create SRV for texture %s with pixel format %s because it does not match the byte size of the texture's pixel format %s."),
|
|
Texture->Name, GPixelFormats[Desc.Format].Name, GPixelFormats[ResourcePixelFormat].Name);
|
|
}
|
|
|
|
checkf((Desc.MipLevel + Desc.NumMipLevels) <= Texture->Desc.NumMips, TEXT("Failed to create SRV at mips %d-%d: the texture %s has only %d mip levels."),
|
|
Desc.MipLevel, (Desc.MipLevel + Desc.NumMipLevels), Texture->Name, Texture->Desc.NumMips);
|
|
|
|
checkf(Desc.MetaData != ERDGTextureMetaDataAccess::FMask || GRHISupportsExplicitFMask,
|
|
TEXT("Failed to create FMask SRV for texture %s because the current RHI doesn't support it. Be sure to gate the call with GRHISupportsExplicitFMask."),
|
|
Texture->Name);
|
|
|
|
checkf(Desc.MetaData != ERDGTextureMetaDataAccess::HTile || GRHISupportsExplicitHTile,
|
|
TEXT("Failed to create HTile SRV for texture %s because the current RHI doesn't support it. Be sure to gate the call with GRHISupportsExplicitHTile."),
|
|
Texture->Name);
|
|
}
|
|
|
|
void FRDGUserValidation::ValidateCreateSRV(const FRDGBufferSRVDesc& Desc)
|
|
{
|
|
FRDGBufferRef Buffer = Desc.Buffer;
|
|
checkf(Buffer, TEXT("Buffer SRV created with a null buffer."));
|
|
ExecuteGuard(TEXT("CreateSRV"), Buffer->Name);
|
|
}
|
|
|
|
void FRDGUserValidation::ValidateCreateUAV(const FRDGTextureUAVDesc& Desc)
|
|
{
|
|
FRDGTextureRef Texture = Desc.Texture;
|
|
|
|
checkf(Texture, TEXT("Texture UAV created with a null texture."));
|
|
ExecuteGuard(TEXT("CreateUAV"), Texture->Name);
|
|
|
|
checkf(Texture->Desc.Flags & TexCreate_UAV, TEXT("Attempted to create UAV from texture %s which was not created with TexCreate_UAV"), Texture->Name);
|
|
checkf(Desc.MipLevel < Texture->Desc.NumMips, TEXT("Failed to create UAV at mip %d: the texture %s has only %d mip levels."), Desc.MipLevel, Texture->Name, Texture->Desc.NumMips);
|
|
}
|
|
|
|
void FRDGUserValidation::ValidateCreateUAV(const FRDGBufferUAVDesc& Desc)
|
|
{
|
|
FRDGBufferRef Buffer = Desc.Buffer;
|
|
checkf(Buffer, TEXT("Buffer UAV created with a null buffer."));
|
|
ExecuteGuard(TEXT("CreateUAV"), Buffer->Name);
|
|
}
|
|
|
|
void FRDGUserValidation::ValidateCreateUniformBuffer(const void* ParameterStruct, const FShaderParametersMetadata* Metadata)
|
|
{
|
|
check(Metadata);
|
|
const TCHAR* Name = Metadata->GetShaderVariableName();
|
|
checkf(ParameterStruct, TEXT("Uniform buffer '%s' created with null parameters."), Name);
|
|
ExecuteGuard(TEXT("CreateUniformBuffer"), Name);
|
|
}
|
|
|
|
void FRDGUserValidation::ValidateUploadBuffer(FRDGBufferRef Buffer, const void* InitialData, uint64 InitialDataSize)
|
|
{
|
|
check(Buffer);
|
|
checkf(!Buffer->bQueuedForUpload, TEXT("Buffer %s already has an upload queued. Only one upload can be done for each graph."), Buffer->Name);
|
|
check(InitialData || InitialDataSize == 0);
|
|
}
|
|
void FRDGUserValidation::ValidateUploadBuffer(FRDGBufferRef Buffer, const void* InitialData, uint64 InitialDataSize, const FRDGBufferInitialDataFreeCallback& InitialDataFreeCallback)
|
|
{
|
|
check(Buffer);
|
|
checkf(!Buffer->bQueuedForUpload, TEXT("Buffer %s already has an upload queued. Only one upload can be done for each graph."), Buffer->Name);
|
|
check((InitialData || InitialDataSize == 0) && InitialDataFreeCallback);
|
|
}
|
|
|
|
void FRDGUserValidation::ValidateUploadBuffer(FRDGBufferRef Buffer, const FRDGBufferInitialDataCallback& InitialDataCallback, const FRDGBufferInitialDataSizeCallback& InitialDataSizeCallback)
|
|
{
|
|
check(Buffer);
|
|
checkf(!Buffer->bQueuedForUpload, TEXT("Buffer %s already has an upload queued. Only one upload can be done for each graph."), Buffer->Name);
|
|
check(InitialDataCallback && InitialDataSizeCallback);
|
|
}
|
|
|
|
void FRDGUserValidation::ValidateUploadBuffer(FRDGBufferRef Buffer, const FRDGBufferInitialDataCallback& InitialDataCallback, const FRDGBufferInitialDataSizeCallback& InitialDataSizeCallback, const FRDGBufferInitialDataFreeCallback& InitialDataFreeCallback)
|
|
{
|
|
check(Buffer);
|
|
checkf(!Buffer->bQueuedForUpload, TEXT("Buffer %s already has an upload queued. Only one upload can be done for each graph."), Buffer->Name);
|
|
check(InitialDataCallback && InitialDataSizeCallback && InitialDataFreeCallback);
|
|
}
|
|
|
|
void FRDGUserValidation::ValidateExtractTexture(FRDGTextureRef Texture, TRefCountPtr<IPooledRenderTarget>* OutTexturePtr)
|
|
{
|
|
ValidateExtractResource(Texture);
|
|
checkf(OutTexturePtr, TEXT("Texture %s was extracted, but the output texture pointer is null."), Texture->Name);
|
|
}
|
|
|
|
void FRDGUserValidation::ValidateExtractBuffer(FRDGBufferRef Buffer, TRefCountPtr<FRDGPooledBuffer>* OutBufferPtr)
|
|
{
|
|
ValidateExtractResource(Buffer);
|
|
checkf(OutBufferPtr, TEXT("Texture %s was extracted, but the output texture pointer is null."), Buffer->Name);
|
|
}
|
|
|
|
void FRDGUserValidation::ValidateExtractResource(FRDGParentResourceRef Resource)
|
|
{
|
|
check(Resource);
|
|
|
|
checkf(!bHasExecuteBegun || !Resource->bTransient,
|
|
TEXT("Unable to queue the extraction of the resource %s because passes in the graph have already executed and it was made transient."),
|
|
Resource->Name);
|
|
|
|
checkf(Resource->bProduced || Resource->bExternal || Resource->bQueuedForUpload,
|
|
TEXT("Unable to queue the extraction of the resource %s because it has not been produced by any pass."),
|
|
Resource->Name);
|
|
|
|
/** Increment pass access counts for externally registered buffers and textures to avoid
|
|
* emitting a 'produced but never used' warning. We don't have the history of registered
|
|
* resources to be able to emit a proper warning.
|
|
*/
|
|
Resource->GetParentDebugData().PassAccessCount++;
|
|
}
|
|
|
|
void FRDGUserValidation::ValidateConvertToExternalResource(FRDGParentResourceRef Resource)
|
|
{
|
|
check(Resource);
|
|
checkf(!bHasExecuteBegun || !Resource->bTransient,
|
|
TEXT("Unable to convert resource %s to external because passes in the graph have already executed."),
|
|
Resource->Name);
|
|
}
|
|
|
|
void FRDGUserValidation::RemoveUnusedWarning(FRDGParentResourceRef Resource)
|
|
{
|
|
check(Resource);
|
|
ExecuteGuard(TEXT("RemoveUnusedResourceWarning"), Resource->Name);
|
|
|
|
// Removes 'produced but not used' warning.
|
|
Resource->GetParentDebugData().PassAccessCount++;
|
|
|
|
// Removes 'not used' warning.
|
|
Resource->GetDebugData().bIsActuallyUsedByPass = true;
|
|
}
|
|
|
|
bool FRDGUserValidation::TryMarkForClobber(FRDGParentResourceRef Resource) const
|
|
{
|
|
check(Resource);
|
|
FRDGParentResourceDebugData& DebugData = Resource->GetParentDebugData();
|
|
|
|
const bool bClobber = !DebugData.bHasBeenClobbered && !Resource->bExternal && IsDebugAllowedForResource(Resource->Name);
|
|
|
|
if (bClobber)
|
|
{
|
|
DebugData.bHasBeenClobbered = true;
|
|
}
|
|
|
|
return bClobber;
|
|
}
|
|
|
|
void FRDGUserValidation::ValidateGetPooledTexture(FRDGTextureRef Texture) const
|
|
{
|
|
check(Texture);
|
|
checkf(Texture->bExternal, TEXT("GetPooledTexture called on texture %s, but it is not external. Call PreallocateTexture or register as an external texture instead."), Texture->Name);
|
|
}
|
|
|
|
void FRDGUserValidation::ValidateGetPooledBuffer(FRDGBufferRef Buffer) const
|
|
{
|
|
check(Buffer);
|
|
checkf(Buffer->bExternal, TEXT("GetPooledBuffer called on buffer %s, but it is not external. Call PreallocateBuffer or register as an external buffer instead."), Buffer->Name);
|
|
}
|
|
|
|
void FRDGUserValidation::ValidateSetAccessFinal(FRDGParentResourceRef Resource, ERHIAccess AccessFinal)
|
|
{
|
|
check(Resource);
|
|
check(AccessFinal != ERHIAccess::Unknown && IsValidAccess(AccessFinal));
|
|
checkf(Resource->bExternal || Resource->bExtracted, TEXT("Cannot set final access on non-external resource '%s' unless it is first extracted or preallocated."), Resource->Name);
|
|
checkf(!Resource->bFinalizedAccess, TEXT("Cannot set final access on finalized resource %s."), Resource->Name);
|
|
}
|
|
|
|
void FRDGUserValidation::ValidateFinalize(FRDGParentResourceRef Resource, ERHIAccess AccessFinal, FRDGPassHandle FinalizePass)
|
|
{
|
|
check(Resource);
|
|
check(AccessFinal != ERHIAccess::Unknown && IsValidAccess(AccessFinal));
|
|
checkf(IsReadOnlyAccess(AccessFinal), TEXT("Cannot convert resource %s to untracked with access %s. Access must be read-only."), Resource->Name, *GetRHIAccessName(AccessFinal));
|
|
checkf(Resource->bExternal || Resource->bExtracted, TEXT("Cannot convert resource %s to untracked unless it is first extracted or made external."), Resource->Name);
|
|
Resource->GetParentDebugData().FinalizePass = FinalizePass;
|
|
}
|
|
|
|
void FRDGUserValidation::ValidateFinalizedAccess(FRDGParentResourceRef Resource, ERHIAccess Access, const FRDGPass* Pass)
|
|
{
|
|
ensureMsgf(EnumHasAnyFlags(Resource->AccessFinal, Access),
|
|
TEXT("Resource %s was finalized with access %s, but is being used in pass %s with access %s. Any future pass must use a subset of the finalized access state."),
|
|
Resource->Name, *GetRHIAccessName(Resource->AccessFinal), Pass->GetName(), *GetRHIAccessName(Access), *GetRHIPipelineName(Pass->GetPipeline()));
|
|
|
|
#if 0 // TODO: Need to account for read-only resources.
|
|
ensureMsgf(Pass->GetPipeline() == ERHIPipeline::Graphics,
|
|
TEXT("Resource %s was finalized but is being used on the async compute pass %s. Only graphics pipe access is allowed for finalized resources."),
|
|
Resource->Name, Pass->GetName());
|
|
#endif
|
|
}
|
|
|
|
void FRDGUserValidation::ValidateAddPass(const FRDGEventName& Name, ERDGPassFlags Flags)
|
|
{
|
|
ExecuteGuard(TEXT("AddPass"), Name.GetTCHAR());
|
|
|
|
checkf(!EnumHasAnyFlags(Flags, ERDGPassFlags::Copy | ERDGPassFlags::Compute | ERDGPassFlags::AsyncCompute | ERDGPassFlags::Raster),
|
|
TEXT("Pass %s may not specify any of the (Copy, Compute, AsyncCompute, Raster) flags, because it has no parameters. Use None instead."), Name.GetTCHAR());
|
|
}
|
|
|
|
void FRDGUserValidation::ValidateAddPass(const void* ParameterStruct, const FShaderParametersMetadata* Metadata, const FRDGEventName& Name, ERDGPassFlags Flags)
|
|
{
|
|
checkf(ParameterStruct, TEXT("Pass '%s' created with null parameters."), Name.GetTCHAR());
|
|
ExecuteGuard(TEXT("AddPass"), Name.GetTCHAR());
|
|
|
|
checkf(EnumHasAnyFlags(Flags, ERDGPassFlags::Raster | ERDGPassFlags::Compute | ERDGPassFlags::AsyncCompute | ERDGPassFlags::Copy),
|
|
TEXT("Pass %s must specify at least one of the following flags: (Copy, Compute, AsyncCompute, Raster)"), Name.GetTCHAR());
|
|
|
|
checkf(!EnumHasAllFlags(Flags, ERDGPassFlags::Compute | ERDGPassFlags::AsyncCompute),
|
|
TEXT("Pass %s specified both Compute and AsyncCompute. They are mutually exclusive."), Name.GetTCHAR());
|
|
|
|
checkf(!EnumHasAllFlags(Flags, ERDGPassFlags::Raster | ERDGPassFlags::AsyncCompute),
|
|
TEXT("Pass %s specified both Raster and AsyncCompute. They are mutually exclusive."), Name.GetTCHAR());
|
|
|
|
checkf(!EnumHasAllFlags(Flags, ERDGPassFlags::SkipRenderPass) || EnumHasAllFlags(Flags, ERDGPassFlags::Raster),
|
|
TEXT("Pass %s specified SkipRenderPass without Raster. Only raster passes support this flag."));
|
|
|
|
checkf(!EnumHasAllFlags(Flags, ERDGPassFlags::NeverMerge) || EnumHasAllFlags(Flags, ERDGPassFlags::Raster),
|
|
TEXT("Pass %s specified NeverMerge without Raster. Only raster passes support this flag."));
|
|
}
|
|
|
|
void FRDGUserValidation::ValidateAddPass(const FRDGPass* Pass, bool bSkipPassAccessMarking)
|
|
{
|
|
const FRenderTargetBindingSlots* RenderTargetBindingSlots = nullptr;
|
|
|
|
// Pass flags are validated as early as possible by the builder in AddPass.
|
|
const ERDGPassFlags PassFlags = Pass->GetFlags();
|
|
const FRDGParameterStruct PassParameters = Pass->GetParameters();
|
|
|
|
const TCHAR* PassName = Pass->GetName();
|
|
const bool bIsRaster = EnumHasAnyFlags(PassFlags, ERDGPassFlags::Raster);
|
|
const bool bIsCopy = EnumHasAnyFlags(PassFlags, ERDGPassFlags::Copy);
|
|
const bool bIsAnyCompute = EnumHasAnyFlags(PassFlags, ERDGPassFlags::Compute | ERDGPassFlags::AsyncCompute);
|
|
const bool bSkipRenderPass = EnumHasAnyFlags(PassFlags, ERDGPassFlags::SkipRenderPass);
|
|
|
|
const auto MarkAsProduced = [&](FRDGParentResourceRef Resource)
|
|
{
|
|
if (!bSkipPassAccessMarking)
|
|
{
|
|
auto& Debug = Resource->GetParentDebugData();
|
|
if (!Debug.FirstProducer)
|
|
{
|
|
Debug.FirstProducer = Pass;
|
|
}
|
|
Debug.PassAccessCount++;
|
|
}
|
|
};
|
|
|
|
const auto MarkTextureAsProduced = [&](FRDGTextureRef Texture)
|
|
{
|
|
checkf(!EnumHasAnyFlags(Texture->Flags, ERDGTextureFlags::ReadOnly), TEXT("Pass %s is attempting to write to texture %s which is marked as ReadOnly."), Pass->GetName(), Texture->Name);
|
|
MarkAsProduced(Texture);
|
|
};
|
|
|
|
const auto MarkBufferAsProduced = [&](FRDGBufferRef Buffer)
|
|
{
|
|
checkf(!EnumHasAnyFlags(Buffer->Flags, ERDGBufferFlags::ReadOnly), TEXT("Pass %s is attempting to write to buffer %s which is marked as ReadOnly."), Pass->GetName(), Buffer->Name);
|
|
MarkAsProduced(Buffer);
|
|
};
|
|
|
|
const auto MarkAsConsumed = [&] (FRDGParentResourceRef Resource)
|
|
{
|
|
ensureMsgf(Resource->bProduced || Resource->bExternal || Resource->bQueuedForUpload,
|
|
TEXT("Pass %s has a read dependency on %s, but it was never written to."),
|
|
PassName, Resource->Name);
|
|
|
|
if (!bSkipPassAccessMarking)
|
|
{
|
|
Resource->GetParentDebugData().PassAccessCount++;
|
|
}
|
|
};
|
|
|
|
const auto CheckValidResource = [&](FRDGResourceRef Resource)
|
|
{
|
|
checkf(ResourceMap.Contains(Resource), TEXT("Resource at %p registered with pass %s is not part of the graph and is likely a dangling pointer or garbage value."), Resource, Pass->GetName());
|
|
};
|
|
|
|
const auto CheckNotCopy = [&](FRDGResourceRef Resource)
|
|
{
|
|
ensureMsgf(!bIsCopy, TEXT("Pass %s, parameter %s is valid for Raster or (Async)Compute, but the pass is a Copy pass."), PassName, Resource->Name);
|
|
};
|
|
|
|
bool bCanProduce = false;
|
|
|
|
const auto CheckResourceAccess = [&](FRDGParentResourceRef Resource, ERHIAccess Access)
|
|
{
|
|
checkf(bIsCopy || !EnumHasAnyFlags(Access, AccessMaskCopy), TEXT("Pass '%s' uses resource '%s' with access '%s' containing states which require the 'ERDGPass::Copy' flag."), Pass->GetName(), Resource->Name, *GetRHIAccessName(Access));
|
|
checkf(bIsAnyCompute || !EnumHasAnyFlags(Access, AccessMaskCompute), TEXT("Pass '%s' uses resource '%s' with access '%s' containing states which require the 'ERDGPass::Compute' or 'ERDGPassFlags::AsyncCompute' flag."), Pass->GetName(), Resource->Name, *GetRHIAccessName(Access));
|
|
checkf(bIsRaster || !EnumHasAnyFlags(Access, AccessMaskRaster), TEXT("Pass '%s' uses resource '%s' with access '%s' containing states which require the 'ERDGPass::Raster' flag."), Pass->GetName(), Resource->Name, *GetRHIAccessName(Access));
|
|
checkf(bIsAnyCompute || bIsRaster || !EnumHasAnyFlags(Access, AccessMaskComputeOrRaster), TEXT("Pass '%s' uses resource '%s' with access '%s' containing states which require the 'ERDGPassFlags::Compute' or 'ERDGPassFlags::AsyncCompute' or 'ERDGPass::Raster' flag."), Pass->GetName(), Resource->Name, *GetRHIAccessName(Access));
|
|
};
|
|
|
|
const auto CheckBufferAccess = [&](FRDGBufferRef Buffer, ERHIAccess Access)
|
|
{
|
|
CheckResourceAccess(Buffer, Access);
|
|
|
|
if (IsWritableAccess(Access))
|
|
{
|
|
MarkBufferAsProduced(Buffer);
|
|
bCanProduce = true;
|
|
}
|
|
};
|
|
|
|
const auto CheckTextureAccess = [&](FRDGTextureRef Texture, ERHIAccess Access)
|
|
{
|
|
CheckResourceAccess(Texture, Access);
|
|
|
|
if (IsWritableAccess(Access))
|
|
{
|
|
MarkTextureAsProduced(Texture);
|
|
bCanProduce = true;
|
|
}
|
|
};
|
|
|
|
PassParameters.Enumerate([&](FRDGParameter Parameter)
|
|
{
|
|
if (Parameter.IsResource())
|
|
{
|
|
if (FRDGResourceRef Resource = Parameter.GetAsResource())
|
|
{
|
|
CheckValidResource(Resource);
|
|
}
|
|
}
|
|
|
|
switch (Parameter.GetType())
|
|
{
|
|
case UBMT_RDG_TEXTURE:
|
|
{
|
|
if (FRDGTextureRef Texture = Parameter.GetAsTexture())
|
|
{
|
|
MarkAsConsumed(Texture);
|
|
}
|
|
}
|
|
break;
|
|
case UBMT_RDG_TEXTURE_SRV:
|
|
{
|
|
if (FRDGTextureSRVRef SRV = Parameter.GetAsTextureSRV())
|
|
{
|
|
FRDGTextureRef Texture = SRV->GetParent();
|
|
CheckNotCopy(Texture);
|
|
MarkAsConsumed(Texture);
|
|
}
|
|
}
|
|
break;
|
|
case UBMT_RDG_TEXTURE_UAV:
|
|
{
|
|
bCanProduce = true;
|
|
if (FRDGTextureUAVRef UAV = Parameter.GetAsTextureUAV())
|
|
{
|
|
FRDGTextureRef Texture = UAV->GetParent();
|
|
CheckNotCopy(Texture);
|
|
MarkTextureAsProduced(Texture);
|
|
}
|
|
}
|
|
break;
|
|
case UBMT_RDG_BUFFER_SRV:
|
|
{
|
|
if (FRDGBufferSRVRef SRV = Parameter.GetAsBufferSRV())
|
|
{
|
|
FRDGBufferRef Buffer = SRV->GetParent();
|
|
CheckNotCopy(Buffer);
|
|
MarkAsConsumed(Buffer);
|
|
}
|
|
}
|
|
break;
|
|
case UBMT_RDG_BUFFER_UAV:
|
|
{
|
|
bCanProduce = true;
|
|
if (FRDGBufferUAVRef UAV = Parameter.GetAsBufferUAV())
|
|
{
|
|
FRDGBufferRef Buffer = UAV->GetParent();
|
|
CheckNotCopy(Buffer);
|
|
MarkBufferAsProduced(Buffer);
|
|
}
|
|
}
|
|
break;
|
|
case UBMT_RDG_TEXTURE_ACCESS:
|
|
{
|
|
FRDGTextureAccess TextureAccess = Parameter.GetAsTextureAccess();
|
|
bCanProduce |= IsWritableAccess(TextureAccess.GetAccess());
|
|
|
|
if (TextureAccess)
|
|
{
|
|
CheckTextureAccess(TextureAccess.GetTexture(), TextureAccess.GetAccess());
|
|
}
|
|
}
|
|
break;
|
|
case UBMT_RDG_TEXTURE_ACCESS_ARRAY:
|
|
{
|
|
const FRDGTextureAccessArray& TextureAccessArray = Parameter.GetAsTextureAccessArray();
|
|
|
|
for (FRDGTextureAccess TextureAccess : TextureAccessArray)
|
|
{
|
|
CheckTextureAccess(TextureAccess.GetTexture(), TextureAccess.GetAccess());
|
|
}
|
|
}
|
|
break;
|
|
case UBMT_RDG_BUFFER_ACCESS:
|
|
{
|
|
FRDGBufferAccess BufferAccess = Parameter.GetAsBufferAccess();
|
|
|
|
if (BufferAccess)
|
|
{
|
|
CheckBufferAccess(BufferAccess.GetBuffer(), BufferAccess.GetAccess());
|
|
}
|
|
}
|
|
break;
|
|
case UBMT_RDG_BUFFER_ACCESS_ARRAY:
|
|
{
|
|
const FRDGBufferAccessArray& BufferAccessArray = Parameter.GetAsBufferAccessArray();
|
|
|
|
for (FRDGBufferAccess BufferAccess : BufferAccessArray)
|
|
{
|
|
CheckBufferAccess(BufferAccess.GetBuffer(), BufferAccess.GetAccess());
|
|
}
|
|
}
|
|
break;
|
|
case UBMT_RENDER_TARGET_BINDING_SLOTS:
|
|
{
|
|
RenderTargetBindingSlots = &Parameter.GetAsRenderTargetBindingSlots();
|
|
bCanProduce = true;
|
|
}
|
|
break;
|
|
}
|
|
});
|
|
|
|
checkf(bCanProduce || EnumHasAnyFlags(PassFlags, ERDGPassFlags::NeverCull) || PassParameters.HasExternalOutputs(),
|
|
TEXT("Pass '%s' has no graph parameters defined on its parameter struct and did not specify 'NeverCull'. The pass will always be culled."), PassName);
|
|
|
|
/** Validate that raster passes have render target binding slots and compute passes don't. */
|
|
if (RenderTargetBindingSlots)
|
|
{
|
|
checkf(bIsRaster, TEXT("Pass '%s' has render target binding slots but is not set to 'Raster'."), PassName);
|
|
}
|
|
else
|
|
{
|
|
checkf(!bIsRaster || bSkipRenderPass, TEXT("Pass '%s' is set to 'Raster' but is missing render target binding slots. Use 'SkipRenderPass' if this is desired."), PassName);
|
|
}
|
|
|
|
/** Validate render target / depth stencil binding usage. */
|
|
if (RenderTargetBindingSlots)
|
|
{
|
|
const auto& RenderTargets = RenderTargetBindingSlots->Output;
|
|
{
|
|
if (FRDGTextureRef Texture = RenderTargetBindingSlots->ShadingRateTexture)
|
|
{
|
|
CheckValidResource(Texture);
|
|
MarkAsConsumed(Texture);
|
|
}
|
|
|
|
const auto& DepthStencil = RenderTargetBindingSlots->DepthStencil;
|
|
|
|
const auto CheckDepthStencil = [&](FRDGTextureRef Texture)
|
|
{
|
|
// Depth stencil only supports one mip, since there isn't actually a way to select the mip level.
|
|
check(Texture->Desc.NumMips == 1);
|
|
CheckValidResource(Texture);
|
|
if (DepthStencil.GetDepthStencilAccess().IsAnyWrite())
|
|
{
|
|
MarkTextureAsProduced(Texture);
|
|
}
|
|
else
|
|
{
|
|
MarkAsConsumed(Texture);
|
|
}
|
|
};
|
|
|
|
FRDGTextureRef Texture = DepthStencil.GetTexture();
|
|
|
|
if (Texture)
|
|
{
|
|
checkf(
|
|
EnumHasAnyFlags(Texture->Desc.Flags, TexCreate_DepthStencilTargetable | TexCreate_DepthStencilResolveTarget),
|
|
TEXT("Pass '%s' attempted to bind texture '%s' as a depth stencil render target, but the texture has not been created with TexCreate_DepthStencilTargetable."),
|
|
PassName, Texture->Name);
|
|
|
|
CheckDepthStencil(Texture);
|
|
}
|
|
}
|
|
|
|
const uint32 RenderTargetCount = RenderTargets.Num();
|
|
|
|
{
|
|
/** Tracks the number of contiguous, non-null textures in the render target output array. */
|
|
uint32 ValidRenderTargetCount = RenderTargetCount;
|
|
|
|
for (uint32 RenderTargetIndex = 0; RenderTargetIndex < RenderTargetCount; ++RenderTargetIndex)
|
|
{
|
|
const FRenderTargetBinding& RenderTarget = RenderTargets[RenderTargetIndex];
|
|
|
|
FRDGTextureRef Texture = RenderTarget.GetTexture();
|
|
FRDGTextureRef ResolveTexture = RenderTarget.GetResolveTexture();
|
|
|
|
if (ResolveTexture && ResolveTexture != Texture)
|
|
{
|
|
checkf(RenderTarget.GetTexture(), TEXT("Pass %s specified resolve target '%s' with a null render target."), PassName, ResolveTexture->Name);
|
|
|
|
ensureMsgf(
|
|
EnumHasAnyFlags(ResolveTexture->Desc.Flags, TexCreate_ResolveTargetable),
|
|
TEXT("Pass '%s' attempted to bind texture '%s' as a render target, but the texture has not been created with TexCreate_ResolveTargetable."),
|
|
PassName, ResolveTexture->Name);
|
|
|
|
CheckValidResource(Texture);
|
|
MarkTextureAsProduced(ResolveTexture);
|
|
}
|
|
|
|
if (Texture)
|
|
{
|
|
ensureMsgf(
|
|
EnumHasAnyFlags(Texture->Desc.Flags, TexCreate_RenderTargetable | TexCreate_ResolveTargetable),
|
|
TEXT("Pass '%s' attempted to bind texture '%s' as a render target, but the texture has not been created with TexCreate_RenderTargetable."),
|
|
PassName, Texture->Name);
|
|
|
|
CheckValidResource(Texture);
|
|
|
|
/** Mark the pass as a producer for render targets with a store action. */
|
|
MarkTextureAsProduced(Texture);
|
|
}
|
|
else
|
|
{
|
|
/** Found end of contiguous interval of valid render targets. */
|
|
ValidRenderTargetCount = RenderTargetIndex;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/** Validate that no holes exist in the render target output array. Render targets must be bound contiguously. */
|
|
for (uint32 RenderTargetIndex = ValidRenderTargetCount; RenderTargetIndex < RenderTargetCount; ++RenderTargetIndex)
|
|
{
|
|
const FRenderTargetBinding& RenderTarget = RenderTargets[RenderTargetIndex];
|
|
checkf(RenderTarget.GetTexture() == nullptr && RenderTarget.GetResolveTexture() == nullptr, TEXT("Render targets must be packed. No empty spaces in the array."));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FRDGUserValidation::ValidateExecuteBegin()
|
|
{
|
|
checkf(!bHasExecuted, TEXT("Render graph execution should only happen once to ensure consistency with immediate mode."));
|
|
check(!bHasExecuteBegun);
|
|
bHasExecuteBegun = true;
|
|
}
|
|
|
|
void FRDGUserValidation::ValidateExecuteEnd()
|
|
{
|
|
check(bHasExecuteBegun);
|
|
|
|
bHasExecuted = true;
|
|
GRDGBuilderActive = false;
|
|
|
|
if (GRDGDebug)
|
|
{
|
|
auto ValidateResourceAtExecuteEnd = [](const FRDGParentResourceRef Resource)
|
|
{
|
|
check(Resource->ReferenceCount == Resource->bExtracted ? 1 : 0);
|
|
|
|
const auto& ParentDebugData = Resource->GetParentDebugData();
|
|
const bool bProducedButNeverUsed = ParentDebugData.PassAccessCount == 1 && ParentDebugData.FirstProducer;
|
|
|
|
if (bProducedButNeverUsed)
|
|
{
|
|
check(Resource->bProduced || Resource->bExternal || Resource->bExtracted);
|
|
|
|
EmitRDGWarningf(
|
|
TEXT("Resource %s has been produced by the pass %s, but never used by another pass."),
|
|
Resource->Name, ParentDebugData.FirstProducer->GetName());
|
|
}
|
|
};
|
|
|
|
for (const FRDGTextureRef Texture : TrackedTextures)
|
|
{
|
|
ValidateResourceAtExecuteEnd(Texture);
|
|
|
|
const auto& ParentDebugData = Texture->GetParentDebugData();
|
|
const auto& TextureDebugData = Texture->GetTextureDebugData();
|
|
|
|
const bool bHasBeenProducedByGraph = !Texture->bExternal && ParentDebugData.PassAccessCount > 0;
|
|
|
|
if (bHasBeenProducedByGraph && !TextureDebugData.bHasNeededUAV && EnumHasAnyFlags(Texture->Desc.Flags, TexCreate_UAV))
|
|
{
|
|
EmitRDGWarningf(
|
|
TEXT("Resource %s first produced by the pass %s had the TexCreate_UAV flag, but no UAV has been used."),
|
|
Texture->Name, ParentDebugData.FirstProducer->GetName());
|
|
}
|
|
|
|
if (bHasBeenProducedByGraph && !TextureDebugData.bHasBeenBoundAsRenderTarget && EnumHasAnyFlags(Texture->Desc.Flags, TexCreate_RenderTargetable))
|
|
{
|
|
EmitRDGWarningf(
|
|
TEXT("Resource %s first produced by the pass %s had the TexCreate_RenderTargetable flag, but has never been bound as a render target of a pass."),
|
|
Texture->Name, ParentDebugData.FirstProducer->GetName());
|
|
}
|
|
}
|
|
|
|
for (const FRDGBufferRef Buffer : TrackedBuffers)
|
|
{
|
|
ValidateResourceAtExecuteEnd(Buffer);
|
|
}
|
|
}
|
|
|
|
TrackedTextures.Empty();
|
|
TrackedBuffers.Empty();
|
|
}
|
|
|
|
void FRDGUserValidation::ValidateExecutePassBegin(const FRDGPass* Pass)
|
|
{
|
|
if (bParallelExecuteEnabled)
|
|
{
|
|
return;
|
|
}
|
|
|
|
SetAllowRHIAccess(Pass, true);
|
|
|
|
if (GRDGDebug)
|
|
{
|
|
Pass->GetParameters().EnumerateUniformBuffers([&](FRDGUniformBufferBinding UniformBuffer)
|
|
{
|
|
// Global uniform buffers are always marked as used, because FShader traversal doesn't know about them.
|
|
if (UniformBuffer.IsStatic())
|
|
{
|
|
UniformBuffer->MarkResourceAsUsed();
|
|
}
|
|
});
|
|
|
|
const auto ValidateTextureAccess = [](FRDGTextureRef Texture, ERHIAccess Access)
|
|
{
|
|
if (EnumHasAnyFlags(Access, ERHIAccess::UAVMask))
|
|
{
|
|
Texture->GetTextureDebugData().bHasNeededUAV = true;
|
|
}
|
|
if (EnumHasAnyFlags(Access, ERHIAccess::RTV | ERHIAccess::DSVRead | ERHIAccess::DSVWrite))
|
|
{
|
|
Texture->GetTextureDebugData().bHasBeenBoundAsRenderTarget = true;
|
|
}
|
|
Texture->MarkResourceAsUsed();
|
|
};
|
|
|
|
Pass->GetParameters().Enumerate([&](FRDGParameter Parameter)
|
|
{
|
|
switch (Parameter.GetType())
|
|
{
|
|
case UBMT_RDG_TEXTURE_UAV:
|
|
if (FRDGTextureUAVRef UAV = Parameter.GetAsTextureUAV())
|
|
{
|
|
FRDGTextureRef Texture = UAV->Desc.Texture;
|
|
Texture->GetTextureDebugData().bHasNeededUAV = true;
|
|
}
|
|
break;
|
|
case UBMT_RDG_TEXTURE_ACCESS:
|
|
if (FRDGTextureAccess TextureAccess = Parameter.GetAsTextureAccess())
|
|
{
|
|
ValidateTextureAccess(TextureAccess.GetTexture(), TextureAccess.GetAccess());
|
|
}
|
|
break;
|
|
case UBMT_RDG_TEXTURE_ACCESS_ARRAY:
|
|
{
|
|
const FRDGTextureAccessArray& TextureAccessArray = Parameter.GetAsTextureAccessArray();
|
|
|
|
for (FRDGTextureAccess TextureAccess : TextureAccessArray)
|
|
{
|
|
ValidateTextureAccess(TextureAccess.GetTexture(), TextureAccess.GetAccess());
|
|
}
|
|
}
|
|
break;
|
|
case UBMT_RDG_BUFFER_ACCESS:
|
|
if (FRDGBufferRef Buffer = Parameter.GetAsBuffer())
|
|
{
|
|
Buffer->MarkResourceAsUsed();
|
|
}
|
|
break;
|
|
case UBMT_RDG_BUFFER_ACCESS_ARRAY:
|
|
for (FRDGBufferAccess BufferAccess : Parameter.GetAsBufferAccessArray())
|
|
{
|
|
BufferAccess->MarkResourceAsUsed();
|
|
}
|
|
break;
|
|
case UBMT_RENDER_TARGET_BINDING_SLOTS:
|
|
{
|
|
const FRenderTargetBindingSlots& RenderTargets = Parameter.GetAsRenderTargetBindingSlots();
|
|
|
|
RenderTargets.Enumerate([&](FRenderTargetBinding RenderTarget)
|
|
{
|
|
FRDGTextureRef Texture = RenderTarget.GetTexture();
|
|
Texture->GetTextureDebugData().bHasBeenBoundAsRenderTarget = true;
|
|
Texture->MarkResourceAsUsed();
|
|
});
|
|
|
|
if (FRDGTextureRef Texture = RenderTargets.DepthStencil.GetTexture())
|
|
{
|
|
Texture->GetTextureDebugData().bHasBeenBoundAsRenderTarget = true;
|
|
Texture->MarkResourceAsUsed();
|
|
}
|
|
|
|
if (FRDGTextureRef Texture = RenderTargets.ShadingRateTexture)
|
|
{
|
|
Texture->MarkResourceAsUsed();
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
void FRDGUserValidation::ValidateExecutePassEnd(const FRDGPass* Pass)
|
|
{
|
|
if (bParallelExecuteEnabled)
|
|
{
|
|
return;
|
|
}
|
|
|
|
SetAllowRHIAccess(Pass, false);
|
|
|
|
const FRDGParameterStruct PassParameters = Pass->GetParameters();
|
|
|
|
if (GRDGDebug)
|
|
{
|
|
uint32 TrackedResourceCount = 0;
|
|
uint32 UsedResourceCount = 0;
|
|
|
|
PassParameters.Enumerate([&](FRDGParameter Parameter)
|
|
{
|
|
if (Parameter.IsResource())
|
|
{
|
|
if (FRDGResourceRef Resource = Parameter.GetAsResource())
|
|
{
|
|
TrackedResourceCount++;
|
|
UsedResourceCount += Resource->GetDebugData().bIsActuallyUsedByPass ? 1 : 0;
|
|
}
|
|
}
|
|
});
|
|
|
|
if (TrackedResourceCount != UsedResourceCount)
|
|
{
|
|
FString WarningMessage = FString::Printf(
|
|
TEXT("'%d' of the '%d' resources of the pass '%s' were not actually used."),
|
|
TrackedResourceCount - UsedResourceCount, TrackedResourceCount, Pass->GetName());
|
|
|
|
PassParameters.Enumerate([&](FRDGParameter Parameter)
|
|
{
|
|
if (Parameter.IsResource())
|
|
{
|
|
if (const FRDGResourceRef Resource = Parameter.GetAsResource())
|
|
{
|
|
if (!Resource->GetDebugData().bIsActuallyUsedByPass)
|
|
{
|
|
WarningMessage += FString::Printf(TEXT("\n %s"), Resource->Name);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
EmitRDGWarning(WarningMessage);
|
|
}
|
|
}
|
|
|
|
PassParameters.Enumerate([&](FRDGParameter Parameter)
|
|
{
|
|
if (Parameter.IsResource())
|
|
{
|
|
if (FRDGResourceRef Resource = Parameter.GetAsResource())
|
|
{
|
|
Resource->GetDebugData().bIsActuallyUsedByPass = false;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
void FRDGUserValidation::SetAllowRHIAccess(const FRDGPass* Pass, bool bAllowAccess)
|
|
{
|
|
Pass->GetParameters().Enumerate([&](FRDGParameter Parameter)
|
|
{
|
|
if (Parameter.IsResource())
|
|
{
|
|
if (FRDGResourceRef Resource = Parameter.GetAsResource())
|
|
{
|
|
Resource->GetDebugData().bAllowRHIAccess = bAllowAccess;
|
|
}
|
|
}
|
|
else if (Parameter.IsBufferAccessArray())
|
|
{
|
|
for (FRDGBufferAccess BufferAccess : Parameter.GetAsBufferAccessArray())
|
|
{
|
|
BufferAccess->GetDebugData().bAllowRHIAccess = bAllowAccess;
|
|
}
|
|
}
|
|
else if (Parameter.IsTextureAccessArray())
|
|
{
|
|
for (FRDGTextureAccess TextureAccess : Parameter.GetAsTextureAccessArray())
|
|
{
|
|
TextureAccess->GetDebugData().bAllowRHIAccess = bAllowAccess;
|
|
}
|
|
}
|
|
else if (Parameter.IsRenderTargetBindingSlots())
|
|
{
|
|
const FRenderTargetBindingSlots& RenderTargets = Parameter.GetAsRenderTargetBindingSlots();
|
|
|
|
RenderTargets.Enumerate([&](FRenderTargetBinding RenderTarget)
|
|
{
|
|
RenderTarget.GetTexture()->GetDebugData().bAllowRHIAccess = bAllowAccess;
|
|
|
|
if (FRDGTextureRef ResolveTexture = RenderTarget.GetResolveTexture())
|
|
{
|
|
ResolveTexture->GetDebugData().bAllowRHIAccess = bAllowAccess;
|
|
}
|
|
});
|
|
|
|
if (FRDGTextureRef Texture = RenderTargets.DepthStencil.GetTexture())
|
|
{
|
|
Texture->GetDebugData().bAllowRHIAccess = bAllowAccess;
|
|
}
|
|
|
|
if (FRDGTexture* Texture = RenderTargets.ShadingRateTexture)
|
|
{
|
|
Texture->GetDebugData().bAllowRHIAccess = bAllowAccess;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
FRDGBarrierValidation::FRDGBarrierValidation(const FRDGPassRegistry* InPasses, const FRDGEventName& InGraphName)
|
|
: Passes(InPasses)
|
|
, GraphName(InGraphName.GetTCHAR())
|
|
{
|
|
check(Passes);
|
|
}
|
|
|
|
void FRDGBarrierValidation::ValidateBarrierBatchBegin(const FRDGPass* Pass, const FRDGBarrierBatchBegin& Batch)
|
|
{
|
|
if (!GRDGTransitionLog)
|
|
{
|
|
return;
|
|
}
|
|
|
|
FResourceMap* ResourceMap = BatchMap.Find(&Batch);
|
|
|
|
if (!ResourceMap)
|
|
{
|
|
ResourceMap = &BatchMap.Emplace(&Batch);
|
|
|
|
for (int32 Index = 0; Index < Batch.Transitions.Num(); ++Index)
|
|
{
|
|
FRDGParentResourceRef Resource = Batch.DebugTransitionResources[Index];
|
|
const FRHITransitionInfo& Transition = Batch.Transitions[Index];
|
|
|
|
if (Resource->Type == ERDGParentResourceType::Texture)
|
|
{
|
|
ResourceMap->Textures.FindOrAdd(static_cast<FRDGTextureRef>(Resource)).Add(Transition);
|
|
}
|
|
else
|
|
{
|
|
check(Resource->Type == ERDGParentResourceType::Buffer);
|
|
ResourceMap->Buffers.Emplace(static_cast<FRDGBufferRef>(Resource), Transition);
|
|
}
|
|
}
|
|
|
|
for (int32 Index = 0; Index < Batch.Aliases.Num(); ++Index)
|
|
{
|
|
ResourceMap->Aliases.Emplace(Batch.DebugAliasingResources[Index], Batch.Aliases[Index]);
|
|
}
|
|
}
|
|
|
|
if (!IsDebugAllowedForGraph(GraphName) || !IsDebugAllowedForPass(Pass->GetName()))
|
|
{
|
|
return;
|
|
}
|
|
|
|
bool bFoundFirst = false;
|
|
|
|
const auto LogHeader = [&]()
|
|
{
|
|
if (!bFoundFirst)
|
|
{
|
|
bFoundFirst = true;
|
|
UE_LOG(LogRDG, Display, TEXT("[%s(Index: %d, Pipeline: %s): %s] (Begin):"), Pass->GetName(), Pass->GetHandle().GetIndex(), *GetRHIPipelineName(Pass->GetPipeline()), Batch.DebugName);
|
|
}
|
|
};
|
|
|
|
for (const auto& KeyValue : ResourceMap->Aliases)
|
|
{
|
|
const FRHITransientAliasingInfo& Info = KeyValue.Value;
|
|
if (Info.IsAcquire())
|
|
{
|
|
FRDGParentResourceRef Resource = KeyValue.Key;
|
|
|
|
if (IsDebugAllowedForResource(Resource->Name))
|
|
{
|
|
LogHeader();
|
|
UE_LOG(LogRDG, Display, TEXT("\tRDG(%p) RHI(%p) %s - Acquire"), Resource, Resource->GetRHIUnchecked(), Resource->Name);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const auto& Pair : ResourceMap->Textures)
|
|
{
|
|
FRDGTextureRef Texture = Pair.Key;
|
|
|
|
if (!IsDebugAllowedForResource(Texture->Name))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const auto& Transitions = Pair.Value;
|
|
if (Transitions.Num())
|
|
{
|
|
LogHeader();
|
|
UE_LOG(LogRDG, Display, TEXT("\tRDG(%p) RHI(%p) %s:"), Texture, Texture->GetRHIUnchecked(), Texture->Name);
|
|
}
|
|
|
|
const FRDGTextureSubresourceLayout SubresourceLayout = Texture->GetSubresourceLayout();
|
|
|
|
for (const FRHITransitionInfo& Transition : Transitions)
|
|
{
|
|
check(SubresourceLayout.GetSubresourceCount() > 0);
|
|
|
|
EnumerateSubresources(Transition, SubresourceLayout.NumMips, SubresourceLayout.NumArraySlices, SubresourceLayout.NumPlaneSlices,
|
|
[&](FRDGTextureSubresource Subresource)
|
|
{
|
|
const int32 SubresourceIndex = SubresourceLayout.GetSubresourceIndex(Subresource);
|
|
|
|
UE_LOG(LogRDG, Display, TEXT("\t\tMip(%d), Array(%d), Slice(%d): [%s, %s] -> [%s, %s]"),
|
|
Subresource.MipIndex, Subresource.ArraySlice, Subresource.PlaneSlice,
|
|
*GetRHIAccessName(Transition.AccessBefore),
|
|
*GetRHIPipelineName(Batch.DebugPipelinesToBegin),
|
|
*GetRHIAccessName(Transition.AccessAfter),
|
|
*GetRHIPipelineName(Batch.DebugPipelinesToEnd));
|
|
});
|
|
}
|
|
}
|
|
|
|
for (const auto& Pair : ResourceMap->Buffers)
|
|
{
|
|
FRDGBufferRef Buffer = Pair.Key;
|
|
const FRHITransitionInfo& Transition = Pair.Value;
|
|
|
|
if (!IsDebugAllowedForResource(Buffer->Name))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
LogHeader();
|
|
|
|
UE_LOG(LogRDG, Display, TEXT("\tRDG(%p) RHI(%p) %s: [%s, %s] -> [%s, %s]"),
|
|
Buffer,
|
|
Buffer->GetRHIUnchecked(),
|
|
Buffer->Name,
|
|
*GetRHIAccessName(Transition.AccessBefore),
|
|
*GetRHIPipelineName(Batch.DebugPipelinesToBegin),
|
|
*GetRHIAccessName(Transition.AccessAfter),
|
|
*GetRHIPipelineName(Batch.DebugPipelinesToEnd));
|
|
}
|
|
}
|
|
|
|
void FRDGBarrierValidation::ValidateBarrierBatchEnd(const FRDGPass* Pass, const FRDGBarrierBatchEnd& Batch)
|
|
{
|
|
if (!GRDGTransitionLog || !IsDebugAllowedForGraph(GraphName) || !IsDebugAllowedForPass(Pass->GetName()))
|
|
{
|
|
return;
|
|
}
|
|
|
|
const bool bAllowedForPass = IsDebugAllowedForGraph(GraphName) && IsDebugAllowedForPass(Pass->GetName());
|
|
|
|
bool bFoundFirst = false;
|
|
|
|
for (const FRDGBarrierBatchBegin* Dependent : Batch.Dependencies)
|
|
{
|
|
if (Dependent->PipelinesToEnd == ERHIPipeline::None)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const FResourceMap& ResourceMap = BatchMap.FindChecked(Dependent);
|
|
|
|
TArray<FRDGTextureRef> Textures;
|
|
if (ResourceMap.Textures.Num())
|
|
{
|
|
ResourceMap.Textures.GetKeys(Textures);
|
|
}
|
|
|
|
TArray<FRDGBufferRef> Buffers;
|
|
if (ResourceMap.Buffers.Num())
|
|
{
|
|
ResourceMap.Buffers.GetKeys(Buffers);
|
|
}
|
|
|
|
const auto LogHeader = [&]()
|
|
{
|
|
if (!bFoundFirst)
|
|
{
|
|
bFoundFirst = true;
|
|
UE_LOG(LogRDG, Display, TEXT("[%s(Index: %d, Pipeline: %s) %s] (End):"), Pass->GetName(), Pass->GetHandle().GetIndex(), Dependent->DebugName, *GetRHIPipelineName(Pass->GetPipeline()));
|
|
}
|
|
};
|
|
|
|
for (FRDGTextureRef Texture : Textures)
|
|
{
|
|
if (IsDebugAllowedForResource(Texture->Name))
|
|
{
|
|
LogHeader();
|
|
UE_LOG(LogRDG, Display, TEXT("\tRDG(%p) RHI(%p) %s - End:"), Texture, Texture->GetRHIUnchecked(), Texture->Name);
|
|
}
|
|
}
|
|
|
|
for (FRDGBufferRef Buffer : Buffers)
|
|
{
|
|
if (IsDebugAllowedForResource(Buffer->Name))
|
|
{
|
|
LogHeader();
|
|
UE_LOG(LogRDG, Display, TEXT("\tRDG(%p) RHI(%p) %s - End"), Buffer, Buffer->GetRHIUnchecked(), Buffer->Name);
|
|
}
|
|
}
|
|
|
|
for (const auto& KeyValue : ResourceMap.Aliases)
|
|
{
|
|
const FRHITransientAliasingInfo& Info = KeyValue.Value;
|
|
if (Info.IsDiscard())
|
|
{
|
|
FRDGParentResourceRef Resource = KeyValue.Key;
|
|
|
|
if (IsDebugAllowedForResource(Resource->Name))
|
|
{
|
|
LogHeader();
|
|
UE_LOG(LogRDG, Display, TEXT("\tRDG(%p) RHI(%p) %s - Discard"), Resource, Resource->GetRHIUnchecked(), Resource->Name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace
|
|
{
|
|
const TCHAR* RasterColorName = TEXT("#ff7070");
|
|
const TCHAR* ComputeColorName = TEXT("#70b8ff");
|
|
const TCHAR* AsyncComputeColorName = TEXT("#70ff99");
|
|
const TCHAR* CopyColorName = TEXT("#ffdb70");
|
|
const TCHAR* TextureColorAttributes = TEXT("color=\"#5800a1\", fontcolor=\"#5800a1\"");
|
|
const TCHAR* BufferColorAttributes = TEXT("color=\"#007309\", fontcolor=\"#007309\"");
|
|
const TCHAR* AliasColorAttributes = TEXT("color=\"#00ff00\", fontcolor=\"#00ff00\"");
|
|
const TCHAR* AllPipelinesColorName = TEXT("#f170ff");
|
|
|
|
const TCHAR* GetPassColorName(ERDGPassFlags Flags)
|
|
{
|
|
if (EnumHasAnyFlags(Flags, ERDGPassFlags::Raster))
|
|
{
|
|
return RasterColorName;
|
|
}
|
|
if (EnumHasAnyFlags(Flags, ERDGPassFlags::Compute))
|
|
{
|
|
return ComputeColorName;
|
|
}
|
|
if (EnumHasAnyFlags(Flags, ERDGPassFlags::AsyncCompute))
|
|
{
|
|
return AsyncComputeColorName;
|
|
}
|
|
if (EnumHasAnyFlags(Flags, ERDGPassFlags::Copy))
|
|
{
|
|
return CopyColorName;
|
|
}
|
|
return TEXT("#ffffff");
|
|
}
|
|
|
|
FString GetSubresourceStateLabel(FRDGSubresourceState State)
|
|
{
|
|
const ERHIPipeline Pipelines = State.GetPipelines();
|
|
const TCHAR* FontColor = nullptr;
|
|
switch (Pipelines)
|
|
{
|
|
default:
|
|
checkNoEntry();
|
|
case ERHIPipeline::Graphics:
|
|
FontColor = RasterColorName;
|
|
break;
|
|
case ERHIPipeline::AsyncCompute:
|
|
FontColor = AsyncComputeColorName;
|
|
break;
|
|
case ERHIPipeline::All:
|
|
FontColor = AllPipelinesColorName;
|
|
break;
|
|
}
|
|
return FString::Printf(TEXT("<font color=\"%s\">%s</font>"), FontColor, *GetRHIAccessName(State.Access));
|
|
}
|
|
}
|
|
|
|
FString FRDGLogFile::GetProducerName(FRDGPassHandle PassHandle)
|
|
{
|
|
check(PassHandle.IsValid());
|
|
return GetNodeName(PassHandle);
|
|
}
|
|
|
|
FString FRDGLogFile::GetConsumerName(FRDGPassHandle PassHandle)
|
|
{
|
|
check(PassHandle.IsValid());
|
|
return GetNodeName(PassHandle);
|
|
}
|
|
|
|
FString FRDGLogFile::GetNodeName(FRDGPassHandle PassHandle)
|
|
{
|
|
PassesReferenced.Add(PassHandle);
|
|
return FString::Printf(TEXT("P%d"), PassHandle.GetIndex());
|
|
}
|
|
|
|
FString FRDGLogFile::GetNodeName(const FRDGTexture* Texture)
|
|
{
|
|
return FString::Printf(TEXT("T%d"), Textures.AddUnique(Texture));
|
|
}
|
|
|
|
FString FRDGLogFile::GetNodeName(const FRDGBuffer* Buffer)
|
|
{
|
|
return FString::Printf(TEXT("B%d"), Buffers.AddUnique(Buffer));
|
|
}
|
|
|
|
void FRDGLogFile::AddLine(const FString& Line)
|
|
{
|
|
File += Indentation + Line + TEXT("\n");
|
|
}
|
|
|
|
void FRDGLogFile::AddBraceBegin()
|
|
{
|
|
AddLine(TEXT("{"));
|
|
Indentation += TEXT("\t");
|
|
}
|
|
|
|
void FRDGLogFile::AddBraceEnd()
|
|
{
|
|
const bool bSuccess = Indentation.RemoveFromEnd(TEXT("\t"));
|
|
check(bSuccess);
|
|
|
|
AddLine(TEXT("}"));
|
|
}
|
|
|
|
void FRDGLogFile::Begin(const FRDGEventName& InGraphName)
|
|
{
|
|
if (GRDGDumpGraph)
|
|
{
|
|
if (IsImmediateMode())
|
|
{
|
|
UE_LOG(LogRDG, Warning, TEXT("Dump graph (%d) requested, but immediate mode is enabled. Skipping."), GRDGDumpGraph);
|
|
return;
|
|
}
|
|
|
|
check(File.IsEmpty());
|
|
|
|
GraphName = InGraphName.GetTCHAR();
|
|
|
|
if (GraphName.IsEmpty())
|
|
{
|
|
const int32 UnknownGraphIndex = GRDGDumpGraphUnknownCount++;
|
|
GraphName = FString::Printf(TEXT("Unknown%d"), UnknownGraphIndex);
|
|
}
|
|
|
|
AddLine(TEXT("digraph RDG"));
|
|
AddBraceBegin();
|
|
AddLine(TEXT("rankdir=LR; labelloc=\"t\""));
|
|
|
|
bOpen = true;
|
|
}
|
|
}
|
|
|
|
void FRDGLogFile::End()
|
|
{
|
|
if (!GRDGDumpGraph || !bOpen)
|
|
{
|
|
return;
|
|
}
|
|
|
|
TArray<FRDGPassHandle> PassesGraphics;
|
|
TArray<FRDGPassHandle> PassesAsyncCompute;
|
|
|
|
for (FRDGPassHandle PassHandle = Passes.Begin(); PassHandle != Passes.End(); ++PassHandle)
|
|
{
|
|
const FRDGPass* Pass = Passes[PassHandle];
|
|
const ERHIPipeline Pipeline = Pass->GetPipeline();
|
|
|
|
switch (Pipeline)
|
|
{
|
|
case ERHIPipeline::Graphics:
|
|
PassesGraphics.Add(PassHandle);
|
|
break;
|
|
case ERHIPipeline::AsyncCompute:
|
|
PassesAsyncCompute.Add(PassHandle);
|
|
break;
|
|
default:
|
|
checkNoEntry();
|
|
}
|
|
}
|
|
|
|
if (GRDGDumpGraph == RDG_DUMP_GRAPH_TRACKS)
|
|
{
|
|
FRDGPassHandle PrevPassesByPipeline[uint32(ERHIPipeline::Num)];
|
|
|
|
for (FRDGPassHandle PassHandle = Passes.Begin(); PassHandle != Passes.End(); ++PassHandle)
|
|
{
|
|
const FRDGPass* Pass = Passes[PassHandle];
|
|
|
|
if (!EnumHasAnyFlags(Pass->GetFlags(), ERDGPassFlags::Copy | ERDGPassFlags::Raster | ERDGPassFlags::Compute | ERDGPassFlags::AsyncCompute))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
ERHIPipeline PassPipeline = Pass->GetPipeline();
|
|
checkf(FMath::IsPowerOfTwo(uint32(PassPipeline)), TEXT("This logic doesn't handle multi-pipe passes."));
|
|
uint32 PipeIndex = FMath::FloorLog2(uint32(PassPipeline));
|
|
|
|
FRDGPassHandle& PrevPassInPipelineHandle = PrevPassesByPipeline[PipeIndex];
|
|
|
|
if (PrevPassInPipelineHandle.IsValid())
|
|
{
|
|
AddLine(FString::Printf(TEXT("\"%s\" -> \"%s\" [style=\"filled\", penwidth=2, color=\"%s\"]"),
|
|
*GetNodeName(PrevPassInPipelineHandle), *GetNodeName(PassHandle), GetPassColorName(Pass->GetFlags())));
|
|
}
|
|
|
|
if (Pass->GetPipeline() == ERHIPipeline::AsyncCompute)
|
|
{
|
|
const auto AddCrossPipelineEdge = [&](FRDGPassHandle PassBefore, FRDGPassHandle PassAfter)
|
|
{
|
|
AddLine(FString::Printf(TEXT("\"%s\" -> \"%s\" [penwidth=5, style=\"dashed\" color=\"#f003fc\"]"),
|
|
*GetNodeName(PassBefore), *GetNodeName(PassAfter)));
|
|
};
|
|
|
|
if (Pass->IsAsyncComputeBegin())
|
|
{
|
|
AddCrossPipelineEdge(Pass->GetGraphicsForkPass(), PassHandle);
|
|
}
|
|
|
|
if (Pass->IsAsyncComputeEnd())
|
|
{
|
|
AddCrossPipelineEdge(PassHandle, Pass->GetGraphicsJoinPass());
|
|
}
|
|
}
|
|
|
|
PrevPassInPipelineHandle = PassHandle;
|
|
}
|
|
}
|
|
else if (GRDGDumpGraph == RDG_DUMP_GRAPH_PRODUCERS)
|
|
{
|
|
for (FRDGPassHandle PassHandle = Passes.Begin(); PassHandle < Passes.Last(); ++PassHandle)
|
|
{
|
|
const FRDGPass* Pass = Passes[PassHandle];
|
|
|
|
for (const FRDGPassHandle ProducerHandle : Pass->GetProducers())
|
|
{
|
|
const FRDGPass* Producer = Passes[ProducerHandle];
|
|
|
|
File += FString::Printf(TEXT("\t\"%s\" -> \"%s\" [penwidth=2, color=\"%s:%s\"]\n"),
|
|
*GetNodeName(ProducerHandle), *GetNodeName(PassHandle), GetPassColorName(Pass->GetFlags()), GetPassColorName(Producer->GetFlags()));
|
|
}
|
|
}
|
|
}
|
|
|
|
AddLine(TEXT("subgraph Passes"));
|
|
AddBraceBegin();
|
|
|
|
const auto AddPass = [&](FRDGPassHandle PassHandle)
|
|
{
|
|
if (!PassesReferenced.Contains(PassHandle))
|
|
{
|
|
return;
|
|
}
|
|
|
|
const FRDGPass* Pass = Passes[PassHandle];
|
|
const TCHAR* Style = Pass->IsCulled() ? TEXT("dashed") : TEXT("filled");
|
|
FString PassName = FString::Printf(TEXT("[%d]: %s"), PassHandle.GetIndex(), Pass->GetName());
|
|
|
|
if (Pass->GetParameters().HasExternalOutputs())
|
|
{
|
|
PassName += TEXT("\n(Has External UAVs)");
|
|
}
|
|
|
|
AddLine(FString::Printf(TEXT("\"%s\" [shape=box, style=%s, label=\"%s\", color=\"%s\"]"), *GetNodeName(PassHandle), Style, *PassName, GetPassColorName(Pass->GetFlags())));
|
|
};
|
|
|
|
{
|
|
uint32 RenderTargetClusterCount = 0;
|
|
|
|
for (FRDGPassHandle PassHandle : PassesGraphics)
|
|
{
|
|
const FRDGPass* Pass = Passes[PassHandle];
|
|
|
|
if (Pass->IsMergedRenderPassBegin())
|
|
{
|
|
const uint32 RenderTargetClusterIndex = RenderTargetClusterCount++;
|
|
|
|
AddLine(FString::Printf(TEXT("subgraph cluster_%d"), RenderTargetClusterIndex));
|
|
AddBraceBegin();
|
|
AddLine(TEXT("style=filled;color=\"#ffe0e0\";fontcolor=\"#aa0000\";label=\"Render Pass Merge\";fontsize=10"));
|
|
}
|
|
|
|
AddPass(PassHandle);
|
|
|
|
if (Pass->IsMergedRenderPassEnd())
|
|
{
|
|
AddBraceEnd();
|
|
}
|
|
}
|
|
}
|
|
|
|
for (FRDGPassHandle PassHandle : PassesAsyncCompute)
|
|
{
|
|
AddPass(PassHandle);
|
|
}
|
|
|
|
AddBraceEnd();
|
|
|
|
AddLine(TEXT("subgraph Textures"));
|
|
AddBraceBegin();
|
|
for (const FRDGTexture* Texture : Textures)
|
|
{
|
|
FString Line = FString::Printf(TEXT("\"%s\" [shape=oval, %s, label=\"%s"), *GetNodeName(Texture), TextureColorAttributes, Texture->Name);
|
|
if (Texture->IsExternal())
|
|
{
|
|
Line += TEXT("\n(External)");
|
|
}
|
|
Line += TEXT("\"]");
|
|
AddLine(Line);
|
|
}
|
|
AddBraceEnd();
|
|
|
|
AddLine(TEXT("subgraph Buffers"));
|
|
AddBraceBegin();
|
|
for (const FRDGBuffer* Buffer : Buffers)
|
|
{
|
|
FString Line = FString::Printf(TEXT("\"%s\" [shape=oval, %s, label=\"%s"), *GetNodeName(Buffer), BufferColorAttributes, Buffer->Name);
|
|
if (Buffer->IsExternal())
|
|
{
|
|
Line += TEXT("\n(External)");
|
|
}
|
|
Line += TEXT("\"]");
|
|
AddLine(Line);
|
|
}
|
|
AddBraceEnd();
|
|
|
|
uint32 NumPassesActive = 0;
|
|
uint32 NumPassesCulled = 0;
|
|
|
|
Passes.Enumerate([&](const FRDGPass* Pass)
|
|
{
|
|
if (Pass->IsCulled())
|
|
{
|
|
NumPassesCulled++;
|
|
}
|
|
else
|
|
{
|
|
NumPassesActive++;
|
|
}
|
|
});
|
|
|
|
AddLine(FString::Printf(TEXT("label=\"%s [Active Passes: %d, Culled Passes: %d, Textures: %d, Buffers: %d]\""), *GraphName, NumPassesActive, NumPassesCulled, Textures.Num(), Buffers.Num()));
|
|
|
|
AddBraceEnd();
|
|
check(Indentation.IsEmpty());
|
|
|
|
const TCHAR* DumpType = TEXT("");
|
|
|
|
switch (GRDGDumpGraph)
|
|
{
|
|
case RDG_DUMP_GRAPH_RESOURCES:
|
|
DumpType = TEXT("_resources");
|
|
break;
|
|
case RDG_DUMP_GRAPH_PRODUCERS:
|
|
DumpType = TEXT("_producers");
|
|
break;
|
|
case RDG_DUMP_GRAPH_TRACKS:
|
|
DumpType = TEXT("_tracks");
|
|
break;
|
|
}
|
|
|
|
FFileHelper::SaveStringToFile(File, *(FPaths::ProjectLogDir() / FString::Printf(TEXT("RDG_%s%s.gv"), *GraphName, DumpType)));
|
|
|
|
bOpen = false;
|
|
}
|
|
|
|
bool FRDGLogFile::IncludeTransitionEdgeInGraph(FRDGPassHandle Pass) const
|
|
{
|
|
return Pass.IsValid() && !Passes[Pass]->IsSentinel();
|
|
}
|
|
|
|
bool FRDGLogFile::IncludeTransitionEdgeInGraph(FRDGPassHandle PassBefore, FRDGPassHandle PassAfter) const
|
|
{
|
|
return IncludeTransitionEdgeInGraph(PassBefore) && IncludeTransitionEdgeInGraph(PassAfter) && PassBefore < PassAfter;
|
|
}
|
|
|
|
void FRDGLogFile::AddFirstEdge(const FRDGTextureRef Texture, FRDGPassHandle FirstPass)
|
|
{
|
|
if (GRDGDumpGraph == RDG_DUMP_GRAPH_RESOURCES && bOpen && IncludeTransitionEdgeInGraph(FirstPass) && IsDebugAllowedForResource(Texture->Name))
|
|
{
|
|
AddLine(FString::Printf(TEXT("\"%s\" -> \"%s\" [%s]"),
|
|
*GetNodeName(Texture),
|
|
*GetNodeName(FirstPass),
|
|
TextureColorAttributes));
|
|
}
|
|
}
|
|
|
|
void FRDGLogFile::AddFirstEdge(const FRDGBufferRef Buffer, FRDGPassHandle FirstPass)
|
|
{
|
|
if (GRDGDumpGraph == RDG_DUMP_GRAPH_RESOURCES && bOpen && IncludeTransitionEdgeInGraph(FirstPass) && IsDebugAllowedForResource(Buffer->Name))
|
|
{
|
|
AddLine(FString::Printf(TEXT("\"%s\" -> \"%s\" [%s]"),
|
|
*GetNodeName(Buffer),
|
|
*GetNodeName(FirstPass),
|
|
BufferColorAttributes));
|
|
}
|
|
}
|
|
|
|
|
|
void FRDGLogFile::AddAliasEdge(const FRDGTextureRef TextureBefore, FRDGPassHandle BeforePass, const FRDGTextureRef TextureAfter, FRDGPassHandle AfterPass)
|
|
{
|
|
if (GRDGDumpGraph == RDG_DUMP_GRAPH_RESOURCES && bOpen && IncludeTransitionEdgeInGraph(BeforePass, AfterPass) && IsDebugAllowedForResource(TextureBefore->Name) && IsDebugAllowedForResource(TextureAfter->Name))
|
|
{
|
|
AddLine(FString::Printf(TEXT("\"%s\" -> \"%s\" [%s, label=<Alias: <b>%s -> %s</b>>]"),
|
|
*GetProducerName(BeforePass),
|
|
*GetConsumerName(AfterPass),
|
|
AliasColorAttributes,
|
|
TextureBefore->Name,
|
|
TextureAfter->Name));
|
|
}
|
|
}
|
|
|
|
void FRDGLogFile::AddAliasEdge(const FRDGBufferRef BufferBefore, FRDGPassHandle BeforePass, const FRDGBufferRef BufferAfter, FRDGPassHandle AfterPass)
|
|
{
|
|
if (GRDGDumpGraph == RDG_DUMP_GRAPH_RESOURCES && bOpen && IncludeTransitionEdgeInGraph(BeforePass, AfterPass) && IsDebugAllowedForResource(BufferBefore->Name) && IsDebugAllowedForResource(BufferAfter->Name))
|
|
{
|
|
AddLine(FString::Printf(TEXT("\"%s\" -> \"%s\" [%s, label=<Alias: <b>%s -> %s</b>>]"),
|
|
*GetProducerName(BeforePass),
|
|
*GetConsumerName(AfterPass),
|
|
AliasColorAttributes,
|
|
BufferBefore->Name,
|
|
BufferAfter->Name));
|
|
}
|
|
}
|
|
|
|
void FRDGLogFile::AddTransitionEdge(FRDGPassHandle PassHandle, const FRDGSubresourceState& StateBefore, const FRDGSubresourceState& StateAfter, const FRDGTextureRef Texture)
|
|
{
|
|
if (GRDGDumpGraph == RDG_DUMP_GRAPH_RESOURCES && bOpen && IsDebugAllowedForResource(Texture->Name))
|
|
{
|
|
if (IncludeTransitionEdgeInGraph(StateBefore.GetLastPass(), StateAfter.GetFirstPass()) && FRDGSubresourceState::IsTransitionRequired(StateBefore, StateAfter))
|
|
{
|
|
AddLine(FString::Printf(TEXT("\"%s\" -> \"%s\" [%s, label=<%s: <b>%s -> %s</b>>]"),
|
|
*GetProducerName(StateBefore.GetLastPass()),
|
|
*GetConsumerName(StateAfter.GetFirstPass()),
|
|
TextureColorAttributes,
|
|
Texture->Name,
|
|
*GetSubresourceStateLabel(StateBefore),
|
|
*GetSubresourceStateLabel(StateAfter)));
|
|
}
|
|
else if (IncludeTransitionEdgeInGraph(StateBefore.LogFilePass, PassHandle))
|
|
{
|
|
AddLine(FString::Printf(TEXT("\"%s\" -> \"%s\" [%s, label=<%s: <b>%s</b>>]"),
|
|
*GetProducerName(StateBefore.LogFilePass),
|
|
*GetConsumerName(PassHandle),
|
|
TextureColorAttributes,
|
|
Texture->Name,
|
|
*GetSubresourceStateLabel(StateBefore)));
|
|
}
|
|
|
|
StateAfter.LogFilePass = PassHandle;
|
|
}
|
|
}
|
|
|
|
void FRDGLogFile::AddTransitionEdge(FRDGPassHandle PassHandle, const FRDGSubresourceState& StateBefore, const FRDGSubresourceState& StateAfter, const FRDGTextureRef Texture, FRDGTextureSubresource Subresource)
|
|
{
|
|
if (GRDGDumpGraph == RDG_DUMP_GRAPH_RESOURCES && bOpen && IsDebugAllowedForResource(Texture->Name))
|
|
{
|
|
if (IncludeTransitionEdgeInGraph(StateBefore.GetLastPass(), StateAfter.GetFirstPass()) && FRDGSubresourceState::IsTransitionRequired(StateBefore, StateAfter))
|
|
{
|
|
AddLine(FString::Printf(TEXT("\"%s\" -> \"%s\" [%s, label=<%s[%d][%d][%d]: <b>%s -> %s</b>>]"),
|
|
*GetProducerName(StateBefore.GetLastPass()),
|
|
*GetConsumerName(StateAfter.GetFirstPass()),
|
|
TextureColorAttributes,
|
|
Texture->Name,
|
|
Subresource.MipIndex, Subresource.ArraySlice, Subresource.PlaneSlice,
|
|
*GetSubresourceStateLabel(StateBefore),
|
|
*GetSubresourceStateLabel(StateAfter)));
|
|
}
|
|
else if (IncludeTransitionEdgeInGraph(StateBefore.LogFilePass, PassHandle))
|
|
{
|
|
AddLine(FString::Printf(TEXT("\"%s\" -> \"%s\" [%s, label=<%s[%d][%d][%d]: <b>%s</b>>]"),
|
|
*GetProducerName(StateBefore.LogFilePass),
|
|
*GetConsumerName(PassHandle),
|
|
TextureColorAttributes,
|
|
Texture->Name,
|
|
Subresource.MipIndex, Subresource.ArraySlice, Subresource.PlaneSlice,
|
|
*GetSubresourceStateLabel(StateBefore)));
|
|
}
|
|
|
|
StateAfter.LogFilePass = PassHandle;
|
|
}
|
|
}
|
|
|
|
void FRDGLogFile::AddTransitionEdge(FRDGPassHandle PassHandle, const FRDGSubresourceState& StateBefore, const FRDGSubresourceState& StateAfter, const FRDGBufferRef Buffer)
|
|
{
|
|
if (GRDGDumpGraph == RDG_DUMP_GRAPH_RESOURCES && bOpen && IsDebugAllowedForResource(Buffer->Name))
|
|
{
|
|
if (IncludeTransitionEdgeInGraph(StateBefore.GetLastPass(), StateAfter.GetFirstPass()) && FRDGSubresourceState::IsTransitionRequired(StateBefore, StateAfter))
|
|
{
|
|
AddLine(FString::Printf(TEXT("\"%s\" -> \"%s\" [%s, label=<%s: <b>%s -> %s</b>>]"),
|
|
*GetProducerName(StateBefore.GetLastPass()),
|
|
*GetConsumerName(StateAfter.GetFirstPass()),
|
|
BufferColorAttributes,
|
|
Buffer->Name,
|
|
*GetSubresourceStateLabel(StateBefore),
|
|
*GetSubresourceStateLabel(StateAfter)));
|
|
}
|
|
else if (IncludeTransitionEdgeInGraph(StateBefore.LogFilePass, PassHandle))
|
|
{
|
|
AddLine(FString::Printf(TEXT("\"%s\" -> \"%s\" [%s, label=<%s: <b>%s</b>>]"),
|
|
*GetProducerName(StateBefore.LogFilePass),
|
|
*GetConsumerName(PassHandle),
|
|
BufferColorAttributes,
|
|
Buffer->Name,
|
|
*GetSubresourceStateLabel(StateBefore)));
|
|
}
|
|
|
|
StateAfter.LogFilePass = PassHandle;
|
|
}
|
|
}
|
|
|
|
#endif |