mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-08 04:08:32 +09:00
[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:
@@ -220,6 +220,11 @@ namespace MobileGL {
|
||||
// and leave the query readable later.
|
||||
Bool (*GetQueryResult64)(BackendQueryHandle query, Bool wait, Uint64* outNanoseconds);
|
||||
void (*DeleteBackendQuery)(BackendQueryHandle query);
|
||||
// GL_SAMPLES_PASSED occlusion queries (optional; null = unsupported,
|
||||
// the frontend then rejects the target). Results/deletion flow through
|
||||
// GetQueryResult64 / DeleteBackendQuery like timer queries.
|
||||
BackendQueryHandle (*BeginOcclusionQuery)();
|
||||
void (*EndOcclusionQuery)(BackendQueryHandle query);
|
||||
Int64 (*GetGpuTimestampNs)(); // glGetInteger64v(GL_TIMESTAMP); 0 if unsupported
|
||||
};
|
||||
struct GlobalBackendFunctionsTable {
|
||||
|
||||
@@ -635,6 +635,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
funcsTable.GL.DeleteBackendQuery = DeleteBackendQuery;
|
||||
funcsTable.GL.GetGpuTimestampNs = GetGpuTimestampNs;
|
||||
}
|
||||
// Occlusion queries share the handle-based result/delete entries, which must
|
||||
// exist even when timer queries are disabled.
|
||||
funcsTable.GL.BeginOcclusionQuery = BeginOcclusionQuery;
|
||||
funcsTable.GL.EndOcclusionQuery = EndOcclusionQuery;
|
||||
funcsTable.GL.IsQueryResultAvailable = IsQueryResultAvailable;
|
||||
funcsTable.GL.GetQueryResult64 = GetQueryResult64;
|
||||
funcsTable.GL.DeleteBackendQuery = DeleteBackendQuery;
|
||||
funcsTableInitialized = true;
|
||||
}
|
||||
return funcsTable;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -123,6 +123,8 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// only while a live renderer exists whose device can actually time.
|
||||
Bool IsTimerQuerySupported();
|
||||
BackendQueryHandle BeginTimeElapsedQuery();
|
||||
BackendQueryHandle BeginOcclusionQuery();
|
||||
void EndOcclusionQuery(BackendQueryHandle query);
|
||||
void EndTimeElapsedQuery(BackendQueryHandle query);
|
||||
BackendQueryHandle QueryCounterTimestamp();
|
||||
Bool IsQueryResultAvailable(BackendQueryHandle query);
|
||||
|
||||
@@ -2782,6 +2782,10 @@ void main() {
|
||||
}
|
||||
m_vertexInputStateFactory.reset();
|
||||
m_xfbCounterBuffer.Destroy();
|
||||
if (m_occlusionQueryPool != VK_NULL_HANDLE) {
|
||||
vkDestroyQueryPool(m_device, m_occlusionQueryPool, nullptr);
|
||||
m_occlusionQueryPool = VK_NULL_HANDLE;
|
||||
}
|
||||
m_bufferManager.Shutdown();
|
||||
|
||||
// Device is idle (vkDeviceWaitIdle above); query pools can be destroyed.
|
||||
@@ -4059,6 +4063,23 @@ void main() {
|
||||
auto colorLogicOpEnabled =
|
||||
MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::ColorLogicOp) && m_logicOpFeatureEnabled;
|
||||
auto stencilTestEnabled = MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::StencilTest);
|
||||
// A framebuffer without a depth (stencil) attachment behaves as if the depth
|
||||
// (stencil) test always passes and nothing is written - even when the bound
|
||||
// image is a packed depth-stencil texture attached through only one half.
|
||||
{
|
||||
const auto& gatingFbo =
|
||||
MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Draw).GetBoundObject();
|
||||
if (gatingFbo != nullptr && !gatingFbo->IsDefaultFramebuffer()) {
|
||||
const auto& depthAtt = gatingFbo->GetAttachment(MobileGL::FramebufferAttachmentType::Depth);
|
||||
const auto& stencilAtt = gatingFbo->GetAttachment(MobileGL::FramebufferAttachmentType::Stencil);
|
||||
if (!depthAtt.IsValid() || depthAtt.IsEmpty()) {
|
||||
depthTestEnabled = false;
|
||||
}
|
||||
if (!stencilAtt.IsValid() || stencilAtt.IsEmpty()) {
|
||||
stencilTestEnabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
const StencilFaceState& frontStencil = MG_State::pGLContext->GetStencilState(StencilFace::Front);
|
||||
const StencilFaceState& backStencil = MG_State::pGLContext->GetStencilState(StencilFace::Back);
|
||||
const VkPolygonMode requestedPolygonMode =
|
||||
@@ -7767,14 +7788,95 @@ void main() {
|
||||
VkCommandBuffer& commandBuffer = frame.commandBuffer;
|
||||
|
||||
const Bool xfbActive = BeginXfbCaptureForDraw(frame);
|
||||
const Bool occlusionActive = BeginOcclusionForDraw(commandBuffer);
|
||||
vkCmdDraw(commandBuffer,
|
||||
payload.params.vertexCount,
|
||||
payload.params.instanceCount,
|
||||
payload.params.firstVertex,
|
||||
payload.params.firstInstance);
|
||||
EndOcclusionForDraw(commandBuffer, occlusionActive);
|
||||
EndXfbCaptureForDraw(frame, xfbActive);
|
||||
}
|
||||
|
||||
Bool VulkanRenderer::StartOcclusionQueryCapture() {
|
||||
if (!m_hostQueryResetEnabled || s_vkResetQueryPool == nullptr) {
|
||||
return false;
|
||||
}
|
||||
if (m_occlusionQueryPool == VK_NULL_HANDLE) {
|
||||
VkQueryPoolCreateInfo poolInfo{};
|
||||
poolInfo.sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO;
|
||||
poolInfo.queryType = VK_QUERY_TYPE_OCCLUSION;
|
||||
poolInfo.queryCount = kOcclusionQuerySlots;
|
||||
if (vkCreateQueryPool(m_device, &poolInfo, nullptr, &m_occlusionQueryPool) != VK_SUCCESS) {
|
||||
MGLOG_E("StartOcclusionQueryCapture: vkCreateQueryPool failed");
|
||||
m_occlusionQueryPool = VK_NULL_HANDLE;
|
||||
return false;
|
||||
}
|
||||
s_vkResetQueryPool(m_device, m_occlusionQueryPool, 0, kOcclusionQuerySlots);
|
||||
}
|
||||
m_occlusionActiveSlots.clear();
|
||||
m_occlusionCaptureActive = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void VulkanRenderer::StopOcclusionQueryCapture(Vector<Uint32>& outSlots) {
|
||||
outSlots = Move(m_occlusionActiveSlots);
|
||||
m_occlusionActiveSlots.clear();
|
||||
m_occlusionCaptureActive = false;
|
||||
}
|
||||
|
||||
Bool VulkanRenderer::ResolveOcclusionQueryResult(const Vector<Uint32>& slots, Uint64& outSamples) {
|
||||
outSamples = 0;
|
||||
if (slots.empty()) {
|
||||
return true;
|
||||
}
|
||||
if (m_occlusionQueryPool == VK_NULL_HANDLE) {
|
||||
return true;
|
||||
}
|
||||
auto& frame = m_frameContext.GetCurrent();
|
||||
if (frame.isCommandRecording) {
|
||||
if (VkRenderPassManager::GetActiveRenderPass() != nullptr) {
|
||||
VkRenderPassManager::EndRenderPass(frame.commandBuffer);
|
||||
}
|
||||
if (!SubmitReadbackCommandsAndWait(frame)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const Uint32 slot : slots) {
|
||||
Uint64 value = 0;
|
||||
const VkResult result =
|
||||
vkGetQueryPoolResults(m_device, m_occlusionQueryPool, slot, 1, sizeof(value), &value, sizeof(value),
|
||||
VK_QUERY_RESULT_64_BIT | VK_QUERY_RESULT_WAIT_BIT);
|
||||
if (result == VK_SUCCESS) {
|
||||
outSamples += value;
|
||||
}
|
||||
s_vkResetQueryPool(m_device, m_occlusionQueryPool, slot, 1);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool VulkanRenderer::BeginOcclusionForDraw(VkCommandBuffer commandBuffer) {
|
||||
if (!m_occlusionCaptureActive || m_occlusionQueryPool == VK_NULL_HANDLE) {
|
||||
return false;
|
||||
}
|
||||
const Uint32 slot = m_occlusionSlotCursor;
|
||||
m_occlusionSlotCursor = (m_occlusionSlotCursor + 1) % kOcclusionQuerySlots;
|
||||
// Slots recycle after their read; a wrapped-past unread slot is stale, so
|
||||
// reset it here (host reset - the slot's prior GPU use has long retired).
|
||||
s_vkResetQueryPool(m_device, m_occlusionQueryPool, slot, 1);
|
||||
vkCmdBeginQuery(commandBuffer, m_occlusionQueryPool, slot,
|
||||
m_occlusionQueryPreciseEnabled ? VK_QUERY_CONTROL_PRECISE_BIT : 0);
|
||||
m_occlusionActiveSlots.push_back(slot);
|
||||
return true;
|
||||
}
|
||||
|
||||
void VulkanRenderer::EndOcclusionForDraw(VkCommandBuffer commandBuffer, Bool began) {
|
||||
if (!began) {
|
||||
return;
|
||||
}
|
||||
vkCmdEndQuery(commandBuffer, m_occlusionQueryPool, m_occlusionActiveSlots.back());
|
||||
}
|
||||
|
||||
void VulkanRenderer::DrawElements(const DrawIndexedCmd& payload) {
|
||||
auto& frame = m_frameContext.GetCurrent();
|
||||
|
||||
@@ -7800,12 +7902,14 @@ void main() {
|
||||
VkCommandBuffer& commandBuffer = frame.commandBuffer;
|
||||
|
||||
const Bool xfbActive = BeginXfbCaptureForDraw(frame);
|
||||
const Bool occlusionActive = BeginOcclusionForDraw(commandBuffer);
|
||||
vkCmdDrawIndexed(commandBuffer,
|
||||
payload.params.indexCount,
|
||||
payload.params.instanceCount,
|
||||
payload.params.firstIndex,
|
||||
payload.params.vertexOffset,
|
||||
payload.params.firstInstance);
|
||||
EndOcclusionForDraw(commandBuffer, occlusionActive);
|
||||
EndXfbCaptureForDraw(frame, xfbActive);
|
||||
}
|
||||
|
||||
@@ -9163,6 +9267,10 @@ void main() {
|
||||
// to isotropic filtering (and the extension goes unadvertised) when the device lacks it.
|
||||
deviceFeatures.samplerAnisotropy = supportedDeviceFeatures.samplerAnisotropy;
|
||||
m_samplerAnisotropyFeatureEnabled = deviceFeatures.samplerAnisotropy == VK_TRUE;
|
||||
// GL_SAMPLES_PASSED needs exact sample counts; without the feature the boolean
|
||||
// occlusion result still satisfies any-samples-style consumers.
|
||||
deviceFeatures.occlusionQueryPrecise = supportedDeviceFeatures.occlusionQueryPrecise;
|
||||
m_occlusionQueryPreciseEnabled = deviceFeatures.occlusionQueryPrecise == VK_TRUE;
|
||||
|
||||
VkDeviceCreateInfo deviceCreateInfo{};
|
||||
deviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
|
||||
@@ -9309,6 +9417,27 @@ void main() {
|
||||
MGLOG_W("VK_EXT_transform_feedback is unavailable; transform feedback capture will not work");
|
||||
}
|
||||
|
||||
// Host query reset lets the occlusion-query ring recycle slots without a
|
||||
// command-buffer round trip.
|
||||
m_hostQueryResetEnabled = false;
|
||||
VkPhysicalDeviceHostQueryResetFeatures hostQueryResetFeatures{};
|
||||
hostQueryResetFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_HOST_QUERY_RESET_FEATURES;
|
||||
if (IsExtensionSupported(availableExtensions, VK_EXT_HOST_QUERY_RESET_EXTENSION_NAME) &&
|
||||
getPhysicalDeviceFeatures2 != nullptr) {
|
||||
VkPhysicalDeviceFeatures2 featureQuery{};
|
||||
featureQuery.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
|
||||
featureQuery.pNext = &hostQueryResetFeatures;
|
||||
getPhysicalDeviceFeatures2(m_physicalDevice.handle, &featureQuery);
|
||||
if (hostQueryResetFeatures.hostQueryReset == VK_TRUE) {
|
||||
if (!IsExtensionAlreadyEnabled(enabledDeviceExtensions, VK_EXT_HOST_QUERY_RESET_EXTENSION_NAME)) {
|
||||
enabledDeviceExtensions.push_back(VK_EXT_HOST_QUERY_RESET_EXTENSION_NAME);
|
||||
}
|
||||
hostQueryResetFeatures.pNext = const_cast<void*>(deviceCreateInfo.pNext);
|
||||
deviceCreateInfo.pNext = &hostQueryResetFeatures;
|
||||
m_hostQueryResetEnabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
deviceCreateInfo.enabledExtensionCount = static_cast<Uint32>(enabledDeviceExtensions.size());
|
||||
deviceCreateInfo.ppEnabledExtensionNames = enabledDeviceExtensions.data();
|
||||
MGLOG_I("Device feature support: robustBufferAccess=%s geometryShader=%s independentBlend=%s logicOp=%s shaderClipDistance=%s "
|
||||
@@ -9378,6 +9507,17 @@ void main() {
|
||||
m_transformFeedbackFeatureEnabled = false;
|
||||
}
|
||||
}
|
||||
if (m_hostQueryResetEnabled) {
|
||||
s_vkResetQueryPool =
|
||||
reinterpret_cast<PFN_vkResetQueryPool>(vkGetDeviceProcAddr(m_device, "vkResetQueryPool"));
|
||||
if (s_vkResetQueryPool == nullptr) {
|
||||
s_vkResetQueryPool =
|
||||
reinterpret_cast<PFN_vkResetQueryPool>(vkGetDeviceProcAddr(m_device, "vkResetQueryPoolEXT"));
|
||||
}
|
||||
if (s_vkResetQueryPool == nullptr) {
|
||||
m_hostQueryResetEnabled = false;
|
||||
}
|
||||
}
|
||||
MGLOG_I("index type uint8 enabled: %s", m_indexTypeUint8ExtensionEnabled ? "true" : "false");
|
||||
MGLOG_I("Logical device created.");
|
||||
|
||||
|
||||
@@ -296,6 +296,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
const VkTimerQueryManager::TimestampRecord& end) const;
|
||||
Uint64 GetTimerQueryTimestampNs(const VkTimerQueryManager::TimestampRecord& record) const;
|
||||
|
||||
// GL_SAMPLES_PASSED occlusion queries: every app draw between Start and Stop is
|
||||
// wrapped in a Vulkan occlusion query slot; the result is the slot sum. Requires
|
||||
// hostQueryReset for slot recycling - Start fails (frontend keeps the query
|
||||
// unsupported) when the device lacks it.
|
||||
Bool StartOcclusionQueryCapture();
|
||||
void StopOcclusionQueryCapture(Vector<Uint32>& outSlots);
|
||||
// Flushes pending commands, waits, sums the slots, and recycles them.
|
||||
Bool ResolveOcclusionQueryResult(const Vector<Uint32>& slots, Uint64& outSamples);
|
||||
|
||||
void RequestSwapchainResize(Uint32 width, Uint32 height);
|
||||
// Re-query the surface and report whether the live swapchain no longer matches it
|
||||
// (size or orientation). This - not a VK_SUBOPTIMAL_KHR result - is what decides a
|
||||
@@ -482,6 +491,18 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
||||
// when GL transform feedback is active; binds capture buffers on demand.
|
||||
Bool BeginXfbCaptureForDraw(FrameContext::FrameData& frame);
|
||||
void EndXfbCaptureForDraw(FrameContext::FrameData& frame, Bool began);
|
||||
// Wrap one app draw in an occlusion-query slot while a GL_SAMPLES_PASSED
|
||||
// query is active. Returns whether a slot was begun (End must mirror it).
|
||||
Bool BeginOcclusionForDraw(VkCommandBuffer commandBuffer);
|
||||
void EndOcclusionForDraw(VkCommandBuffer commandBuffer, Bool began);
|
||||
Bool m_occlusionQueryPreciseEnabled = false;
|
||||
Bool m_hostQueryResetEnabled = false;
|
||||
PFN_vkResetQueryPool s_vkResetQueryPool = nullptr;
|
||||
VkQueryPool m_occlusionQueryPool = VK_NULL_HANDLE;
|
||||
static constexpr Uint32 kOcclusionQuerySlots = 8192;
|
||||
Uint32 m_occlusionSlotCursor = 0;
|
||||
Bool m_occlusionCaptureActive = false;
|
||||
Vector<Uint32> m_occlusionActiveSlots;
|
||||
|
||||
VkCommandPool m_commandPool = VK_NULL_HANDLE;
|
||||
|
||||
|
||||
@@ -46,6 +46,8 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
// Ids of the queries active on the transform feedback targets (0 = none).
|
||||
GLuint g_activePrimitivesWrittenQueryId = 0;
|
||||
GLuint g_activePrimitivesGeneratedQueryId = 0;
|
||||
// Id of the query active on GL_SAMPLES_PASSED (0 = none).
|
||||
GLuint g_activeSamplesPassedQueryId = 0;
|
||||
|
||||
Bool TimerQueryDisabled() {
|
||||
return MG_Config::Features.DisableTimerQuery;
|
||||
@@ -131,6 +133,11 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
outValue = 0;
|
||||
return true;
|
||||
}
|
||||
// ANY_SAMPLES_PASSED* report a boolean.
|
||||
if (queryObject->target == GL_ANY_SAMPLES_PASSED ||
|
||||
queryObject->target == GL_ANY_SAMPLES_PASSED_CONSERVATIVE) {
|
||||
result = result != 0 ? 1 : 0;
|
||||
}
|
||||
// Final value produced (or no GetQueryResult64 hook: the
|
||||
// query degrades to a zero result); the backend handle is
|
||||
// consumed and the value cached for later reads.
|
||||
@@ -185,7 +192,24 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
}
|
||||
QueryObject* queryObject = it->second;
|
||||
if (queryObject->active) {
|
||||
EndTimeElapsedQueryLocked(queryObject); // implicitly end before deletion
|
||||
// Implicitly end before deletion, releasing the matching active slot.
|
||||
if (queryObject->target == GL_SAMPLES_PASSED || queryObject->target == GL_ANY_SAMPLES_PASSED ||
|
||||
queryObject->target == GL_ANY_SAMPLES_PASSED_CONSERVATIVE) {
|
||||
if (const auto endOcclusionQuery = MG_Backend::gBackendFunctionsTable.GL.EndOcclusionQuery;
|
||||
endOcclusionQuery && queryObject->backendHandle) {
|
||||
endOcclusionQuery(queryObject->backendHandle);
|
||||
}
|
||||
queryObject->active = false;
|
||||
g_activeSamplesPassedQueryId = 0;
|
||||
} else if (queryObject->target == GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN ||
|
||||
queryObject->target == GL_PRIMITIVES_GENERATED) {
|
||||
queryObject->active = false;
|
||||
(queryObject->target == GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN
|
||||
? g_activePrimitivesWrittenQueryId
|
||||
: g_activePrimitivesGeneratedQueryId) = 0;
|
||||
} else {
|
||||
EndTimeElapsedQueryLocked(queryObject);
|
||||
}
|
||||
}
|
||||
if (queryObject->backendHandle) {
|
||||
if (const auto deleteBackendQuery = MG_Backend::gBackendFunctionsTable.GL.DeleteBackendQuery) {
|
||||
@@ -211,10 +235,13 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
void BeginQuery(GLenum target, GLuint id) {
|
||||
const Bool isTransformFeedbackQuery =
|
||||
target == GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN || target == GL_PRIMITIVES_GENERATED;
|
||||
if (target != GL_TIME_ELAPSED && !isTransformFeedbackQuery) {
|
||||
// GL_TIME_ELAPSED timer queries and the transform feedback primitive
|
||||
// queries are implemented (occlusion queries remain stubs);
|
||||
// GL_TIMESTAMP is not a valid BeginQuery target either.
|
||||
const Bool isOcclusionQuery =
|
||||
(target == GL_SAMPLES_PASSED || target == GL_ANY_SAMPLES_PASSED ||
|
||||
target == GL_ANY_SAMPLES_PASSED_CONSERVATIVE) &&
|
||||
MG_Backend::gBackendFunctionsTable.GL.BeginOcclusionQuery != nullptr;
|
||||
if (target != GL_TIME_ELAPSED && !isTransformFeedbackQuery && !isOcclusionQuery) {
|
||||
// GL_TIMESTAMP is not a valid BeginQuery target; the occlusion targets
|
||||
// need backend support.
|
||||
RecordQueryError(ErrorCode::InvalidEnum, __FUNCTION__, "Query target is not supported.");
|
||||
return;
|
||||
}
|
||||
@@ -231,7 +258,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
GLuint& activeQueryId = isTransformFeedbackQuery
|
||||
? (target == GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN ? g_activePrimitivesWrittenQueryId
|
||||
: g_activePrimitivesGeneratedQueryId)
|
||||
: g_activeTimeElapsedQueryId;
|
||||
: (isOcclusionQuery ? g_activeSamplesPassedQueryId : g_activeTimeElapsedQueryId);
|
||||
if (activeQueryId != 0) {
|
||||
RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__,
|
||||
"A query is already active on this target.");
|
||||
@@ -255,6 +282,8 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
// result is the delta between Begin and End. Without geometry-stage
|
||||
// amplification the assembled count IS the written/generated count.
|
||||
queryObject->counterSnapshot = MG_State::pGLContext->GetTransformFeedbackPrimitiveCounter();
|
||||
} else if (isOcclusionQuery) {
|
||||
queryObject->backendHandle = MG_Backend::gBackendFunctionsTable.GL.BeginOcclusionQuery();
|
||||
} else {
|
||||
const auto beginTimeElapsedQuery = MG_Backend::gBackendFunctionsTable.GL.BeginTimeElapsedQuery;
|
||||
queryObject->backendHandle =
|
||||
@@ -266,7 +295,11 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
void EndQuery(GLenum target) {
|
||||
const Bool isTransformFeedbackQuery =
|
||||
target == GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN || target == GL_PRIMITIVES_GENERATED;
|
||||
if (target != GL_TIME_ELAPSED && !isTransformFeedbackQuery) {
|
||||
const Bool isOcclusionQuery =
|
||||
(target == GL_SAMPLES_PASSED || target == GL_ANY_SAMPLES_PASSED ||
|
||||
target == GL_ANY_SAMPLES_PASSED_CONSERVATIVE) &&
|
||||
MG_Backend::gBackendFunctionsTable.GL.BeginOcclusionQuery != nullptr;
|
||||
if (target != GL_TIME_ELAPSED && !isTransformFeedbackQuery && !isOcclusionQuery) {
|
||||
RecordQueryError(ErrorCode::InvalidEnum, __FUNCTION__, "Query target is not supported.");
|
||||
return;
|
||||
}
|
||||
@@ -274,7 +307,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
GLuint& activeQueryId = isTransformFeedbackQuery
|
||||
? (target == GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN ? g_activePrimitivesWrittenQueryId
|
||||
: g_activePrimitivesGeneratedQueryId)
|
||||
: g_activeTimeElapsedQueryId;
|
||||
: (isOcclusionQuery ? g_activeSamplesPassedQueryId : g_activeTimeElapsedQueryId);
|
||||
if (activeQueryId == 0) {
|
||||
RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__, "No query is active on this target.");
|
||||
return;
|
||||
@@ -293,6 +326,16 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
activeQueryId = 0;
|
||||
return;
|
||||
}
|
||||
if (isOcclusionQuery) {
|
||||
if (const auto endOcclusionQuery = MG_Backend::gBackendFunctionsTable.GL.EndOcclusionQuery;
|
||||
endOcclusionQuery && queryObject->backendHandle) {
|
||||
endOcclusionQuery(queryObject->backendHandle);
|
||||
}
|
||||
queryObject->active = false;
|
||||
queryObject->ended = true;
|
||||
activeQueryId = 0;
|
||||
return;
|
||||
}
|
||||
EndTimeElapsedQueryLocked(queryObject);
|
||||
}
|
||||
|
||||
@@ -336,9 +379,25 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
switch (pname) {
|
||||
case GL_CURRENT_QUERY: {
|
||||
const std::lock_guard<std::mutex> lock(g_queryObjectsMutex);
|
||||
// Only GL_TIME_ELAPSED queries can be active; GL_TIMESTAMP queries
|
||||
// never are, and other targets remain unimplemented.
|
||||
*params = target == GL_TIME_ELAPSED ? static_cast<GLint>(g_activeTimeElapsedQueryId) : 0;
|
||||
switch (target) {
|
||||
case GL_TIME_ELAPSED:
|
||||
*params = static_cast<GLint>(g_activeTimeElapsedQueryId);
|
||||
break;
|
||||
case GL_SAMPLES_PASSED:
|
||||
case GL_ANY_SAMPLES_PASSED:
|
||||
case GL_ANY_SAMPLES_PASSED_CONSERVATIVE:
|
||||
*params = static_cast<GLint>(g_activeSamplesPassedQueryId);
|
||||
break;
|
||||
case GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN:
|
||||
*params = static_cast<GLint>(g_activePrimitivesWrittenQueryId);
|
||||
break;
|
||||
case GL_PRIMITIVES_GENERATED:
|
||||
*params = static_cast<GLint>(g_activePrimitivesGeneratedQueryId);
|
||||
break;
|
||||
default:
|
||||
*params = 0;
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
case GL_QUERY_COUNTER_BITS: {
|
||||
@@ -346,7 +405,13 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
// time: IsTimerQuerySupported is the dynamic truth (extension /
|
||||
// entry points / timestamp valid bits at call time, not at table
|
||||
// init), and the MOBILEGL_DISABLE_TIMERQUERY kill switch always
|
||||
// wins. Non-timer targets remain unimplemented and report 0.
|
||||
// wins.
|
||||
if (target == GL_SAMPLES_PASSED || target == GL_ANY_SAMPLES_PASSED ||
|
||||
target == GL_ANY_SAMPLES_PASSED_CONSERVATIVE) {
|
||||
const Bool occlusionSupported = MG_Backend::gBackendFunctionsTable.GL.BeginOcclusionQuery != nullptr;
|
||||
*params = occlusionSupported ? (target == GL_SAMPLES_PASSED ? 32 : 1) : 0;
|
||||
return;
|
||||
}
|
||||
const Bool timerTarget = target == GL_TIME_ELAPSED || target == GL_TIMESTAMP;
|
||||
const auto isTimerQuerySupported = MG_Backend::gBackendFunctionsTable.GL.IsTimerQuerySupported;
|
||||
const Bool supported =
|
||||
|
||||
Reference in New Issue
Block a user