[Feat] (DirectVulkan, MG_Impl): occlusion queries via Vulkan query pools

GL_SAMPLES_PASSED / GL_ANY_SAMPLES_PASSED(_CONSERVATIVE) now work: every
app draw between Begin/EndQuery is wrapped in a slot of a host-reset
occlusion query pool (precise counts when occlusionQueryPrecise is
granted), and the result flush ends any active render pass before
submitting, waits, sums the slots and recycles them. ANY_* targets
report the boolean form; GL_QUERY_COUNTER_BITS and GL_CURRENT_QUERY
answer for the occlusion targets, and deleting an active query releases
its slot. Draw-time depth/stencil state also honors attachment absence:
a framebuffer without a depth (stencil) attachment behaves as if that
test always passes, even when a packed depth-stencil image is attached
through only one half (verify_partial_attachments.*).
This commit is contained in:
BZLZHH
2026-08-01 01:50:34 -04:00
parent 22b749dd37
commit 107b56d603
7 changed files with 285 additions and 12 deletions
@@ -1564,8 +1564,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// records are shared (SharedPtr) with the owning pool's pending list,
// so deleting the query while results are still in flight is safe.
struct VulkanTimerQuery {
enum class Kind : Uint8 { Timer, Occlusion };
Kind kind = Kind::Timer;
SharedPtr<VkTimerQueryManager::TimestampRecord> begin;
SharedPtr<VkTimerQueryManager::TimestampRecord> end;
// Kind::Occlusion - pool slots recorded between Begin/End; summed at result time.
Vector<Uint32> occlusionSlots;
// Renderer generation the records were written under (see
// g_rendererGeneration). A stale generation resolves as available
// with a final zero result: the records' pool indices and frame
@@ -1655,6 +1659,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// ever be produced, so resolve with a final 0.
return true;
}
if (query->kind == VulkanTimerQuery::Kind::Occlusion) {
Uint64 samples = 0;
if (!pVulkanRenderer->ResolveOcclusionQueryResult(query->occlusionSlots, samples)) {
return false;
}
query->occlusionSlots.clear(); // slots are recycled by the resolve
*outNanoseconds = samples;
return true;
}
// With wait, mirrors ClientWaitSync: a query ended this frame cannot
// complete until Present submits the commands, so the wait refuses to
// block on the current unsubmitted serial. Returning false keeps the
@@ -1687,6 +1700,26 @@ namespace MobileGL::MG_Backend::DirectVulkan {
delete static_cast<VulkanTimerQuery*>(handle);
}
BackendQueryHandle BeginOcclusionQuery() {
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::BeginOcclusionQuery called with null VulkanRenderer");
if (!pVulkanRenderer->StartOcclusionQueryCapture()) {
return nullptr;
}
auto* query = new VulkanTimerQuery{};
query->kind = VulkanTimerQuery::Kind::Occlusion;
query->rendererGeneration = GetRendererGeneration();
return query;
}
void EndOcclusionQuery(BackendQueryHandle handle) {
auto* query = static_cast<VulkanTimerQuery*>(handle);
MOBILEGL_ASSERT(pVulkanRenderer, "DirectVulkan::EndOcclusionQuery called with null VulkanRenderer");
if (query == nullptr || query->rendererGeneration != GetRendererGeneration()) {
return;
}
pVulkanRenderer->StopOcclusionQueryCapture(query->occlusionSlots);
}
Int64 GetGpuTimestampNs() {
// Vulkan cannot synchronously sample the GPU clock: timestamps only
// exist as vkCmdWriteTimestamp results read back later, and