mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-11 13:48:30 +09:00
[Fix] (MG_State/MG_Impl/MG_Backend): render Flywheel instanced+indirect on both backends
Create 6 / Flywheel 1.0.6 now renders correctly with both flywheel:instancing and flywheel:indirect on DirectGLES and DirectVulkan (verified in-game on Adreno 830: waterwheels and cogwheels solid, animated, correct pairing, no crashes across all four combinations). - MG_State/MG_Impl: sync explicitly-ranged SSBO bindings of FLUSH_EXPLICIT persistent maps to the backend before compute dispatches. Flywheel writes its scatter-copy descriptors into the staging ring's persistent map and never flushes that span (UB per spec, works on drivers whose maps alias GPU-visible memory); our maps alias the CPU shadow, so the descriptors never reached the GPU: the scatter compute copied nothing (GLES: empty draw commands) or stale garbage (Vulkan: wild indirect commands ending in VK_ERROR_DEVICE_LOST). - MG_Impl/MG_Backend: real glFenceSync objects backed by backend fences (GLES: native ES syncs guarded by context generation and owner thread; Vulkan: buffer-manager frame serials), replacing always-signaled stubs that let Flywheel reclaim staging memory the GPU still reads. - MG_Backend/DirectGLES: compute dispatches now run the same per-program resource sync as draws (uniform-block bindings and sampler units must be re-established through the API because layout(binding) is stripped from transpiled ESSL) and rebind texture units afterwards; the cull shader used to read a stale _FlwFrameUniforms binding and the depth-pyramid downsample sampled a stale unit-0 texture, zeroing the Hi-Z pyramid and occlusion-culling all Flywheel geometry. Image uniforms are excluded from glUniform1i (ES bakes their unit via layout(binding)); image-unit sync is clamped to the device limit; eliminated/SSBO-classified uniform blocks are skipped. - MG_Backend/DirectGLES: gl_BaseInstance in native indirect draws reads the GPU-written command buffer through an injected mg_IndirectParams SSBO view addressed per draw instead of the zero CPU shadow; layout(binding) is preserved for SSBO/image declarations (ES has no API rebinding for them); the ES context ownership claim moved to a global atomic owner thread with an EGL ground-truth check, and deferred buffer op state is mutex-guarded, so ops cannot silently no-op after context migration. - MG_Backend/DirectVulkan: new RebaseInstanceIndexPass rewrites vertex InstanceIndex loads to (InstanceIndex - BaseInstance). glslang's relaxed Vulkan mode aliases gl_InstanceID to InstanceIndex, which includes firstInstance, but GL's gl_InstanceID is zero-based - draws with nonzero baseInstance paired meshes with wrong instance data (cogwheel drawn as a waterwheel, another wheel collapsed invisible). Gated on the shaderDrawParameters device feature. Sampled-read barriers additionally cover the compute stage (the Hi-Z downsample samples the depth attachment from compute), and short uniform-buffer ranges keep the existing zero-padding. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -34,6 +34,9 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
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";
|
||||
constexpr const char* BASE_INSTANCE_LOWERED_NAME = "mg_BaseInstanceLowered";
|
||||
constexpr const char* BASE_INSTANCE_WORD_INDEX_UNIFORM_NAME = "mg_BaseInstanceWordIndex";
|
||||
constexpr const char* INDIRECT_PARAMS_BLOCK_NAME = "mg_IndirectParams";
|
||||
|
||||
static Bool IsAngleLlvmpipeRenderer() {
|
||||
return g_GLESCapabilities.GLESRendererString.find("ANGLE") != String::npos &&
|
||||
@@ -111,20 +114,28 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
if (shaderType != GL_VERTEX_SHADER || source.find("gl_BaseInstance") == String::npos) {
|
||||
return source;
|
||||
}
|
||||
source = ReplaceIdentifier(std::move(source), "gl_BaseInstance", BASE_INSTANCE_UNIFORM_NAME);
|
||||
return InjectUniformAfterVersion(std::move(source),
|
||||
String replaced = ReplaceIdentifier(source, "gl_BaseInstance", BASE_INSTANCE_UNIFORM_NAME);
|
||||
if (replaced == source) {
|
||||
// Only a substring hit (e.g. gl_BaseInstanceARB inside a SPIRV-Cross #ifdef
|
||||
// fallback); nothing was rewritten, so nothing must be declared either.
|
||||
return source;
|
||||
}
|
||||
return InjectUniformAfterVersion(std::move(replaced),
|
||||
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.
|
||||
// Private globals (mg_DrawID / mg_BaseInstanceLowered / mg_BaseVertex); SPIRV-Cross then
|
||||
// emits them as ordinary global declarations. mg_DrawID / mg_BaseVertex become uniforms fed
|
||||
// per (sub-)draw. gl_BaseInstance is special: for indirect draws its value lives in the
|
||||
// (possibly GPU-written) indirect command buffer, so its declaration expands into a
|
||||
// std430 SSBO view of that buffer indexed by a CPU-computed word index, with the plain
|
||||
// mg_BaseInstance uniform as the fallback for non-indirect draws.
|
||||
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* name : {DRAW_ID_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 + ";";
|
||||
@@ -143,6 +154,29 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (const char* declPrefix : {"highp int ", "mediump int ", "lowp int ", "int "}) {
|
||||
const String declaration = String(declPrefix) + BASE_INSTANCE_LOWERED_NAME + ";";
|
||||
const SizeT pos = source.find(declaration);
|
||||
if (pos == String::npos) {
|
||||
continue;
|
||||
}
|
||||
const Int paramsBinding = g_GLESCapabilities.MaxShaderStorageBufferBindings > 0
|
||||
? g_GLESCapabilities.MaxShaderStorageBufferBindings - 1
|
||||
: 0;
|
||||
String machinery;
|
||||
if (source.find(String("uniform highp int ") + BASE_INSTANCE_UNIFORM_NAME + ";") == String::npos) {
|
||||
machinery += String("uniform highp int ") + BASE_INSTANCE_UNIFORM_NAME + ";\n";
|
||||
}
|
||||
machinery += String("uniform highp int ") + BASE_INSTANCE_WORD_INDEX_UNIFORM_NAME + ";\n";
|
||||
machinery += String("layout(std430, binding = ") + std::to_string(paramsBinding) +
|
||||
") readonly buffer " + INDIRECT_PARAMS_BLOCK_NAME +
|
||||
" { highp uint mg_indirectWords[]; };\n";
|
||||
machinery += String("#define ") + BASE_INSTANCE_LOWERED_NAME + " ((" +
|
||||
BASE_INSTANCE_WORD_INDEX_UNIFORM_NAME + " >= 0) ? int(mg_indirectWords[uint(" +
|
||||
BASE_INSTANCE_WORD_INDEX_UNIFORM_NAME + ")]) : " + BASE_INSTANCE_UNIFORM_NAME + ")";
|
||||
source.replace(pos, declaration.size(), machinery);
|
||||
break;
|
||||
}
|
||||
return source;
|
||||
}
|
||||
|
||||
@@ -196,6 +230,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
resource.storageSize == bufferObject.GetSize();
|
||||
}
|
||||
|
||||
|
||||
|
||||
void UploadRangeNow(GLESBufferResource& resource, BufferObject& bufferObject, SizeT start, SizeT end) {
|
||||
#ifdef TRACY_ENABLE
|
||||
ZoneScopedC(TRACY_ZONECOLOR_BACKEND);
|
||||
@@ -2050,6 +2086,18 @@ 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);
|
||||
m_baseInstanceWordIndexUniformLocation =
|
||||
g_GLESFuncs.glGetUniformLocation(m_backendProgramId, BASE_INSTANCE_WORD_INDEX_UNIFORM_NAME);
|
||||
// The mg_IndirectParams block binding is baked into the ESSL (ES cannot rebind
|
||||
// SSBO blocks after compile); record it so draws bind the indirect buffer there.
|
||||
m_indirectParamsBinding = -1;
|
||||
if (m_baseInstanceWordIndexUniformLocation >= 0 && g_GLESFuncs.glGetProgramResourceIndex) {
|
||||
const GLuint blockIndex = g_GLESFuncs.glGetProgramResourceIndex(
|
||||
m_backendProgramId, GL_SHADER_STORAGE_BLOCK, INDIRECT_PARAMS_BLOCK_NAME);
|
||||
if (blockIndex != GL_INVALID_INDEX && g_GLESCapabilities.MaxShaderStorageBufferBindings > 0) {
|
||||
m_indirectParamsBinding = g_GLESCapabilities.MaxShaderStorageBufferBindings - 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Create global UBO
|
||||
if (stateProgramObject->GetUBOSize() > 0) {
|
||||
@@ -2074,10 +2122,19 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
}
|
||||
|
||||
void BackendProgramObjectImpl::SetBaseInstance(Uint32 baseInstance) const {
|
||||
if (m_baseInstanceUniformLocation < 0) {
|
||||
return;
|
||||
if (m_baseInstanceUniformLocation >= 0) {
|
||||
g_GLESFuncs.glUniform1i(m_baseInstanceUniformLocation, static_cast<GLint>(baseInstance));
|
||||
}
|
||||
// A direct value disables the indirect-command-buffer read.
|
||||
if (m_baseInstanceWordIndexUniformLocation >= 0) {
|
||||
g_GLESFuncs.glUniform1i(m_baseInstanceWordIndexUniformLocation, -1);
|
||||
}
|
||||
}
|
||||
|
||||
void BackendProgramObjectImpl::SetBaseInstanceWordIndex(Int32 wordIndex) const {
|
||||
if (m_baseInstanceWordIndexUniformLocation >= 0) {
|
||||
g_GLESFuncs.glUniform1i(m_baseInstanceWordIndexUniformLocation, wordIndex);
|
||||
}
|
||||
g_GLESFuncs.glUniform1i(m_baseInstanceUniformLocation, static_cast<GLint>(baseInstance));
|
||||
}
|
||||
|
||||
void BackendProgramObjectImpl::SetDrawID(Uint32 drawId) const {
|
||||
|
||||
Reference in New Issue
Block a user