Files
UnrealEngine/Engine/Source/Runtime/Renderer/Private/RayTracing/RayTracingShaderBindingTable.cpp
2025-05-18 13:04:45 +08:00

593 lines
24 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "RayTracingShaderBindingTable.h"
#include "RayTracingDefinitions.h"
#include "RayTracingScene.h"
#include "RayTracing.h"
#include "RayTracingGeometry.h"
#include "RendererModule.h"
#if RHI_RAYTRACING
static int32 GPersistentSBTEnabled = 1;
static FAutoConsoleVariableRef CVarRayTracingPersistentSBT(
TEXT("r.RayTracing.PersistentSBT"),
GPersistentSBTEnabled,
TEXT("Enable persistent RayTracing ShaderBindingTables."),
ECVF_RenderThreadSafe
);
static int32 GForceAlwaysDirty = 0;
static FAutoConsoleVariableRef CVarRayTracingPersistentSBTForceAlwaysDirty(
TEXT("r.RayTracing.PersistentSBT.ForceAlwaysDirty"),
GForceAlwaysDirty,
TEXT("Force all visible shader bindings as dirty (debug mode)."),
ECVF_RenderThreadSafe
);
static int32 GMinLocalBindingDataSize = 96;
static FAutoConsoleVariableRef CVarRayTracingPersistentSBTMinLocalBindingDataSize(
TEXT("r.RayTracing.PersistentSBT.MinLocalBindingDataSize"),
GMinLocalBindingDataSize,
TEXT("Minimum local binding data size of the persistent SBT (can dynamically grow if need by hit shaders used in the RTPSO)."),
ECVF_ReadOnly
);
static int32 GMinMissShaderSlots = 128;
static FAutoConsoleVariableRef CVarRayTracingPersistentSBTMinMissShaderSlots(
TEXT("r.RayTracing.PersistentSBT.MinMissShaderSlots"),
GMinMissShaderSlots,
TEXT("Minimum amount of miss shader slots reserved in the persistent SBT (can dynamically grow if need by number of miss shaders used in the RTPSO)."),
ECVF_ReadOnly
);
static int32 GMinStaticGeometrySegments = 256;
static FAutoConsoleVariableRef CVarRayTracingPersistentSBTMinStaticGeometrySegments(
TEXT("r.RayTracing.PersistentSBT.MinStaticGeometrySegments"),
GMinStaticGeometrySegments,
TEXT("Minimum amount of static geometry segments reserved in the persistent SBT (can dynamically grow if need by number of allocated static SBT allocations in the scene)."),
ECVF_ReadOnly
);
static int32 GMinDynamicGeometrySegments = 256;
static FAutoConsoleVariableRef CVarRayTracingPersistentSBTMinDynamicGeometrySegments(
TEXT("r.RayTracing.PersistentSBT.MinDynamicGeometrySegments"),
GMinDynamicGeometrySegments,
TEXT("Minimum amount of dynamic geometry segments reserved in the persistent SBT (can dynamically grow if need by number of allocated static SBT allocations in the scene)."),
ECVF_ReadOnly
);
#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
static int32 GValidatePersistentBindings = 1;
#else
static int32 GValidatePersistentBindings = 0;
#endif
static FAutoConsoleVariableRef CVarRayTracingPersistentSBTValidateBindings(
TEXT("r.RayTracing.PersistentSBT.ValidateBindings"),
GValidatePersistentBindings,
TEXT("Force all visible shader bindings as dirty (debug mode)."),
ECVF_RenderThreadSafe
);
static bool UseRayTracingPersistentSBTs()
{
return GRHIGlobals.RayTracing.SupportsPersistentSBTs && GPersistentSBTEnabled;
}
uint32 FRayTracingSBTAllocation::GetRecordIndex(ERayTracingShaderBindingLayer Layer, uint32 SegmentIndex) const
{
check(HasLayer(Layer));
// Find out all the bits set below the given layer
// and count the set bits to know the offset
uint32 LayerMask = (1 << (uint32)Layer) - 1;
LayerMask = (uint32)AllocatedLayers & LayerMask;
uint32 RecordTypeBaseOffset = FMath::CountBits(LayerMask) * RecordsPerLayer;
check(RecordTypeBaseOffset + SegmentIndex * RAY_TRACING_NUM_SHADER_SLOTS + RAY_TRACING_NUM_SHADER_SLOTS <= NumRecords);
return BaseRecordIndex + RecordTypeBaseOffset + SegmentIndex * RAY_TRACING_NUM_SHADER_SLOTS;
}
int32 FRayTracingSBTAllocation::GetSegmentCount() const
{
return NumRecords / RAY_TRACING_NUM_SHADER_SLOTS;
}
FRayTracingShaderBindingTable::FRayTracingShaderBindingTable() : NumShaderSlotsPerGeometrySegment(RAY_TRACING_NUM_SHADER_SLOTS), StaticRangeAllocator(true /*bInGrowOnly*/)
{
PersistentSBTInitializer.ShaderBindingMode = ERayTracingShaderBindingMode::RTPSO;
PersistentSBTInitializer.Lifetime = UseRayTracingPersistentSBTs() ? ERayTracingShaderBindingTableLifetime::Persistent : ERayTracingShaderBindingTableLifetime::Transient;
PersistentSBTInitializer.LocalBindingDataSize = GMinLocalBindingDataSize;
PersistentSBTInitializer.NumMissShaderSlots = GMinMissShaderSlots;
}
/**
* Make sure all dynamic allocation objects are freed and assure all static allocations have been requested deleted already
*/
FRayTracingShaderBindingTable::~FRayTracingShaderBindingTable()
{
ResetDynamicAllocationData();
for (FRayTracingSBTAllocation* SBTAllocation : FreeDynamicAllocationPool)
{
delete SBTAllocation;
}
FreeDynamicAllocationPool.Empty();
// Assume empty?
check(TrackedAllocationMap.IsEmpty());
check(StaticRangeAllocator.GetSparselyAllocatedSize() == 0);
}
/**
* Mark all currently allocated dynamic ranges as free again so they can be allocated
* Setup the CurrentDynamicRangeOffset from where dynamic SBT records will be stored
* After this call no static SBT ranges can be allocated anymore until the end of the 'frame'
*/
void FRayTracingShaderBindingTable::ResetDynamicAllocationData()
{
// Release all dynamic allocation back to the pool
FreeDynamicAllocationPool.Append(ActiveDynamicAllocations);
ActiveDynamicAllocations.Empty(ActiveDynamicAllocations.Num());
NumDynamicGeometrySegments = 0;
// Static allocations are not allowed anymore because dynamic allocations are stored right after all static allocations
bStaticAllocationsLocked = true;
// Dynamic segments will be stored right after the currently allocated
uint32 AllocatedStaticSegmentSize = GetMaxAllocatedStaticSegmentCount();
StartDynamicRangeOffset = AllocatedStaticSegmentSize * NumShaderSlotsPerGeometrySegment;
CurrentDynamicRangeOffset = StartDynamicRangeOffset;
}
void FRayTracingShaderBindingTable::ResetMissAndCallableShaders()
{
CallableCommands.Reset();
NumMissShaderSlots = 1;
NumCallableShaderSlots = 0;
}
void FRayTracingShaderBindingTable::EndFrame()
{
TransientUniformBuffers.Reset();
ResetMissAndCallableShaders();
}
FRayTracingShaderBindingDataOneFrameArray FRayTracingShaderBindingTable::GetDirtyBindings(const FRayTracingShaderBindingDataOneFrameArray& VisibleBindings, bool bForceNonPersistent)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FRayTracingShaderBindingTable::GetDirtyBindings);
FRayTracingShaderBindingDataOneFrameArray DirtyBindings;
// Persistent SBTs disabled (none of the cached SBTs have any persistent bindings so safe to just copy visible bindings and update type inside binding will be ignored)
if (!UseRayTracingPersistentSBTs())
{
DirtyBindings = VisibleBindings;
}
else
{
// Check if one of the cached persistent SBTs doesn't support persistent bindings (eg pathtracer) or requested via cvar or via param
// (RTPSO is still compiling and bindings should be marked as persistent yet)
bool bForceAllTransient = bForceNonPersistent || GForceAlwaysDirty || PersistentSBTInitializer.Lifetime == ERayTracingShaderBindingTableLifetime::Transient;
DirtyBindings.Reserve(VisibleBindings.Num());
for (const FRayTracingShaderBindingData& VisibleBinding : VisibleBindings)
{
if (bForceAllTransient)
{
FRayTracingShaderBindingData ValidBinding = VisibleBinding;
ValidBinding.BindingType = ERayTracingLocalShaderBindingType::Transient;
DirtyBindings.Add(ValidBinding);
}
else
{
FBitReference RecordIndex = ValidPersistentRecords[VisibleBinding.SBTRecordIndex];
if (!RecordIndex || VisibleBinding.BindingType == ERayTracingLocalShaderBindingType::Transient)
{
DirtyBindings.Add(VisibleBinding);
RecordIndex = true;
}
else if (GValidatePersistentBindings)
{
FRayTracingShaderBindingData ValidBinding = VisibleBinding;
ValidBinding.BindingType = ERayTracingLocalShaderBindingType::Validation;
DirtyBindings.Add(ValidBinding);
}
}
}
}
return MoveTemp(DirtyBindings);
}
void FRayTracingShaderBindingTable::AllocatePersistentShaderBindingTable(FRHICommandListBase& RHICmdList, FRayTracingPersistentShaderBindingTableID PersistentSBTID, ERayTracingShaderBindingMode ShaderBindingMode)
{
// Update the shader binding mode on the shared peristent initializer
FRayTracingShaderBindingTableInitializer SBTInitializer = PersistentSBTInitializer;
SBTInitializer.ShaderBindingMode = ShaderBindingMode;
PersistentSBTs[PersistentSBTID].ShaderBindingTable = RHICmdList.CreateRayTracingShaderBindingTable(SBTInitializer);
FRHISizeAndStride InlineBindingDataSizeAndStride = PersistentSBTs[PersistentSBTID].ShaderBindingTable->GetInlineBindingDataSizeAndStride();
if (InlineBindingDataSizeAndStride.Size > 0)
{
const uint32 InlineBindingDataElementCount = InlineBindingDataSizeAndStride.Size / InlineBindingDataSizeAndStride.Stride;
PersistentSBTs[PersistentSBTID].InlineBindingDataPooledBuffer = AllocatePooledBuffer(FRDGBufferDesc::CreateStructuredDesc(InlineBindingDataSizeAndStride.Stride, InlineBindingDataElementCount), TEXT("InlineRayTracingBindingData"));
}
}
FRayTracingPersistentShaderBindingTableID FRayTracingShaderBindingTable::AllocatePersistentSBTID(FRHICommandListBase& RHICmdList, ERayTracingShaderBindingMode ShaderBindingMode)
{
FRayTracingPersistentShaderBindingTableID ResultID = INDEX_NONE;
for (int32 Index = 0; Index < PersistentSBTs.Num(); ++Index)
{
if (PersistentSBTs[Index].ShaderBindingTable == nullptr)
{
ResultID = Index;
break;
}
}
if (ResultID == INDEX_NONE)
{
ResultID = PersistentSBTs.AddDefaulted();
}
// Also recreate all current SBTs because valid records will be cleared with clearing the current valid persistent records
for (int32 Index = 0; Index < PersistentSBTs.Num(); ++Index)
{
if (PersistentSBTs[Index].ShaderBindingTable)
{
AllocatePersistentShaderBindingTable(RHICmdList, Index, PersistentSBTs[Index].ShaderBindingMode);
}
}
// Allocate the RHI object with current initializer settings and store the current shader binding mode
PersistentSBTs[ResultID].ShaderBindingMode = ShaderBindingMode;
AllocatePersistentShaderBindingTable(RHICmdList, ResultID, ShaderBindingMode);
ValidPersistentRecords.Init(false, PersistentSBTInitializer.NumGeometrySegments * PersistentSBTInitializer.NumShaderSlotsPerGeometrySegment);
return ResultID;
}
void FRayTracingShaderBindingTable::CheckPersistentRHI(FRHICommandListBase& RHICmdList, uint32 LocalBindingDataSize)
{
uint32 NumPersistentStaticGeometrySegments = FMath::Max(uint32(GMinStaticGeometrySegments), FMath::RoundUpToPowerOfTwo(GetMaxAllocatedStaticSegmentCount()));
uint32 NumPersistentDynamicGeometrySegments = FMath::Max(uint32(GMinDynamicGeometrySegments), FMath::RoundUpToPowerOfTwo(MaxNumDynamicGeometrySegments));
uint32 NumMissShaderSlotsAligned = FMath::RoundUpToPowerOfTwo(NumMissShaderSlots);
// Build the new SBT initializer
FRayTracingShaderBindingTableInitializer NewSBTInitializer;
NewSBTInitializer.Lifetime = UseRayTracingPersistentSBTs() ? ERayTracingShaderBindingTableLifetime::Persistent : ERayTracingShaderBindingTableLifetime::Transient;
NewSBTInitializer.ShaderBindingMode = PersistentSBTInitializer.ShaderBindingMode;
NewSBTInitializer.HitGroupIndexingMode = PersistentSBTInitializer.HitGroupIndexingMode;
NewSBTInitializer.NumShaderSlotsPerGeometrySegment = NumShaderSlotsPerGeometrySegment;
NewSBTInitializer.NumGeometrySegments = FMath::Max(NumPersistentStaticGeometrySegments + NumPersistentDynamicGeometrySegments, PersistentSBTInitializer.NumGeometrySegments);
NewSBTInitializer.NumMissShaderSlots = FMath::Max(NumMissShaderSlotsAligned, PersistentSBTInitializer.NumMissShaderSlots);
NewSBTInitializer.NumCallableShaderSlots = FMath::Max(NumCallableShaderSlots, PersistentSBTInitializer.NumCallableShaderSlots);
NewSBTInitializer.LocalBindingDataSize = FMath::Max(LocalBindingDataSize, PersistentSBTInitializer.LocalBindingDataSize);
// Always force recreate when persistent SBT is not enabled
bool bRecreate = !UseRayTracingPersistentSBTs();
if (UseRayTracingPersistentSBTs())
{
FStringBuilderBase Reason;
auto NewDifferent = [&Reason](const TCHAR* MemberName, uint32 CurrentValue, uint32 NewValue)
{
if (CurrentValue != NewValue)
{
Reason.Appendf(TEXT("\n\t\t%s changed: current: %d - new: %d"), MemberName, CurrentValue, NewValue);
return true;
}
return false;
};
auto NewBigger = [&Reason](const TCHAR* MemberName, uint32 CurrentValue, uint32 NewValue)
{
if (CurrentValue < NewValue)
{
Reason.Appendf(TEXT("\n\t\t%s changed: current: %d - new: %d"), MemberName, CurrentValue, NewValue);
return true;
}
return false;
};
bRecreate = NewDifferent(TEXT("Lifetime"), (uint32)PersistentSBTInitializer.Lifetime, (uint32)NewSBTInitializer.Lifetime);
bRecreate = NewDifferent(TEXT("ShaderBindingMode"), (uint32)PersistentSBTInitializer.ShaderBindingMode, (uint32)NewSBTInitializer.ShaderBindingMode) || bRecreate;
bRecreate = NewDifferent(TEXT("HitGroupIndexingMode"), (uint32)PersistentSBTInitializer.HitGroupIndexingMode, (uint32)NewSBTInitializer.HitGroupIndexingMode) || bRecreate;
bRecreate = NewDifferent(TEXT("NumShaderSlotsPerGeometrySegment"), PersistentSBTInitializer.NumShaderSlotsPerGeometrySegment, NewSBTInitializer.NumShaderSlotsPerGeometrySegment) || bRecreate;
bRecreate = NewBigger(TEXT("NumGeometrySegments"), PersistentSBTInitializer.NumGeometrySegments, NewSBTInitializer.NumGeometrySegments) || bRecreate;
bRecreate = NewBigger(TEXT("NumMissShaderSlots"), PersistentSBTInitializer.NumMissShaderSlots, NewSBTInitializer.NumMissShaderSlots) || bRecreate;
bRecreate = NewBigger(TEXT("NumCallableShaderSlots"), PersistentSBTInitializer.NumCallableShaderSlots, NewSBTInitializer.NumCallableShaderSlots) || bRecreate;
bRecreate = NewBigger(TEXT("LocalBindingDataSize"), PersistentSBTInitializer.LocalBindingDataSize, NewSBTInitializer.LocalBindingDataSize) || bRecreate;
if (bRecreate)
{
UE_LOG(LogRenderer, Log, TEXT("Recreating Persistent SBTs due to initializer changes: %s"), Reason.ToString());
}
}
// Recreate new RHI object if either persistent is not enabled or current allocated RHI object doesn't match the new initializer or doesn't have enough space to store all bindings
// (number of bindings stored in SBT only grows rights now)
if (bRecreate)
{
PersistentSBTInitializer = NewSBTInitializer;
// Reallocate all the persistent SBTs because all valid records will be reset without clearing the already used records first
// Another option is supporting persistent state overwrite on the SBT records but doesn't allow for correct record state validation (first clear before persisntent record can be written again)
// All used records could be cleared on the other persistent SBTs as well but will be a lot records and recreating the SBT is easier
for (int32 Index = 0; Index < PersistentSBTs.Num(); ++Index)
{
if (PersistentSBTs[Index].ShaderBindingTable)
{
AllocatePersistentShaderBindingTable(RHICmdList, Index, PersistentSBTs[Index].ShaderBindingMode);
}
}
ValidPersistentRecords.Init(false, NewSBTInitializer.NumGeometrySegments * NewSBTInitializer.NumShaderSlotsPerGeometrySegment);
}
}
FShaderBindingTableRHIRef FRayTracingShaderBindingTable::AllocateTransientRHI(
FRHICommandListBase& RHICmdList,
ERayTracingShaderBindingMode ShaderBindingMode,
ERayTracingHitGroupIndexingMode HitGroupIndexingMode,
uint32 LocalBindingDataSize) const
{
uint32 AllocatedStaticSegmentSize = GetMaxAllocatedStaticSegmentCount();
FRayTracingShaderBindingTableInitializer SBTInitializer;
SBTInitializer.ShaderBindingMode = ShaderBindingMode;
SBTInitializer.HitGroupIndexingMode = HitGroupIndexingMode;
SBTInitializer.NumGeometrySegments = AllocatedStaticSegmentSize + NumDynamicGeometrySegments;
SBTInitializer.NumShaderSlotsPerGeometrySegment = NumShaderSlotsPerGeometrySegment;
SBTInitializer.NumMissShaderSlots = NumMissShaderSlots;
SBTInitializer.NumCallableShaderSlots = NumCallableShaderSlots;
SBTInitializer.LocalBindingDataSize = LocalBindingDataSize;
return RHICmdList.CreateRayTracingShaderBindingTable(SBTInitializer);
}
uint32 FRayTracingShaderBindingTable::GetNumGeometrySegments() const
{
return GetMaxAllocatedStaticSegmentCount() + NumDynamicGeometrySegments;
}
uint32 FRayTracingShaderBindingTable::GetMaxAllocatedStaticSegmentCount() const
{
//ensure(bStaticAllocationsLocked);
return StaticRangeAllocator.GetMaxSize() / NumShaderSlotsPerGeometrySegment;
}
void FRayTracingShaderBindingTable::MarkDirty(FRayTracingSBTAllocation* SBTAllocation)
{
int32 MaxRecordIndex = SBTAllocation->BaseRecordIndex + SBTAllocation->NumRecords;
if (ValidPersistentRecords.Num() < MaxRecordIndex)
{
ValidPersistentRecords.SetNum(FMath::RoundUpToPowerOfTwo(MaxRecordIndex), false);
}
for (uint32 Index = 0; Index < SBTAllocation->NumRecords; ++Index)
{
ValidPersistentRecords[SBTAllocation->BaseRecordIndex + Index] = false;
}
}
FRayTracingSBTAllocation* FRayTracingShaderBindingTable::AllocateStaticRangeInternal(
ERayTracingShaderBindingLayerMask AllocatedLayers,
uint32 SegmentCount,
const FRHIRayTracingGeometry* Geometry,
FRayTracingCachedMeshCommandFlags Flags)
{
// Should be allowed to make static SBT allocations
ensure(!bStaticAllocationsLocked);
uint32 LayersCount = FMath::CountBits((uint32)AllocatedLayers);
uint32 RecordsPerLayer = SegmentCount * NumShaderSlotsPerGeometrySegment;
uint32 RecordCount = RecordsPerLayer * LayersCount;
uint32 BaseIndex = StaticRangeAllocator.Allocate(RecordCount);
FRayTracingSBTAllocation* Allocation = new FRayTracingSBTAllocation();
Allocation->InitStatic(AllocatedLayers, BaseIndex, RecordsPerLayer, RecordCount, Geometry, Flags);
MarkDirty(Allocation);
AllocatedStaticSegmentCount += SegmentCount * LayersCount;
return Allocation;
}
FRayTracingSBTAllocation* FRayTracingShaderBindingTable::AllocateStaticRange(uint32 SegmentCount, const FRHIRayTracingGeometry* Geometry, FRayTracingCachedMeshCommandFlags Flags)
{
check(Geometry != nullptr);
// No allocation if we are not rendering decals and all segments are decals
if (RayTracing::ShouldExcludeDecals() && Flags.bAllSegmentsDecal)
{
return nullptr;
}
ERayTracingShaderBindingLayerMask AllocatedLayers = ERayTracingShaderBindingLayerMask::None;
if (!Flags.bAllSegmentsDecal)
{
EnumAddFlags(AllocatedLayers, ERayTracingShaderBindingLayerMask::Base);
}
if (Flags.bAnySegmentsDecal && !RayTracing::ShouldExcludeDecals())
{
EnumAddFlags(AllocatedLayers, ERayTracingShaderBindingLayerMask::Decals);
}
if (AllocatedLayers == ERayTracingShaderBindingLayerMask::None)
{
return nullptr;
}
FScopeLock ScopeLock(&StaticAllocationCS);
// Setup the key needed for deduplication
FAllocationKey Key;
Key.Geometry = Geometry;
Key.Flags = Flags;
// Already allocated for given hash
FRefCountedAllocation& Allocation = TrackedAllocationMap.FindOrAdd(Key);
if (Allocation.RefCount == 0)
{
Allocation.Allocation = AllocateStaticRangeInternal(AllocatedLayers, SegmentCount, Geometry, Flags);
}
else
{
TotalStaticAllocationCount++;
}
check(Allocation.Allocation->AllocatedLayers == AllocatedLayers);
Allocation.RefCount++;
return Allocation.Allocation;
}
void FRayTracingShaderBindingTable::FreeStaticRange(const FRayTracingSBTAllocation* Allocation)
{
if (Allocation == nullptr)
{
return;
}
FScopeLock ScopeLock(&StaticAllocationCS);
TotalStaticAllocationCount--;
// If geometry is stored then it could have been deduplicatedf and we can build the allocation key again
if (Allocation->Geometry)
{
FAllocationKey Key;
Key.Geometry = Allocation->Geometry;
Key.Flags = Allocation->Flags;
FRefCountedAllocation* RefAllocation = TrackedAllocationMap.Find(Key);
check(Allocation);
RefAllocation->RefCount--;
if (RefAllocation->RefCount == 0)
{
StaticRangeAllocator.Free(RefAllocation->Allocation->BaseRecordIndex, RefAllocation->Allocation->NumRecords);
AllocatedStaticSegmentCount -= (RefAllocation->Allocation->NumRecords / NumShaderSlotsPerGeometrySegment);
TrackedAllocationMap.Remove(Key);
PersistentAllocationsToClear.Add(RefAllocation->Allocation);
}
}
else
{
StaticRangeAllocator.Free(Allocation->BaseRecordIndex, Allocation->NumRecords);
AllocatedStaticSegmentCount -= (Allocation->NumRecords / NumShaderSlotsPerGeometrySegment);
delete Allocation;
}
}
FRayTracingSBTAllocation* FRayTracingShaderBindingTable::AllocateDynamicRange(ERayTracingShaderBindingLayerMask AllocatedLayers, uint32 SegmentCount)
{
// Don't need lock right now because all dynamic allocation are allocated linearly on the same thread
// So the FreeDynamicAllocationPool can't be shared with the static allocations right now because those would require a lock
FRayTracingSBTAllocation* Allocation = FreeDynamicAllocationPool.IsEmpty() ? nullptr : FreeDynamicAllocationPool.Pop(EAllowShrinking::No);
if (Allocation == nullptr)
{
Allocation = new FRayTracingSBTAllocation();
}
uint32 LayersCount = FMath::CountBits((uint32)AllocatedLayers);
uint32 BaseIndex = CurrentDynamicRangeOffset;
uint32 RecordsPerLayer = SegmentCount * NumShaderSlotsPerGeometrySegment;
uint32 RecordCount = RecordsPerLayer * LayersCount;
CurrentDynamicRangeOffset += RecordCount;
Allocation->InitDynamic(AllocatedLayers, BaseIndex, RecordsPerLayer, RecordCount);
MarkDirty(Allocation);
NumDynamicGeometrySegments += SegmentCount * LayersCount;
MaxNumDynamicGeometrySegments = FMath::Max(MaxNumDynamicGeometrySegments, NumDynamicGeometrySegments);
ActiveDynamicAllocations.Add(Allocation);
return Allocation;
}
bool FRayTracingShaderBindingTable::IsDirty(uint32 RecordIndex) const
{
return !ValidPersistentRecords[RecordIndex];
}
bool FRayTracingShaderBindingTable::IsPersistent() const
{
return PersistentSBTInitializer.Lifetime == ERayTracingShaderBindingTableLifetime::Persistent;
}
void FRayTracingShaderBindingTable::FlushAllocationsToClear(FRHICommandList& RHICmdList)
{
FScopeLock ScopeLock(&StaticAllocationCS);
// Don't clear outside of current allocated range - could have allocated and cleared ranges before calling CheckPersistentRHI
// and then the RHI SBT doesn't have those new ranges allocated yet
uint32 MaxNumValidRecords = PersistentSBTInitializer.NumGeometrySegments * PersistentSBTInitializer.NumShaderSlotsPerGeometrySegment;
check(MaxNumValidRecords <= (uint32)ValidPersistentRecords.Num());
// Build bit array if records to clear (make sure we don't clear the same record twice)
TBitArray<> PersistentRecordsToClear;
PersistentRecordsToClear.Init(false, MaxNumValidRecords);
uint32 TotalRecordsToClear = 0;
for (const FRayTracingSBTAllocation* Allocation : PersistentAllocationsToClear)
{
for (uint32 RangeIndex = 0; RangeIndex < Allocation->NumRecords; ++RangeIndex)
{
uint32 RecordIndex = Allocation->BaseRecordIndex + RangeIndex;
if (RecordIndex < MaxNumValidRecords)
{
FBitReference RecordFlag = PersistentRecordsToClear[RecordIndex];
if (!RecordFlag)
{
TotalRecordsToClear++;
RecordFlag = true;
}
}
}
// TODO: pool static allocations as well?
delete Allocation;
}
PersistentAllocationsToClear.Reset(PersistentAllocationsToClear.Num());
if (TotalRecordsToClear == 0)
{
return;
}
FSceneRenderingBulkObjectAllocator Allocator;
const uint32 LocalBindingsSize = sizeof(FRayTracingLocalShaderBindings) * TotalRecordsToClear;
FRayTracingLocalShaderBindings* LocalBindings = (FRayTracingLocalShaderBindings*)(RHICmdList.Bypass()
? Allocator.Malloc(LocalBindingsSize, alignof(FRayTracingLocalShaderBindings))
: RHICmdList.Alloc(LocalBindingsSize, alignof(FRayTracingLocalShaderBindings)));
uint32 LocalBindingIndex = 0;
for (TConstSetBitIterator<> It(PersistentRecordsToClear); It; ++It)
{
FRayTracingLocalShaderBindings& RecordBindingToClear = LocalBindings[LocalBindingIndex++];
RecordBindingToClear = FRayTracingLocalShaderBindings();
RecordBindingToClear.RecordIndex = It.GetIndex();
RecordBindingToClear.BindingType = ERayTracingLocalShaderBindingType::Clear;
}
const bool bCopyDataToInlineStorage = false; // Storage is already allocated from RHICmdList, no extra copy necessary
for (FPersistentSBTData& PersistentSBT : PersistentSBTs)
{
if (PersistentSBT.ShaderBindingTable)
{
RHICmdList.SetBindingsOnShaderBindingTable(
PersistentSBT.ShaderBindingTable,
nullptr,
TotalRecordsToClear, LocalBindings,
ERayTracingBindingType::HitGroup,
bCopyDataToInlineStorage);
}
}
}
#endif // RHI_RAYTRACING