[Magma] consume wire-owned transform feedback capture spans

This commit is contained in:
2026-09-20 11:15:58 -04:00
parent c1bf7e15c3
commit d0c6fdbc14
3 changed files with 93 additions and 22 deletions
@@ -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<SizeT>(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<Uint32>(bufferCount), buffers,
offsets, sizes);
return BeginXfbCaptureWithBuffers(frame, static_cast<Uint32>(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<VkDeviceSize>(counterSlot) * 16 + static_cast<VkDeviceSize>(i) * 4;
}
@@ -12003,12 +12018,14 @@ void main() {
if (!began) {
return;
}
const auto& program = MGB_CTX->GetTransformFeedbackProgram();
const SizeT bufferCount = program ? std::min<SizeT>(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<VkDeviceSize>(counterSlot) * 16 + static_cast<VkDeviceSize>(i) * 4;
}
@@ -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
@@ -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<Uint32>(std::min<SizeT>(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<Uint8>& 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.