[Feat] (MG_Backend/DirectGLES): support Flywheel indirect rendering

- Advertise ARB_gpu_shader5 / ARB_multi_bind / ARB_shading_language_420pack /
  ARB_vertex_attrib_binding / ARB_shader_image_size so LWJGL reports
  SUPPORTS_INDIRECT.
- New LowerDrawParametersPass demotes DrawIndex/BaseInstance/BaseVertex
  builtins to Private globals (mg_DrawID/mg_BaseInstance/mg_BaseVertex) for
  the ESSL transpile; SPIRV-Cross otherwise throws for ES profiles. The
  program manager promotes the emitted globals to uniforms and feeds them
  per (sub-)draw.
- Indirect draws now execute natively on the GPU (glDrawElementsIndirect /
  glDrawArraysIndirect per command) when an indirect buffer is bound, so
  compute-written command fields (Flywheel culling updates instanceCount)
  are honored; detects GL_EXT_base_instance and falls back to the CPU loop
  when the command's baseInstance cannot be consumed natively.
- Sync SSBO binding points for graphics draws, not just compute (Flywheel
  vertex shaders read instance data from SSBOs).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-08 13:33:05 +00:00
co-authored by Claude Fable 5
parent 9fbb708e64
commit 5ea49f3c50
11 changed files with 417 additions and 63 deletions
+53 -1
View File
@@ -9,6 +9,7 @@
#include "Managers.h"
#include "Utils.h"
#include "DirectGLES.h"
#include <MG_Util/ShaderTranspiler/ShaderCompiler.h>
#include <MG_Util/BackendLoaders/OpenGL/Loader.h>
#include <MG_Util/Converters/GLToStr/GLEnumConverter.h>
@@ -31,6 +32,8 @@
namespace MobileGL::MG_Backend::DirectGLES {
constexpr Bool PREFER_MAP_BUFFER_RANGE_FOR_BUFFER_SYNC = false;
constexpr const char* BASE_INSTANCE_UNIFORM_NAME = "mg_BaseInstance";
constexpr const char* DRAW_ID_UNIFORM_NAME = "mg_DrawID";
constexpr const char* BASE_VERTEX_UNIFORM_NAME = "mg_BaseVertex";
static Bool IsAngleLlvmpipeRenderer() {
return g_GLESCapabilities.GLESRendererString.find("ANGLE") != String::npos &&
@@ -113,6 +116,36 @@ namespace MobileGL::MG_Backend::DirectGLES {
String("uniform highp int ") + BASE_INSTANCE_UNIFORM_NAME + ";");
}
// The LowerDrawParametersPass demotes gl_DrawID / gl_BaseInstance / gl_BaseVertex to plain
// Private globals named mg_DrawID / mg_BaseInstance / mg_BaseVertex; SPIRV-Cross then emits
// them as ordinary global declarations. Turn those declarations into uniforms so the draw
// paths can feed real values per (sub-)draw.
String PromoteDrawParameterGlobalsToUniforms(String source, GLenum shaderType) {
if (shaderType != GL_VERTEX_SHADER) {
return source;
}
for (const char* name : {DRAW_ID_UNIFORM_NAME, BASE_INSTANCE_UNIFORM_NAME, BASE_VERTEX_UNIFORM_NAME}) {
for (const char* declPrefix : {"highp int ", "mediump int ", "lowp int ", "int ", "highp uint ",
"mediump uint ", "uint "}) {
const String declaration = String(declPrefix) + name + ";";
const SizeT pos = source.find(declaration);
if (pos == String::npos) {
continue;
}
// Only promote a standalone global declaration, not a uniform we already emitted.
const Bool alreadyUniform = pos >= 8 && source.compare(pos - 8, 8, "uniform ") == 0;
if (!alreadyUniform) {
const Bool hasPrecision = std::strncmp(declPrefix, "int ", 4) != 0 &&
std::strncmp(declPrefix, "uint ", 5) != 0;
const String qualifier = hasPrecision ? "uniform " : "uniform highp ";
source.replace(pos, declaration.size(), qualifier + declaration);
}
break;
}
}
return source;
}
namespace BufferImpl {
namespace {
using MG_State::GLState::BackendBufferResource;
@@ -1919,7 +1952,17 @@ namespace MobileGL::MG_Backend::DirectGLES {
String source;
auto& spirvCode = shaderSpirvs[index];
MG_Util::ShaderTranspiler::SpvcSession spvcSession(spirvCode,
// ESSL cannot express gl_DrawID/gl_BaseInstance/gl_BaseVertex; demote them to
// plain globals (mg_*) before handing the module to SPIRV-Cross.
Vector<unsigned int> loweredSpirv;
const Vector<unsigned int>* effectiveSpirv = &spirvCode;
if (glShaderType == GL_VERTEX_SHADER &&
MG_Util::ShaderTranspiler::ShaderCompiler::LowerDrawParametersForEssl(spirvCode, loweredSpirv) &&
!loweredSpirv.empty()) {
effectiveSpirv = &loweredSpirv;
}
MG_Util::ShaderTranspiler::SpvcSession spvcSession(*effectiveSpirv,
MG_Util::ShaderTranspiler::SessionUsageBit::Transpile);
spvc_compiler_options options;
@@ -1950,6 +1993,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
source = ProcessOutColorLocations(source);
source = ForceFlatIntegerVaryings(source, glShaderType);
source = EmulateBaseInstanceInVertexShader(std::move(source), glShaderType);
source = PromoteDrawParameterGlobalsToUniforms(std::move(source), glShaderType);
source = ForceSupporterOutput(source);
source = ClampNormFallbackOutputs(std::move(source), glShaderType,
m_snormFallbackClampOutputMask,
@@ -2005,6 +2049,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
m_baseInstanceUniformLocation = g_GLESFuncs.glGetUniformLocation(m_backendProgramId,
BASE_INSTANCE_UNIFORM_NAME);
m_drawIdUniformLocation = g_GLESFuncs.glGetUniformLocation(m_backendProgramId, DRAW_ID_UNIFORM_NAME);
// Create global UBO
if (stateProgramObject->GetUBOSize() > 0) {
@@ -2034,6 +2079,13 @@ namespace MobileGL::MG_Backend::DirectGLES {
}
g_GLESFuncs.glUniform1i(m_baseInstanceUniformLocation, static_cast<GLint>(baseInstance));
}
void BackendProgramObjectImpl::SetDrawID(Uint32 drawId) const {
if (m_drawIdUniformLocation < 0) {
return;
}
g_GLESFuncs.glUniform1i(m_drawIdUniformLocation, static_cast<GLint>(drawId));
}
} // namespace PrgramImpl
namespace SamplerImpl {