mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-12 06:08: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:
@@ -239,6 +239,26 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
MG_Backend::gBackendFunctionsTable.GL.DrawArraysIndirect(mode, indirect);
|
||||
}
|
||||
|
||||
// Flywheel-style engines write GPU-copy descriptors into a FLUSH_EXPLICIT
|
||||
// persistent map and glBindBufferRange that span as an SSBO for a compute
|
||||
// dispatch WITHOUT ever flushing it (undefined per spec, but real drivers'
|
||||
// persistent maps alias GPU-visible memory, so it works there). MobileGL's
|
||||
// persistent maps alias the CPU shadow, so those bytes would never reach the
|
||||
// GPU: push every explicitly-ranged SSBO binding of such maps down right
|
||||
// before each dispatch. Whole-buffer (BindBufferBase) bindings are excluded
|
||||
// on purpose — ranges the app DID flush already arrived, and re-uploading a
|
||||
// 16MB staging ring per dispatch would be prohibitive.
|
||||
static void SyncUnflushedMappedSsboRangesForDispatch() {
|
||||
const auto pointCount = MG_State::pGLContext->GetBufferBindingPointCount(BufferTarget::ShaderStorage);
|
||||
for (SizeT i = 0; i < pointCount; ++i) {
|
||||
auto& point = MG_State::pGLContext->GetBufferBindingPoint(BufferTarget::ShaderStorage, i);
|
||||
if (!point.HasExplicitRange()) continue;
|
||||
const auto& bufferObject = point.GetBoundObject();
|
||||
if (!bufferObject) continue;
|
||||
bufferObject->SyncMappedRangeForGpuRead(point.GetRange());
|
||||
}
|
||||
}
|
||||
|
||||
/* @INSERTION_POINT:FUNCTION_IMPLEMENTATION@ */
|
||||
void DispatchCompute(GLuint numGroupsX, GLuint numGroupsY, GLuint numGroupsZ) {
|
||||
auto dispatchCompute = MG_Backend::gBackendFunctionsTable.GL.DispatchCompute;
|
||||
@@ -249,6 +269,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
return;
|
||||
}
|
||||
if (!ValidateCurrentProgramForCompute(__func__)) return;
|
||||
SyncUnflushedMappedSsboRangesForDispatch();
|
||||
dispatchCompute(numGroupsX, numGroupsY, numGroupsZ);
|
||||
}
|
||||
|
||||
@@ -262,6 +283,7 @@ namespace MobileGL::MG_Impl::GLImpl {
|
||||
return;
|
||||
}
|
||||
if (!ValidateCurrentProgramForCompute(__func__)) return;
|
||||
SyncUnflushedMappedSsboRangesForDispatch();
|
||||
dispatchComputeIndirect(indirect);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,56 +7,120 @@
|
||||
// End of Source File Header
|
||||
|
||||
#include "GL_Sync.h"
|
||||
#include <MG_Backend/BackendObjects.h>
|
||||
|
||||
namespace MobileGL::MG_Impl::GLImpl {
|
||||
namespace {
|
||||
int g_stubSyncObject = 0;
|
||||
}
|
||||
// Frontend sync object: wraps an optional backend fence handle. A null
|
||||
// backend handle (backend has no fence support, or could not create a
|
||||
// fence at call time) keeps the legacy always-signaled behavior.
|
||||
struct SyncObject {
|
||||
MG_Backend::BackendSyncHandle backendHandle = nullptr;
|
||||
GLenum condition = GL_SYNC_GPU_COMMANDS_COMPLETE;
|
||||
GLbitfield flags = 0;
|
||||
};
|
||||
|
||||
// glSync semantics not really needed right now, stubbing them out
|
||||
// Sync calls may arrive from any thread (launchers migrate the context
|
||||
// across JVM threads), so the live-object registry is mutex-guarded.
|
||||
// Entries left at process shutdown are simply dropped; their backend
|
||||
// handles die with the backend.
|
||||
std::mutex g_syncObjectsMutex;
|
||||
UnorderedMap<GLsync, SyncObject*> g_liveSyncObjects;
|
||||
|
||||
SyncObject* FindSyncObject(GLsync sync) {
|
||||
const std::lock_guard<std::mutex> lock(g_syncObjectsMutex);
|
||||
const auto it = g_liveSyncObjects.find(sync);
|
||||
return it != g_liveSyncObjects.end() ? it->second : nullptr;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
GLsync FenceSync(GLenum condition, GLbitfield flags) {
|
||||
(void)condition;
|
||||
(void)flags;
|
||||
return reinterpret_cast<GLsync>(&g_stubSyncObject);
|
||||
auto* syncObject = new SyncObject;
|
||||
syncObject->condition = condition;
|
||||
syncObject->flags = flags;
|
||||
if (const auto backendFenceSync = MG_Backend::gBackendFunctionsTable.GL.FenceSync) {
|
||||
syncObject->backendHandle = backendFenceSync();
|
||||
}
|
||||
const GLsync handle = reinterpret_cast<GLsync>(syncObject);
|
||||
const std::lock_guard<std::mutex> lock(g_syncObjectsMutex);
|
||||
g_liveSyncObjects[handle] = syncObject;
|
||||
return handle;
|
||||
}
|
||||
|
||||
GLboolean IsSync(GLsync sync) {
|
||||
return sync != nullptr ? GL_TRUE : GL_FALSE;
|
||||
return FindSyncObject(sync) != nullptr ? GL_TRUE : GL_FALSE;
|
||||
}
|
||||
|
||||
GLenum ClientWaitSync(GLsync sync, GLbitfield flags, GLuint64 timeout) {
|
||||
(void)sync;
|
||||
(void)flags;
|
||||
(void)timeout;
|
||||
return GL_ALREADY_SIGNALED;
|
||||
const auto* syncObject = FindSyncObject(sync);
|
||||
if (!syncObject) {
|
||||
return GL_WAIT_FAILED;
|
||||
}
|
||||
const auto backendClientWaitSync = MG_Backend::gBackendFunctionsTable.GL.ClientWaitSync;
|
||||
if (!backendClientWaitSync || !syncObject->backendHandle) {
|
||||
return GL_ALREADY_SIGNALED; // legacy always-signaled fallback
|
||||
}
|
||||
return backendClientWaitSync(syncObject->backendHandle, flags, timeout);
|
||||
}
|
||||
|
||||
void WaitSync(GLsync sync, GLbitfield flags, GLuint64 timeout) {
|
||||
(void)sync;
|
||||
(void)flags;
|
||||
(void)timeout;
|
||||
const auto* syncObject = FindSyncObject(sync);
|
||||
if (!syncObject) {
|
||||
return;
|
||||
}
|
||||
const auto backendWaitSync = MG_Backend::gBackendFunctionsTable.GL.WaitSync;
|
||||
if (backendWaitSync && syncObject->backendHandle) {
|
||||
backendWaitSync(syncObject->backendHandle, flags, timeout);
|
||||
}
|
||||
}
|
||||
|
||||
void DeleteSync(GLsync sync) {
|
||||
(void)sync;
|
||||
if (sync == nullptr) {
|
||||
return; // glDeleteSync(0) is silently ignored
|
||||
}
|
||||
SyncObject* syncObject = nullptr;
|
||||
{
|
||||
const std::lock_guard<std::mutex> lock(g_syncObjectsMutex);
|
||||
const auto it = g_liveSyncObjects.find(sync);
|
||||
if (it == g_liveSyncObjects.end()) {
|
||||
return;
|
||||
}
|
||||
syncObject = it->second;
|
||||
g_liveSyncObjects.erase(it);
|
||||
}
|
||||
const auto backendDeleteSync = MG_Backend::gBackendFunctionsTable.GL.DeleteSync;
|
||||
if (backendDeleteSync && syncObject->backendHandle) {
|
||||
backendDeleteSync(syncObject->backendHandle);
|
||||
}
|
||||
delete syncObject;
|
||||
}
|
||||
|
||||
void GetSynciv(GLsync sync, GLenum pname, GLsizei bufSize, GLsizei* length, GLint* values) {
|
||||
(void)sync;
|
||||
const auto* syncObject = FindSyncObject(sync);
|
||||
if (!syncObject) {
|
||||
if (length) {
|
||||
*length = 0;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
GLint value = 0;
|
||||
switch (pname) {
|
||||
case GL_OBJECT_TYPE:
|
||||
value = GL_SYNC_FENCE;
|
||||
break;
|
||||
case GL_SYNC_STATUS:
|
||||
value = GL_SIGNALED;
|
||||
case GL_SYNC_STATUS: {
|
||||
const auto backendGetSyncStatus = MG_Backend::gBackendFunctionsTable.GL.GetSyncStatus;
|
||||
const Bool signaled = !backendGetSyncStatus || !syncObject->backendHandle ||
|
||||
backendGetSyncStatus(syncObject->backendHandle);
|
||||
value = signaled ? GL_SIGNALED : GL_UNSIGNALED;
|
||||
break;
|
||||
}
|
||||
case GL_SYNC_CONDITION:
|
||||
value = GL_SYNC_GPU_COMMANDS_COMPLETE;
|
||||
value = static_cast<GLint>(syncObject->condition);
|
||||
break;
|
||||
case GL_SYNC_FLAGS:
|
||||
value = 0;
|
||||
value = static_cast<GLint>(syncObject->flags);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user