diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index c4f0b137..1a4840b8 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -11904,10 +11904,6 @@ void main() { if (MGB_CTX->IsTransformFeedbackPaused()) { return false; } - const auto& program = MGB_CTX->GetTransformFeedbackProgram(); - if (!program || program->GetTransformFeedbackVaryingCount() == 0) { - return false; - } // The bound pipeline's last pre-rasterization stage has to have been declared with Xfb // (VUID-vkCmdBeginTransformFeedbackEXT-None-04128). Everything above this line reads GL // state, which cannot answer that: a program can be built as a capture variant and still @@ -11921,24 +11917,19 @@ void main() { "undefined behaviour rather than a capture"); return false; } +#if MOBILEGL_BUILD_DISAGGREGATED + if (MG_Config::Transport != MG_Config::TransportMode::Monolith) + return BeginWireXfbCaptureForDraw(frame); +#endif + const auto& program = MGB_CTX->GetTransformFeedbackProgram(); + if (!program || program->GetTransformFeedbackVaryingCount() == 0) { + return false; + } const SizeT bufferCount = std::min(program->GetTransformFeedbackBufferCount(), 4); if (bufferCount == 0) { return false; } - if (!m_xfbCounterBuffer.IsValid()) { - if (!m_xfbCounterBuffer.Create({ - .allocator = m_allocator, - .size = 16 * kXfbCounterObjectSlots, - .usage = VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_COUNTER_BUFFER_BIT_EXT | - VK_BUFFER_USAGE_TRANSFER_DST_BIT, - .memoryUsage = VMA_MEMORY_USAGE_AUTO, - })) { - MGLOG_E_ONCE("BeginXfbCaptureForDraw: failed to create the counter buffer"); - return false; - } - } - VkBuffer buffers[4] = {}; VkDeviceSize offsets[4] = {}; VkDeviceSize sizes[4] = {}; @@ -11976,8 +11967,31 @@ void main() { sizes[i] = rangeSize; } - s_vkCmdBindTransformFeedbackBuffersEXT(frame.commandBuffer, 0, static_cast(bufferCount), buffers, - offsets, sizes); + return BeginXfbCaptureWithBuffers(frame, static_cast(bufferCount), buffers, offsets, sizes); + } + + Bool VulkanRenderer::BeginXfbCaptureWithBuffers(FrameContext::FrameData& frame, Uint32 bufferCount, + const VkBuffer* buffers, const VkDeviceSize* offsets, const VkDeviceSize* sizes) { + if (!m_xfbCounterBuffer.IsValid()) { + if (!m_xfbCounterBuffer.Create({ + .allocator = m_allocator, + .size = 16 * kXfbCounterObjectSlots, + .usage = VK_BUFFER_USAGE_TRANSFORM_FEEDBACK_COUNTER_BUFFER_BIT_EXT | + VK_BUFFER_USAGE_TRANSFER_DST_BIT, + .memoryUsage = VMA_MEMORY_USAGE_AUTO, + })) { + MGLOG_E_ONCE("BeginXfbCaptureForDraw: failed to create the counter buffer"); + return false; + } + } + m_currentDrawXfbBufferCount = bufferCount; + m_currentDrawXfbBufferMask = 0; + for (Uint32 i = 0; i < bufferCount; ++i) { + if (buffers[i] == VK_NULL_HANDLE) continue; + m_currentDrawXfbBufferMask |= 1u << i; + s_vkCmdBindTransformFeedbackBuffersEXT(frame.commandBuffer, i, 1, buffers + i, + offsets + i, sizes + i); + } const Uint32 counterSlot = CurrentXfbCounterSlot(); const Uint64 generation = MGB_CTX->GetTransformFeedbackGeneration(); @@ -11987,6 +12001,7 @@ void main() { VkBuffer counterBuffers[4] = {}; VkDeviceSize counterOffsets[4] = {}; for (SizeT i = 0; i < bufferCount; ++i) { + if (!(m_currentDrawXfbBufferMask & (1u << i))) continue; counterBuffers[i] = m_xfbCounterBuffer.GetHandle(); counterOffsets[i] = static_cast(counterSlot) * 16 + static_cast(i) * 4; } @@ -12003,12 +12018,14 @@ void main() { if (!began) { return; } - const auto& program = MGB_CTX->GetTransformFeedbackProgram(); - const SizeT bufferCount = program ? std::min(program->GetTransformFeedbackBufferCount(), 4) : 0; + // Exactly the targets used by this draw's Begin, independent of frontend + // objects and of whether the span came from the wire or monolith state. + const Uint32 bufferCount = m_currentDrawXfbBufferCount; const Uint32 counterSlot = CurrentXfbCounterSlot(); VkBuffer counterBuffers[4] = {}; VkDeviceSize counterOffsets[4] = {}; for (SizeT i = 0; i < bufferCount; ++i) { + if (!(m_currentDrawXfbBufferMask & (1u << i))) continue; counterBuffers[i] = m_xfbCounterBuffer.GetHandle(); counterOffsets[i] = static_cast(counterSlot) * 16 + static_cast(i) * 4; } diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h index 59a5be39..ee84a346 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h @@ -806,6 +806,14 @@ namespace MobileGL::MG_Backend::DirectVulkan { // Wraps a recorded draw with BeginTransformFeedbackEXT/EndTransformFeedbackEXT // when GL transform feedback is active; binds capture buffers on demand. Bool BeginXfbCaptureForDraw(FrameContext::FrameData& frame); + Bool BeginXfbCaptureWithBuffers(FrameContext::FrameData& frame, Uint32 count, + const VkBuffer* buffers, const VkDeviceSize* offsets, + const VkDeviceSize* sizes); +#if MOBILEGL_BUILD_DISAGGREGATED + Bool BeginWireXfbCaptureForDraw(FrameContext::FrameData& frame); +#endif + Uint32 m_currentDrawXfbBufferCount = 0; + Uint32 m_currentDrawXfbBufferMask = 0; void EndXfbCaptureForDraw(FrameContext::FrameData& frame, Bool began); // Makes the captured bytes visible to whatever reads them next. Deferred rather than // recorded next to the capture, because the capturing draw runs inside a render pass diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDraw.inc b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDraw.inc index 23c30a80..db835c2d 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDraw.inc +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDraw.inc @@ -16,6 +16,47 @@ std::abort(); } + static const MG_Pipe::MGPStreamOutputBegin& WireXfbSpan() { + const auto& state = MG_Pipe::MGPipeApplier(); + const auto found = state.StreamOutputSpans.find(state.BoundStreamOutputLifetimeId); + if (found == state.StreamOutputSpans.end()) MagmaWireFatal("xfb-span-record"); + return found->second; + } + + Bool VulkanRenderer::BeginWireXfbCaptureForDraw(FrameContext::FrameData& frame) { + const auto& span = WireXfbSpan(); + const auto program = WireProgramSource(span.CaptureProgram); + const Uint32 count = static_cast(std::min(program.GetTransformFeedbackBufferCount(), 4)); + if (!count || !program.GetTransformFeedbackVaryingCount()) return false; + VkBuffer buffers[4]{}; + VkDeviceSize offsets[4]{}; + VkDeviceSize sizes[4]{}; + Bool hasTarget = false; + for (Uint32 i = 0; i < count; ++i) { + // A leading gl_NextBuffer leaves a hole in the binding indices. No + // output names that zero-stride target, and GL need not bind a buffer. + if (!program.GetTransformFeedbackStride(i)) continue; + const auto& target = span.Targets[i]; + BufferSlice slice{}; + if (MG_Pipe::MGPipeHandleIsNull(target.Res) || + !m_bufferManager.AcquireWireSlice(BufferKind::Vertex, target.Res, slice)) + MagmaWireFatal("xfb-buffer-resource"); + if (target.Offset >= slice.size) return false; + const Uint64 size = target.Size == 0 || target.Size == MG_Pipe::kMGPipeWholeBuffer + ? slice.size - target.Offset : target.Size; + if (!size || size > slice.size - target.Offset) return false; + buffers[i] = slice.buffer; + offsets[i] = slice.offset + target.Offset; + sizes[i] = size; + hasTarget = true; + // The server owns the storage. Publish dirtiness through the existing + // reverse channel so CPU readback does not reuse the client's old shadow. + m_bufferManager.MarkWireBufferGpuWritten(target.Res, target.Offset, size); + } + if (!hasTarget) return false; + return BeginXfbCaptureWithBuffers(frame, count, buffers, offsets, sizes); + } + static Bool ConvertWireVertexStream(const MGPVertexAttribWire& attr, VertexInputStateFactory::VertexStreamConversion conversion, const Uint8* source, SizeT stride, SizeT count, Vector& output) { @@ -159,8 +200,9 @@ auto& state = MG_Pipe::MGPipeApplier(); const auto program = WireProgramSource(state.BoundShaderCso); if (!program.GetLinkStatus() || !program.GetSpirvStatus()) return false; - if (aspects & DrawSetupAspect::IndirectDrawBuffer || MGB_CTX->IsTransformFeedbackActive()) + if (aspects & DrawSetupAspect::IndirectDrawBuffer) WireBufferLegacyFatal(); + MakeXfbWritesVisible(); const auto* fbo = state.DrawFramebuffer(); if (!fbo) MagmaWireFatal("draw-framebuffer-record"); RetireWireDrawPass(); @@ -170,6 +212,9 @@ ProgramFactory::CompileOptionFlags flags = ProgramFactory::CompileOptionFlags(GetBaseTransformFlagsRaw(fbo->IsDefault)); + if (m_transformFeedbackFeatureEnabled && MGB_CTX->IsTransformFeedbackActive() && + program.GetTransformFeedbackVaryingCount() > 0) + flags |= ProgramFactory::CompileOptionBit::XfbCapture; const auto* programObject = &m_programFactory->GetOrCreateProgram(program, flags); if (!(aspects & DrawSetupAspect::IndexBuffer) && programObject->readsBaseVertexBuiltin) { flags |= ProgramFactory::CompileOptionBit::ZeroBaseVertex; @@ -177,6 +222,7 @@ } if (programObject->pointSizeCapabilityUnsupported) return false; const auto& programObj = *programObject; + m_currentDrawXfbCaptureDeclined = programObj.xfbCaptureDeclined; if (!m_uniformManager->PrepareWireTextureResources(program, programObj)) return false; // Lazy image uploads/promotions can submit the old recording. Resolve // ALL attachments before descriptor binding captures the current cmd.