Fix query leaks when GetQueryResult call is missed
This commit is contained in:
@@ -627,6 +627,7 @@ uint64 GPUContextDX11::BeginQuery(GPUQueryType type)
|
||||
ASSERT_LOW_LAYER(query.State == GPUQueryDataDX11::Ready);
|
||||
ASSERT_LOW_LAYER(query.Type == type);
|
||||
query.State = GPUQueryDataDX11::Begin;
|
||||
query.TTL = 50; // Auto-destroy after some frames when user fails to call GetQueryResult
|
||||
auto context = _device->GetIM();
|
||||
if (type == GPUQueryType::Timer)
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Engine/Core/Math/Vector4.h"
|
||||
#include "Engine/Graphics/GPUContext.h"
|
||||
#include "GPUDeviceDX11.h"
|
||||
#include "GPUPipelineStateDX11.h"
|
||||
|
||||
@@ -998,13 +998,13 @@ void GPUDeviceDX11::DrawEnd()
|
||||
}
|
||||
#endif
|
||||
|
||||
// Auto-return finished queries back to the pool
|
||||
// Auto-return finished queries back to the pool (or leaked queries)
|
||||
auto* queries = _queries.Get();
|
||||
int32 queriesCount = _queries.Count();
|
||||
for (int32 i = 0; i < queriesCount; i++)
|
||||
{
|
||||
auto& query = queries[i];
|
||||
if (query.State == GPUQueryDataDX11::Finished)
|
||||
if (query.State == GPUQueryDataDX11::Finished || (query.State == GPUQueryDataDX11::End && --query.TTL == 0))
|
||||
{
|
||||
query.State = GPUQueryDataDX11::Ready;
|
||||
query.Result = 0;
|
||||
@@ -1027,7 +1027,8 @@ bool GPUDeviceDX11::GetQueryResult(uint64 queryID, uint64& result, bool wait)
|
||||
result = query.Result;
|
||||
return true;
|
||||
}
|
||||
ASSERT_LOW_LAYER(query.State == GPUQueryDataDX11::End);
|
||||
if (query.State != GPUQueryDataDX11::End)
|
||||
return false;
|
||||
auto context = GetIM();
|
||||
|
||||
RETRY:
|
||||
@@ -1062,13 +1063,17 @@ RETRY:
|
||||
}
|
||||
else if (q.Type == (uint16)GPUQueryType::Occlusion)
|
||||
{
|
||||
hasData = context->GetData(query.Query, &result, sizeof(UINT64), 0) == S_OK;
|
||||
UINT64 data;
|
||||
hasData = context->GetData(query.Query, &data, sizeof(data), 0) == S_OK;
|
||||
if (hasData)
|
||||
result = data;
|
||||
}
|
||||
else
|
||||
{
|
||||
BOOL resultBool;
|
||||
hasData = context->GetData(query.Query, &resultBool, sizeof(BOOL), 0) == S_OK;
|
||||
result = resultBool ? 1 : 0;
|
||||
BOOL data;
|
||||
hasData = context->GetData(query.Query, &data, sizeof(data), 0) == S_OK;
|
||||
if (hasData)
|
||||
result = data ? 1 : 0;
|
||||
}
|
||||
|
||||
if (!hasData && wait)
|
||||
|
||||
@@ -41,7 +41,8 @@ struct GPUQueryDataDX11
|
||||
ID3D11Query* TimerBeginQuery = nullptr;
|
||||
ID3D11Query* DisjointQuery = nullptr;
|
||||
uint64 Result = 0;
|
||||
enum States { Ready, Begin, End, Finished } State = Ready;
|
||||
uint16 TTL = 0;
|
||||
enum States : uint16 { Ready, Begin, End, Finished } State = Ready;
|
||||
GPUQueryType Type = GPUQueryType::MAX;
|
||||
|
||||
void Release();
|
||||
|
||||
@@ -1003,7 +1003,7 @@ void GPUDeviceDX12::RenderEnd()
|
||||
|
||||
// Resolve the queries
|
||||
for (auto heap : QueryHeaps)
|
||||
heap->EndQueryBatchAndResolveQueryData(_mainContext);
|
||||
heap->EndFrame(_mainContext);
|
||||
}
|
||||
|
||||
void GPUDeviceDX12::OnCrash()
|
||||
|
||||
@@ -79,13 +79,20 @@ void QueryHeapDX12::Destroy()
|
||||
_resultData.SetCapacity(0);
|
||||
}
|
||||
|
||||
void QueryHeapDX12::EndQueryBatchAndResolveQueryData(GPUContextDX12* context)
|
||||
void QueryHeapDX12::EndFrame(GPUContextDX12* context)
|
||||
{
|
||||
ASSERT(_currentBatch.Open);
|
||||
if (_currentBatch.Count == 0)
|
||||
return;
|
||||
// Update existing batches to avoid leaks
|
||||
for (int32 i = 0; i < _batches.Count(); i++)
|
||||
{
|
||||
if (--_batches[i].TTL == 0)
|
||||
{
|
||||
_batches.RemoveAt(i--);
|
||||
}
|
||||
}
|
||||
|
||||
// Close the current batch
|
||||
// Close the current batch (if not empty)
|
||||
if (!_currentBatch.Open || _currentBatch.Count == 0)
|
||||
return;
|
||||
_currentBatch.Open = false;
|
||||
|
||||
// Resolve the batch
|
||||
@@ -203,6 +210,7 @@ void QueryHeapDX12::StartQueryBatch()
|
||||
// Start a new batch
|
||||
_currentBatch.Start = _currentIndex;
|
||||
_currentBatch.Open = true;
|
||||
_currentBatch.TTL = 50; // Auto-destroy after some frames when user fails to call GetQueryResult
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -75,6 +75,8 @@ private:
|
||||
/// </summary>
|
||||
bool Open = false;
|
||||
|
||||
uint8 TTL = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if this query batch contains a given element contains the element.
|
||||
/// </summary>
|
||||
@@ -146,7 +148,7 @@ public:
|
||||
/// Stops tracking the current batch of begin/end query calls that will be resolved together. This implicitly starts a new batch.
|
||||
/// </summary>
|
||||
/// <param name="context">The context.</param>
|
||||
void EndQueryBatchAndResolveQueryData(GPUContextDX12* context);
|
||||
void EndFrame(GPUContextDX12* context);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if can alloc a new query (without rolling the existing batch).
|
||||
|
||||
@@ -677,7 +677,6 @@ void QueryPoolVulkan::Reset(CmdBufferVulkan* cmdBuffer)
|
||||
|
||||
BufferedQueryPoolVulkan::BufferedQueryPoolVulkan(GPUDeviceVulkan* device, int32 capacity, GPUQueryType type)
|
||||
: QueryPoolVulkan(device, capacity, type)
|
||||
, _lastBeginIndex(0)
|
||||
{
|
||||
_queryOutput.Resize(capacity);
|
||||
_usedQueryBits.AddZeroed((capacity + 63) / 64);
|
||||
@@ -702,10 +701,12 @@ bool BufferedQueryPoolVulkan::AcquireQuery(CmdBufferVulkan* cmdBuffer, uint32& r
|
||||
resultIndex += wordIndex * 64;
|
||||
const uint64 bit = (uint64)1 << (uint64)(resultIndex % 64);
|
||||
_usedQueryBits[wordIndex] = _usedQueryBits[wordIndex] | bit;
|
||||
_startedQueryBits[wordIndex] &= ~bit;
|
||||
_readResultsBits[wordIndex] &= ~bit;
|
||||
_lastBeginIndex = resultIndex + 1;
|
||||
if (ResetBeforeUse)
|
||||
Reset(cmdBuffer);
|
||||
_lastUsedFrame = Engine::FrameCount;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -728,6 +729,7 @@ void BufferedQueryPoolVulkan::ReleaseQuery(uint32 queryIndex)
|
||||
_lastBeginIndex = (uint32)queryIndex;
|
||||
}
|
||||
}
|
||||
_lastUsedFrame = Engine::FrameCount;
|
||||
if (_usedQueryBits[word] == 0)
|
||||
{
|
||||
// Check if pool got empty and reset the pointer back to start
|
||||
@@ -749,23 +751,21 @@ void BufferedQueryPoolVulkan::MarkQueryAsStarted(uint32 queryIndex)
|
||||
|
||||
bool BufferedQueryPoolVulkan::GetResults(uint32 index, uint64& result)
|
||||
{
|
||||
const uint64 bit = (uint64)(index % 64);
|
||||
const uint64 bitMask = (uint64)1 << bit;
|
||||
const uint64 bit = (uint64)1 << (index % 64);
|
||||
const uint32 word = index / 64;
|
||||
|
||||
if ((_startedQueryBits[word] & bitMask) == 0)
|
||||
if ((_startedQueryBits[word] & bit) == 0)
|
||||
{
|
||||
// Query never started/ended
|
||||
result = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((_readResultsBits[word] & bitMask) == 0)
|
||||
if ((_readResultsBits[word] & bit) == 0)
|
||||
{
|
||||
const VkResult vkResult = vkGetQueryPoolResults(_device->Device, _handle, index, 1, sizeof(uint64), &_queryOutput[index], sizeof(uint64), VK_QUERY_RESULT_64_BIT);
|
||||
if (vkResult == VK_SUCCESS)
|
||||
{
|
||||
_readResultsBits[word] = _readResultsBits[word] | bitMask;
|
||||
_readResultsBits[word] = _readResultsBits[word] | bit;
|
||||
|
||||
#if VULKAN_RESET_QUERY_POOLS
|
||||
// Add to reset
|
||||
@@ -777,7 +777,6 @@ bool BufferedQueryPoolVulkan::GetResults(uint32 index, uint64& result)
|
||||
else if (vkResult == VK_NOT_READY)
|
||||
{
|
||||
// No results yet
|
||||
result = 0;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
@@ -786,13 +785,15 @@ bool BufferedQueryPoolVulkan::GetResults(uint32 index, uint64& result)
|
||||
}
|
||||
}
|
||||
|
||||
// Get result
|
||||
result = _queryOutput[index];
|
||||
_lastUsedFrame = Engine::FrameCount;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BufferedQueryPoolVulkan::HasRoom() const
|
||||
{
|
||||
const uint64 allUsedMask = MAX_uint64;
|
||||
constexpr uint64 allUsedMask = MAX_uint64;
|
||||
if (_lastBeginIndex < _usedQueryBits.Count() * 64)
|
||||
{
|
||||
ASSERT((_usedQueryBits[_lastBeginIndex / 64] & allUsedMask) != allUsedMask);
|
||||
@@ -801,6 +802,49 @@ bool BufferedQueryPoolVulkan::HasRoom() const
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BufferedQueryPoolVulkan::TryExpire()
|
||||
{
|
||||
// Auto-destroy pools some frames when user fails to call GetQueryResult
|
||||
if (Engine::FrameCount - _lastUsedFrame < 50)
|
||||
return false;
|
||||
|
||||
// Reset back to clean state all unfinished queries
|
||||
for (uint32 index = 0; index < _queryOutput.Count(); index++)
|
||||
{
|
||||
const uint64 bit = (uint64)1 << (index % 64);
|
||||
const uint32 word = index / 64;
|
||||
if ((_usedQueryBits[word] & bit) == 0)
|
||||
continue;
|
||||
if ((_readResultsBits[word] & bit) == 0)
|
||||
{
|
||||
const VkResult vkResult = vkGetQueryPoolResults(_device->Device, _handle, index, 1, sizeof(uint64), &_queryOutput[index], sizeof(uint64), VK_QUERY_RESULT_64_BIT);
|
||||
if (vkResult == VK_SUCCESS)
|
||||
{
|
||||
_readResultsBits[word] = _readResultsBits[word] | bit;
|
||||
|
||||
#if VULKAN_RESET_QUERY_POOLS
|
||||
// Add to reset
|
||||
if (!_device->QueriesToReset.Contains(this))
|
||||
_device->QueriesToReset.Add(this);
|
||||
_resetRanges.Add(Range{ index, 1 });
|
||||
#endif
|
||||
}
|
||||
}
|
||||
_usedQueryBits[word] = _usedQueryBits[word] & ~bit;
|
||||
}
|
||||
constexpr uint64 allUsedMask = MAX_uint64;
|
||||
for (int32 wordIndex = 0; wordIndex < _usedQueryBits.Count(); wordIndex++)
|
||||
{
|
||||
if (_usedQueryBits[wordIndex] != allUsedMask)
|
||||
{
|
||||
_lastBeginIndex = wordIndex * 64;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return HasRoom();
|
||||
}
|
||||
|
||||
HelperResourcesVulkan::HelperResourcesVulkan(GPUDeviceVulkan* device)
|
||||
: _device(device)
|
||||
{
|
||||
@@ -1276,7 +1320,7 @@ int32 GPUDeviceVulkan::GetOrCreateQueryPool(GPUQueryType type)
|
||||
for (int32 i = 0; i < QueryPools.Count(); i++)
|
||||
{
|
||||
auto pool = pools[i];
|
||||
if (pool->Type == type && pool->HasRoom())
|
||||
if (pool->Type == type && (pool->HasRoom() || pool->TryExpire()))
|
||||
return i;
|
||||
}
|
||||
|
||||
|
||||
@@ -312,7 +312,9 @@ private:
|
||||
Array<uint64> _readResultsBits;
|
||||
|
||||
// Last potentially free index in the pool
|
||||
int32 _lastBeginIndex;
|
||||
int32 _lastBeginIndex = 0;
|
||||
// Last frame when pool was used
|
||||
uint64 _lastUsedFrame = 0;
|
||||
|
||||
public:
|
||||
BufferedQueryPoolVulkan(GPUDeviceVulkan* device, int32 capacity, GPUQueryType type);
|
||||
@@ -321,6 +323,7 @@ public:
|
||||
void MarkQueryAsStarted(uint32 queryIndex);
|
||||
bool GetResults(uint32 index, uint64& result);
|
||||
bool HasRoom() const;
|
||||
bool TryExpire();
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -642,7 +642,7 @@ void GPUSwapChainVulkan::Present(bool vsync)
|
||||
|
||||
// Cache a command buffer to wait on its fence before drawing to this backbuffer again
|
||||
auto& acquiredBackBuffer = _backBuffers[_acquiredImageIndex];
|
||||
ASSERT(acquiredBackBuffer.SubmitCmdBuffer == nullptr);
|
||||
ASSERT(acquiredBackBuffer.SubmitCmdBuffer == nullptr || !acquiredBackBuffer.SubmitCmdBuffer->IsSubmitted());
|
||||
acquiredBackBuffer.SubmitCmdBuffer = context->GetCmdBufferManager()->GetActiveCmdBuffer();
|
||||
|
||||
context->GetCmdBufferManager()->SubmitActiveCmdBuffer(_backBuffers[_acquiredImageIndex].RenderingDoneSemaphore);
|
||||
|
||||
@@ -17,10 +17,10 @@
|
||||
|
||||
#include <webgpu/webgpu.h>
|
||||
|
||||
// Utiltiy macro to convert WGPUStringView into UTF-16 string (on stack)
|
||||
// Utility macro to convert WGPUStringView into UTF-16 string (on stack)
|
||||
#define WEBGPU_TO_STR(strView) StringAsUTF16<>(strView.data, strView.data ? strView.length : 0).Get()
|
||||
|
||||
// Utiltiy macro to get WGPUStringView for a text constant
|
||||
// Utility macro to get WGPUStringView for a text constant
|
||||
#define WEBGPU_STR(str) { str, ARRAY_COUNT(str) - 1 }
|
||||
|
||||
#define WEBGPU_MAX_QUERY_SETS 8
|
||||
|
||||
Reference in New Issue
Block a user