mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-11 21:58:31 +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.
|
// and leave the query readable later.
|
||||||
Bool (*GetQueryResult64)(BackendQueryHandle query, Bool wait, Uint64* outNanoseconds);
|
Bool (*GetQueryResult64)(BackendQueryHandle query, Bool wait, Uint64* outNanoseconds);
|
||||||
void (*DeleteBackendQuery)(BackendQueryHandle query);
|
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
|
Int64 (*GetGpuTimestampNs)(); // glGetInteger64v(GL_TIMESTAMP); 0 if unsupported
|
||||||
};
|
};
|
||||||
struct GlobalBackendFunctionsTable {
|
struct GlobalBackendFunctionsTable {
|
||||||
|
|||||||
@@ -635,6 +635,13 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
funcsTable.GL.DeleteBackendQuery = DeleteBackendQuery;
|
funcsTable.GL.DeleteBackendQuery = DeleteBackendQuery;
|
||||||
funcsTable.GL.GetGpuTimestampNs = GetGpuTimestampNs;
|
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;
|
funcsTableInitialized = true;
|
||||||
}
|
}
|
||||||
return funcsTable;
|
return funcsTable;
|
||||||
|
|||||||
@@ -1564,8 +1564,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
// records are shared (SharedPtr) with the owning pool's pending list,
|
// records are shared (SharedPtr) with the owning pool's pending list,
|
||||||
// so deleting the query while results are still in flight is safe.
|
// so deleting the query while results are still in flight is safe.
|
||||||
struct VulkanTimerQuery {
|
struct VulkanTimerQuery {
|
||||||
|
enum class Kind : Uint8 { Timer, Occlusion };
|
||||||
|
Kind kind = Kind::Timer;
|
||||||
SharedPtr<VkTimerQueryManager::TimestampRecord> begin;
|
SharedPtr<VkTimerQueryManager::TimestampRecord> begin;
|
||||||
SharedPtr<VkTimerQueryManager::TimestampRecord> end;
|
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
|
// Renderer generation the records were written under (see
|
||||||
// g_rendererGeneration). A stale generation resolves as available
|
// g_rendererGeneration). A stale generation resolves as available
|
||||||
// with a final zero result: the records' pool indices and frame
|
// 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.
|
// ever be produced, so resolve with a final 0.
|
||||||
return true;
|
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
|
// With wait, mirrors ClientWaitSync: a query ended this frame cannot
|
||||||
// complete until Present submits the commands, so the wait refuses to
|
// complete until Present submits the commands, so the wait refuses to
|
||||||
// block on the current unsubmitted serial. Returning false keeps the
|
// block on the current unsubmitted serial. Returning false keeps the
|
||||||
@@ -1687,6 +1700,26 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
delete static_cast<VulkanTimerQuery*>(handle);
|
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() {
|
Int64 GetGpuTimestampNs() {
|
||||||
// Vulkan cannot synchronously sample the GPU clock: timestamps only
|
// Vulkan cannot synchronously sample the GPU clock: timestamps only
|
||||||
// exist as vkCmdWriteTimestamp results read back later, and
|
// 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.
|
// only while a live renderer exists whose device can actually time.
|
||||||
Bool IsTimerQuerySupported();
|
Bool IsTimerQuerySupported();
|
||||||
BackendQueryHandle BeginTimeElapsedQuery();
|
BackendQueryHandle BeginTimeElapsedQuery();
|
||||||
|
BackendQueryHandle BeginOcclusionQuery();
|
||||||
|
void EndOcclusionQuery(BackendQueryHandle query);
|
||||||
void EndTimeElapsedQuery(BackendQueryHandle query);
|
void EndTimeElapsedQuery(BackendQueryHandle query);
|
||||||
BackendQueryHandle QueryCounterTimestamp();
|
BackendQueryHandle QueryCounterTimestamp();
|
||||||
Bool IsQueryResultAvailable(BackendQueryHandle query);
|
Bool IsQueryResultAvailable(BackendQueryHandle query);
|
||||||
|
|||||||
@@ -2782,6 +2782,10 @@ void main() {
|
|||||||
}
|
}
|
||||||
m_vertexInputStateFactory.reset();
|
m_vertexInputStateFactory.reset();
|
||||||
m_xfbCounterBuffer.Destroy();
|
m_xfbCounterBuffer.Destroy();
|
||||||
|
if (m_occlusionQueryPool != VK_NULL_HANDLE) {
|
||||||
|
vkDestroyQueryPool(m_device, m_occlusionQueryPool, nullptr);
|
||||||
|
m_occlusionQueryPool = VK_NULL_HANDLE;
|
||||||
|
}
|
||||||
m_bufferManager.Shutdown();
|
m_bufferManager.Shutdown();
|
||||||
|
|
||||||
// Device is idle (vkDeviceWaitIdle above); query pools can be destroyed.
|
// Device is idle (vkDeviceWaitIdle above); query pools can be destroyed.
|
||||||
@@ -4059,6 +4063,23 @@ void main() {
|
|||||||
auto colorLogicOpEnabled =
|
auto colorLogicOpEnabled =
|
||||||
MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::ColorLogicOp) && m_logicOpFeatureEnabled;
|
MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::ColorLogicOp) && m_logicOpFeatureEnabled;
|
||||||
auto stencilTestEnabled = MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::StencilTest);
|
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& frontStencil = MG_State::pGLContext->GetStencilState(StencilFace::Front);
|
||||||
const StencilFaceState& backStencil = MG_State::pGLContext->GetStencilState(StencilFace::Back);
|
const StencilFaceState& backStencil = MG_State::pGLContext->GetStencilState(StencilFace::Back);
|
||||||
const VkPolygonMode requestedPolygonMode =
|
const VkPolygonMode requestedPolygonMode =
|
||||||
@@ -7767,14 +7788,95 @@ void main() {
|
|||||||
VkCommandBuffer& commandBuffer = frame.commandBuffer;
|
VkCommandBuffer& commandBuffer = frame.commandBuffer;
|
||||||
|
|
||||||
const Bool xfbActive = BeginXfbCaptureForDraw(frame);
|
const Bool xfbActive = BeginXfbCaptureForDraw(frame);
|
||||||
|
const Bool occlusionActive = BeginOcclusionForDraw(commandBuffer);
|
||||||
vkCmdDraw(commandBuffer,
|
vkCmdDraw(commandBuffer,
|
||||||
payload.params.vertexCount,
|
payload.params.vertexCount,
|
||||||
payload.params.instanceCount,
|
payload.params.instanceCount,
|
||||||
payload.params.firstVertex,
|
payload.params.firstVertex,
|
||||||
payload.params.firstInstance);
|
payload.params.firstInstance);
|
||||||
|
EndOcclusionForDraw(commandBuffer, occlusionActive);
|
||||||
EndXfbCaptureForDraw(frame, xfbActive);
|
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) {
|
void VulkanRenderer::DrawElements(const DrawIndexedCmd& payload) {
|
||||||
auto& frame = m_frameContext.GetCurrent();
|
auto& frame = m_frameContext.GetCurrent();
|
||||||
|
|
||||||
@@ -7800,12 +7902,14 @@ void main() {
|
|||||||
VkCommandBuffer& commandBuffer = frame.commandBuffer;
|
VkCommandBuffer& commandBuffer = frame.commandBuffer;
|
||||||
|
|
||||||
const Bool xfbActive = BeginXfbCaptureForDraw(frame);
|
const Bool xfbActive = BeginXfbCaptureForDraw(frame);
|
||||||
|
const Bool occlusionActive = BeginOcclusionForDraw(commandBuffer);
|
||||||
vkCmdDrawIndexed(commandBuffer,
|
vkCmdDrawIndexed(commandBuffer,
|
||||||
payload.params.indexCount,
|
payload.params.indexCount,
|
||||||
payload.params.instanceCount,
|
payload.params.instanceCount,
|
||||||
payload.params.firstIndex,
|
payload.params.firstIndex,
|
||||||
payload.params.vertexOffset,
|
payload.params.vertexOffset,
|
||||||
payload.params.firstInstance);
|
payload.params.firstInstance);
|
||||||
|
EndOcclusionForDraw(commandBuffer, occlusionActive);
|
||||||
EndXfbCaptureForDraw(frame, xfbActive);
|
EndXfbCaptureForDraw(frame, xfbActive);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -9163,6 +9267,10 @@ void main() {
|
|||||||
// to isotropic filtering (and the extension goes unadvertised) when the device lacks it.
|
// to isotropic filtering (and the extension goes unadvertised) when the device lacks it.
|
||||||
deviceFeatures.samplerAnisotropy = supportedDeviceFeatures.samplerAnisotropy;
|
deviceFeatures.samplerAnisotropy = supportedDeviceFeatures.samplerAnisotropy;
|
||||||
m_samplerAnisotropyFeatureEnabled = deviceFeatures.samplerAnisotropy == VK_TRUE;
|
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{};
|
VkDeviceCreateInfo deviceCreateInfo{};
|
||||||
deviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
|
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");
|
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.enabledExtensionCount = static_cast<Uint32>(enabledDeviceExtensions.size());
|
||||||
deviceCreateInfo.ppEnabledExtensionNames = enabledDeviceExtensions.data();
|
deviceCreateInfo.ppEnabledExtensionNames = enabledDeviceExtensions.data();
|
||||||
MGLOG_I("Device feature support: robustBufferAccess=%s geometryShader=%s independentBlend=%s logicOp=%s shaderClipDistance=%s "
|
MGLOG_I("Device feature support: robustBufferAccess=%s geometryShader=%s independentBlend=%s logicOp=%s shaderClipDistance=%s "
|
||||||
@@ -9378,6 +9507,17 @@ void main() {
|
|||||||
m_transformFeedbackFeatureEnabled = false;
|
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("index type uint8 enabled: %s", m_indexTypeUint8ExtensionEnabled ? "true" : "false");
|
||||||
MGLOG_I("Logical device created.");
|
MGLOG_I("Logical device created.");
|
||||||
|
|
||||||
|
|||||||
@@ -296,6 +296,15 @@ namespace MobileGL::MG_Backend::DirectVulkan {
|
|||||||
const VkTimerQueryManager::TimestampRecord& end) const;
|
const VkTimerQueryManager::TimestampRecord& end) const;
|
||||||
Uint64 GetTimerQueryTimestampNs(const VkTimerQueryManager::TimestampRecord& record) 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);
|
void RequestSwapchainResize(Uint32 width, Uint32 height);
|
||||||
// Re-query the surface and report whether the live swapchain no longer matches it
|
// 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
|
// (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.
|
// when GL transform feedback is active; binds capture buffers on demand.
|
||||||
Bool BeginXfbCaptureForDraw(FrameContext::FrameData& frame);
|
Bool BeginXfbCaptureForDraw(FrameContext::FrameData& frame);
|
||||||
void EndXfbCaptureForDraw(FrameContext::FrameData& frame, Bool began);
|
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;
|
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).
|
// Ids of the queries active on the transform feedback targets (0 = none).
|
||||||
GLuint g_activePrimitivesWrittenQueryId = 0;
|
GLuint g_activePrimitivesWrittenQueryId = 0;
|
||||||
GLuint g_activePrimitivesGeneratedQueryId = 0;
|
GLuint g_activePrimitivesGeneratedQueryId = 0;
|
||||||
|
// Id of the query active on GL_SAMPLES_PASSED (0 = none).
|
||||||
|
GLuint g_activeSamplesPassedQueryId = 0;
|
||||||
|
|
||||||
Bool TimerQueryDisabled() {
|
Bool TimerQueryDisabled() {
|
||||||
return MG_Config::Features.DisableTimerQuery;
|
return MG_Config::Features.DisableTimerQuery;
|
||||||
@@ -131,6 +133,11 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
outValue = 0;
|
outValue = 0;
|
||||||
return true;
|
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
|
// Final value produced (or no GetQueryResult64 hook: the
|
||||||
// query degrades to a zero result); the backend handle is
|
// query degrades to a zero result); the backend handle is
|
||||||
// consumed and the value cached for later reads.
|
// consumed and the value cached for later reads.
|
||||||
@@ -185,7 +192,24 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
}
|
}
|
||||||
QueryObject* queryObject = it->second;
|
QueryObject* queryObject = it->second;
|
||||||
if (queryObject->active) {
|
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 (queryObject->backendHandle) {
|
||||||
if (const auto deleteBackendQuery = MG_Backend::gBackendFunctionsTable.GL.DeleteBackendQuery) {
|
if (const auto deleteBackendQuery = MG_Backend::gBackendFunctionsTable.GL.DeleteBackendQuery) {
|
||||||
@@ -211,10 +235,13 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
void BeginQuery(GLenum target, GLuint id) {
|
void BeginQuery(GLenum target, GLuint id) {
|
||||||
const Bool isTransformFeedbackQuery =
|
const Bool isTransformFeedbackQuery =
|
||||||
target == GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN || target == GL_PRIMITIVES_GENERATED;
|
target == GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN || target == GL_PRIMITIVES_GENERATED;
|
||||||
if (target != GL_TIME_ELAPSED && !isTransformFeedbackQuery) {
|
const Bool isOcclusionQuery =
|
||||||
// GL_TIME_ELAPSED timer queries and the transform feedback primitive
|
(target == GL_SAMPLES_PASSED || target == GL_ANY_SAMPLES_PASSED ||
|
||||||
// queries are implemented (occlusion queries remain stubs);
|
target == GL_ANY_SAMPLES_PASSED_CONSERVATIVE) &&
|
||||||
// GL_TIMESTAMP is not a valid BeginQuery target either.
|
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.");
|
RecordQueryError(ErrorCode::InvalidEnum, __FUNCTION__, "Query target is not supported.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -231,7 +258,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
GLuint& activeQueryId = isTransformFeedbackQuery
|
GLuint& activeQueryId = isTransformFeedbackQuery
|
||||||
? (target == GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN ? g_activePrimitivesWrittenQueryId
|
? (target == GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN ? g_activePrimitivesWrittenQueryId
|
||||||
: g_activePrimitivesGeneratedQueryId)
|
: g_activePrimitivesGeneratedQueryId)
|
||||||
: g_activeTimeElapsedQueryId;
|
: (isOcclusionQuery ? g_activeSamplesPassedQueryId : g_activeTimeElapsedQueryId);
|
||||||
if (activeQueryId != 0) {
|
if (activeQueryId != 0) {
|
||||||
RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__,
|
RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__,
|
||||||
"A query is already active on this target.");
|
"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
|
// result is the delta between Begin and End. Without geometry-stage
|
||||||
// amplification the assembled count IS the written/generated count.
|
// amplification the assembled count IS the written/generated count.
|
||||||
queryObject->counterSnapshot = MG_State::pGLContext->GetTransformFeedbackPrimitiveCounter();
|
queryObject->counterSnapshot = MG_State::pGLContext->GetTransformFeedbackPrimitiveCounter();
|
||||||
|
} else if (isOcclusionQuery) {
|
||||||
|
queryObject->backendHandle = MG_Backend::gBackendFunctionsTable.GL.BeginOcclusionQuery();
|
||||||
} else {
|
} else {
|
||||||
const auto beginTimeElapsedQuery = MG_Backend::gBackendFunctionsTable.GL.BeginTimeElapsedQuery;
|
const auto beginTimeElapsedQuery = MG_Backend::gBackendFunctionsTable.GL.BeginTimeElapsedQuery;
|
||||||
queryObject->backendHandle =
|
queryObject->backendHandle =
|
||||||
@@ -266,7 +295,11 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
void EndQuery(GLenum target) {
|
void EndQuery(GLenum target) {
|
||||||
const Bool isTransformFeedbackQuery =
|
const Bool isTransformFeedbackQuery =
|
||||||
target == GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN || target == GL_PRIMITIVES_GENERATED;
|
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.");
|
RecordQueryError(ErrorCode::InvalidEnum, __FUNCTION__, "Query target is not supported.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -274,7 +307,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
GLuint& activeQueryId = isTransformFeedbackQuery
|
GLuint& activeQueryId = isTransformFeedbackQuery
|
||||||
? (target == GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN ? g_activePrimitivesWrittenQueryId
|
? (target == GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN ? g_activePrimitivesWrittenQueryId
|
||||||
: g_activePrimitivesGeneratedQueryId)
|
: g_activePrimitivesGeneratedQueryId)
|
||||||
: g_activeTimeElapsedQueryId;
|
: (isOcclusionQuery ? g_activeSamplesPassedQueryId : g_activeTimeElapsedQueryId);
|
||||||
if (activeQueryId == 0) {
|
if (activeQueryId == 0) {
|
||||||
RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__, "No query is active on this target.");
|
RecordQueryError(ErrorCode::InvalidOperation, __FUNCTION__, "No query is active on this target.");
|
||||||
return;
|
return;
|
||||||
@@ -293,6 +326,16 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
activeQueryId = 0;
|
activeQueryId = 0;
|
||||||
return;
|
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);
|
EndTimeElapsedQueryLocked(queryObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -336,9 +379,25 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
switch (pname) {
|
switch (pname) {
|
||||||
case GL_CURRENT_QUERY: {
|
case GL_CURRENT_QUERY: {
|
||||||
const std::lock_guard<std::mutex> lock(g_queryObjectsMutex);
|
const std::lock_guard<std::mutex> lock(g_queryObjectsMutex);
|
||||||
// Only GL_TIME_ELAPSED queries can be active; GL_TIMESTAMP queries
|
switch (target) {
|
||||||
// never are, and other targets remain unimplemented.
|
case GL_TIME_ELAPSED:
|
||||||
*params = target == GL_TIME_ELAPSED ? static_cast<GLint>(g_activeTimeElapsedQueryId) : 0;
|
*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;
|
return;
|
||||||
}
|
}
|
||||||
case GL_QUERY_COUNTER_BITS: {
|
case GL_QUERY_COUNTER_BITS: {
|
||||||
@@ -346,7 +405,13 @@ namespace MobileGL::MG_Impl::GLImpl {
|
|||||||
// time: IsTimerQuerySupported is the dynamic truth (extension /
|
// time: IsTimerQuerySupported is the dynamic truth (extension /
|
||||||
// entry points / timestamp valid bits at call time, not at table
|
// entry points / timestamp valid bits at call time, not at table
|
||||||
// init), and the MOBILEGL_DISABLE_TIMERQUERY kill switch always
|
// 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 Bool timerTarget = target == GL_TIME_ELAPSED || target == GL_TIMESTAMP;
|
||||||
const auto isTimerQuerySupported = MG_Backend::gBackendFunctionsTable.GL.IsTimerQuerySupported;
|
const auto isTimerQuerySupported = MG_Backend::gBackendFunctionsTable.GL.IsTimerQuerySupported;
|
||||||
const Bool supported =
|
const Bool supported =
|
||||||
|
|||||||
Reference in New Issue
Block a user