Fix occlusion queries on Vulkan

This commit is contained in:
2026-08-28 19:33:14 +02:00
parent 6ccb518438
commit 846ee47003
8 changed files with 63 additions and 60 deletions
@@ -289,7 +289,7 @@ void CmdBufferManagerVulkan::SubmitActiveCmdBuffer(SemaphoreVulkan* signalSemaph
}
#else
for (int32 i = 0; i < _activeTimerQueries.Count(); i++)
queries->Interrupt(_activeCmdBuffer);
queries[i]->Interrupt(_context);
#endif
#endif
@@ -347,7 +347,7 @@ void CmdBufferManagerVulkan::GetNewActiveCommandBuffer()
#else
for (int32 i = 0; i < _activeTimerQueries.Count(); i++)
{
queries->Resume(_activeCmdBuffer);
queries[i]->Resume(_context);
}
#endif
#endif
@@ -437,6 +437,7 @@ void GPUContextVulkan::BeginRenderPass()
// Build render targets layout descriptor and framebuffer key
FramebufferVulkan::Key framebufferKey;
Platform::MemoryClear(&framebufferKey, sizeof(framebufferKey));
framebufferKey.AttachmentCount = _rtCount;
RenderTargetLayoutVulkan layout;
Platform::MemoryClear(&layout, sizeof(layout));
@@ -445,7 +446,7 @@ void GPUContextVulkan::BeginRenderPass()
layout.DepthFormat = _rtDepth ? _rtDepth->GetFormat() : PixelFormat::Unknown;
VkClearValue clearValues[GPU_MAX_RT_BINDED + 1];
PendingClear clear;
for (int32 i = 0; i < GPU_MAX_RT_BINDED; i++)
for (int32 i = 0; i < _rtCount; i++)
{
auto handle = _rtTargets[i];
if (handle)
@@ -470,11 +471,6 @@ void GPUContextVulkan::BeginRenderPass()
layout.StoreDontCare |= mask;
}
}
else
{
layout.RTVsFormats[i] = PixelFormat::Unknown;
framebufferKey.Attachments[i] = VK_NULL_HANDLE;
}
}
GPUTextureViewVulkan* handle;
if (_rtDepth)
@@ -536,6 +532,7 @@ void GPUContextVulkan::BeginRenderPass()
FlushBarriers();
cmdBuffer->BeginRenderPass(renderPass, framebuffer, ARRAY_COUNT(clearValues), clearValues);
_rtDirtyFlag = false;
}
void GPUContextVulkan::EndRenderPass()
@@ -802,7 +799,7 @@ void GPUContextVulkan::OnDrawCall()
}
// Allocate sets if need to
//if (needsWrite) // TODO: write on change only?
if (needsWrite)
{
if (!pipelineState->CurrentTypedDescriptorPoolSet->AllocateDescriptorSets(*pipelineState->DescriptorSetsLayout, pipelineState->DescriptorSetHandles.Get()))
return;
@@ -862,7 +859,6 @@ void GPUContextVulkan::OnDrawCall()
vkCmdBindDescriptorSets(cmdBuffer->GetHandle(), VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineState->GetLayout()->Handle, 0, descriptorSets.Count(), descriptorSets.Get(), dynamicOffsets.Count(), dynamicOffsets.Get());
}
_rtDirtyFlag = false;
#if VK_ENABLE_BARRIERS_DEBUG
LOG(Warning, "Draw");
#endif
@@ -904,7 +900,7 @@ void GPUContextVulkan::FrameBegin()
if (_device->QueriesToReset.HasItems())
{
for (auto query : _device->QueriesToReset)
query->Reset(cmdBuffer);
query->Reset(this);
_device->QueriesToReset.Clear();
}
#endif
@@ -1442,7 +1438,7 @@ uint64 GPUContextVulkan::BeginQuery(GPUQueryType type)
auto pool = _device->QueryPools[poolIndex];
uint32 index = 0;
const auto cmdBuffer = _cmdBufferManager->GetCmdBuffer();
if (!pool->AcquireQuery(cmdBuffer, index))
if (!pool->AcquireQuery(this, index))
return 0;
GPUQueryVulkan query;
query.PoolIndex = (uint16)poolIndex;
@@ -1455,19 +1451,21 @@ uint64 GPUContextVulkan::BeginQuery(GPUQueryType type)
{
case GPUQueryType::Timer:
// Timer queries need 2 slots (begin + end)
pool->AcquireQuery(cmdBuffer, index);
pool->AcquireQuery(this, index);
query.SecondQueryIndex = (uint16)index;
vkCmdWriteTimestamp(cmdBuffer->GetHandle(), VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, pool->GetHandle(), query.QueryIndex);
#if GPU_VULKAN_PAUSE_QUERIES
_cmdBufferManager->OnTimerQueryBegin(query.Raw);
#endif
break;
case GPUQueryType::Occlusion:
vkCmdBeginQuery(cmdBuffer->GetHandle(), pool->GetHandle(), query.QueryIndex, 0);
break;
case GPUQueryType::BinaryOcclusion:
vkCmdBeginQuery(cmdBuffer->GetHandle(), pool->GetHandle(), query.QueryIndex, VK_QUERY_CONTROL_PRECISE_BIT);
// Flush render pass when starting occlusion query
if (_rtDirtyFlag && cmdBuffer->IsInsideRenderPass())
EndRenderPass();
if (cmdBuffer->IsOutsideRenderPass())
BeginRenderPass();
vkCmdBeginQuery(cmdBuffer->GetHandle(), pool->GetHandle(), query.QueryIndex, type == GPUQueryType::Occlusion ? VK_QUERY_CONTROL_PRECISE_BIT : 0);
break;
}
pool->MarkQueryAsStarted(query.QueryIndex);
@@ -527,7 +527,7 @@ RenderPassVulkan::RenderPassVulkan(GPUDeviceVulkan* device, const RenderTargetLa
: Device(device)
, Handle(VK_NULL_HANDLE)
, Layout(layout)
, CanDepthWrite(true)
, CanDepthStencilWrite(true)
{
const int32 colorAttachmentsCount = layout.RTsCount;
const bool hasDepthStencilAttachment = layout.DepthFormat != PixelFormat::Unknown;
@@ -615,7 +615,7 @@ RenderPassVulkan::RenderPassVulkan(GPUDeviceVulkan* device, const RenderTargetLa
depthStencilReference.layout = depthStencilLayout;
subpassDesc.pDepthStencilAttachment = &depthStencilReference;
if (!layout.WriteDepth && !layout.WriteStencil)
CanDepthWrite = false;
CanDepthStencilWrite = false;
}
VkRenderPassCreateInfo createInfo;
@@ -642,7 +642,7 @@ QueryPoolVulkan::QueryPoolVulkan(GPUDeviceVulkan* device, int32 capacity, GPUQue
{
VkQueryPoolCreateInfo createInfo;
RenderToolsVulkan::ZeroStruct(createInfo, VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO);
createInfo.queryType = type != GPUQueryType::Timer ? VK_QUERY_TYPE_TIMESTAMP : VK_QUERY_TYPE_OCCLUSION;
createInfo.queryType = type == GPUQueryType::Timer ? VK_QUERY_TYPE_TIMESTAMP : VK_QUERY_TYPE_OCCLUSION;
createInfo.queryCount = capacity;
VALIDATE_VULKAN_RESULT(vkCreateQueryPool(device->Device, &createInfo, nullptr, &_handle));
@@ -663,11 +663,18 @@ QueryPoolVulkan::~QueryPoolVulkan()
#if VULKAN_RESET_QUERY_POOLS
void QueryPoolVulkan::Reset(CmdBufferVulkan* cmdBuffer)
void QueryPoolVulkan::Reset(GPUContextVulkan* context)
{
if (_resetRanges.IsEmpty())
return;
const auto cmdBuffer = context->GetCmdBufferManager()->GetCmdBuffer();
if (cmdBuffer->IsInsideRenderPass())
context->EndRenderPass(); // vkCmdResetQueryPool can be called only outside the render pass
// TODO: batch sequential reset operations
auto commandBuffer = cmdBuffer->GetHandle();
for (auto& range : _resetRanges)
{
vkCmdResetQueryPool(cmdBuffer->GetHandle(), _handle, range.Start, range.Count);
vkCmdResetQueryPool(commandBuffer, _handle, range.Start, range.Count);
}
_resetRanges.Clear();
ResetBeforeUse = false;
@@ -684,7 +691,7 @@ BufferedQueryPoolVulkan::BufferedQueryPoolVulkan(GPUDeviceVulkan* device, int32
_readResultsBits.AddZeroed((capacity + 63) / 64);
}
bool BufferedQueryPoolVulkan::AcquireQuery(CmdBufferVulkan* cmdBuffer, uint32& resultIndex)
bool BufferedQueryPoolVulkan::AcquireQuery(GPUContextVulkan* context, uint32& resultIndex)
{
const uint64 allUsedMask = MAX_uint64;
for (int32 wordIndex = _lastBeginIndex / 64; wordIndex < _usedQueryBits.Count(); wordIndex++)
@@ -705,7 +712,7 @@ bool BufferedQueryPoolVulkan::AcquireQuery(CmdBufferVulkan* cmdBuffer, uint32& r
_readResultsBits[wordIndex] &= ~bit;
_lastBeginIndex = resultIndex + 1;
if (ResetBeforeUse)
Reset(cmdBuffer);
Reset(context);
_lastUsedFrame = Engine::FrameCount;
return true;
}
@@ -757,6 +764,7 @@ bool BufferedQueryPoolVulkan::GetResults(uint32 index, uint64& result)
if ((_startedQueryBits[word] & bit) == 0)
{
// Query never started/ended
result = 0;
return true;
}
@@ -809,27 +817,23 @@ bool BufferedQueryPoolVulkan::TryExpire()
return false;
// Reset back to clean state all unfinished queries
for (uint32 index = 0; index < _queryOutput.Count(); index++)
bool canUseNow = true;
for (uint32 index = 0; index < (uint32)_queryOutput.Count(); index++)
{
const uint64 bit = (uint64)1 << (index % 64);
const uint32 word = index / 64;
if ((_usedQueryBits[word] & bit) == 0)
continue;
#if VULKAN_RESET_QUERY_POOLS
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
}
// Add to reset
if (!_device->QueriesToReset.Contains(this))
_device->QueriesToReset.Add(this);
_resetRanges.Add(Range{ index, 1 });
canUseNow = false;
}
#endif
_usedQueryBits[word] = _usedQueryBits[word] & ~bit;
}
constexpr uint64 allUsedMask = MAX_uint64;
@@ -842,7 +846,7 @@ bool BufferedQueryPoolVulkan::TryExpire()
}
}
return HasRoom();
return canUseNow && HasRoom();
}
HelperResourcesVulkan::HelperResourcesVulkan(GPUDeviceVulkan* device)
@@ -260,7 +260,7 @@ public:
GPUDeviceVulkan* Device;
VkRenderPass Handle;
RenderTargetLayoutVulkan Layout;
bool CanDepthWrite;
bool CanDepthStencilWrite;
#if VULKAN_USE_DEBUG_DATA
VkRenderPassCreateInfo DebugCreateInfo;
#endif
@@ -299,7 +299,7 @@ public:
#if VULKAN_RESET_QUERY_POOLS
bool ResetBeforeUse;
void Reset(CmdBufferVulkan* cmdBuffer);
void Reset(GPUContextVulkan* context);
#endif
};
@@ -318,7 +318,7 @@ private:
public:
BufferedQueryPoolVulkan(GPUDeviceVulkan* device, int32 capacity, GPUQueryType type);
bool AcquireQuery(CmdBufferVulkan* cmdBuffer, uint32& resultIndex);
bool AcquireQuery(GPUContextVulkan* context, uint32& resultIndex);
void ReleaseQuery(uint32 queryIndex);
void MarkQueryAsStarted(uint32 queryIndex);
bool GetResults(uint32 index, uint64& result);
@@ -315,10 +315,13 @@ VkPipeline GPUPipelineStateVulkan::GetState(RenderPassVulkan* renderPass, GPUVer
// Create object
auto depthWrite = _descDepthStencil.depthWriteEnable;
_descDepthStencil.depthWriteEnable &= renderPass->CanDepthWrite ? 1 : 0;
auto stencilWriteMask = _descDepthStencil.front.writeMask;
_descDepthStencil.depthWriteEnable &= renderPass->CanDepthStencilWrite ? 1 : 0;
_descDepthStencil.front.writeMask &= renderPass->CanDepthStencilWrite ? MAX_uint32 : 0;
const VkResult result = vkCreateGraphicsPipelines(_device->Device, _device->PipelineCache, 1, &_desc, nullptr, &pipeline);
_device->PipelineCacheUsage++;
_descDepthStencil.depthWriteEnable = depthWrite;
_descDepthStencil.front.writeMask = _descDepthStencil.back.writeMask = stencilWriteMask;
LOG_VULKAN_RESULT(result);
if (result != VK_SUCCESS)
{
@@ -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 || !acquiredBackBuffer.SubmitCmdBuffer->IsSubmitted());
ASSERT(acquiredBackBuffer.SubmitCmdBuffer == nullptr || acquiredBackBuffer.SubmitCmdBuffer->IsSubmitted());
acquiredBackBuffer.SubmitCmdBuffer = context->GetCmdBufferManager()->GetActiveCmdBuffer();
context->GetCmdBufferManager()->SubmitActiveCmdBuffer(_backBuffers[_acquiredImageIndex].RenderingDoneSemaphore);
@@ -83,16 +83,16 @@ float GPUTimerQueryVulkan::GetResult()
#else
void GPUTimerQueryVulkan::Interrupt(CmdBufferVulkan* cmdBuffer)
void GPUTimerQueryVulkan::Interrupt(GPUContextVulkan* context)
{
if (!_interrupted)
{
_interrupted = true;
WriteTimestamp(cmdBuffer, _queries[_queryIndex].End, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT);
WriteTimestamp(context, _queries[_queryIndex].End, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT);
}
}
void GPUTimerQueryVulkan::Resume(CmdBufferVulkan* cmdBuffer)
void GPUTimerQueryVulkan::Resume(GPUContextVulkan* context)
{
ASSERT(_interrupted);
@@ -100,7 +100,7 @@ void GPUTimerQueryVulkan::Resume(CmdBufferVulkan* cmdBuffer)
e.End.Pool = nullptr;
_interrupted = false;
WriteTimestamp(cmdBuffer, e.Begin, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT);
WriteTimestamp(context, e.Begin, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT);
_queries.Add(e);
_queryIndex++;
@@ -127,13 +127,13 @@ bool GPUTimerQueryVulkan::GetResult(Query& query)
return false;
}
void GPUTimerQueryVulkan::WriteTimestamp(CmdBufferVulkan* cmdBuffer, Query& query, VkPipelineStageFlagBits stage) const
void GPUTimerQueryVulkan::WriteTimestamp(GPUContextVulkan* context, Query& query, VkPipelineStageFlagBits stage) const
{
auto pool = _device->QueryPools[_device->GetOrCreateQueryPool(GPUQueryType::Timer)];
uint32 index;
if (pool->AcquireQuery(cmdBuffer, index))
if (pool->AcquireQuery(context, index))
{
vkCmdWriteTimestamp(cmdBuffer->GetHandle(), stage, pool->GetHandle(), index);
vkCmdWriteTimestamp(context->GetCmdBufferManager()->GetCmdBuffer()->GetHandle(), stage, pool->GetHandle(), index);
pool->MarkQueryAsStarted(index);
query.Pool = pool;
query.Index = index;
@@ -215,14 +215,13 @@ void GPUTimerQueryVulkan::Begin()
if (UseQueries())
{
const auto context = (GPUContextVulkan*)_device->GetMainContext();
const auto cmdBuffer = context->GetCmdBufferManager()->GetCmdBuffer();
QueryPair e;
e.End.Pool = nullptr;
_queryIndex = 0;
_interrupted = false;
WriteTimestamp(cmdBuffer, e.Begin, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT);
WriteTimestamp(context, e.Begin, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT);
context->GetCmdBufferManager()->OnTimerQueryBegin(this);
ASSERT(_queries.IsEmpty());
@@ -241,11 +240,10 @@ void GPUTimerQueryVulkan::End()
if (UseQueries())
{
const auto context = (GPUContextVulkan*)_device->GetMainContext();
const auto cmdBuffer = context->GetCmdBufferManager()->GetCmdBuffer();
if (!_interrupted)
{
WriteTimestamp(cmdBuffer, _queries[_queryIndex].End, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT);
WriteTimestamp(context, _queries[_queryIndex].End, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT);
}
context->GetCmdBufferManager()->OnTimerQueryEnd(this);
}
@@ -51,20 +51,20 @@ public:
#if !GPU_VULKAN_QUERY_NEW
public:
/// <summary>
/// Interrupts an in-progress query, allowing the command buffer to submitted. Interrupted queries must be resumed using Resume().
/// Interrupts an in-progress query, allowing the command buffer to submit. Interrupted queries must be resumed using Resume().
/// </summary>
/// <param name="cmdBuffer">The GPU commands buffer.</param>
void Interrupt(CmdBufferVulkan* cmdBuffer);
/// <param name="context">The GPU context.</param>
void Interrupt(GPUContextVulkan* context);
/// <summary>
/// Resumes an interrupted query, restoring it back to its original in-progress state.
/// </summary>
/// <param name="cmdBuffer">The GPU commands buffer.</param>
void Resume(CmdBufferVulkan* cmdBuffer);
/// <param name="context">The GPU context.</param>
void Resume(GPUContextVulkan* context);
private:
bool GetResult(Query& query);
void WriteTimestamp(CmdBufferVulkan* cmdBuffer, Query& query, VkPipelineStageFlagBits stage) const;
void WriteTimestamp(GPUContextVulkan* context, Query& query, VkPipelineStageFlagBits stage) const;
bool TryGetResult();
bool UseQueries();
#endif