mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-08 04:08:32 +09:00
237 lines
11 KiB
C++
237 lines
11 KiB
C++
// MobileGL - MobileGL/MG_Impl/GLImpl/Sync/GL_Sync.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 "GL_Sync.h"
|
|
#include <MG_Backend/BackendObjects.h>
|
|
#include <MG_State/GLState/Core.h>
|
|
|
|
namespace MobileGL::MG_Impl::GLImpl {
|
|
namespace {
|
|
// 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;
|
|
};
|
|
|
|
// 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) {
|
|
// GL 4.6 core 4.1.2: GL_SYNC_GPU_COMMANDS_COMPLETE is the only condition and the only
|
|
// legal flags value is zero; both violations return 0 rather than a handle. A caller that
|
|
// then hands the 0 back to glDeleteSync hits the glDeleteSync(0) no-op below.
|
|
if (condition != GL_SYNC_GPU_COMMANDS_COMPLETE) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidEnum,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
|
"condition must be GL_SYNC_GPU_COMMANDS_COMPLETE."));
|
|
return nullptr;
|
|
}
|
|
if (flags != 0) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "flags must be zero."));
|
|
return nullptr;
|
|
}
|
|
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 FindSyncObject(sync) != nullptr ? GL_TRUE : GL_FALSE;
|
|
}
|
|
|
|
GLenum ClientWaitSync(GLsync sync, GLbitfield flags, GLuint64 timeout) {
|
|
// GL 4.6 core 4.1.1: GL_SYNC_FLUSH_COMMANDS_BIT is the only bit this call accepts, and
|
|
// any other bit is INVALID_VALUE. Silently ignoring the stray bits used to make a caller
|
|
// that passed, say, GL_SYNC_GPU_COMMANDS_COMPLETE by mistake think it had asked for a
|
|
// flush it never got.
|
|
if ((flags & ~static_cast<GLbitfield>(GL_SYNC_FLUSH_COMMANDS_BIT)) != 0) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
|
"flags must be zero or GL_SYNC_FLUSH_COMMANDS_BIT."));
|
|
return GL_WAIT_FAILED;
|
|
}
|
|
const auto* syncObject = FindSyncObject(sync);
|
|
if (!syncObject) {
|
|
// The spec pairs the GL_WAIT_FAILED return with a recorded INVALID_VALUE; returning
|
|
// the enum alone left glGetError() clean and the failure indistinguishable from a
|
|
// genuine wait failure on a live sync.
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "sync is not the name of a sync object."));
|
|
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) {
|
|
// GL 4.6 core 4.1.2: the server-side wait takes no flags and no finite timeout - both
|
|
// arguments exist only to be forward-compatible, and anything else is INVALID_VALUE.
|
|
// Neither backend ever honored a nonzero timeout (DirectGLES hard-codes
|
|
// 0/GL_TIMEOUT_IGNORED, DirectVulkan's queue ordering makes the wait implicit), so
|
|
// rejecting the call loses no wait that used to happen.
|
|
if (flags != 0 || timeout != GL_TIMEOUT_IGNORED) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
|
"flags must be zero and timeout must be GL_TIMEOUT_IGNORED."));
|
|
return;
|
|
}
|
|
const auto* syncObject = FindSyncObject(sync);
|
|
if (!syncObject) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "sync is not the name of a sync object."));
|
|
return;
|
|
}
|
|
const auto backendWaitSync = MG_Backend::gBackendFunctionsTable.GL.WaitSync;
|
|
if (backendWaitSync && syncObject->backendHandle) {
|
|
backendWaitSync(syncObject->backendHandle, flags, timeout);
|
|
}
|
|
}
|
|
|
|
void DeleteSync(GLsync 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) {
|
|
// GL 4.6 core 4.1: a negative bufSize is INVALID_VALUE, an unnamed sync is INVALID_VALUE
|
|
// and an unrecognised pname is INVALID_ENUM. All three used to leave glGetError() clean
|
|
// and write a plausible-looking zero, which is the one failure mode a caller cannot tell
|
|
// apart from a real answer - GL_SYNC_STATUS legitimately answers GL_UNSIGNALED (0x9118),
|
|
// but a mistyped pname answered a bare 0 that no query ever returns.
|
|
if (bufSize < 0) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "bufSize must not be negative."));
|
|
return;
|
|
}
|
|
const auto* syncObject = FindSyncObject(sync);
|
|
if (!syncObject) {
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidValue,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "sync is not the name of a sync object."));
|
|
if (length) {
|
|
*length = 0;
|
|
}
|
|
return;
|
|
}
|
|
|
|
GLint value = 0;
|
|
switch (pname) {
|
|
case GL_OBJECT_TYPE:
|
|
value = GL_SYNC_FENCE;
|
|
break;
|
|
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 = static_cast<GLint>(syncObject->condition);
|
|
break;
|
|
case GL_SYNC_FLAGS:
|
|
value = static_cast<GLint>(syncObject->flags);
|
|
break;
|
|
default:
|
|
MG_State::pGLContext->RecordError(
|
|
ErrorCode::InvalidEnum,
|
|
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
|
|
"pname must be GL_OBJECT_TYPE, GL_SYNC_STATUS, GL_SYNC_CONDITION or "
|
|
"GL_SYNC_FLAGS."));
|
|
if (length) {
|
|
*length = 0;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (length) {
|
|
*length = bufSize > 0 && values ? 1 : 0;
|
|
}
|
|
if (bufSize > 0 && values) {
|
|
values[0] = value;
|
|
}
|
|
}
|
|
|
|
void DestroyAllSyncObjects() {
|
|
// Detach the registry under the lock, release outside it. Entries the app
|
|
// already deleted were erased by DeleteSync, so nothing here double-frees;
|
|
// a DeleteSync racing this sweep finds an empty registry and returns. A
|
|
// thread still blocked inside ClientWaitSync/GetSynciv during teardown
|
|
// holds a raw SyncObject* these deletes invalidate - the same undefined
|
|
// race an app-driven DeleteSync already has.
|
|
UnorderedMap<GLsync, SyncObject*> orphans;
|
|
{
|
|
const std::lock_guard<std::mutex> lock(g_syncObjectsMutex);
|
|
orphans.swap(g_liveSyncObjects);
|
|
}
|
|
if (orphans.empty()) {
|
|
return;
|
|
}
|
|
// Both backends' DeleteSync only free the heap wrapper once their GL
|
|
// context/renderer is gone (generation/current-thread guards), so this is
|
|
// safe after the backend has released its EGL resources - but not after
|
|
// the function table itself is cleared.
|
|
const auto backendDeleteSync = MG_Backend::gBackendFunctionsTable.GL.DeleteSync;
|
|
for (const auto& [_, syncObject] : orphans) {
|
|
if (backendDeleteSync && syncObject->backendHandle) {
|
|
backendDeleteSync(syncObject->backendHandle);
|
|
}
|
|
delete syncObject;
|
|
}
|
|
MGLOG_D("DestroyAllSyncObjects: reclaimed %zu sync object(s) the app left undeleted", orphans.size());
|
|
}
|
|
} // namespace MobileGL::MG_Impl::GLImpl
|