[Feat] (DirectGLES): implement transform feedback capture

Transform feedback was frontend-only on DirectGLES: glBeginTransformFeedback
just flipped MobileGL's own capture state and the real ES driver was never told
to capture anything, so every capture buffer read back as whatever it held
before the draw (zeros for a fresh glBufferData(NULL)). DirectVulkan drives its
capture from its own draw recording, so the shared GLFunctionsTable had no
entries for the span at all.

Capture now runs on the real driver:

- The backend program declares the capture set with glTransformFeedbackVaryings
  before it links. SPIRV-Cross keeps user output names verbatim in the
  transpiled ESSL, so the frontend's requested names carry over unchanged.
- New GLFunctionsTable Begin/EndTransformFeedback entries hand the span
  boundaries to the backend (null for DirectVulkan, which is unaffected).
- The driver-side begin is deferred to the first draw of the span: ES needs the
  capturing program current and the capture buffers bound, and both only become
  true once PrepareForDraw has run. A span that never draws never touches the
  driver, which is what the GL semantics amount to anyway.
- The end mirrors the captured ranges back into the frontend buffer shadows -
  the GPU wrote them behind the frontend's back, so MapBuffer/GetBufferSubData
  would otherwise still return the pre-draw bytes.

Takes KHR-GL32.transform_feedback from 13/21 to 19/21; the two remaining
failures are the geometry-amplified primitive queries, which still go through
the frontend's CPU accounting.
This commit is contained in:
BZLZHH
2026-08-01 11:51:19 -04:00
parent 4a533a215a
commit df7d1edeca
6 changed files with 166 additions and 0 deletions
@@ -3386,6 +3386,26 @@ namespace MobileGL::MG_Backend::DirectGLES {
MGLOG_D("Processed shader source length: %zu", source.length());
}
// Transform feedback capture runs on the real driver (see XfbImpl in
// DirectGLES.cpp), so the capture set has to be declared on the backend
// program before it links. SPIRV-Cross keeps user output names verbatim in
// the transpiled ESSL (`out vec4 result_0;` stays `result_0`), so the
// frontend's requested names carry over unchanged.
if (stateProgramObject->GetTransformFeedbackVaryingCount() > 0 &&
g_GLESFuncs.glTransformFeedbackVaryings != nullptr) {
const auto& xfbVaryings = stateProgramObject->GetTransformFeedbackVaryings();
Vector<const GLchar*> xfbNames;
xfbNames.reserve(xfbVaryings.size());
for (const auto& xfbVarying : xfbVaryings) {
xfbNames.push_back(xfbVarying.name.c_str());
}
MGLOG_D("Declaring %zu transform feedback varyings on program %u", xfbNames.size(),
m_backendProgramId);
g_GLESFuncs.glTransformFeedbackVaryings(m_backendProgramId, static_cast<GLsizei>(xfbNames.size()),
xfbNames.data(),
stateProgramObject->GetTransformFeedbackBufferMode());
}
// Link program
MGLOG_D("Linking program %u", m_backendProgramId);
g_GLESFuncs.glLinkProgram(m_backendProgramId);