// 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