mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-08 04:08:32 +09:00
[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:
@@ -187,6 +187,7 @@ set(SOURCE_FILES
|
||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/EliminateFloatEqualsZeroPass.cpp
|
||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/RenameSamplerFunctionParameterPass.cpp
|
||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/DecomposeWorkgroupVec3Pass.cpp
|
||||
MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/LowerDrawParametersPass.cpp
|
||||
|
||||
MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp
|
||||
MobileGL/MG_Util/BackendLoaders/Vulkan/Loader.cpp
|
||||
|
||||
@@ -788,7 +788,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
E_GL_EXT_framebuffer_object, E_GL_ARB_depth_texture, E_GL_ARB_buffer_storage,
|
||||
E_GL_ARB_texture_storage, E_GL_ARB_direct_state_access,
|
||||
E_GL_ARB_multi_draw_indirect, E_GL_ARB_indirect_parameters,
|
||||
E_GL_ARB_shader_draw_parameters},
|
||||
E_GL_ARB_shader_draw_parameters, E_GL_ARB_gpu_shader5, E_GL_ARB_multi_bind,
|
||||
E_GL_ARB_shading_language_420pack, E_GL_ARB_vertex_attrib_binding,
|
||||
E_GL_ARB_shader_image_size},
|
||||
.IsCompatibilityProfile = false // Is Compatibility Profile
|
||||
},
|
||||
.StaticBackendCapability = {.AllowVSOnlyPrograms = false} // Backend Capability
|
||||
|
||||
@@ -276,6 +276,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
}
|
||||
|
||||
SyncBufferBindingPoints(BufferTarget::Uniform, GL_UNIFORM_BUFFER);
|
||||
// Graphics shaders may also read SSBOs (e.g. Flywheel's indirect vertex shaders pull
|
||||
// instance data from storage buffers), so keep those binding points in sync for draws
|
||||
// and not just for compute dispatches.
|
||||
SyncBufferBindingPoints(BufferTarget::ShaderStorage, GL_SHADER_STORAGE_BUFFER);
|
||||
}
|
||||
|
||||
void SyncComputeBuffers(Bool includeDispatchIndirectBuffer) {
|
||||
@@ -1015,6 +1019,121 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
}
|
||||
}
|
||||
|
||||
void SetCurrentDrawID(Uint32 drawId) {
|
||||
const auto& currentProgram = MG_State::pGLContext->GetCurrentProgram();
|
||||
if (!currentProgram || !currentProgram->GetLinkStatus()) {
|
||||
return;
|
||||
}
|
||||
const auto& backendProgramIt = PrgramImpl::g_backendProgramObjects.find(currentProgram.get());
|
||||
if (backendProgramIt != PrgramImpl::g_backendProgramObjects.end()) {
|
||||
backendProgramIt->second->SetDrawID(drawId);
|
||||
}
|
||||
}
|
||||
|
||||
static Bool SupportsNativeIndirectDraws() {
|
||||
const auto& version = g_GLESCapabilities.GLESVersion;
|
||||
const Bool esVersionOk = version.Major > 3 || (version.Major == 3 && version.Minor >= 1);
|
||||
return esVersionOk && g_GLESFuncs.glDrawElementsIndirect != nullptr &&
|
||||
g_GLESFuncs.glDrawArraysIndirect != nullptr;
|
||||
}
|
||||
|
||||
// Runs an (indexed) indirect multi-draw. When a GL_DRAW_INDIRECT_BUFFER is bound the draws
|
||||
// execute natively on the GPU so commands written by compute shaders (e.g. Flywheel's
|
||||
// culling pipeline updating instanceCount) are honored; the CPU shadow is still consulted
|
||||
// for the per-command baseInstance, which is CPU-authored, to feed the mg_BaseInstance
|
||||
// shader emulation. Falls back to the CPU per-command loop for client-memory commands or
|
||||
// when the driver cannot consume the command's baseInstance field (no GL_EXT_base_instance).
|
||||
static void ExecuteIndexedIndirectCommands(GLenum mode, GLenum type, SizeT indexSize, const Uint8* commandBytes,
|
||||
SizeT commandOffset, Bool hasIndirectBuffer, GLsizei drawcount,
|
||||
GLsizei stride, const char* label) {
|
||||
Bool useNative = hasIndirectBuffer && SupportsNativeIndirectDraws();
|
||||
if (useNative && !g_GLESCapabilities.SupportsBaseInstance) {
|
||||
for (GLsizei i = 0; i < drawcount; ++i) {
|
||||
DrawElementsIndirectCommand cmd{};
|
||||
std::memcpy(&cmd, commandBytes + static_cast<SizeT>(i) * stride, sizeof(cmd));
|
||||
if (cmd.baseInstance != 0) {
|
||||
useNative = false;
|
||||
MGLOG_W("%s: non-zero baseInstance without GL_EXT_base_instance, falling back to CPU "
|
||||
"emulation (GPU-written command fields will not be honored)",
|
||||
label);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (useNative) {
|
||||
for (GLsizei i = 0; i < drawcount; ++i) {
|
||||
DrawElementsIndirectCommand cmd{};
|
||||
std::memcpy(&cmd, commandBytes + static_cast<SizeT>(i) * stride, sizeof(cmd));
|
||||
SetCurrentDrawID(static_cast<Uint32>(i));
|
||||
SetCurrentBaseInstance(cmd.baseInstance);
|
||||
g_GLESFuncs.glDrawElementsIndirect(
|
||||
mode, type, reinterpret_cast<const void*>(commandOffset + static_cast<SizeT>(i) * stride));
|
||||
}
|
||||
} else {
|
||||
for (GLsizei i = 0; i < drawcount; ++i) {
|
||||
DrawElementsIndirectCommand cmd{};
|
||||
std::memcpy(&cmd, commandBytes + static_cast<SizeT>(i) * stride, sizeof(cmd));
|
||||
if (cmd.count == 0 || cmd.instanceCount == 0) {
|
||||
continue;
|
||||
}
|
||||
SetCurrentDrawID(static_cast<Uint32>(i));
|
||||
SetCurrentBaseInstance(cmd.baseInstance);
|
||||
const auto indexByteOffset = static_cast<SizeT>(cmd.firstIndex) * indexSize;
|
||||
g_GLESFuncs.glDrawElementsInstancedBaseVertex(
|
||||
mode, static_cast<GLsizei>(cmd.count), type, reinterpret_cast<const GLvoid*>(indexByteOffset),
|
||||
static_cast<GLsizei>(cmd.instanceCount), cmd.baseVertex);
|
||||
}
|
||||
}
|
||||
SetCurrentDrawID(0);
|
||||
SetCurrentBaseInstance(0);
|
||||
}
|
||||
|
||||
static void ExecuteArraysIndirectCommands(GLenum mode, const Uint8* commandBytes, SizeT commandOffset,
|
||||
Bool hasIndirectBuffer, GLsizei drawcount, GLsizei stride,
|
||||
const char* label) {
|
||||
Bool useNative = hasIndirectBuffer && SupportsNativeIndirectDraws();
|
||||
if (useNative && !g_GLESCapabilities.SupportsBaseInstance) {
|
||||
for (GLsizei i = 0; i < drawcount; ++i) {
|
||||
DrawArraysIndirectCommand cmd{};
|
||||
std::memcpy(&cmd, commandBytes + static_cast<SizeT>(i) * stride, sizeof(cmd));
|
||||
if (cmd.baseInstance != 0) {
|
||||
useNative = false;
|
||||
MGLOG_W("%s: non-zero baseInstance without GL_EXT_base_instance, falling back to CPU "
|
||||
"emulation (GPU-written command fields will not be honored)",
|
||||
label);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (useNative) {
|
||||
for (GLsizei i = 0; i < drawcount; ++i) {
|
||||
DrawArraysIndirectCommand cmd{};
|
||||
std::memcpy(&cmd, commandBytes + static_cast<SizeT>(i) * stride, sizeof(cmd));
|
||||
SetCurrentDrawID(static_cast<Uint32>(i));
|
||||
SetCurrentBaseInstance(cmd.baseInstance);
|
||||
g_GLESFuncs.glDrawArraysIndirect(
|
||||
mode, reinterpret_cast<const void*>(commandOffset + static_cast<SizeT>(i) * stride));
|
||||
}
|
||||
} else {
|
||||
for (GLsizei i = 0; i < drawcount; ++i) {
|
||||
DrawArraysIndirectCommand cmd{};
|
||||
std::memcpy(&cmd, commandBytes + static_cast<SizeT>(i) * stride, sizeof(cmd));
|
||||
if (cmd.count == 0 || cmd.instanceCount == 0) {
|
||||
continue;
|
||||
}
|
||||
SetCurrentDrawID(static_cast<Uint32>(i));
|
||||
SetCurrentBaseInstance(cmd.baseInstance);
|
||||
g_GLESFuncs.glDrawArraysInstanced(mode, static_cast<GLint>(cmd.first),
|
||||
static_cast<GLsizei>(cmd.count),
|
||||
static_cast<GLsizei>(cmd.instanceCount));
|
||||
}
|
||||
}
|
||||
SetCurrentDrawID(0);
|
||||
SetCurrentBaseInstance(0);
|
||||
}
|
||||
|
||||
void PrepareForCompute(Bool includeDispatchIndirectBuffer) {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
@@ -1170,19 +1289,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return;
|
||||
}
|
||||
|
||||
for (GLsizei i = 0; i < drawcount; ++i) {
|
||||
DrawElementsIndirectCommand cmd{};
|
||||
std::memcpy(&cmd, commandBytes + static_cast<SizeT>(i) * stride, sizeof(cmd));
|
||||
if (cmd.count == 0 || cmd.instanceCount == 0) {
|
||||
continue;
|
||||
}
|
||||
SetCurrentBaseInstance(cmd.baseInstance);
|
||||
const auto indexByteOffset = static_cast<SizeT>(cmd.firstIndex) * indexSize;
|
||||
g_GLESFuncs.glDrawElementsInstancedBaseVertex(
|
||||
mode, static_cast<GLsizei>(cmd.count), type, reinterpret_cast<const GLvoid*>(indexByteOffset),
|
||||
static_cast<GLsizei>(cmd.instanceCount), cmd.baseVertex);
|
||||
}
|
||||
SetCurrentBaseInstance(0);
|
||||
const Bool hasIndirectBuffer =
|
||||
MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject() != nullptr;
|
||||
ExecuteIndexedIndirectCommands(mode, type, indexSize, commandBytes, reinterpret_cast<SizeT>(indirect),
|
||||
hasIndirectBuffer, drawcount, stride, "MultiDrawElementsIndirect");
|
||||
}
|
||||
|
||||
void MultiDrawElementsIndirectCount(GLenum mode, GLenum type, const void* indirect, GLintptr drawcount,
|
||||
@@ -1242,19 +1352,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
Uint32 actualDrawCount = 0;
|
||||
std::memcpy(&actualDrawCount, parameterData->data() + drawcount, sizeof(actualDrawCount));
|
||||
actualDrawCount = std::min<Uint32>(actualDrawCount, static_cast<Uint32>(maxdrawcount));
|
||||
for (Uint32 i = 0; i < actualDrawCount; ++i) {
|
||||
DrawElementsIndirectCommand cmd{};
|
||||
std::memcpy(&cmd, drawData->data() + commandOffset + static_cast<SizeT>(i) * stride, sizeof(cmd));
|
||||
if (cmd.count == 0 || cmd.instanceCount == 0) {
|
||||
continue;
|
||||
}
|
||||
SetCurrentBaseInstance(cmd.baseInstance);
|
||||
const auto indexByteOffset = static_cast<SizeT>(cmd.firstIndex) * indexSize;
|
||||
g_GLESFuncs.glDrawElementsInstancedBaseVertex(
|
||||
mode, static_cast<GLsizei>(cmd.count), type, reinterpret_cast<const GLvoid*>(indexByteOffset),
|
||||
static_cast<GLsizei>(cmd.instanceCount), cmd.baseVertex);
|
||||
}
|
||||
SetCurrentBaseInstance(0);
|
||||
ExecuteIndexedIndirectCommands(mode, type, indexSize, drawData->data() + commandOffset, commandOffset,
|
||||
/*hasIndirectBuffer=*/true, static_cast<GLsizei>(actualDrawCount), stride,
|
||||
"MultiDrawElementsIndirectCount");
|
||||
}
|
||||
|
||||
void MultiDrawArraysIndirect(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride) {
|
||||
@@ -1284,18 +1384,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return;
|
||||
}
|
||||
|
||||
for (GLsizei i = 0; i < drawcount; ++i) {
|
||||
DrawArraysIndirectCommand cmd{};
|
||||
std::memcpy(&cmd, commandBytes + static_cast<SizeT>(i) * stride, sizeof(cmd));
|
||||
if (cmd.count == 0 || cmd.instanceCount == 0) {
|
||||
continue;
|
||||
}
|
||||
SetCurrentBaseInstance(cmd.baseInstance);
|
||||
g_GLESFuncs.glDrawArraysInstanced(
|
||||
mode, static_cast<GLint>(cmd.first), static_cast<GLsizei>(cmd.count),
|
||||
static_cast<GLsizei>(cmd.instanceCount));
|
||||
}
|
||||
SetCurrentBaseInstance(0);
|
||||
const Bool hasIndirectBuffer =
|
||||
MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject() != nullptr;
|
||||
ExecuteArraysIndirectCommands(mode, commandBytes, reinterpret_cast<SizeT>(indirect), hasIndirectBuffer,
|
||||
drawcount, stride, "MultiDrawArraysIndirect");
|
||||
}
|
||||
|
||||
void DrawRangeElementsBaseVertex(GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type,
|
||||
@@ -1358,18 +1450,11 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return;
|
||||
}
|
||||
|
||||
DrawElementsIndirectCommand cmd{};
|
||||
std::memcpy(&cmd, commandBytes, sizeof(cmd));
|
||||
if (cmd.count == 0 || cmd.instanceCount == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
SetCurrentBaseInstance(cmd.baseInstance);
|
||||
const auto indexByteOffset = static_cast<SizeT>(cmd.firstIndex) * indexSize;
|
||||
g_GLESFuncs.glDrawElementsInstancedBaseVertex(
|
||||
mode, static_cast<GLsizei>(cmd.count), type, reinterpret_cast<const GLvoid*>(indexByteOffset),
|
||||
static_cast<GLsizei>(cmd.instanceCount), cmd.baseVertex);
|
||||
SetCurrentBaseInstance(0);
|
||||
const Bool hasIndirectBuffer =
|
||||
MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject() != nullptr;
|
||||
ExecuteIndexedIndirectCommands(mode, type, indexSize, commandBytes, reinterpret_cast<SizeT>(indirect),
|
||||
hasIndirectBuffer, 1, sizeof(DrawElementsIndirectCommand),
|
||||
"DrawElementsIndirect");
|
||||
}
|
||||
|
||||
void DrawArraysInstancedBaseInstance(GLenum mode, GLint first, GLsizei count, GLsizei instancecount,
|
||||
@@ -1397,17 +1482,10 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
return;
|
||||
}
|
||||
|
||||
DrawArraysIndirectCommand cmd{};
|
||||
std::memcpy(&cmd, commandBytes, sizeof(cmd));
|
||||
if (cmd.count == 0 || cmd.instanceCount == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
SetCurrentBaseInstance(cmd.baseInstance);
|
||||
g_GLESFuncs.glDrawArraysInstanced(
|
||||
mode, static_cast<GLint>(cmd.first), static_cast<GLsizei>(cmd.count),
|
||||
static_cast<GLsizei>(cmd.instanceCount));
|
||||
SetCurrentBaseInstance(0);
|
||||
const Bool hasIndirectBuffer =
|
||||
MG_State::pGLContext->GetBufferBindingSlot(BufferTarget::DrawIndirect).GetBoundObject() != nullptr;
|
||||
ExecuteArraysIndirectCommands(mode, commandBytes, reinterpret_cast<SizeT>(indirect), hasIndirectBuffer, 1,
|
||||
sizeof(DrawArraysIndirectCommand), "DrawArraysIndirect");
|
||||
}
|
||||
|
||||
void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
namespace MobileGL::MG_Backend::DirectGLES {
|
||||
String EmulateBaseInstanceInVertexShader(String source, GLenum shaderType);
|
||||
String PromoteDrawParameterGlobalsToUniforms(String source, GLenum shaderType);
|
||||
|
||||
template <typename StateObject, typename BackendObject>
|
||||
class StateBackendObjectRegistry {
|
||||
@@ -316,6 +317,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
void SyncToBackend(const SharedPtr<MG_State::GLState::ProgramObject>& stateProgramObject);
|
||||
void Use() const;
|
||||
void SetBaseInstance(Uint32 baseInstance) const;
|
||||
void SetDrawID(Uint32 drawId) const;
|
||||
Uint GetBackendProgramId() const { return m_backendProgramId; }
|
||||
Uint GetBackendGlobalUBOId() const { return m_backendGlobalUBOId; }
|
||||
Uint32 GetSnormFallbackClampOutputMask() const { return m_snormFallbackClampOutputMask; }
|
||||
@@ -325,6 +327,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
Uint m_backendProgramId = 0;
|
||||
Uint m_backendGlobalUBOId = 0;
|
||||
Int m_baseInstanceUniformLocation = -1;
|
||||
Int m_drawIdUniformLocation = -1;
|
||||
Uint32 m_snormFallbackClampOutputMask = 0;
|
||||
Uint32 m_unormFallbackClampOutputMask = 0;
|
||||
Bool m_isInitialized = false;
|
||||
|
||||
@@ -574,6 +574,9 @@ namespace MobileGL::MG_Util::BackendLoader {
|
||||
if (std::strcmp(extension, "GL_EXT_texture_norm16") == 0) {
|
||||
caps.SupportsNorm16Texture = true;
|
||||
}
|
||||
if (std::strcmp(extension, "GL_EXT_base_instance") == 0) {
|
||||
caps.SupportsBaseInstance = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1018,6 +1018,7 @@ namespace MobileGL {
|
||||
String GLESShadingLanguageVersionString;
|
||||
Bool SupportsPersistentMapping = false;
|
||||
Bool SupportsNorm16Texture = false;
|
||||
Bool SupportsBaseInstance = false;
|
||||
Int UniformBufferOffsetAlignment = 256;
|
||||
Float AliasedLineWidthRangeMin = 1.0f;
|
||||
Float AliasedLineWidthRangeMax = 1.0f;
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "SpirvPasses/FlattenInterfaceStructPass.h"
|
||||
#include "SpirvPasses/RenameSamplerFunctionParameterPass.h"
|
||||
#include "SpirvPasses/DecomposeWorkgroupVec3Pass.h"
|
||||
#include "SpirvPasses/LowerDrawParametersPass.h"
|
||||
#include "spirv-tools/libspirv.h"
|
||||
#include "spirv-tools/optimizer.hpp"
|
||||
|
||||
@@ -251,6 +252,18 @@ namespace MobileGL {
|
||||
return optimizer.Run(inputBinary.data(), inputBinary.size(), &outputBinary, options);
|
||||
}
|
||||
|
||||
bool ShaderCompiler::LowerDrawParametersForEssl(const Vector<Uint32>& inputBinary,
|
||||
Vector<uint32_t>& outputBinary) {
|
||||
using namespace spvtools;
|
||||
OptimizerOptions options;
|
||||
options.set_run_validator(false);
|
||||
|
||||
Optimizer optimizer(SPV_ENV_VULKAN_1_1);
|
||||
optimizer.RegisterPass(LowerDrawParametersPass::CreateLowerDrawParametersPass());
|
||||
|
||||
return optimizer.Run(inputBinary.data(), inputBinary.size(), &outputBinary, options);
|
||||
}
|
||||
|
||||
Result<String> ShaderCompiler::DecompileShader(SpvcSession& session) {
|
||||
spvc_compiler_options options;
|
||||
session.CreateOptions(&options);
|
||||
|
||||
@@ -22,6 +22,11 @@ namespace MobileGL {
|
||||
static Result<Vector<Vector<unsigned>>> GetSpirvBinaryFromProgram(const ProgramBinaryAttrib& attrib);
|
||||
static bool SanitizeAndOptimizeBinary(const Vector<Uint32>& inputBinary,
|
||||
Vector<uint32_t>& outputBinary);
|
||||
// Demotes DrawIndex/BaseInstance/BaseVertex builtins to plain Private globals
|
||||
// (mg_DrawID/mg_BaseInstance/mg_BaseVertex) so SPIRV-Cross can emit ESSL.
|
||||
// Only for backends without native draw-parameter support (DirectGLES).
|
||||
static bool LowerDrawParametersForEssl(const Vector<Uint32>& inputBinary,
|
||||
Vector<uint32_t>& outputBinary);
|
||||
static Result<String> DecompileShader(SpvcSession& session);
|
||||
};
|
||||
} // namespace ShaderTranspiler
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
// MobileGL - MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/LowerDrawParametersPass.cpp
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
#include "LowerDrawParametersPass.h"
|
||||
|
||||
#include "spirv.hpp"
|
||||
#include "source/opt/def_use_manager.h"
|
||||
#include "source/opt/instruction.h"
|
||||
#include "source/opt/ir_context.h"
|
||||
#include "source/opt/module.h"
|
||||
#include "source/util/make_unique.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace MobileGL {
|
||||
namespace MG_Util {
|
||||
namespace ShaderTranspiler {
|
||||
namespace {
|
||||
using spvtools::opt::Instruction;
|
||||
using spvtools::opt::IRContext;
|
||||
using spvtools::opt::Operand;
|
||||
|
||||
const char* LoweredNameForBuiltin(spv::BuiltIn builtin) {
|
||||
switch (builtin) {
|
||||
case spv::BuiltIn::BaseVertex:
|
||||
return "mg_BaseVertex";
|
||||
case spv::BuiltIn::BaseInstance:
|
||||
return "mg_BaseInstance";
|
||||
case spv::BuiltIn::DrawIndex:
|
||||
return "mg_DrawID";
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void ReplaceName(IRContext* context, uint32_t id, const char* name) {
|
||||
for (auto& debugInst : context->debugs2()) {
|
||||
if (debugInst.opcode() == spv::Op::OpName && debugInst.GetSingleWordInOperand(0) == id) {
|
||||
debugInst.SetInOperand(
|
||||
1, spvtools::utils::MakeVector<spvtools::opt::Operand::OperandData>(name));
|
||||
return;
|
||||
}
|
||||
}
|
||||
context->AddDebug2Inst(spvtools::MakeUnique<Instruction>(
|
||||
context, spv::Op::OpName, 0, 0,
|
||||
std::initializer_list<Operand>{
|
||||
{SPV_OPERAND_TYPE_ID, {id}},
|
||||
{SPV_OPERAND_TYPE_LITERAL_STRING, spvtools::utils::MakeVector(name)}}));
|
||||
}
|
||||
|
||||
void RemoveFromEntryPointInterfaces(IRContext* context, uint32_t id) {
|
||||
for (Instruction& entryPoint : context->module()->entry_points()) {
|
||||
std::vector<Operand> newOperands;
|
||||
Bool changed = false;
|
||||
for (uint32_t i = 0; i < entryPoint.NumInOperands(); ++i) {
|
||||
const Operand& operand = entryPoint.GetInOperand(i);
|
||||
// Interface ids start after execution model, entry-point id and name.
|
||||
if (i >= 3 && operand.type == SPV_OPERAND_TYPE_ID &&
|
||||
entryPoint.GetSingleWordInOperand(i) == id) {
|
||||
changed = true;
|
||||
continue;
|
||||
}
|
||||
newOperands.push_back(operand);
|
||||
}
|
||||
if (changed) {
|
||||
entryPoint.SetInOperands(std::move(newOperands));
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
spvtools::opt::Pass::Status LowerDrawParametersPass::Process() {
|
||||
auto* irContext = context();
|
||||
auto* defUseMgr = irContext->get_def_use_mgr();
|
||||
|
||||
// Collect the BuiltIn decorations we want to lower first; mutating while
|
||||
// iterating annotations invalidates the range.
|
||||
struct LoweredVariable {
|
||||
Instruction* variable = nullptr;
|
||||
Instruction* decoration = nullptr;
|
||||
const char* name = nullptr;
|
||||
};
|
||||
std::vector<LoweredVariable> targets;
|
||||
|
||||
for (auto& annotation : irContext->annotations()) {
|
||||
if (annotation.opcode() != spv::Op::OpDecorate ||
|
||||
static_cast<spv::Decoration>(annotation.GetSingleWordInOperand(1)) !=
|
||||
spv::Decoration::BuiltIn) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto builtin = static_cast<spv::BuiltIn>(annotation.GetSingleWordInOperand(2));
|
||||
const char* loweredName = LoweredNameForBuiltin(builtin);
|
||||
if (loweredName == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Instruction* variable = defUseMgr->GetDef(annotation.GetSingleWordInOperand(0));
|
||||
if (variable == nullptr || variable->opcode() != spv::Op::OpVariable ||
|
||||
static_cast<spv::StorageClass>(variable->GetSingleWordInOperand(0)) !=
|
||||
spv::StorageClass::Input) {
|
||||
continue;
|
||||
}
|
||||
|
||||
targets.push_back({variable, &annotation, loweredName});
|
||||
}
|
||||
|
||||
if (targets.empty()) {
|
||||
return Status::SuccessWithoutChange;
|
||||
}
|
||||
|
||||
auto* typeMgr = irContext->get_type_mgr();
|
||||
|
||||
for (auto& target : targets) {
|
||||
Instruction* variable = target.variable;
|
||||
const uint32_t variableId = variable->result_id();
|
||||
|
||||
// Demote the Input builtin to a plain Private global.
|
||||
Instruction* pointerType = defUseMgr->GetDef(variable->type_id());
|
||||
const uint32_t pointeeTypeId = pointerType->GetSingleWordInOperand(1);
|
||||
const uint32_t privatePointerTypeId =
|
||||
typeMgr->FindPointerToType(pointeeTypeId, spv::StorageClass::Private);
|
||||
variable->SetResultType(privatePointerTypeId);
|
||||
variable->SetInOperand(0, {static_cast<uint32_t>(spv::StorageClass::Private)});
|
||||
|
||||
irContext->KillInst(target.decoration);
|
||||
RemoveFromEntryPointInterfaces(irContext, variableId);
|
||||
ReplaceName(irContext, variableId, target.name);
|
||||
}
|
||||
|
||||
// The DrawParameters capability only covered these builtins; it must not leak
|
||||
// into the ESSL decompile.
|
||||
std::vector<Instruction*> deadModuleInsts;
|
||||
for (auto& capability : irContext->module()->capabilities()) {
|
||||
if (static_cast<spv::Capability>(capability.GetSingleWordInOperand(0)) ==
|
||||
spv::Capability::DrawParameters) {
|
||||
deadModuleInsts.push_back(&capability);
|
||||
}
|
||||
}
|
||||
for (auto& extension : irContext->module()->extensions()) {
|
||||
if (extension.GetInOperand(0).AsString() == "SPV_KHR_shader_draw_parameters") {
|
||||
deadModuleInsts.push_back(&extension);
|
||||
}
|
||||
}
|
||||
for (auto* inst : deadModuleInsts) {
|
||||
irContext->KillInst(inst);
|
||||
}
|
||||
|
||||
return Status::SuccessWithChange;
|
||||
}
|
||||
|
||||
spvtools::Optimizer::PassToken LowerDrawParametersPass::CreateLowerDrawParametersPass() {
|
||||
return spvtools::Optimizer::PassToken(MakeUnique<LowerDrawParametersPass>());
|
||||
}
|
||||
} // namespace ShaderTranspiler
|
||||
} // namespace MG_Util
|
||||
} // namespace MobileGL
|
||||
@@ -0,0 +1,34 @@
|
||||
// MobileGL - MobileGL/MG_Util/ShaderTranspiler/SpirvPasses/LowerDrawParametersPass.h
|
||||
// Copyright (c) 2025-2026 MobileGL-Dev
|
||||
// Licensed under the GNU Lesser General Public License v3.0:
|
||||
// https://www.gnu.org/licenses/gpl-3.0.txt
|
||||
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
// End of Source File Header
|
||||
|
||||
#pragma once
|
||||
#include "source/opt/pass.h"
|
||||
#include "spirv-tools/optimizer.hpp"
|
||||
|
||||
#include <Includes.h>
|
||||
|
||||
namespace MobileGL {
|
||||
namespace MG_Util {
|
||||
namespace ShaderTranspiler {
|
||||
// ESSL has no gl_DrawID / gl_BaseInstance / gl_BaseVertex builtins and SPIRV-Cross
|
||||
// refuses to emit them for ES targets. This pass demotes the DrawIndex /
|
||||
// BaseInstance / BaseVertex builtin inputs to plain Private globals with
|
||||
// well-known names (mg_DrawID / mg_BaseInstance / mg_BaseVertex) so the decompiled
|
||||
// ESSL declares ordinary globals; the DirectGLES program manager then upgrades the
|
||||
// declarations to uniforms and feeds them per (sub-)draw. Only meant for the
|
||||
// DirectGLES transpile path - the Vulkan backend keeps the native builtins.
|
||||
class LowerDrawParametersPass : public spvtools::opt::Pass {
|
||||
public:
|
||||
const char* name() const override { return "lower-draw-parameters"; }
|
||||
Status Process() override;
|
||||
|
||||
static spvtools::Optimizer::PassToken CreateLowerDrawParametersPass();
|
||||
};
|
||||
} // namespace ShaderTranspiler
|
||||
} // namespace MG_Util
|
||||
} // namespace MobileGL
|
||||
Reference in New Issue
Block a user