Fix render target pool over-allocation when changing render resolution frequently

#2077
This commit is contained in:
Wojtek Figat
2023-12-16 16:15:52 +01:00
parent 074ad171ba
commit 2bef880e21
3 changed files with 33 additions and 40 deletions

View File

@@ -192,6 +192,9 @@ bool RenderBuffers::Init(int32 width, int32 height)
_viewport = Viewport(0, 0, static_cast<float>(width), static_cast<float>(height));
LastEyeAdaptationTime = 0;
// Flush any pool render targets to prevent over-allocating GPU memory when resizing game viewport
RenderTargetPool::Flush(false, 4);
return result;
}

View File

@@ -7,35 +7,31 @@
struct Entry
{
bool IsOccupied;
GPUTexture* RT;
uint64 LastFrameTaken;
uint64 LastFrameReleased;
uint32 DescriptionHash;
bool IsOccupied;
};
namespace
{
Array<Entry> TemporaryRTs(64);
Array<Entry> TemporaryRTs;
}
void RenderTargetPool::Flush(bool force)
void RenderTargetPool::Flush(bool force, int32 framesOffset)
{
const uint64 framesOffset = 3 * 60;
if (framesOffset < 0)
framesOffset = 3 * 60; // For how many frames RTs should be cached (by default)
const uint64 maxReleaseFrame = Engine::FrameCount - framesOffset;
force |= Engine::ShouldExit();
for (int32 i = 0; i < TemporaryRTs.Count(); i++)
{
auto& tmp = TemporaryRTs[i];
if (!tmp.IsOccupied && (force || (tmp.LastFrameReleased < maxReleaseFrame)))
const auto& e = TemporaryRTs[i];
if (!e.IsOccupied && (force || e.LastFrameReleased < maxReleaseFrame))
{
// Release
tmp.RT->DeleteObjectNow();
TemporaryRTs.RemoveAt(i);
i--;
e.RT->DeleteObjectNow();
TemporaryRTs.RemoveAt(i--);
if (TemporaryRTs.IsEmpty())
break;
}
@@ -48,19 +44,14 @@ GPUTexture* RenderTargetPool::Get(const GPUTextureDescription& desc)
const uint32 descHash = GetHash(desc);
for (int32 i = 0; i < TemporaryRTs.Count(); i++)
{
auto& tmp = TemporaryRTs[i];
if (!tmp.IsOccupied && tmp.DescriptionHash == descHash)
auto& e = TemporaryRTs[i];
if (!e.IsOccupied && e.DescriptionHash == descHash)
{
ASSERT(tmp.RT);
// Mark as used
tmp.IsOccupied = true;
tmp.LastFrameTaken = Engine::FrameCount;
return tmp.RT;
e.IsOccupied = true;
return e.RT;
}
}
#if !BUILD_RELEASE
if (TemporaryRTs.Count() > 2000)
{
@@ -71,24 +62,23 @@ GPUTexture* RenderTargetPool::Get(const GPUTextureDescription& desc)
// Create new rt
const String name = TEXT("TemporaryRT_") + StringUtils::ToString(TemporaryRTs.Count());
auto newRenderTarget = GPUDevice::Instance->CreateTexture(name);
if (newRenderTarget->Init(desc))
GPUTexture* rt = GPUDevice::Instance->CreateTexture(name);
if (rt->Init(desc))
{
Delete(newRenderTarget);
Delete(rt);
LOG(Error, "Cannot create temporary render target. Description: {0}", desc.ToString());
return nullptr;
}
// Create temporary rt entry
Entry entry;
entry.IsOccupied = true;
entry.LastFrameReleased = 0;
entry.LastFrameTaken = Engine::FrameCount;
entry.RT = newRenderTarget;
entry.DescriptionHash = descHash;
TemporaryRTs.Add(entry);
Entry e;
e.IsOccupied = true;
e.LastFrameReleased = 0;
e.RT = rt;
e.DescriptionHash = descHash;
TemporaryRTs.Add(e);
return newRenderTarget;
return rt;
}
void RenderTargetPool::Release(GPUTexture* rt)
@@ -98,14 +88,13 @@ void RenderTargetPool::Release(GPUTexture* rt)
for (int32 i = 0; i < TemporaryRTs.Count(); i++)
{
auto& tmp = TemporaryRTs[i];
if (tmp.RT == rt)
auto& e = TemporaryRTs[i];
if (e.RT == rt)
{
// Mark as free
ASSERT(tmp.IsOccupied);
tmp.IsOccupied = false;
tmp.LastFrameReleased = Engine::FrameCount;
ASSERT(e.IsOccupied);
e.IsOccupied = false;
e.LastFrameReleased = Engine::FrameCount;
return;
}
}

View File

@@ -15,7 +15,8 @@ public:
/// Flushes the temporary render targets.
/// </summary>
/// <param name="force">True if release unused render targets by force, otherwise will use a few frames of delay.</param>
static void Flush(bool force = false);
/// <param name="framesOffset">Amount of previous frames that should persist in the pool after flush. Resources used more than given value wil be freed. Use value of -1 to auto pick default duration.</param>
static void Flush(bool force = false, int32 framesOffset = -1);
/// <summary>
/// Gets a temporary render target.