[Feat] (MG_Impl, MG_Backend): GL_ARB_timer_query on both backends

Implements GL timer queries end to end: a frontend query registry
(modeled on the sync module - mutex-guarded objects wrapping opaque
backend handles behind optional function pointers) serving
glGenQueries/glBeginQuery/glEndQuery(GL_TIME_ELAPSED)/glQueryCounter
(GL_TIMESTAMP)/glGetQueryObject*/glGetQueryiv with GL 3.3 error
semantics and a graceful zero-result fallback when a backend cannot
time.

DirectGLES backs spans with GL_EXT_disjoint_timer_query (context-
generation-stamped handles, bounded result waits). DirectVulkan gets a
VkTimerQueryManager: per-frame-in-flight timestamp query pools reset at
command-buffer begin (outside render passes), records harvested by
frame serial before their pool recycles, elapsed = masked tick delta x
timestampPeriod; handles are stamped with a renderer generation that
also now guards fence syncs across renderer recreation. GL_QUERY_
COUNTER_BITS reports 0 unless the live backend can actually time
(dynamic IsTimerQuerySupported hook), and a failed blocking read keeps
the handle alive so the real value stays reachable once the frame
submits.

GL_ARB_timer_query is advertised only when the device supports timing
and MOBILEGL_DISABLE_TIMERQUERY is unset - LWJGL keys Minecraft's F3
'GPU: x%' line off exactly that extension string; verified on device
(Adreno 830) on both backends.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-10 01:08:38 +00:00
co-authored by Claude Fable 5
parent 7d31a6fcd7
commit 7eb3994b02
25 changed files with 1917 additions and 91 deletions
@@ -20,6 +20,7 @@
#include "VkRenderPassManager.h"
#include "VkSamplerManager.h"
#include "VkTextureManager.h"
#include "VkTimerQueryManager.h"
#include "MG_Util/Math/VectorTypes.h"
#include <Includes.h>
#include <vk_mem_alloc.h>
@@ -101,7 +102,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
};
class VulkanRenderer : public IBufferCopyCommandProvider {
class VulkanRenderer : public IBufferCopyCommandProvider, public FrameContext::IRecordingObserver {
public:
VulkanRenderer(NativeWindowType window, const VulkanRendererConfig& cfg = {});
~VulkanRenderer();
@@ -113,6 +114,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// render pass, for immediate staged buffer copies.
VkCommandBuffer AcquireBufferCopyCommandBuffer() override;
// FrameContext::IRecordingObserver: prepares the frame's timer-query
// pool (harvest + reset) right after the frame command buffer begins
// recording, before any render pass.
void OnFrameCommandRecordingBegan(VkCommandBuffer commandBuffer) override;
Bool SetupDraw(FrameContext::FrameData& frame, GLenum mode, Flags<DrawSetupAspect> aspects,
const DrawCmdParam& drawParams,
const IndexBufferView* pIndexBufferView = nullptr);
@@ -178,6 +184,25 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// current, not-yet-presented frame) or when the wait failed.
Bool WaitForFrameSerial(Uint64 serial, Uint64 timeoutNs);
// GPU timer queries, backing the GL_TIME_ELAPSED / GL_TIMESTAMP
// frontend. Timestamp support (queue timestampValidBits > 0 and a
// non-zero timestampPeriod) is cached at device creation.
Bool IsTimerQuerySupported() const;
// Ensures the frame command buffer is recording (same lazy pattern as
// SetupDraw) and writes a bottom-of-pipe timestamp into the current
// frame's pool. Null when unsupported or the pool is exhausted.
SharedPtr<VkTimerQueryManager::TimestampRecord> WriteTimerQueryTimestamp();
// Non-blocking: true once the record's raw ticks are on the CPU
// (harvests the slot once its frame serial has completed).
Bool IsTimerQueryResultReady(VkTimerQueryManager::TimestampRecord& record);
// Blocking wait, mirroring ClientWaitSync's caveat: a record written
// this frame cannot complete until Present submits the commands, so
// this returns false (result reads as 0) instead of deadlocking.
Bool WaitForTimerQueryResult(VkTimerQueryManager::TimestampRecord& record);
Uint64 GetTimerQueryElapsedNs(const VkTimerQueryManager::TimestampRecord& begin,
const VkTimerQueryManager::TimestampRecord& end) const;
Uint64 GetTimerQueryTimestampNs(const VkTimerQueryManager::TimestampRecord& record) const;
void RequestSwapchainResize(Uint32 width, Uint32 height);
void RecreateSwapchain();
@@ -246,6 +271,11 @@ namespace MobileGL::MG_Backend::DirectVulkan {
Bool m_multiDrawIndirectFeatureEnabled = false;
Bool m_shaderDrawParametersExtensionEnabled = false;
Bool m_shaderDrawParametersFeatureEnabled = false;
// Cached at device creation from the graphics queue family properties
// and device limits; drives timer-query support.
Uint32 m_timestampValidBits = 0;
Float m_timestampPeriodNs = 0.0f;
Bool m_timerQuerySupported = false;
using PFNDrawIndexedIndirectCountFunc = void(VKAPI_PTR*)(VkCommandBuffer commandBuffer, VkBuffer buffer,
VkDeviceSize offset, VkBuffer countBuffer,
VkDeviceSize countBufferOffset, Uint32 maxDrawCount,
@@ -268,6 +298,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
UniquePtr<VkRenderPassManager> m_renderPassManager;
UniquePtr<VkTextureManager> m_textureManager;
UniquePtr<VkSamplerManager> m_samplerManager;
UniquePtr<VkTimerQueryManager> m_timerQueryManager;
BlitResources m_blitResources;
DepthMipmapResources m_depthMipmapResources;
Vector<DeferredDepthMipmapCleanup> m_deferredDepthMipmapCleanup;