2019-07-16 13:06:34 -04:00
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
# include "RenderGraphValidation.h"
# if RDG_ENABLE_DEBUG
/** This utility class tracks read / write events on individual resources within a pass in order to ensure
* that sub - resources are not being simultaneously bound for read / write . The RHI currently only supports
* disjoint read - write access on a texture for mip - map generation purposes .
*/
class FRDGPassResourceValidator
{
public :
static const uint32 AllMipLevels = ~ 0 ;
FRDGPassResourceValidator ( const FRDGPass & InPass )
: Pass ( InPass )
, bIsGenerateMips ( InPass . IsGenerateMips ( ) )
{ }
void Read ( FRDGBufferRef Buffer )
{
check ( Buffer ) ;
ensureMsgf ( ! bIsGenerateMips ,
TEXT ( " Attempting to read from Buffer %s on Pass %s which is marked as 'GenerateMips'. Only a single texture is supported on this type of pass. " ) ,
Buffer - > Name , Pass . GetName ( ) ) ;
FBufferAccess & BufferAccess = BufferAccessMap . FindOrAdd ( Buffer ) ;
ensureMsgf ( BufferAccess . Access ! = FRDGResourceState : : EAccess : : Write ,
TEXT ( " Buffer %s is being used for both read and write access on Pass %s. This is not supported. " ) ,
Buffer - > Name , Pass . GetName ( ) ) ;
BufferAccess . Access = FRDGResourceState : : EAccess : : Read ;
}
void Write ( FRDGBufferRef Buffer )
{
check ( Buffer ) ;
ensureMsgf ( ! bIsGenerateMips ,
TEXT ( " Attempting to write to Buffer %s on Pass %s which is marked as 'GenerateMips'. Only a single texture is supported on this type of pass. " ) ,
Buffer - > Name , Pass . GetName ( ) ) ;
FBufferAccess & BufferAccess = BufferAccessMap . FindOrAdd ( Buffer ) ;
ensureMsgf ( BufferAccess . Access ! = FRDGResourceState : : EAccess : : Read ,
TEXT ( " Attempting to simultaneously read and write from Buffer %s on Pass %s. This is not supported. " ) ,
Buffer - > Name , Pass . GetName ( ) ) ;
BufferAccess . Access = FRDGResourceState : : EAccess : : Read ;
}
void Read ( FRDGTextureRef Texture , uint32 MipLevel = AllMipLevels )
{
check ( Texture ) ;
const uint32 MipMask = ( MipLevel = = AllMipLevels ) ? AllMipLevels : ( 1 < < MipLevel ) ;
FTextureAccess & TextureAccess = TextureAccessMap . FindOrAdd ( Texture ) ;
TextureAccess . SetAllRead ( MipMask ) ;
ensureMsgf ( ! TextureAccess . IsAnyWrite ( MipMask ) ,
TEXT ( " Attempting to access mips of Texture %s for both read and write access on Pass %s. This is not supported. " ) ,
MipLevel , Texture - > Name , Pass . GetName ( ) ) ;
if ( bIsGenerateMips )
{
ensureMsgf ( TextureAccessMap . Num ( ) = = 1 ,
TEXT ( " Attempting to access more than one texture in Pass %s when 'GenerateMips' is set. Only a single texture is supported on this type of pass. " ) ,
Pass . GetName ( ) ) ;
}
else
{
ensureMsgf ( ! TextureAccess . Mips . Write ,
TEXT ( " Texture %s is being used for both read and write access on the same Pass %s. This is only supported via the 'GenerateMips' flag. " ) ,
Pass . GetName ( ) ) ;
}
}
void Write ( FRDGTextureRef Texture , uint32 MipLevel )
{
check ( Texture ) ;
const uint32 MipMask = 1 < < MipLevel ;
FTextureAccess & TextureAccess = TextureAccessMap . FindOrAdd ( Texture ) ;
TextureAccess . SetAllWrite ( MipMask ) ;
ensureMsgf ( ! TextureAccess . IsAnyRead ( MipMask ) ,
TEXT ( " Attempting to use Mip %d of Texture %s for both read and write access on Pass %s. This is not supported. " ) ,
MipLevel , Texture - > Name , Pass . GetName ( ) ) ;
if ( bIsGenerateMips )
{
ensureMsgf ( TextureAccessMap . Num ( ) = = 1 ,
TEXT ( " Attempting to access more than one texture in Pass %s when 'GenerateMips' is set. Only a single texture is supported on this type of pass. " ) ,
Pass . GetName ( ) ) ;
}
else
{
ensureMsgf ( ! TextureAccess . Mips . Read ,
TEXT ( " Texture %s is being used for both read and write access on the same Pass %s. This is only supported via the 'GenerateMips' flag. " ) ,
Texture - > Name , Pass . GetName ( ) ) ;
}
}
private :
struct FBufferAccess
{
FRDGResourceState : : EAccess Access = FRDGResourceState : : EAccess : : Unknown ;
FORCEINLINE bool operator ! = ( FBufferAccess Other ) const
{
return Access ! = Other . Access ;
}
FORCEINLINE bool operator < ( FBufferAccess Other ) const
{
return Access < Other . Access ;
}
} ;
struct FTextureAccess
{
FTextureAccess ( ) = default ;
union
{
uint64 PackedData = 0 ;
struct
{
uint32 Read ;
uint32 Write ;
} Mips ;
} ;
FORCEINLINE bool IsAnyRead ( uint32 MipMask ) const
{
return ( Mips . Read & MipMask ) ! = 0 ;
}
FORCEINLINE bool IsAnyWrite ( uint32 MipMask ) const
{
return ( Mips . Write & MipMask ) ! = 0 ;
}
FORCEINLINE void SetAllRead ( uint32 MipMask )
{
Mips . Read | = MipMask ;
}
FORCEINLINE void SetAllWrite ( uint32 MipMask )
{
Mips . Write | = MipMask ;
}
FORCEINLINE bool operator ! = ( FTextureAccess Other ) const
{
return PackedData ! = Other . PackedData ;
}
FORCEINLINE bool operator < ( FTextureAccess Other ) const
{
return PackedData < Other . PackedData ;
}
} ;
TMap < FRDGBufferRef , FBufferAccess , SceneRenderingSetAllocator > BufferAccessMap ;
TMap < FRDGTextureRef , FTextureAccess , SceneRenderingSetAllocator > TextureAccessMap ;
const FRDGPass & Pass ;
const bool bIsGenerateMips ;
} ;
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 ( FRDGTrackedResourceRef Resource )
{
check ( Resource ) ;
TrackedResources . Add ( Resource ) ;
}
void FRDGUserValidation : : ValidateCreateExternalResource ( FRDGTrackedResourceRef Resource )
{
ValidateCreateResource ( Resource ) ;
Resource - > MarkAsExternal ( ) ;
}
void FRDGUserValidation : : ValidateExtractResource ( FRDGTrackedResourceRef Resource )
{
check ( Resource ) ;
checkf ( Resource - > HasBeenProduced ( ) ,
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 - > PassAccessCount + + ;
}
void FRDGUserValidation : : RemoveUnusedWarning ( FRDGTrackedResourceRef Resource )
{
check ( Resource ) ;
Resource - > PassAccessCount + + ;
}
void FRDGUserValidation : : ValidateAllocPassParameters ( const void * Parameters )
{
check ( Parameters ) ;
AllocatedUnusedPassParameters . Add ( Parameters ) ;
}
void FRDGUserValidation : : ValidateAddPass ( const FRDGPass * Pass )
{
checkf ( ! bHasExecuted , TEXT ( " Render graph pass %s needs to be added before the builder execution. " ) , Pass - > GetName ( ) ) ;
{
const void * ParameterStructData = Pass - > GetParameters ( ) . GetContents ( ) ;
/** The lifetime of each pass parameter structure must extend until deferred pass execution; therefore, it needs to be
* allocated with FRDGBuilder : : AllocParameters ( ) . Also , all references held by the parameter structure are released
* immediately after pass execution , so a pass parameter struct instance must be 1 - to - 1 with a pass instance ( i . e . one
* per AddPass ( ) call ) .
*/
checkf (
AllocatedUnusedPassParameters . Contains ( ParameterStructData ) ,
TEXT ( " The pass parameter structure has not been allocated for correct life time FRDGBuilder::AllocParameters() or has already " )
TEXT ( " been used by another previous FRDGBuilder::AddPass(). " ) ) ;
AllocatedUnusedPassParameters . Remove ( ParameterStructData ) ;
}
const FRenderTargetBindingSlots * RenderTargetBindingSlots = nullptr ;
const TCHAR * PassName = Pass - > GetName ( ) ;
const bool bIsCompute = Pass - > IsCompute ( ) ;
const bool bIsGeneratingMips = Pass - > IsGenerateMips ( ) ;
const bool bRenderTargetSlotsRequired = ! bIsCompute ;
FRDGPassResourceValidator PassResourceValidator ( * Pass ) ;
FRDGPassParameterStruct ParameterStruct = Pass - > GetParameters ( ) ;
const uint32 ParameterCount = ParameterStruct . GetParameterCount ( ) ;
for ( uint32 ParameterIndex = 0 ; ParameterIndex < ParameterCount ; + + ParameterIndex )
{
FRDGPassParameter Parameter = ParameterStruct . GetParameter ( ParameterIndex ) ;
switch ( Parameter . GetType ( ) )
{
case UBMT_RDG_TEXTURE :
{
if ( FRDGTextureRef Texture = Parameter . GetAsTexture ( ) )
{
PassResourceValidator . Read ( Texture ) ;
checkf ( Texture - > HasBeenProduced ( ) ,
TEXT ( " Pass %s has a dependency over the texture %s that has never been produced. " ) ,
PassName , Texture - > Name ) ;
Texture - > PassAccessCount + + ;
}
}
break ;
case UBMT_RDG_TEXTURE_SRV :
{
if ( FRDGTextureSRVRef SRV = Parameter . GetAsTextureSRV ( ) )
{
FRDGTextureRef Texture = SRV - > Desc . Texture ;
PassResourceValidator . Read ( Texture , SRV - > Desc . MipLevel ) ;
checkf ( Texture - > HasBeenProduced ( ) ,
TEXT ( " Pass %s has a dependency over the texture %s that has never been produced. " ) ,
PassName , Texture - > Name ) ;
Texture - > PassAccessCount + + ;
}
}
break ;
case UBMT_RDG_TEXTURE_UAV :
{
if ( FRDGTextureUAVRef UAV = Parameter . GetAsTextureUAV ( ) )
{
FRDGTextureRef Texture = UAV - > Desc . Texture ;
PassResourceValidator . Write ( Texture , UAV - > Desc . MipLevel ) ;
Texture - > MarkAsProducedBy ( Pass ) ;
Texture - > PassAccessCount + + ;
}
}
break ;
case UBMT_RDG_BUFFER :
{
if ( FRDGBufferRef Buffer = Parameter . GetAsBuffer ( ) )
{
PassResourceValidator . Read ( Buffer ) ;
checkf ( Buffer - > HasBeenProduced ( ) ,
TEXT ( " Pass %s has a dependency over the buffer %s that has never been produced. " ) ,
PassName , Buffer - > Name ) ;
Buffer - > PassAccessCount + + ;
}
}
break ;
case UBMT_RDG_BUFFER_SRV :
{
if ( FRDGBufferSRVRef SRV = Parameter . GetAsBufferSRV ( ) )
{
FRDGBufferRef Buffer = SRV - > Desc . Buffer ;
PassResourceValidator . Read ( Buffer ) ;
checkf ( Buffer - > HasBeenProduced ( ) ,
TEXT ( " Pass %s has a dependency over the buffer %s that has never been produced. " ) ,
PassName , SRV - > Desc . Buffer - > Name ) ;
Buffer - > PassAccessCount + + ;
}
}
break ;
case UBMT_RDG_BUFFER_UAV :
{
if ( FRDGBufferUAVRef UAV = Parameter . GetAsBufferUAV ( ) )
{
FRDGBufferRef Buffer = UAV - > Desc . Buffer ;
PassResourceValidator . Write ( Buffer ) ;
Buffer - > MarkAsProducedBy ( Pass ) ;
Buffer - > PassAccessCount + + ;
}
}
break ;
case UBMT_RENDER_TARGET_BINDING_SLOTS :
{
if ( ! RenderTargetBindingSlots )
{
RenderTargetBindingSlots = & Parameter . GetAsRenderTargetBindingSlots ( ) ;
}
else if ( IsRDGDebugEnabled ( ) )
{
EmitRDGWarningf (
TEXT ( " Pass %s have duplicated render target binding slots. " ) ,
PassName ) ;
}
}
break ;
default :
break ;
}
}
/** Validate that raster passes have render target binding slots and compute passes don't. */
if ( RenderTargetBindingSlots )
{
checkf ( ! bIsCompute , TEXT ( " Pass '%s' has render target binding slots but is flagged as 'Compute'. " ) , PassName ) ;
}
else
{
checkf ( bIsCompute , TEXT ( " Pass '%s' is missing render target binding slots. Add the 'Compute' flag if render targets are not required. " ) , PassName ) ;
}
/** Validate render target / depth stencil binding usage. */
if ( RenderTargetBindingSlots )
{
const auto & RenderTargets = RenderTargetBindingSlots - > Output ;
{
const auto & DepthStencil = RenderTargetBindingSlots - > DepthStencil ;
if ( FRDGTextureRef Texture = DepthStencil . GetTexture ( ) )
{
ensureMsgf ( Texture - > Desc . NumSamples = = 1 ,
TEXT ( " Pass '%s' uses an MSAA depth-stencil render target. This is not yet supported. " ) ,
Pass - > GetName ( ) ) ;
ensureMsgf ( ! bIsGeneratingMips ,
TEXT ( " Pass '%s' is marked to generate mips but has a depth stencil texture. This is not supported. " ) ,
Pass - > GetName ( ) ) ;
// Depth stencil only supports one mip, since there isn't actually a way to select the mip level.
const uint32 MipLevel = 0 ;
check ( Texture - > Desc . NumMips = = 1 ) ;
if ( DepthStencil . GetDepthStencilAccess ( ) . IsAnyWrite ( ) )
{
PassResourceValidator . Write ( Texture , MipLevel ) ;
Texture - > MarkAsProducedBy ( Pass ) ;
}
else
{
PassResourceValidator . Read ( Texture , MipLevel ) ;
}
Texture - > PassAccessCount + + ;
}
}
const uint32 RenderTargetCount = RenderTargets . Num ( ) ;
{
/** Tracks the number of contiguous, non-null textures in the render target output array. */
uint32 ValidRenderTargetCount = 0 ;
for ( uint32 RenderTargetIndex = 0 ; RenderTargetIndex < RenderTargetCount ; + + RenderTargetIndex )
{
const FRenderTargetBinding & RenderTarget = RenderTargets [ RenderTargetIndex ] ;
if ( FRDGTextureRef Texture = RenderTarget . GetTexture ( ) )
{
PassResourceValidator . Write ( Texture , RenderTarget . GetMipIndex ( ) ) ;
const bool bIsLoadAction = RenderTarget . GetLoadAction ( ) = = ERenderTargetLoadAction : : ELoad ;
2019-07-23 17:49:23 -04:00
ensureMsgf (
Texture - > Desc . TargetableFlags & TexCreate_RenderTargetable ,
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 ) ;
/** Validate that load action is correct. We can only load contents if a pass previously produced something. */
{
const bool bIsLoadActionInvalid = bIsLoadAction & & ! Texture - > HasBeenProduced ( ) ;
checkf (
! bIsLoadActionInvalid ,
TEXT ( " Pass '%s' attempted to bind texture '%s' as a render target with the 'Load' action specified, but the texture has not been produced yet. The render target must use either 'Clear' or 'NoAction' action instead. " ) ,
PassName ,
Texture - > Name ) ;
}
/** Validate that any previously produced texture contents are loaded. This occurs if the user failed to specify a load action
* on a texture that was produced by a previous pass , effectively losing that data . This can also happen if the user ' re - uses '
* a texture for some other purpose . The latter is considered bad practice , since it increases memory pressure on the render
* target pool . Instead , the user should create a new texture instance . An exception to this rule are untracked render targets ,
* which are not actually managed by the render target pool and likely represent the frame buffer .
*/
{
// We only validate single-mip textures since we don't track production at the subresource level.
const bool bFailedToLoadProducedContent = ! bIsLoadAction & & Texture - > HasBeenProduced ( ) & & Texture - > Desc . NumMips = = 1 ;
// Untracked render targets aren't actually managed by the render target pool.
const bool bIsUntrackedRenderTarget = Texture - > PooledRenderTarget & & ! Texture - > PooledRenderTarget - > IsTracked ( ) ;
ensureMsgf ( ! bFailedToLoadProducedContent | | bIsUntrackedRenderTarget ,
TEXT ( " Pass '%s' attempted to bind texture '%s' as a render target without the 'Load' action specified, despite a prior pass having produced it. It's invalid to completely clobber the contents of a resource. Create a new texture instance instead. " ) ,
PassName ,
Texture - > Name ) ;
}
2019-07-16 13:06:34 -04:00
/** Mark the pass as a producer for render targets with a store action. */
{
const bool bIsStoreAction = RenderTarget . GetStoreAction ( ) ! = ERenderTargetStoreAction : : ENoAction ;
check ( bIsStoreAction ) ; // already been validated in FRenderTargetBinding::Validate()
Texture - > MarkAsProducedBy ( Pass ) ;
}
Texture - > PassAccessCount + + ;
}
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 , 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. " ) ) ;
/** FRDGBuilder::AllocParameters() allocates shader parameter structure for the lifetime until pass execution.
* But they are allocated on a FMemStack for CPU performance , and are destructed immediately after pass execution .
* Therefore allocating pass parameter unused by a FRDGBuilder : : AddPass ( ) can lead on a memory leak of RHI resources
* that have been reference in the parameter structure .
*/
checkf (
AllocatedUnusedPassParameters . Num ( ) = = 0 ,
TEXT ( " %i pass parameter structure has been allocated with FRDGBuilder::AllocParameters(), but has not be used by a " )
TEXT ( " FRDGBuilder::AddPass() that can cause RHI resource leak. " ) , AllocatedUnusedPassParameters . Num ( ) ) ;
}
void FRDGUserValidation : : ValidateExecuteEnd ( )
{
bHasExecuted = true ;
const bool bEmitWarnings = IsRDGDebugEnabled ( ) ;
for ( const FRDGTrackedResourceRef Resource : TrackedResources )
{
check ( Resource - > ReferenceCount = = 0 ) ;
const bool bProducedButNeverUsed = Resource - > PassAccessCount = = 1 & & Resource - > FirstProducer ;
if ( bEmitWarnings & & bProducedButNeverUsed )
{
check ( Resource - > HasBeenProduced ( ) ) ;
EmitRDGWarningf (
TEXT ( " Resources %s has been produced by the pass %s, but never used by another pass. " ) ,
Resource - > Name , Resource - > FirstProducer - > GetName ( ) ) ;
}
}
TrackedResources . Empty ( ) ;
}
void FRDGUserValidation : : ValidateExecutePassBegin ( const FRDGPass * Pass )
{
SetAllowRHIAccess ( Pass , true ) ;
}
void FRDGUserValidation : : ValidateExecutePassEnd ( const FRDGPass * Pass )
{
SetAllowRHIAccess ( Pass , false ) ;
FRDGPassParameterStruct ParameterStruct = Pass - > GetParameters ( ) ;
const uint32 ParameterCount = ParameterStruct . GetParameterCount ( ) ;
if ( IsRDGDebugEnabled ( ) )
{
uint32 TrackedResourceCount = 0 ;
uint32 UsedResourceCount = 0 ;
for ( uint32 ParameterIndex = 0 ; ParameterIndex < ParameterCount ; + + ParameterIndex )
{
FRDGPassParameter Parameter = ParameterStruct . GetParameter ( ParameterIndex ) ;
if ( Parameter . IsResource ( ) )
{
if ( FRDGResourceRef Resource = Parameter . GetAsResource ( ) )
{
TrackedResourceCount + + ;
UsedResourceCount + = Resource - > bIsActuallyUsedByPass ? 1 : 0 ;
}
}
}
if ( TrackedResourceCount ! = UsedResourceCount )
{
FString WarningMessage = FString : : Printf (
TEXT ( " '%d' of the '%d' resources of the pass '%s' where not actually used. " ) ,
TrackedResourceCount - UsedResourceCount , TrackedResourceCount , Pass - > GetName ( ) ) ;
for ( uint32 ParameterIndex = 0 ; ParameterIndex < ParameterCount ; + + ParameterIndex )
{
FRDGPassParameter Parameter = ParameterStruct . GetParameter ( ParameterIndex ) ;
if ( Parameter . IsResource ( ) )
{
if ( const FRDGResourceRef Resource = Parameter . GetAsResource ( ) )
{
if ( ! Resource - > bIsActuallyUsedByPass )
{
WarningMessage + = FString : : Printf ( TEXT ( " \n %s " ) , Resource - > Name ) ;
}
}
}
}
EmitRDGWarning ( WarningMessage ) ;
}
}
for ( uint32 ParameterIndex = 0 ; ParameterIndex < ParameterCount ; + + ParameterIndex )
{
FRDGPassParameter Parameter = ParameterStruct . GetParameter ( ParameterIndex ) ;
if ( Parameter . IsResource ( ) )
{
if ( const FRDGResourceRef Resource = Parameter . GetAsResource ( ) )
{
Resource - > bIsActuallyUsedByPass = false ;
}
}
}
}
void FRDGUserValidation : : SetAllowRHIAccess ( const FRDGPass * Pass , bool bAllowAccess )
{
FRDGPassParameterStruct ParameterStruct = Pass - > GetParameters ( ) ;
const uint32 ParameterCount = ParameterStruct . GetParameterCount ( ) ;
for ( uint32 ParameterIndex = 0 ; ParameterIndex < ParameterCount ; + + ParameterIndex )
{
FRDGPassParameter Parameter = ParameterStruct . GetParameter ( ParameterIndex ) ;
if ( Parameter . IsResource ( ) )
{
if ( FRDGResourceRef Resource = Parameter . GetAsResource ( ) )
{
Resource - > bAllowRHIAccess = bAllowAccess ;
}
}
else if ( Parameter . GetType ( ) = = UBMT_RENDER_TARGET_BINDING_SLOTS )
{
const FRenderTargetBindingSlots & RenderTargetBindingSlots = Parameter . GetAsRenderTargetBindingSlots ( ) ;
const auto & RenderTargets = RenderTargetBindingSlots . Output ;
const auto & DepthStencil = RenderTargetBindingSlots . DepthStencil ;
const uint32 RenderTargetCount = RenderTargets . Num ( ) ;
for ( uint32 RenderTargetIndex = 0 ; RenderTargetIndex < RenderTargetCount ; RenderTargetIndex + + )
{
const FRenderTargetBinding & RenderTarget = RenderTargets [ RenderTargetIndex ] ;
if ( FRDGTextureRef Texture = RenderTarget . GetTexture ( ) )
{
Texture - > bAllowRHIAccess = bAllowAccess ;
}
else
{
break ;
}
}
if ( FRDGTextureRef Texture = DepthStencil . GetTexture ( ) )
{
Texture - > bAllowRHIAccess = bAllowAccess ;
}
}
}
}
# endif