diff --git a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp index d5f4b2fc..575469c4 100644 --- a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp @@ -1578,6 +1578,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { // (and, via the SharedPtrs, the records), never pool slots, so // stale queries are always safe to delete. Uint64 rendererGeneration = 0; + // Kind::XfbGenerated - the frontend's paused-draw primitive counter when the + // query began. VK_QUERY_TYPE_TRANSFORM_FEEDBACK_STREAM_EXT counts only what the + // capture saw, so a draw made while the span was paused is invisible to it - + // but GL_PRIMITIVES_GENERATED counts what the last vertex processing stage + // emitted regardless. The delta closes that gap at result time. + Uint64 pausedPrimitiveSnapshot = 0; }; } // namespace @@ -1676,6 +1682,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { primitives)) { return false; } + if (query->kind == VulkanTimerQuery::Kind::XfbGenerated && MG_State::pGLContext != nullptr) { + primitives += MG_State::pGLContext->GetTransformFeedbackPausedPrimitiveCounter() - + query->pausedPrimitiveSnapshot; + } *outNanoseconds = primitives; return true; } @@ -1719,6 +1729,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { auto* query = new VulkanTimerQuery{}; query->kind = generated ? VulkanTimerQuery::Kind::XfbGenerated : VulkanTimerQuery::Kind::XfbWritten; query->rendererGeneration = GetRendererGeneration(); + query->pausedPrimitiveSnapshot = + MG_State::pGLContext ? MG_State::pGLContext->GetTransformFeedbackPausedPrimitiveCounter() : 0; return query; } diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index 2a3860d7..e1bf0d5c 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -2798,6 +2798,10 @@ void main() { } m_vertexInputStateFactory.reset(); m_xfbCounterBuffer.Destroy(); + m_xfbCounterSlotByObject.clear(); + m_xfbNextCounterSlot = 0; + m_xfbCountersValid.fill(false); + m_xfbLastSeenGeneration.fill(0); if (m_occlusionQueryPool != VK_NULL_HANDLE) { vkDestroyQueryPool(m_device, m_occlusionQueryPool, nullptr); m_occlusionQueryPool = VK_NULL_HANDLE; @@ -8000,11 +8004,30 @@ void main() { resource->layout = finalLayout; } + Uint32 VulkanRenderer::CurrentXfbCounterSlot() { + const Uint name = MG_State::pGLContext->GetBoundTransformFeedbackName(); + const auto it = m_xfbCounterSlotByObject.find(name); + if (it != m_xfbCounterSlotByObject.end()) { + return it->second; + } + // Past the tracked set every object shares slot group 0. Only concurrently-paused + // spans need distinct groups, and applications do not keep sixteen of those open. + const Uint32 slot = m_xfbNextCounterSlot < kXfbCounterObjectSlots ? m_xfbNextCounterSlot++ : 0; + m_xfbCounterSlotByObject[name] = slot; + return slot; + } + Bool VulkanRenderer::BeginXfbCaptureForDraw(FrameContext::FrameData& frame) { if (!m_transformFeedbackFeatureEnabled || MG_State::pGLContext == nullptr || !MG_State::pGLContext->IsTransformFeedbackActive()) { return false; } + // A paused span captures nothing, and the counter buffers keep their values, so the + // next resumed draw appends exactly where the last captured one stopped - which is + // what pause/resume means (ARB_transform_feedback2). + if (MG_State::pGLContext->IsTransformFeedbackPaused()) { + return false; + } const auto& program = MG_State::pGLContext->GetTransformFeedbackProgram(); if (!program || program->GetTransformFeedbackVaryingCount() == 0) { return false; @@ -8017,7 +8040,7 @@ void main() { if (!m_xfbCounterBuffer.IsValid()) { if (!m_xfbCounterBuffer.Create({ .allocator = m_allocator, - .size = 16, + .size = 16 * kXfbCounterObjectSlots, .usage = VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_COUNTER_BUFFER_BIT_EXT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, .memoryUsage = VMA_MEMORY_USAGE_AUTO, @@ -8058,15 +8081,16 @@ void main() { s_vkCmdBindTransformFeedbackBuffersEXT(frame.commandBuffer, 0, static_cast(bufferCount), buffers, offsets, sizes); + const Uint32 counterSlot = CurrentXfbCounterSlot(); const Uint64 generation = MG_State::pGLContext->GetTransformFeedbackGeneration(); - const Bool resume = m_xfbCountersValid && m_xfbLastSeenGeneration == generation; - m_xfbLastSeenGeneration = generation; + const Bool resume = m_xfbCountersValid[counterSlot] && m_xfbLastSeenGeneration[counterSlot] == generation; + m_xfbLastSeenGeneration[counterSlot] = generation; VkBuffer counterBuffers[4] = {}; VkDeviceSize counterOffsets[4] = {}; for (SizeT i = 0; i < bufferCount; ++i) { counterBuffers[i] = m_xfbCounterBuffer.GetHandle(); - counterOffsets[i] = static_cast(i) * 4; + counterOffsets[i] = static_cast(counterSlot) * 16 + static_cast(i) * 4; } if (resume) { s_vkCmdBeginTransformFeedbackEXT(frame.commandBuffer, 0, static_cast(bufferCount), @@ -8083,15 +8107,16 @@ void main() { } const auto& program = MG_State::pGLContext->GetTransformFeedbackProgram(); const SizeT bufferCount = program ? std::min(program->GetTransformFeedbackBufferCount(), 4) : 0; + const Uint32 counterSlot = CurrentXfbCounterSlot(); VkBuffer counterBuffers[4] = {}; VkDeviceSize counterOffsets[4] = {}; for (SizeT i = 0; i < bufferCount; ++i) { counterBuffers[i] = m_xfbCounterBuffer.GetHandle(); - counterOffsets[i] = static_cast(i) * 4; + counterOffsets[i] = static_cast(counterSlot) * 16 + static_cast(i) * 4; } s_vkCmdEndTransformFeedbackEXT(frame.commandBuffer, 0, static_cast(bufferCount), counterBuffers, counterOffsets); - m_xfbCountersValid = true; + m_xfbCountersValid[counterSlot] = true; } void VulkanRenderer::DrawArrays(const DrawCmd& payload) { diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h index 8f8d6063..4b0221f7 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h @@ -498,12 +498,19 @@ namespace MobileGL::MG_Backend::DirectVulkan { static inline PFN_vkCmdBeginTransformFeedbackEXT s_vkCmdBeginTransformFeedbackEXT = nullptr; static inline PFN_vkCmdEndTransformFeedbackEXT s_vkCmdEndTransformFeedbackEXT = nullptr; // Counter buffers (one 4-byte slot per capture binding) let consecutive - // draws within one glBeginTransformFeedback append GL-style. + // draws within one glBeginTransformFeedback append GL-style. Transform feedback + // objects can each hold an open, paused span at the same time, so the counters are + // per object: one group of four slots each, handed out on first use. + static constexpr SizeT kXfbCounterObjectSlots = 16; VkBufferObject m_xfbCounterBuffer; - // Non-zero while inside a GL Begin/End with at least one captured draw - // recorded; selects counter-buffer resume on the next captured draw. - Bool m_xfbCountersValid = false; - Uint64 m_xfbLastSeenGeneration = 0; + UnorderedMap m_xfbCounterSlotByObject; + Uint32 m_xfbNextCounterSlot = 0; + // Set for a slot once a captured draw has been recorded into its span; selects + // counter-buffer resume on the next captured draw of the same span. + Array m_xfbCountersValid{}; + Array m_xfbLastSeenGeneration{}; + // Counter slot group of the bound transform feedback object. + Uint32 CurrentXfbCounterSlot(); // Wraps a recorded draw with BeginTransformFeedbackEXT/EndTransformFeedbackEXT // when GL transform feedback is active; binds capture buffers on demand. Bool BeginXfbCaptureForDraw(FrameContext::FrameData& frame); diff --git a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp index 94678efd..e34c8156 100644 --- a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp +++ b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp @@ -75,7 +75,10 @@ namespace MobileGL::MG_Impl::GLImpl { if (!MG_State::pGLContext->IsTransformFeedbackActive()) return; // A paused span captures nothing, so a draw made while paused contributes to // PRIMITIVES_GENERATED but not to TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN. - if (MG_State::pGLContext->IsTransformFeedbackPaused()) return; + if (MG_State::pGLContext->IsTransformFeedbackPaused()) { + MG_State::pGLContext->AddTransformFeedbackPausedPrimitives(CountPrimitivesForDraw(mode, count)); + return; + } Uint64 primitives = CountPrimitivesForDraw(mode, count); if (primitives == 0) return; MG_State::pGLContext->AddTransformFeedbackInputPrimitives(primitives); diff --git a/MobileGL/MG_State/GLState/Core.cpp b/MobileGL/MG_State/GLState/Core.cpp index 2b62242e..4894d792 100644 --- a/MobileGL/MG_State/GLState/Core.cpp +++ b/MobileGL/MG_State/GLState/Core.cpp @@ -764,6 +764,7 @@ namespace MobileGL::MG_State { object.paused = m_transformFeedbackPaused; object.primitiveMode = m_transformFeedbackPrimitiveMode; object.program = m_transformFeedbackProgram; + object.generation = m_transformFeedbackGeneration; object.capturedVertices = m_transformFeedbackCapturedVertices; object.inputPrimitives = m_transformFeedbackInputPrimitives; } @@ -783,6 +784,10 @@ namespace MobileGL::MG_State { m_transformFeedbackPaused = object.paused; m_transformFeedbackPrimitiveMode = object.primitiveMode; m_transformFeedbackProgram = object.program; + // The generation identifies one capture span, and a span belongs to the object + // that opened it - a backend keys its append state on it, so switching objects + // has to bring the right one back. + m_transformFeedbackGeneration = object.generation; m_transformFeedbackCapturedVertices = object.capturedVertices; m_transformFeedbackInputPrimitives = object.inputPrimitives; } diff --git a/MobileGL/MG_State/GLState/Core.h b/MobileGL/MG_State/GLState/Core.h index 5d9dde65..a84d7ec2 100644 --- a/MobileGL/MG_State/GLState/Core.h +++ b/MobileGL/MG_State/GLState/Core.h @@ -221,7 +221,7 @@ namespace MobileGL { m_transformFeedbackPaused = false; m_transformFeedbackPrimitiveMode = primitiveMode; m_transformFeedbackProgram = program; - ++m_transformFeedbackGeneration; + m_transformFeedbackGeneration = ++m_transformFeedbackNextGeneration; m_transformFeedbackCapturedVertices = 0; m_transformFeedbackInputPrimitives = 0; } @@ -251,6 +251,15 @@ namespace MobileGL { m_transformFeedbackPrimitiveCounter += primitives; } Uint64 GetTransformFeedbackPrimitiveCounter() const { return m_transformFeedbackPrimitiveCounter; } + // Primitives a draw assembled while the capture was paused. GL counts those in + // PRIMITIVES_GENERATED, but a backend that answers the query with its own + // transform feedback counter cannot see them - nothing was being captured. + void AddTransformFeedbackPausedPrimitives(Uint64 primitives) { + m_transformFeedbackPausedPrimitiveCounter += primitives; + } + Uint64 GetTransformFeedbackPausedPrimitiveCounter() const { + return m_transformFeedbackPausedPrimitiveCounter; + } // Vertices already captured since BeginTransformFeedback (drives the // buffer-capacity clamp on the primitives-written accounting). void AddTransformFeedbackCapturedVertices(Uint64 vertices) { @@ -327,9 +336,12 @@ namespace MobileGL { GLenum m_transformFeedbackPrimitiveMode = GL_POINTS; SharedPtr m_transformFeedbackProgram; Uint64 m_transformFeedbackGeneration = 0; + // Source of the per-span ids above; never rolls back with an object switch. + Uint64 m_transformFeedbackNextGeneration = 0; // Not object state: the transform feedback queries snapshot it at BeginQuery // and take the delta at EndQuery, which spans whatever objects were used. Uint64 m_transformFeedbackPrimitiveCounter = 0; + Uint64 m_transformFeedbackPausedPrimitiveCounter = 0; Uint64 m_transformFeedbackCapturedVertices = 0; Uint64 m_transformFeedbackInputPrimitives = 0; @@ -345,6 +357,7 @@ namespace MobileGL { Bool paused = false; GLenum primitiveMode = GL_POINTS; SharedPtr program; + Uint64 generation = 0; Uint64 capturedVertices = 0; Uint64 inputPrimitives = 0; Uint64 recordedVertices = 0;