Files
UnrealEngineUWP/Engine/Source/Runtime/RenderCore/Private/RenderResource.cpp
tiago costa 6b7761501e Added flag to FRayTracingGeometry to track if geometry requires an initial BLAS build.
- For example, if it was created using ERTAccelerationStructureBuildPriority::Skip.
- Fix assert for skinned meshes using WorldPositionOffset.

#rb Yuriy.ODonnell, Juan.Canada
#preflight 615f2f21635a7a0001717cdd
#lockdown Michal.Valient

#ROBOMERGE-AUTHOR: tiago.costa
#ROBOMERGE-SOURCE: CL 17753623 via CL 17985240 via CL 18368045 via CL 18368107
#ROBOMERGE-BOT: STARSHIP (Release-Engine-Staging -> Release-Engine-Test) (v895-18170469)

[CL 18368140 by tiago costa in ue5-release-engine-test branch]
2021-12-03 09:55:58 -05:00

1017 lines
28 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
RenderResource.cpp: Render resource implementation.
=============================================================================*/
#include "RenderResource.h"
#include "Misc/ScopedEvent.h"
#include "Misc/App.h"
#include "RenderingThread.h"
#include "ProfilingDebugging/LoadTimeTracker.h"
#include "CoreGlobals.h"
#include "RayTracingGeometryManager.h"
/** Whether to enable mip-level fading or not: +1.0f if enabled, -1.0f if disabled. */
float GEnableMipLevelFading = 1.0f;
// The maximum number of transient vertex buffer bytes to allocate before we start panic logging who is doing the allocations
int32 GMaxVertexBytesAllocatedPerFrame = 32 * 1024 * 1024;
FAutoConsoleVariableRef CVarMaxVertexBytesAllocatedPerFrame(
TEXT("r.MaxVertexBytesAllocatedPerFrame"),
GMaxVertexBytesAllocatedPerFrame,
TEXT("The maximum number of transient vertex buffer bytes to allocate before we start panic logging who is doing the allocations"));
int32 GGlobalBufferNumFramesUnusedThresold = 30;
FAutoConsoleVariableRef CVarReadBufferNumFramesUnusedThresold(
TEXT("r.NumFramesUnusedBeforeReleasingGlobalResourceBuffers"),
GGlobalBufferNumFramesUnusedThresold ,
TEXT("Number of frames after which unused global resource allocations will be discarded. Set 0 to ignore. (default=30)"));
int32 GVarDebugForceRuntimeBLAS = 0;
FAutoConsoleVariableRef CVarDebugForceRuntimeBLAS(
TEXT("r.Raytracing.DebugForceRuntimeBLAS"),
GVarDebugForceRuntimeBLAS,
TEXT("Force building BLAS at runtime."),
ECVF_ReadOnly);
FThreadSafeCounter FRenderResource::ResourceListIterationActive;
TArray<int32>& GetFreeIndicesList()
{
static TArray<int32> FreeIndicesList;
return FreeIndicesList;
}
TArray<FRenderResource*>& FRenderResource::GetResourceList()
{
static TArray<FRenderResource*> RenderResourceList;
return RenderResourceList;
}
/** Initialize all resources initialized before the RHI was initialized */
void FRenderResource::InitPreRHIResources()
{
// Notify all initialized FRenderResources that there's a valid RHI device to create their RHI resources for now.
FRenderResource::InitRHIForAllResources();
#if !PLATFORM_NEEDS_RHIRESOURCELIST
FRenderResource::GetResourceList().Empty();
#endif
}
void FRenderResource::ChangeFeatureLevel(ERHIFeatureLevel::Type NewFeatureLevel)
{
ENQUEUE_RENDER_COMMAND(FRenderResourceChangeFeatureLevel)(
[NewFeatureLevel](FRHICommandList& RHICmdList)
{
FRenderResource::ForAllResources([NewFeatureLevel](FRenderResource* Resource)
{
// Only resources configured for a specific feature level need to be updated
if (Resource->HasValidFeatureLevel() && (Resource->FeatureLevel != NewFeatureLevel))
{
Resource->ReleaseRHI();
Resource->ReleaseDynamicRHI();
Resource->FeatureLevel = NewFeatureLevel;
Resource->InitDynamicRHI();
Resource->InitRHI();
}
});
});
}
void FRenderResource::InitResource()
{
check(IsInRenderingThread());
if (ListIndex == INDEX_NONE)
{
int32 LocalListIndex = INDEX_NONE;
if (PLATFORM_NEEDS_RHIRESOURCELIST || !GIsRHIInitialized)
{
LLM_SCOPE(ELLMTag::SceneRender);
TArray<FRenderResource*>& ResourceList = GetResourceList();
TArray<int32>& FreeIndicesList = GetFreeIndicesList();
// If resource list is currently being iterated, new resources must be added to the end of the list, to ensure they're processed during the iteration
// Otherwise empty slots in the list may be re-used for new resources
if (FreeIndicesList.Num() > 0 && ResourceListIterationActive.GetValue() == 0)
{
LocalListIndex = FreeIndicesList.Pop();
check(ResourceList[LocalListIndex] == nullptr);
ResourceList[LocalListIndex] = this;
}
else
{
LocalListIndex = ResourceList.Add(this);
}
}
else
{
// Mark this resource as initialized
LocalListIndex = 0;
}
if (GIsRHIInitialized)
{
CSV_SCOPED_TIMING_STAT_EXCLUSIVE(InitRenderResource);
InitDynamicRHI();
InitRHI();
}
FPlatformMisc::MemoryBarrier(); // there are some multithreaded reads of ListIndex
ListIndex = LocalListIndex;
}
}
void FRenderResource::ReleaseResource()
{
if ( !GIsCriticalError )
{
check(IsInRenderingThread());
if(ListIndex != INDEX_NONE)
{
if(GIsRHIInitialized)
{
ReleaseRHI();
ReleaseDynamicRHI();
}
#if PLATFORM_NEEDS_RHIRESOURCELIST
TArray<FRenderResource*>& ResourceList = GetResourceList();
TArray<int32>& FreeIndicesList = GetFreeIndicesList();
ResourceList[ListIndex] = nullptr;
FreeIndicesList.Add(ListIndex);
#endif
ListIndex = INDEX_NONE;
}
}
}
void FRenderResource::UpdateRHI()
{
check(IsInRenderingThread());
if(IsInitialized() && GIsRHIInitialized)
{
ReleaseRHI();
ReleaseDynamicRHI();
InitDynamicRHI();
InitRHI();
}
}
FRenderResource::~FRenderResource()
{
if (IsInitialized() && !GIsCriticalError)
{
// Deleting an initialized FRenderResource will result in a crash later since it is still linked
UE_LOG(LogRendererCore, Fatal,TEXT("A FRenderResource was deleted without being released first!"));
}
}
void BeginInitResource(FRenderResource* Resource)
{
ENQUEUE_RENDER_COMMAND(InitCommand)(
[Resource](FRHICommandListImmediate& RHICmdList)
{
Resource->InitResource();
});
}
void BeginUpdateResourceRHI(FRenderResource* Resource)
{
ENQUEUE_RENDER_COMMAND(UpdateCommand)(
[Resource](FRHICommandListImmediate& RHICmdList)
{
Resource->UpdateRHI();
});
}
struct FBatchedReleaseResources
{
enum
{
NumPerBatch = 16
};
int32 NumBatch;
FRenderResource* Resources[NumPerBatch];
FBatchedReleaseResources()
{
Reset();
}
void Reset()
{
NumBatch = 0;
}
void Execute()
{
for (int32 Index = 0; Index < NumBatch; Index++)
{
Resources[Index]->ReleaseResource();
}
Reset();
}
void Flush()
{
if (NumBatch)
{
const FBatchedReleaseResources BatchedReleaseResources = *this;
ENQUEUE_RENDER_COMMAND(BatchReleaseCommand)(
[BatchedReleaseResources](FRHICommandList& RHICmdList)
{
((FBatchedReleaseResources&)BatchedReleaseResources).Execute();
});
Reset();
}
}
void Add(FRenderResource* Resource)
{
if (NumBatch >= NumPerBatch)
{
Flush();
}
check(NumBatch < NumPerBatch);
Resources[NumBatch] = Resource;
NumBatch++;
}
bool IsEmpty()
{
return !NumBatch;
}
};
static bool GBatchedReleaseIsActive = false;
static FBatchedReleaseResources GBatchedRelease;
void StartBatchedRelease()
{
check(IsInGameThread() && !GBatchedReleaseIsActive && GBatchedRelease.IsEmpty());
GBatchedReleaseIsActive = true;
}
void EndBatchedRelease()
{
check(IsInGameThread() && GBatchedReleaseIsActive);
GBatchedRelease.Flush();
GBatchedReleaseIsActive = false;
}
void BeginReleaseResource(FRenderResource* Resource)
{
if (GBatchedReleaseIsActive && IsInGameThread())
{
GBatchedRelease.Add(Resource);
return;
}
ENQUEUE_RENDER_COMMAND(ReleaseCommand)(
[Resource](FRHICommandList& RHICmdList)
{
Resource->ReleaseResource();
});
}
void ReleaseResourceAndFlush(FRenderResource* Resource)
{
// Send the release message.
ENQUEUE_RENDER_COMMAND(ReleaseCommand)(
[Resource](FRHICommandList& RHICmdList)
{
Resource->ReleaseResource();
});
FlushRenderingCommands();
}
FTextureReference::FTextureReference()
: TextureReferenceRHI(NULL)
{
}
FTextureReference::~FTextureReference()
{
}
void FTextureReference::BeginInit_GameThread()
{
bInitialized_GameThread = true;
BeginInitResource(this);
}
void FTextureReference::BeginRelease_GameThread()
{
BeginReleaseResource(this);
bInitialized_GameThread = false;
}
void FTextureReference::InvalidateLastRenderTime()
{
LastRenderTimeRHI.SetLastRenderTime(-FLT_MAX);
}
void FTextureReference::InitRHI()
{
SCOPED_LOADTIMER(FTextureReference_InitRHI);
TextureReferenceRHI = RHICreateTextureReference(&LastRenderTimeRHI);
}
int32 GTextureReferenceRevertsLastRenderContainer = 1;
FAutoConsoleVariableRef CVarTextureReferenceRevertsLastRenderContainer(
TEXT("r.TextureReferenceRevertsLastRenderContainer"),
GTextureReferenceRevertsLastRenderContainer,
TEXT(""));
void FTextureReference::ReleaseRHI()
{
#if PLATFORM_ANDROID
if (TextureReferenceRHI.GetReference())
{
bool bTextureReferenceRevertsLastRenderContainer = GTextureReferenceRevertsLastRenderContainer != 0;
// Check Android's config rules system so we can HF this at startup if needed.
{
static bool bConfigRulesChecked = false;
static TOptional<bool> bConfigRulesRevertsLastRenderContainer;
if (!bConfigRulesChecked)
{
const FString* ConfigRulesStr = FAndroidMisc::GetConfigRulesVariable(TEXT("TextureReferenceRevertsLastRenderContainer"));
if (ConfigRulesStr)
{
bConfigRulesRevertsLastRenderContainer = ConfigRulesStr->Equals("true", ESearchCase::IgnoreCase);
UE_LOG(LogRHI, Log, TEXT("TextureReferenceRevertsLastRenderContainer, set by config rules: %d"), (int)bConfigRulesRevertsLastRenderContainer.GetValue());
}
else
{
UE_LOG(LogRHI, Log, TEXT("TextureReferenceRevertsLastRenderContainer, no config rule set: %d"), (int)bTextureReferenceRevertsLastRenderContainer);
}
bConfigRulesChecked = true;
}
if (bConfigRulesRevertsLastRenderContainer.IsSet())
{
bTextureReferenceRevertsLastRenderContainer = bConfigRulesRevertsLastRenderContainer.GetValue();
}
}
if (bTextureReferenceRevertsLastRenderContainer && TextureReferenceRHI->GetLastRenderTimeContainer() == &LastRenderTimeRHI)
{
// we're going away, TextureReferenceRHI must swap out its (soon to be) dangling ref.
TextureReferenceRHI->SetDefaultLastRenderTimeContainer();
}
}
#endif
TextureReferenceRHI.SafeRelease();
}
FString FTextureReference::GetFriendlyName() const
{
return TEXT("FTextureReference");
}
/** The global null color vertex buffer, which is set with a stride of 0 on meshes without a color component. */
TGlobalResource<FNullColorVertexBuffer> GNullColorVertexBuffer;
/** The global null vertex buffer, which is set with a stride of 0 on meshes */
TGlobalResource<FNullVertexBuffer> GNullVertexBuffer;
/*------------------------------------------------------------------------------
FRayTracingGeometry implementation.
------------------------------------------------------------------------------*/
#if RHI_RAYTRACING
void FRayTracingGeometry::CreateRayTracingGeometry(ERTAccelerationStructureBuildPriority InBuildPriority)
{
// Release previous RHI object if any
ReleaseRHI();
check(RawData.Num() == 0 || Initializer.OfflineData == nullptr);
if (RawData.Num())
{
Initializer.OfflineData = &RawData;
}
if (GVarDebugForceRuntimeBLAS && Initializer.OfflineData != nullptr)
{
Initializer.OfflineData->Discard();
Initializer.OfflineData = nullptr;
}
bool bAllSegmentsAreValid = Initializer.Segments.Num() > 0 || Initializer.OfflineData;
for (const FRayTracingGeometrySegment& Segment : Initializer.Segments)
{
if (!Segment.VertexBuffer)
{
bAllSegmentsAreValid = false;
break;
}
}
if (bAllSegmentsAreValid)
{
RayTracingGeometryRHI = RHICreateRayTracingGeometry(Initializer);
if (Initializer.OfflineData == nullptr)
{
// Request build if not skip
if (InBuildPriority != ERTAccelerationStructureBuildPriority::Skip)
{
RayTracingBuildRequestIndex = GRayTracingGeometryManager.RequestBuildAccelerationStructure(this, InBuildPriority);
bRequiresBuild = false;
}
else
{
bRequiresBuild = true;
}
}
else
{
bRequiresBuild = false;
// Offline data ownership is transferred to the RHI, which discards it after use.
// It is no longer valid to use it after this point.
Initializer.OfflineData = nullptr;
}
}
}
void FRayTracingGeometry::InitRHI()
{
if (!IsRayTracingEnabled())
return;
CreateRayTracingGeometry(ERTAccelerationStructureBuildPriority::Normal);
}
void FRayTracingGeometry::ReleaseRHI()
{
if (HasPendingBuildRequest())
{
GRayTracingGeometryManager.RemoveBuildRequest(RayTracingBuildRequestIndex);
RayTracingBuildRequestIndex = INDEX_NONE;
}
RayTracingGeometryRHI.SafeRelease();
}
void FRayTracingGeometry::ReleaseResource()
{
// Release any resource references held by the initializer.
// This includes index and vertex buffers used for building the BLAS.
Initializer = FRayTracingGeometryInitializer {};
FRenderResource::ReleaseResource();
}
void FRayTracingGeometry::BoostBuildPriority(float InBoostValue) const
{
check(HasPendingBuildRequest());
GRayTracingGeometryManager.BoostPriority(RayTracingBuildRequestIndex, InBoostValue);
}
#endif // RHI_RAYTRACING
/*------------------------------------------------------------------------------
FGlobalDynamicVertexBuffer implementation.
------------------------------------------------------------------------------*/
/**
* An individual dynamic vertex buffer.
*/
class FDynamicVertexBuffer : public FVertexBuffer
{
public:
/** The aligned size of all dynamic vertex buffers. */
enum { ALIGNMENT = (1 << 16) }; // 64KB
/** Pointer to the vertex buffer mapped in main memory. */
uint8* MappedBuffer;
/** Size of the vertex buffer in bytes. */
uint32 BufferSize;
/** Number of bytes currently allocated from the buffer. */
uint32 AllocatedByteCount;
/** Number of successive frames for which AllocatedByteCount == 0. Used as a metric to decide when to free the allocation. */
int32 NumFramesUnused = 0;
/** Default constructor. */
explicit FDynamicVertexBuffer(uint32 InMinBufferSize)
: MappedBuffer(NULL)
, BufferSize(FMath::Max<uint32>(Align(InMinBufferSize,ALIGNMENT),ALIGNMENT))
, AllocatedByteCount(0)
{
}
/**
* Locks the vertex buffer so it may be written to.
*/
void Lock()
{
check(MappedBuffer == NULL);
check(AllocatedByteCount == 0);
check(IsValidRef(VertexBufferRHI));
MappedBuffer = (uint8*)RHILockBuffer(VertexBufferRHI, 0, BufferSize, RLM_WriteOnly);
}
/**
* Unocks the buffer so the GPU may read from it.
*/
void Unlock()
{
check(MappedBuffer != NULL);
check(IsValidRef(VertexBufferRHI));
RHIUnlockBuffer(VertexBufferRHI);
MappedBuffer = NULL;
AllocatedByteCount = 0;
NumFramesUnused = 0;
}
// FRenderResource interface.
virtual void InitRHI() override
{
check(!IsValidRef(VertexBufferRHI));
FRHIResourceCreateInfo CreateInfo(TEXT("FDynamicVertexBuffer"));
VertexBufferRHI = RHICreateVertexBuffer(BufferSize, BUF_Volatile, CreateInfo);
MappedBuffer = NULL;
AllocatedByteCount = 0;
}
virtual void ReleaseRHI() override
{
FVertexBuffer::ReleaseRHI();
MappedBuffer = NULL;
AllocatedByteCount = 0;
}
virtual FString GetFriendlyName() const override
{
return TEXT("FDynamicVertexBuffer");
}
};
/**
* A pool of dynamic vertex buffers.
*/
struct FDynamicVertexBufferPool
{
/** List of vertex buffers. */
TIndirectArray<FDynamicVertexBuffer> VertexBuffers;
/** The current buffer from which allocations are being made. */
FDynamicVertexBuffer* CurrentVertexBuffer;
/** Default constructor. */
FDynamicVertexBufferPool()
: CurrentVertexBuffer(NULL)
{
}
/** Destructor. */
~FDynamicVertexBufferPool()
{
int32 NumVertexBuffers = VertexBuffers.Num();
for (int32 BufferIndex = 0; BufferIndex < NumVertexBuffers; ++BufferIndex)
{
VertexBuffers[BufferIndex].ReleaseResource();
}
}
};
FGlobalDynamicVertexBuffer::FGlobalDynamicVertexBuffer()
: TotalAllocatedSinceLastCommit(0)
{
Pool = new FDynamicVertexBufferPool();
}
FGlobalDynamicVertexBuffer::~FGlobalDynamicVertexBuffer()
{
delete Pool;
Pool = NULL;
}
FGlobalDynamicVertexBuffer::FAllocation FGlobalDynamicVertexBuffer::Allocate(uint32 SizeInBytes)
{
FAllocation Allocation;
TotalAllocatedSinceLastCommit += SizeInBytes;
if (IsRenderAlarmLoggingEnabled())
{
UE_LOG(LogRendererCore, Warning, TEXT("FGlobalDynamicVertexBuffer::Allocate(%u), will have allocated %u total this frame"), SizeInBytes, TotalAllocatedSinceLastCommit);
}
FDynamicVertexBuffer* VertexBuffer = Pool->CurrentVertexBuffer;
if (VertexBuffer == NULL || VertexBuffer->AllocatedByteCount + SizeInBytes > VertexBuffer->BufferSize)
{
// Find a buffer in the pool big enough to service the request.
VertexBuffer = NULL;
for (int32 BufferIndex = 0, NumBuffers = Pool->VertexBuffers.Num(); BufferIndex < NumBuffers; ++BufferIndex)
{
FDynamicVertexBuffer& VertexBufferToCheck = Pool->VertexBuffers[BufferIndex];
if (VertexBufferToCheck.AllocatedByteCount + SizeInBytes <= VertexBufferToCheck.BufferSize)
{
VertexBuffer = &VertexBufferToCheck;
break;
}
}
// Create a new vertex buffer if needed.
if (VertexBuffer == NULL)
{
VertexBuffer = new FDynamicVertexBuffer(SizeInBytes);
Pool->VertexBuffers.Add(VertexBuffer);
VertexBuffer->InitResource();
}
// Lock the buffer if needed.
if (VertexBuffer->MappedBuffer == NULL)
{
VertexBuffer->Lock();
}
// Remember this buffer, we'll try to allocate out of it in the future.
Pool->CurrentVertexBuffer = VertexBuffer;
}
check(VertexBuffer != NULL);
checkf(VertexBuffer->AllocatedByteCount + SizeInBytes <= VertexBuffer->BufferSize, TEXT("Global vertex buffer allocation failed: BufferSize=%d AllocatedByteCount=%d SizeInBytes=%d"), VertexBuffer->BufferSize, VertexBuffer->AllocatedByteCount, SizeInBytes);
Allocation.Buffer = VertexBuffer->MappedBuffer + VertexBuffer->AllocatedByteCount;
Allocation.VertexBuffer = VertexBuffer;
Allocation.VertexOffset = VertexBuffer->AllocatedByteCount;
VertexBuffer->AllocatedByteCount += SizeInBytes;
return Allocation;
}
bool FGlobalDynamicVertexBuffer::IsRenderAlarmLoggingEnabled() const
{
return GMaxVertexBytesAllocatedPerFrame > 0 && TotalAllocatedSinceLastCommit >= (size_t)GMaxVertexBytesAllocatedPerFrame;
}
void FGlobalDynamicVertexBuffer::Commit()
{
for (int32 BufferIndex = 0, NumBuffers = Pool->VertexBuffers.Num(); BufferIndex < NumBuffers; ++BufferIndex)
{
FDynamicVertexBuffer& VertexBuffer = Pool->VertexBuffers[BufferIndex];
if (VertexBuffer.MappedBuffer != NULL)
{
VertexBuffer.Unlock();
}
else if (GGlobalBufferNumFramesUnusedThresold && !VertexBuffer.AllocatedByteCount)
{
++VertexBuffer.NumFramesUnused;
if (VertexBuffer.NumFramesUnused >= GGlobalBufferNumFramesUnusedThresold)
{
// Remove the buffer, assumes they are unordered.
VertexBuffer.ReleaseResource();
Pool->VertexBuffers.RemoveAtSwap(BufferIndex);
--BufferIndex;
--NumBuffers;
}
}
}
Pool->CurrentVertexBuffer = NULL;
TotalAllocatedSinceLastCommit = 0;
}
FGlobalDynamicVertexBuffer InitViewDynamicVertexBuffer;
FGlobalDynamicVertexBuffer InitShadowViewDynamicVertexBuffer;
/*------------------------------------------------------------------------------
FGlobalDynamicIndexBuffer implementation.
------------------------------------------------------------------------------*/
/**
* An individual dynamic index buffer.
*/
class FDynamicIndexBuffer : public FIndexBuffer
{
public:
/** The aligned size of all dynamic index buffers. */
enum { ALIGNMENT = (1 << 16) }; // 64KB
/** Pointer to the index buffer mapped in main memory. */
uint8* MappedBuffer;
/** Size of the index buffer in bytes. */
uint32 BufferSize;
/** Number of bytes currently allocated from the buffer. */
uint32 AllocatedByteCount;
/** Stride of the buffer in bytes. */
uint32 Stride;
/** Number of successive frames for which AllocatedByteCount == 0. Used as a metric to decide when to free the allocation. */
int32 NumFramesUnused = 0;
/** Initialization constructor. */
explicit FDynamicIndexBuffer(uint32 InMinBufferSize, uint32 InStride)
: MappedBuffer(NULL)
, BufferSize(FMath::Max<uint32>(Align(InMinBufferSize,ALIGNMENT),ALIGNMENT))
, AllocatedByteCount(0)
, Stride(InStride)
{
}
/**
* Locks the vertex buffer so it may be written to.
*/
void Lock()
{
check(MappedBuffer == NULL);
check(AllocatedByteCount == 0);
check(IsValidRef(IndexBufferRHI));
MappedBuffer = (uint8*)RHILockBuffer(IndexBufferRHI, 0, BufferSize, RLM_WriteOnly);
}
/**
* Unocks the buffer so the GPU may read from it.
*/
void Unlock()
{
check(MappedBuffer != NULL);
check(IsValidRef(IndexBufferRHI));
RHIUnlockBuffer(IndexBufferRHI);
MappedBuffer = NULL;
AllocatedByteCount = 0;
NumFramesUnused = 0;
}
// FRenderResource interface.
virtual void InitRHI() override
{
check(!IsValidRef(IndexBufferRHI));
FRHIResourceCreateInfo CreateInfo(TEXT("FDynamicIndexBuffer"));
IndexBufferRHI = RHICreateIndexBuffer(Stride, BufferSize, BUF_Volatile, CreateInfo);
MappedBuffer = NULL;
AllocatedByteCount = 0;
}
virtual void ReleaseRHI() override
{
FIndexBuffer::ReleaseRHI();
MappedBuffer = NULL;
AllocatedByteCount = 0;
}
virtual FString GetFriendlyName() const override
{
return TEXT("FDynamicIndexBuffer");
}
};
/**
* A pool of dynamic index buffers.
*/
struct FDynamicIndexBufferPool
{
/** List of index buffers. */
TIndirectArray<FDynamicIndexBuffer> IndexBuffers;
/** The current buffer from which allocations are being made. */
FDynamicIndexBuffer* CurrentIndexBuffer;
/** Stride of buffers in this pool. */
uint32 BufferStride;
/** Initialization constructor. */
explicit FDynamicIndexBufferPool(uint32 InBufferStride)
: CurrentIndexBuffer(NULL)
, BufferStride(InBufferStride)
{
}
/** Destructor. */
~FDynamicIndexBufferPool()
{
int32 NumIndexBuffers = IndexBuffers.Num();
for (int32 BufferIndex = 0; BufferIndex < NumIndexBuffers; ++BufferIndex)
{
IndexBuffers[BufferIndex].ReleaseResource();
}
}
};
FGlobalDynamicIndexBuffer::FGlobalDynamicIndexBuffer()
{
Pools[0] = new FDynamicIndexBufferPool(sizeof(uint16));
Pools[1] = new FDynamicIndexBufferPool(sizeof(uint32));
}
FGlobalDynamicIndexBuffer::~FGlobalDynamicIndexBuffer()
{
for (int32 i = 0; i < 2; ++i)
{
delete Pools[i];
Pools[i] = NULL;
}
}
FGlobalDynamicIndexBuffer::FAllocation FGlobalDynamicIndexBuffer::Allocate(uint32 NumIndices, uint32 IndexStride)
{
FAllocation Allocation;
if (IndexStride != 2 && IndexStride != 4)
{
return Allocation;
}
FDynamicIndexBufferPool* Pool = Pools[IndexStride >> 2]; // 2 -> 0, 4 -> 1
uint32 SizeInBytes = NumIndices * IndexStride;
FDynamicIndexBuffer* IndexBuffer = Pool->CurrentIndexBuffer;
if (IndexBuffer == NULL || IndexBuffer->AllocatedByteCount + SizeInBytes > IndexBuffer->BufferSize)
{
// Find a buffer in the pool big enough to service the request.
IndexBuffer = NULL;
for (int32 BufferIndex = 0, NumBuffers = Pool->IndexBuffers.Num(); BufferIndex < NumBuffers; ++BufferIndex)
{
FDynamicIndexBuffer& IndexBufferToCheck = Pool->IndexBuffers[BufferIndex];
if (IndexBufferToCheck.AllocatedByteCount + SizeInBytes <= IndexBufferToCheck.BufferSize)
{
IndexBuffer = &IndexBufferToCheck;
break;
}
}
// Create a new index buffer if needed.
if (IndexBuffer == NULL)
{
IndexBuffer = new FDynamicIndexBuffer(SizeInBytes, Pool->BufferStride);
Pool->IndexBuffers.Add(IndexBuffer);
IndexBuffer->InitResource();
}
// Lock the buffer if needed.
if (IndexBuffer->MappedBuffer == NULL)
{
IndexBuffer->Lock();
}
Pool->CurrentIndexBuffer = IndexBuffer;
}
check(IndexBuffer != NULL);
checkf(IndexBuffer->AllocatedByteCount + SizeInBytes <= IndexBuffer->BufferSize, TEXT("Global index buffer allocation failed: BufferSize=%d AllocatedByteCount=%d SizeInBytes=%d"), IndexBuffer->BufferSize, IndexBuffer->AllocatedByteCount, SizeInBytes);
Allocation.Buffer = IndexBuffer->MappedBuffer + IndexBuffer->AllocatedByteCount;
Allocation.IndexBuffer = IndexBuffer;
Allocation.FirstIndex = IndexBuffer->AllocatedByteCount / IndexStride;
IndexBuffer->AllocatedByteCount += SizeInBytes;
return Allocation;
}
void FGlobalDynamicIndexBuffer::Commit()
{
for (int32 i = 0; i < 2; ++i)
{
FDynamicIndexBufferPool* Pool = Pools[i];
for (int32 BufferIndex = 0, NumBuffers = Pool->IndexBuffers.Num(); BufferIndex < NumBuffers; ++BufferIndex)
{
FDynamicIndexBuffer& IndexBuffer = Pool->IndexBuffers[BufferIndex];
if (IndexBuffer.MappedBuffer != NULL)
{
IndexBuffer.Unlock();
}
else if (GGlobalBufferNumFramesUnusedThresold && !IndexBuffer.AllocatedByteCount)
{
++IndexBuffer.NumFramesUnused;
if (IndexBuffer.NumFramesUnused >= GGlobalBufferNumFramesUnusedThresold)
{
// Remove the buffer, assumes they are unordered.
IndexBuffer.ReleaseResource();
Pool->IndexBuffers.RemoveAtSwap(BufferIndex);
--BufferIndex;
--NumBuffers;
}
}
}
Pool->CurrentIndexBuffer = NULL;
}
}
/*=============================================================================
FMipBiasFade class
=============================================================================*/
/** Global mip fading settings, indexed by EMipFadeSettings. */
FMipFadeSettings GMipFadeSettings[MipFade_NumSettings] =
{
FMipFadeSettings(0.3f, 0.1f), // MipFade_Normal
FMipFadeSettings(2.0f, 1.0f) // MipFade_Slow
};
/** How "old" a texture must be to be considered a "new texture", in seconds. */
float GMipLevelFadingAgeThreshold = 0.5f;
/**
* Sets up a new interpolation target for the mip-bias.
* @param ActualMipCount Number of mip-levels currently in memory
* @param TargetMipCount Number of mip-levels we're changing to
* @param LastRenderTime Timestamp when it was last rendered (FApp::CurrentTime time space)
* @param FadeSetting Which fade speed settings to use
*/
void FMipBiasFade::SetNewMipCount( float ActualMipCount, float TargetMipCount, double LastRenderTime, EMipFadeSettings FadeSetting )
{
check( ActualMipCount >=0 && TargetMipCount <= ActualMipCount );
float TimeSinceLastRendered = float(FApp::GetCurrentTime() - LastRenderTime);
// Is this a new texture or is this not in-game?
if ( TotalMipCount == 0 || TimeSinceLastRendered >= GMipLevelFadingAgeThreshold || GEnableMipLevelFading < 0.0f )
{
// No fading.
TotalMipCount = ActualMipCount;
MipCountDelta = 0.0f;
MipCountFadingRate = 0.0f;
StartTime = GRenderingRealtimeClock.GetCurrentTime();
BiasOffset = 0.0f;
return;
}
// Calculate the mipcount we're interpolating towards.
float CurrentTargetMipCount = TotalMipCount - BiasOffset + MipCountDelta;
// Is there no change?
if ( FMath::IsNearlyEqual(TotalMipCount, ActualMipCount) && FMath::IsNearlyEqual(TargetMipCount, CurrentTargetMipCount) )
{
return;
}
// Calculate the mip-count at our current interpolation point.
float CurrentInterpolatedMipCount = TotalMipCount - CalcMipBias();
// Clamp it against the available mip-levels.
CurrentInterpolatedMipCount = FMath::Clamp<float>(CurrentInterpolatedMipCount, 0, ActualMipCount);
// Set up a new interpolation from CurrentInterpolatedMipCount to TargetMipCount.
StartTime = GRenderingRealtimeClock.GetCurrentTime();
TotalMipCount = ActualMipCount;
MipCountDelta = TargetMipCount - CurrentInterpolatedMipCount;
// Don't fade if we're already at the target mip-count.
if ( FMath::IsNearlyZero(MipCountDelta) )
{
MipCountDelta = 0.0f;
BiasOffset = 0.0f;
MipCountFadingRate = 0.0f;
}
else
{
BiasOffset = TotalMipCount - CurrentInterpolatedMipCount;
if ( MipCountDelta > 0.0f )
{
MipCountFadingRate = 1.0f / (GMipFadeSettings[FadeSetting].FadeInSpeed * MipCountDelta);
}
else
{
MipCountFadingRate = -1.0f / (GMipFadeSettings[FadeSetting].FadeOutSpeed * MipCountDelta);
}
}
}
class FTextureSamplerStateCache : public FRenderResource
{
public:
TMap<FSamplerStateInitializerRHI, FRHISamplerState*> Samplers;
virtual void ReleaseRHI() override
{
for (auto Pair : Samplers)
{
Pair.Value->Release();
}
Samplers.Empty();
}
};
TGlobalResource<FTextureSamplerStateCache> GTextureSamplerStateCache;
FRHISamplerState* FTexture::GetOrCreateSamplerState(const FSamplerStateInitializerRHI& Initializer)
{
// This sampler cache is supposed to be used only from RT
// Add a lock here if it's used from multiple threads
check(IsInRenderingThread());
FRHISamplerState** Found = GTextureSamplerStateCache.Samplers.Find(Initializer);
if (Found)
{
return *Found;
}
FSamplerStateRHIRef NewState = RHICreateSamplerState(Initializer);
// Add an extra reference so we don't have TRefCountPtr in the maps
NewState->AddRef();
GTextureSamplerStateCache.Samplers.Add(Initializer, NewState);
return NewState;
}
bool IsRayTracingEnabled()
{
checkf(GIsRHIInitialized, TEXT("IsRayTracingEnabled() may only be called once RHI is initialized."));
#if DO_CHECK && WITH_EDITOR
{
// This function must not be called while cooking
if (IsRunningCookCommandlet())
{
return false;
}
}
#endif // DO_CHECK && WITH_EDITOR
extern RENDERCORE_API bool GUseRayTracing;
return GUseRayTracing;
}