mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[Fix] (Client): serialize multi-threaded GL streams, follow Wayland window resizes, export the KHR image aliases
This commit is contained in:
@@ -76,15 +76,22 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
||||
//
|
||||
// An unchanged token emits NOTHING: an identical eglMakeCurrent is a legal no-op in
|
||||
// EGL and the session is already bound to that context.
|
||||
//
|
||||
// THE COMPARISON IS AGAINST THE BINDING LAST SENT, NOT THIS THREAD'S PREVIOUS CONTEXT
|
||||
// (GLStreamScope in EGLImpl.h): the session has one current context and another thread
|
||||
// may have moved it since this one last bound. The caller holds EGLOperationMutex.
|
||||
thread_local Uint64 t_threadContextToken = 0;
|
||||
Uint64 g_boundContextToken = 0; // guarded by EGLOperationMutex
|
||||
|
||||
void EmitContextBinding(EGLStateContext* state, EGLContext switched, Uint64 previousToken) {
|
||||
(void)previousToken;
|
||||
if (MG_Config::Transport == MG_Config::TransportMode::Monolith) return;
|
||||
// The apply thread IS the server in this process; a record emitted there would
|
||||
// wait on the thread that has to apply it (EmitAndWait), which is a deadlock and
|
||||
// not a slow path.
|
||||
if (MG_Remote::Client::RunsAsTheServerRole()) return;
|
||||
const Uint64 token = switched == EGL_NO_CONTEXT ? 0 : state->GetContextClientToken(switched);
|
||||
if (token == previousToken) return;
|
||||
(void)MG_Remote::Client::EmitBindContextRecord(token);
|
||||
t_threadContextToken = switched == EGL_NO_CONTEXT ? 0 : state->GetContextClientToken(switched);
|
||||
StreamBindCallingThreadLocked();
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -93,6 +100,24 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
||||
return mutex;
|
||||
}
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
} // namespace
|
||||
|
||||
Bool StreamGateActive() {
|
||||
return MG_Config::Transport != MG_Config::TransportMode::Monolith &&
|
||||
!MG_Remote::Client::RunsAsTheServerRole();
|
||||
}
|
||||
|
||||
std::recursive_mutex& StreamMutex() { return EGLOperationMutex(); }
|
||||
|
||||
void StreamBindCallingThreadLocked() {
|
||||
const Uint64 token = t_threadContextToken;
|
||||
if (token == g_boundContextToken) return;
|
||||
if (MG_Remote::Client::EmitBindContextRecord(token)) g_boundContextToken = token;
|
||||
}
|
||||
|
||||
namespace {
|
||||
#endif
|
||||
String CurrentThreadIdString() {
|
||||
std::ostringstream stream;
|
||||
stream << std::this_thread::get_id();
|
||||
@@ -220,6 +245,25 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
||||
*out = surface;
|
||||
return true;
|
||||
}
|
||||
|
||||
// A WAYLAND WINDOW THE APPLICATION RESIZED (wl_egl_window_resize). As with a driver's own
|
||||
// window surface, the new size takes effect at the next make-current or swap: the EGL
|
||||
// surface reports it, the wl_shm presentation adopts it, and the drawable behind it is
|
||||
// resized so the frame rendered at the new size is the frame read back. Qt resizes the
|
||||
// window and then makes the context current, so the make-current arm is the one it hits.
|
||||
void ApplyWaylandResize(EGLStateContext* state, EGLDisplay dpy, EGLSurface surface) {
|
||||
const auto it = WaylandSurfaces().find(surface);
|
||||
if (it == WaylandSurfaces().end()) return;
|
||||
EGLint width = 0;
|
||||
EGLint height = 0;
|
||||
if (!it->second->TakeResize(&width, &height)) return;
|
||||
(void)state->ResizeSurface(dpy, surface, width, height);
|
||||
auto* backendObject = MG_Backend::pActiveBackendObject.get();
|
||||
if (backendObject == nullptr ||
|
||||
!backendObject->ResizeEGLWindowSurface(surface, static_cast<Uint32>(width), static_cast<Uint32>(height))) {
|
||||
MGLOG_E("Wayland: the drawable behind a %dx%d window could not be resized", width, height);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
} // namespace
|
||||
|
||||
@@ -321,6 +365,10 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
||||
|
||||
EGLBoolean SwapBuffers(EGLDisplay dpy, EGLSurface draw) {
|
||||
const std::lock_guard<std::recursive_mutex> operationLock(EGLOperationMutex());
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// The present is this thread's context's (GLStreamScope in EGLImpl.h).
|
||||
if (StreamGateActive()) StreamBindCallingThreadLocked();
|
||||
#endif
|
||||
auto* state = GetState();
|
||||
if (!state) {
|
||||
return EGL_FALSE;
|
||||
@@ -348,6 +396,10 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
||||
state->SetError(EGL_BAD_SURFACE);
|
||||
return EGL_FALSE;
|
||||
}
|
||||
#if MOBILEGL_WAYLAND_WINDOWS
|
||||
// A resize made between frames takes effect for the next one.
|
||||
ApplyWaylandResize(state, dpy, draw);
|
||||
#endif
|
||||
return EGL_TRUE;
|
||||
}
|
||||
|
||||
@@ -455,6 +507,9 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
||||
MGLOG_D("eglMakeCurrent begin thread=%s dpy=%p draw=%p read=%p ctx=%p oldDpy=%p oldDraw=%p oldRead=%p oldCtx=%p",
|
||||
threadId.c_str(), dpy, draw, read, ctx, oldDisplay, oldDraw, oldRead, oldContext);
|
||||
|
||||
#if MOBILEGL_WAYLAND_WINDOWS
|
||||
if (draw != EGL_NO_SURFACE) ApplyWaylandResize(state, dpy, draw);
|
||||
#endif
|
||||
if (!state->MakeCurrent(dpy, draw, read, ctx)) {
|
||||
const EGLint error = state->ConsumeError();
|
||||
MGLOG_D("eglMakeCurrent rejected by EGLState thread=%s error=0x%04x", threadId.c_str(), error);
|
||||
@@ -520,6 +575,11 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
if (contextToken != 0 && MG_Config::Transport != MG_Config::TransportMode::Monolith &&
|
||||
!MG_Remote::Client::RunsAsTheServerRole()) {
|
||||
const std::lock_guard<std::recursive_mutex> streamLock(EGLOperationMutex());
|
||||
// The server drops its binding to 0 when the current context is destroyed
|
||||
// (ServerSession::DestroyContext's compare-and-swap); the gate mirrors it so the next
|
||||
// write re-binds.
|
||||
if (g_boundContextToken == contextToken) g_boundContextToken = 0;
|
||||
const auto emitted = MG_Remote::Client::SendDestroyContextFrame(contextToken);
|
||||
if (emitted == MG_Remote::Client::ContextFrameEmit::Refused) {
|
||||
MGLOG_E_ONCE("eglDestroyContext: the server holds no context token %llu; the "
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
#pragma once
|
||||
#include <Includes.h>
|
||||
|
||||
#include <mutex>
|
||||
|
||||
namespace MobileGL::MG_Impl::EGLImpl {
|
||||
EGLSurface CreateWindowSurface(EGLDisplay dpy, EGLConfig config, NativeWindowType window,
|
||||
const EGLint* attrib_list);
|
||||
@@ -62,4 +64,62 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
||||
const EGLAttrib* attrib_list);
|
||||
EGLBoolean WaitSync(EGLDisplay dpy, EGLSync sync, EGLint flags);
|
||||
__eglMustCastToProperFunctionPointerType GetProcAddress(const char* name);
|
||||
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
// THE CLIENT'S STREAM GATE. Under a split transport every thread of the process writes into
|
||||
// ONE ring, and the server attributes each record to the context the last applied
|
||||
// `bind_context` named - one current context per SESSION. A context, though, is current per
|
||||
// THREAD, and Qt renders each window on its own QSGRenderThread: two threads' records then
|
||||
// interleave on the ring with no binding between them and land in the wrong context's
|
||||
// applier (empty sampler windows at a Clear, another context's buffers under an upload).
|
||||
//
|
||||
// So every GL entry point and every EGL operation that writes to the stream runs under one
|
||||
// process-wide lock (EGLOperationMutex, which the EGL side already held), and before it
|
||||
// writes, the server is re-bound to the CALLING thread's context whenever the last binding
|
||||
// actually sent names a different one. A no-op scope on a monolith transport and on the
|
||||
// server's own apply thread.
|
||||
Bool StreamGateActive();
|
||||
std::recursive_mutex& StreamMutex();
|
||||
void StreamBindCallingThreadLocked();
|
||||
|
||||
class GLStreamScope {
|
||||
public:
|
||||
GLStreamScope() {
|
||||
if (!StreamGateActive()) return;
|
||||
m_mutex = &StreamMutex();
|
||||
m_mutex->lock();
|
||||
StreamBindCallingThreadLocked();
|
||||
}
|
||||
~GLStreamScope() {
|
||||
if (m_mutex != nullptr) m_mutex->unlock();
|
||||
}
|
||||
GLStreamScope(const GLStreamScope&) = delete;
|
||||
GLStreamScope& operator=(const GLStreamScope&) = delete;
|
||||
|
||||
private:
|
||||
std::recursive_mutex* m_mutex = nullptr;
|
||||
};
|
||||
// The lock alone, for every EGL entry point: an EGL call may publish into the ring too (a
|
||||
// surface forwarder's catch-up wait, a present, a context frame), and a second thread's GL
|
||||
// record written into the middle of it is a torn record the server latches the session on.
|
||||
class StreamLockScope {
|
||||
public:
|
||||
StreamLockScope() {
|
||||
if (!StreamGateActive()) return;
|
||||
m_mutex = &StreamMutex();
|
||||
m_mutex->lock();
|
||||
}
|
||||
~StreamLockScope() {
|
||||
if (m_mutex != nullptr) m_mutex->unlock();
|
||||
}
|
||||
StreamLockScope(const StreamLockScope&) = delete;
|
||||
StreamLockScope& operator=(const StreamLockScope&) = delete;
|
||||
|
||||
private:
|
||||
std::recursive_mutex* m_mutex = nullptr;
|
||||
};
|
||||
#else
|
||||
class GLStreamScope {};
|
||||
class StreamLockScope {};
|
||||
#endif
|
||||
} // namespace MobileGL::MG_Impl::EGLImpl
|
||||
|
||||
@@ -9,14 +9,34 @@
|
||||
#include <Includes.h>
|
||||
#include "../EGLImpl.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
// The KHR/EXT spellings take EGLint attribute lists where the core 1.5 entry points take
|
||||
// EGLAttrib (pointer-sized). Reinterpreting one as the other reads two EGLints as one
|
||||
// attribute on 64-bit, so the list is widened instead.
|
||||
std::vector<EGLAttrib> WidenAttribList(const EGLint* attribs) {
|
||||
std::vector<EGLAttrib> widened;
|
||||
if (attribs == nullptr) return widened;
|
||||
for (; *attribs != EGL_NONE; attribs += 2) {
|
||||
widened.push_back(static_cast<EGLAttrib>(attribs[0]));
|
||||
widened.push_back(static_cast<EGLAttrib>(attribs[1]));
|
||||
}
|
||||
widened.push_back(EGL_NONE);
|
||||
return widened;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
MOBILEGL_EGL_API EGLSurface eglCreateWindowSurface(EGLDisplay dpy, EGLConfig config, NativeWindowType window,
|
||||
const EGLint* attrib_list) {
|
||||
const MobileGL::MG_Impl::EGLImpl::StreamLockScope mglStreamLock;
|
||||
MGLOG_D("eglCreateWindowSurface(dpy=%p, config=%p, window=%p, attrib_list=%p)", dpy, config, window, attrib_list);
|
||||
return MobileGL::MG_Impl::EGLImpl::CreateWindowSurface(dpy, config, window, attrib_list);
|
||||
}
|
||||
|
||||
MOBILEGL_EGL_API EGLBoolean eglChooseConfig(EGLDisplay dpy, const EGLint* attrib_list, EGLConfig* configs,
|
||||
EGLint config_size, EGLint* num_config) {
|
||||
const MobileGL::MG_Impl::EGLImpl::StreamLockScope mglStreamLock;
|
||||
MGLOG_D("eglChooseConfig(dpy=%p, attrib_list=%p, configs=%p, config_size=%d, num_config=%p)", dpy, attrib_list,
|
||||
configs, config_size, num_config);
|
||||
return MobileGL::MG_Impl::EGLImpl::ChooseConfig(dpy, attrib_list, configs, config_size, num_config);
|
||||
@@ -24,6 +44,7 @@ MOBILEGL_EGL_API EGLBoolean eglChooseConfig(EGLDisplay dpy, const EGLint* attrib
|
||||
|
||||
MOBILEGL_EGL_API EGLContext eglCreateContext(EGLDisplay dpy, EGLConfig config, EGLContext shareCtx,
|
||||
const EGLint* attrib_list) {
|
||||
const MobileGL::MG_Impl::EGLImpl::StreamLockScope mglStreamLock;
|
||||
MGLOG_D("eglCreateContext(dpy=%p, config=%p, shareCtx=%p, attrib_list=%p)", dpy, config, shareCtx, attrib_list);
|
||||
#ifdef TRACY_ENABLE
|
||||
tracy::StartupProfiler();
|
||||
@@ -32,26 +53,31 @@ MOBILEGL_EGL_API EGLContext eglCreateContext(EGLDisplay dpy, EGLConfig config, E
|
||||
}
|
||||
|
||||
MOBILEGL_EGL_API EGLBoolean eglInitialize(EGLDisplay dpy, EGLint* major, EGLint* minor) {
|
||||
const MobileGL::MG_Impl::EGLImpl::StreamLockScope mglStreamLock;
|
||||
MGLOG_D("eglInitialize(dpy=%p, major=%p, minor=%p)", dpy, major, minor);
|
||||
return MobileGL::MG_Impl::EGLImpl::Initialize(dpy, major, minor);
|
||||
}
|
||||
|
||||
MOBILEGL_EGL_API EGLDisplay eglGetDisplay(NativeDisplayType display) {
|
||||
const MobileGL::MG_Impl::EGLImpl::StreamLockScope mglStreamLock;
|
||||
MGLOG_D("eglGetDisplay(display=%p)", display);
|
||||
return MobileGL::MG_Impl::EGLImpl::GetDisplay(display);
|
||||
}
|
||||
|
||||
MOBILEGL_EGL_API EGLint eglGetError() {
|
||||
const MobileGL::MG_Impl::EGLImpl::StreamLockScope mglStreamLock;
|
||||
MGLOG_D("eglGetError()");
|
||||
return MobileGL::MG_Impl::EGLImpl::GetError();
|
||||
}
|
||||
|
||||
MOBILEGL_EGL_API EGLBoolean eglMakeCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) {
|
||||
const MobileGL::MG_Impl::EGLImpl::StreamLockScope mglStreamLock;
|
||||
MGLOG_D("eglMakeCurrent(dpy=%p, draw=%p, read=%p, ctx=%p)", dpy, draw, read, ctx);
|
||||
return MobileGL::MG_Impl::EGLImpl::MakeCurrent(dpy, draw, read, ctx);
|
||||
}
|
||||
|
||||
MOBILEGL_EGL_API EGLBoolean eglDestroyContext(EGLDisplay dpy, EGLContext ctx) {
|
||||
const MobileGL::MG_Impl::EGLImpl::StreamLockScope mglStreamLock;
|
||||
MGLOG_D("eglDestroyContext(dpy=%p, ctx=%p)", dpy, ctx);
|
||||
#ifdef TRACY_ENABLE
|
||||
tracy::ShutdownProfiler();
|
||||
@@ -60,56 +86,67 @@ MOBILEGL_EGL_API EGLBoolean eglDestroyContext(EGLDisplay dpy, EGLContext ctx) {
|
||||
}
|
||||
|
||||
MOBILEGL_EGL_API EGLBoolean eglDestroySurface(EGLDisplay dpy, EGLSurface surface) {
|
||||
const MobileGL::MG_Impl::EGLImpl::StreamLockScope mglStreamLock;
|
||||
MGLOG_D("eglDestroySurface(dpy=%p, surface=%p)", dpy, surface);
|
||||
return MobileGL::MG_Impl::EGLImpl::DestroySurface(dpy, surface);
|
||||
}
|
||||
|
||||
MOBILEGL_EGL_API EGLBoolean eglTerminate(EGLDisplay dpy) {
|
||||
const MobileGL::MG_Impl::EGLImpl::StreamLockScope mglStreamLock;
|
||||
MGLOG_D("eglTerminate(dpy=%p)", dpy);
|
||||
return MobileGL::MG_Impl::EGLImpl::Terminate(dpy);
|
||||
}
|
||||
|
||||
MOBILEGL_EGL_API EGLBoolean eglReleaseThread(void) {
|
||||
const MobileGL::MG_Impl::EGLImpl::StreamLockScope mglStreamLock;
|
||||
MGLOG_D("eglReleaseThread()");
|
||||
return MobileGL::MG_Impl::EGLImpl::ReleaseThread();
|
||||
}
|
||||
|
||||
MOBILEGL_EGL_API EGLContext eglGetCurrentContext(void) {
|
||||
const MobileGL::MG_Impl::EGLImpl::StreamLockScope mglStreamLock;
|
||||
MGLOG_D("eglGetCurrentContext()");
|
||||
return MobileGL::MG_Impl::EGLImpl::GetCurrentContext();
|
||||
}
|
||||
|
||||
MOBILEGL_EGL_API EGLBoolean eglGetConfigAttrib(EGLDisplay dpy, EGLConfig config, EGLint attribute, EGLint* value) {
|
||||
const MobileGL::MG_Impl::EGLImpl::StreamLockScope mglStreamLock;
|
||||
MGLOG_D("eglGetConfigAttrib(dpy=%p, config=%p, attribute=%d, value=%p)", dpy, config, attribute, value);
|
||||
return MobileGL::MG_Impl::EGLImpl::GetConfigAttrib(dpy, config, attribute, value);
|
||||
}
|
||||
|
||||
MOBILEGL_EGL_API EGLBoolean eglBindAPI(EGLenum api) {
|
||||
const MobileGL::MG_Impl::EGLImpl::StreamLockScope mglStreamLock;
|
||||
MGLOG_D("eglBindAPI(api=%u)", api);
|
||||
return MobileGL::MG_Impl::EGLImpl::BindAPI(api);
|
||||
}
|
||||
|
||||
MOBILEGL_EGL_API EGLSurface eglGetCurrentSurface(EGLint readdraw) {
|
||||
const MobileGL::MG_Impl::EGLImpl::StreamLockScope mglStreamLock;
|
||||
MGLOG_D("eglGetCurrentSurface(readdraw=%d)", readdraw);
|
||||
return MobileGL::MG_Impl::EGLImpl::GetCurrentSurface(readdraw);
|
||||
}
|
||||
|
||||
MOBILEGL_EGL_API EGLBoolean eglQuerySurface(EGLDisplay display, EGLSurface surface, EGLint attribute, EGLint* value) {
|
||||
const MobileGL::MG_Impl::EGLImpl::StreamLockScope mglStreamLock;
|
||||
MGLOG_D("eglQuerySurface(display=%p, surface=%p, attribute=%d, value=%p)", display, surface, attribute, value);
|
||||
return MobileGL::MG_Impl::EGLImpl::QuerySurface(display, surface, attribute, value);
|
||||
}
|
||||
|
||||
MOBILEGL_EGL_API char const* eglQueryString(EGLDisplay display, EGLint name) {
|
||||
const MobileGL::MG_Impl::EGLImpl::StreamLockScope mglStreamLock;
|
||||
MGLOG_D("eglQueryString(display=%p, name=%d)", display, name);
|
||||
return MobileGL::MG_Impl::EGLImpl::QueryString(display, name);
|
||||
}
|
||||
|
||||
MOBILEGL_EGL_API EGLBoolean eglSwapInterval(EGLDisplay dpy, EGLint interval) {
|
||||
const MobileGL::MG_Impl::EGLImpl::StreamLockScope mglStreamLock;
|
||||
MGLOG_D("eglSwapInterval(dpy=%p, interval=%d)", dpy, interval);
|
||||
return MobileGL::MG_Impl::EGLImpl::SwapInterval(dpy, interval);
|
||||
}
|
||||
|
||||
MOBILEGL_EGL_API EGLBoolean eglSwapBuffers(EGLDisplay dpy, EGLSurface draw) {
|
||||
const MobileGL::MG_Impl::EGLImpl::StreamLockScope mglStreamLock;
|
||||
MGLOG_D("eglSwapBuffers(dpy=%p, draw=%p)", dpy, draw);
|
||||
#ifdef TRACY_ENABLE
|
||||
FrameMark;
|
||||
@@ -118,32 +155,38 @@ MOBILEGL_EGL_API EGLBoolean eglSwapBuffers(EGLDisplay dpy, EGLSurface draw) {
|
||||
}
|
||||
|
||||
MOBILEGL_EGL_API EGLSurface eglCreatePbufferSurface(EGLDisplay dpy, EGLConfig config, const EGLint* attrib_list) {
|
||||
const MobileGL::MG_Impl::EGLImpl::StreamLockScope mglStreamLock;
|
||||
MGLOG_D("eglCreatePbufferSurface(dpy=%p, config=%p, attrib_list=%p)", dpy, config, attrib_list);
|
||||
return MobileGL::MG_Impl::EGLImpl::CreatePbufferSurface(dpy, config, attrib_list);
|
||||
}
|
||||
|
||||
MOBILEGL_EGL_API __eglMustCastToProperFunctionPointerType eglGetProcAddress(const char* name) {
|
||||
const MobileGL::MG_Impl::EGLImpl::StreamLockScope mglStreamLock;
|
||||
MGLOG_D("eglGetProcAddress(name=%s)", name ? name : "null");
|
||||
return MobileGL::MG_Impl::EGLImpl::GetProcAddress(name);
|
||||
}
|
||||
|
||||
MOBILEGL_EGL_API EGLBoolean eglBindTexImage(EGLDisplay dpy, EGLSurface surface, EGLint buffer) {
|
||||
const MobileGL::MG_Impl::EGLImpl::StreamLockScope mglStreamLock;
|
||||
MGLOG_D("eglBindTexImage(dpy=%p, surface=%p, buffer=%d)", dpy, surface, buffer);
|
||||
return MobileGL::MG_Impl::EGLImpl::BindTexImage(dpy, surface, buffer);
|
||||
}
|
||||
|
||||
MOBILEGL_EGL_API EGLBoolean eglReleaseTexImage(EGLDisplay dpy, EGLSurface surface, EGLint buffer) {
|
||||
const MobileGL::MG_Impl::EGLImpl::StreamLockScope mglStreamLock;
|
||||
MGLOG_D("eglReleaseTexImage(dpy=%p, surface=%p, buffer=%d)", dpy, surface, buffer);
|
||||
return MobileGL::MG_Impl::EGLImpl::ReleaseTexImage(dpy, surface, buffer);
|
||||
}
|
||||
|
||||
MOBILEGL_EGL_API EGLBoolean eglCopyBuffers(EGLDisplay dpy, EGLSurface surface, EGLNativePixmapType target) {
|
||||
const MobileGL::MG_Impl::EGLImpl::StreamLockScope mglStreamLock;
|
||||
MGLOG_D("eglCopyBuffers(dpy=%p, surface=%p, target=%p)", dpy, surface, target);
|
||||
return MobileGL::MG_Impl::EGLImpl::CopyBuffers(dpy, surface, target);
|
||||
}
|
||||
|
||||
MOBILEGL_EGL_API EGLSurface eglCreatePbufferFromClientBuffer(EGLDisplay dpy, EGLenum buftype, EGLClientBuffer buffer,
|
||||
EGLConfig config, const EGLint* attrib_list) {
|
||||
const MobileGL::MG_Impl::EGLImpl::StreamLockScope mglStreamLock;
|
||||
MGLOG_D("eglCreatePbufferFromClientBuffer(dpy=%p, buftype=%u, buffer=%p, config=%p, attrib_list=%p)", dpy, buftype,
|
||||
buffer, config, attrib_list);
|
||||
return MobileGL::MG_Impl::EGLImpl::CreatePbufferFromClientBuffer(dpy, buftype, buffer, config, attrib_list);
|
||||
@@ -151,85 +194,119 @@ MOBILEGL_EGL_API EGLSurface eglCreatePbufferFromClientBuffer(EGLDisplay dpy, EGL
|
||||
|
||||
MOBILEGL_EGL_API EGLSurface eglCreatePixmapSurface(EGLDisplay dpy, EGLConfig config, EGLNativePixmapType pixmap,
|
||||
const EGLint* attrib_list) {
|
||||
const MobileGL::MG_Impl::EGLImpl::StreamLockScope mglStreamLock;
|
||||
MGLOG_D("eglCreatePixmapSurface(dpy=%p, config=%p, pixmap=%p, attrib_list=%p)", dpy, config, pixmap, attrib_list);
|
||||
return MobileGL::MG_Impl::EGLImpl::CreatePixmapSurface(dpy, config, pixmap, attrib_list);
|
||||
}
|
||||
|
||||
MOBILEGL_EGL_API EGLBoolean eglGetConfigs(EGLDisplay dpy, EGLConfig* configs, EGLint config_size, EGLint* num_config) {
|
||||
const MobileGL::MG_Impl::EGLImpl::StreamLockScope mglStreamLock;
|
||||
MGLOG_D("eglGetConfigs(dpy=%p, configs=%p, config_size=%d, num_config=%p)", dpy, configs, config_size, num_config);
|
||||
return MobileGL::MG_Impl::EGLImpl::GetConfigs(dpy, configs, config_size, num_config);
|
||||
}
|
||||
|
||||
MOBILEGL_EGL_API EGLDisplay eglGetCurrentDisplay(void) {
|
||||
const MobileGL::MG_Impl::EGLImpl::StreamLockScope mglStreamLock;
|
||||
MGLOG_D("eglGetCurrentDisplay()");
|
||||
return MobileGL::MG_Impl::EGLImpl::GetCurrentDisplay();
|
||||
}
|
||||
|
||||
MOBILEGL_EGL_API EGLenum eglQueryAPI(void) {
|
||||
const MobileGL::MG_Impl::EGLImpl::StreamLockScope mglStreamLock;
|
||||
MGLOG_D("eglQueryAPI()");
|
||||
return MobileGL::MG_Impl::EGLImpl::QueryAPI();
|
||||
}
|
||||
|
||||
MOBILEGL_EGL_API EGLBoolean eglQueryContext(EGLDisplay dpy, EGLContext ctx, EGLint attribute, EGLint* value) {
|
||||
const MobileGL::MG_Impl::EGLImpl::StreamLockScope mglStreamLock;
|
||||
MGLOG_D("eglQueryContext(dpy=%p, ctx=%p, attribute=%d, value=%p)", dpy, ctx, attribute, value);
|
||||
return MobileGL::MG_Impl::EGLImpl::QueryContext(dpy, ctx, attribute, value);
|
||||
}
|
||||
|
||||
MOBILEGL_EGL_API EGLBoolean eglSurfaceAttrib(EGLDisplay dpy, EGLSurface surface, EGLint attribute, EGLint value) {
|
||||
const MobileGL::MG_Impl::EGLImpl::StreamLockScope mglStreamLock;
|
||||
MGLOG_D("eglSurfaceAttrib(dpy=%p, surface=%p, attribute=%d, value=%d)", dpy, surface, attribute, value);
|
||||
return MobileGL::MG_Impl::EGLImpl::SurfaceAttrib(dpy, surface, attribute, value);
|
||||
}
|
||||
|
||||
MOBILEGL_EGL_API EGLBoolean eglWaitClient(void) {
|
||||
const MobileGL::MG_Impl::EGLImpl::StreamLockScope mglStreamLock;
|
||||
MGLOG_D("eglWaitClient()");
|
||||
return MobileGL::MG_Impl::EGLImpl::WaitClient();
|
||||
}
|
||||
|
||||
MOBILEGL_EGL_API EGLBoolean eglWaitGL(void) {
|
||||
const MobileGL::MG_Impl::EGLImpl::StreamLockScope mglStreamLock;
|
||||
MGLOG_D("eglWaitGL()");
|
||||
return MobileGL::MG_Impl::EGLImpl::WaitGL();
|
||||
}
|
||||
|
||||
MOBILEGL_EGL_API EGLBoolean eglWaitNative(EGLint engine) {
|
||||
const MobileGL::MG_Impl::EGLImpl::StreamLockScope mglStreamLock;
|
||||
MGLOG_D("eglWaitNative(engine=%d)", engine);
|
||||
return MobileGL::MG_Impl::EGLImpl::WaitNative(engine);
|
||||
}
|
||||
|
||||
MOBILEGL_EGL_API EGLSync eglCreateSync(EGLDisplay dpy, EGLenum type, const EGLAttrib* attrib_list) {
|
||||
const MobileGL::MG_Impl::EGLImpl::StreamLockScope mglStreamLock;
|
||||
MGLOG_D("eglCreateSync(dpy=%p, type=%u, attrib_list=%p)", dpy, type, attrib_list);
|
||||
return MobileGL::MG_Impl::EGLImpl::CreateSync(dpy, type, attrib_list);
|
||||
}
|
||||
|
||||
MOBILEGL_EGL_API EGLBoolean eglDestroySync(EGLDisplay dpy, EGLSync sync) {
|
||||
const MobileGL::MG_Impl::EGLImpl::StreamLockScope mglStreamLock;
|
||||
MGLOG_D("eglDestroySync(dpy=%p, sync=%p)", dpy, sync);
|
||||
return MobileGL::MG_Impl::EGLImpl::DestroySync(dpy, sync);
|
||||
}
|
||||
|
||||
MOBILEGL_EGL_API EGLint eglClientWaitSync(EGLDisplay dpy, EGLSync sync, EGLint flags, EGLTime timeout) {
|
||||
const MobileGL::MG_Impl::EGLImpl::StreamLockScope mglStreamLock;
|
||||
MGLOG_D("eglClientWaitSync(dpy=%p, sync=%p, flags=%d, timeout=%llu)", dpy, sync, flags,
|
||||
static_cast<unsigned long long>(timeout));
|
||||
return MobileGL::MG_Impl::EGLImpl::ClientWaitSync(dpy, sync, flags, timeout);
|
||||
}
|
||||
|
||||
MOBILEGL_EGL_API EGLBoolean eglGetSyncAttrib(EGLDisplay dpy, EGLSync sync, EGLint attribute, EGLAttrib* value) {
|
||||
const MobileGL::MG_Impl::EGLImpl::StreamLockScope mglStreamLock;
|
||||
MGLOG_D("eglGetSyncAttrib(dpy=%p, sync=%p, attribute=%d, value=%p)", dpy, sync, attribute, value);
|
||||
return MobileGL::MG_Impl::EGLImpl::GetSyncAttrib(dpy, sync, attribute, value);
|
||||
}
|
||||
|
||||
MOBILEGL_EGL_API EGLImage eglCreateImage(EGLDisplay dpy, EGLContext ctx, EGLenum target, EGLClientBuffer buffer,
|
||||
const EGLAttrib* attrib_list) {
|
||||
const MobileGL::MG_Impl::EGLImpl::StreamLockScope mglStreamLock;
|
||||
MGLOG_D("eglCreateImage(dpy=%p, ctx=%p, target=%u, buffer=%p, attrib_list=%p)", dpy, ctx, target, buffer,
|
||||
attrib_list);
|
||||
return MobileGL::MG_Impl::EGLImpl::CreateImage(dpy, ctx, target, buffer, attrib_list);
|
||||
}
|
||||
|
||||
MOBILEGL_EGL_API EGLBoolean eglDestroyImage(EGLDisplay dpy, EGLImage image) {
|
||||
const MobileGL::MG_Impl::EGLImpl::StreamLockScope mglStreamLock;
|
||||
MGLOG_D("eglDestroyImage(dpy=%p, image=%p)", dpy, image);
|
||||
return MobileGL::MG_Impl::EGLImpl::DestroyImage(dpy, image);
|
||||
}
|
||||
|
||||
// EGL_KHR_image_base's spellings. KWin loads these two by name and calls eglDestroyImageKHR
|
||||
// for every cached client buffer when it dies, wl_shm ones included; a null answer from
|
||||
// eglGetProcAddress made that a jump to address 0.
|
||||
MOBILEGL_EGL_API EGLImage eglCreateImageKHR(EGLDisplay dpy, EGLContext ctx, EGLenum target, EGLClientBuffer buffer,
|
||||
const EGLint* attrib_list) {
|
||||
const MobileGL::MG_Impl::EGLImpl::StreamLockScope mglStreamLock;
|
||||
MGLOG_D("eglCreateImageKHR(dpy=%p, ctx=%p, target=%u, buffer=%p, attrib_list=%p)", dpy, ctx, target, buffer,
|
||||
attrib_list);
|
||||
const std::vector<EGLAttrib> attribs = WidenAttribList(attrib_list);
|
||||
return MobileGL::MG_Impl::EGLImpl::CreateImage(dpy, ctx, target, buffer, attrib_list ? attribs.data() : nullptr);
|
||||
}
|
||||
|
||||
MOBILEGL_EGL_API EGLBoolean eglDestroyImageKHR(EGLDisplay dpy, EGLImage image) {
|
||||
const MobileGL::MG_Impl::EGLImpl::StreamLockScope mglStreamLock;
|
||||
MGLOG_D("eglDestroyImageKHR(dpy=%p, image=%p)", dpy, image);
|
||||
return MobileGL::MG_Impl::EGLImpl::DestroyImage(dpy, image);
|
||||
}
|
||||
|
||||
MOBILEGL_EGL_API EGLDisplay eglGetPlatformDisplay(EGLenum platform, void* native_display,
|
||||
const EGLAttrib* attrib_list) {
|
||||
const MobileGL::MG_Impl::EGLImpl::StreamLockScope mglStreamLock;
|
||||
MGLOG_D("eglGetPlatformDisplay(platform=%u, native_display=%p, attrib_list=%p)", platform, native_display,
|
||||
attrib_list);
|
||||
return MobileGL::MG_Impl::EGLImpl::GetPlatformDisplay(platform, native_display, attrib_list);
|
||||
@@ -237,14 +314,17 @@ MOBILEGL_EGL_API EGLDisplay eglGetPlatformDisplay(EGLenum platform, void* native
|
||||
|
||||
MOBILEGL_EGL_API EGLDisplay eglGetPlatformDisplayEXT(EGLenum platform, void* native_display,
|
||||
const EGLint* attrib_list) {
|
||||
const MobileGL::MG_Impl::EGLImpl::StreamLockScope mglStreamLock;
|
||||
MGLOG_D("eglGetPlatformDisplayEXT(platform=%u, native_display=%p, attrib_list=%p)", platform, native_display,
|
||||
attrib_list);
|
||||
return MobileGL::MG_Impl::EGLImpl::GetPlatformDisplay(
|
||||
platform, native_display, reinterpret_cast<const EGLAttrib*>(attrib_list));
|
||||
const std::vector<EGLAttrib> attribs = WidenAttribList(attrib_list);
|
||||
return MobileGL::MG_Impl::EGLImpl::GetPlatformDisplay(platform, native_display,
|
||||
attrib_list ? attribs.data() : nullptr);
|
||||
}
|
||||
|
||||
MOBILEGL_EGL_API EGLSurface eglCreatePlatformWindowSurface(EGLDisplay dpy, EGLConfig config, void* native_window,
|
||||
const EGLAttrib* attrib_list) {
|
||||
const MobileGL::MG_Impl::EGLImpl::StreamLockScope mglStreamLock;
|
||||
MGLOG_D("eglCreatePlatformWindowSurface(dpy=%p, config=%p, native_window=%p, attrib_list=%p)", dpy, config,
|
||||
native_window, attrib_list);
|
||||
return MobileGL::MG_Impl::EGLImpl::CreatePlatformWindowSurface(dpy, config, native_window, attrib_list);
|
||||
@@ -252,12 +332,14 @@ MOBILEGL_EGL_API EGLSurface eglCreatePlatformWindowSurface(EGLDisplay dpy, EGLCo
|
||||
|
||||
MOBILEGL_EGL_API EGLSurface eglCreatePlatformPixmapSurface(EGLDisplay dpy, EGLConfig config, void* native_pixmap,
|
||||
const EGLAttrib* attrib_list) {
|
||||
const MobileGL::MG_Impl::EGLImpl::StreamLockScope mglStreamLock;
|
||||
MGLOG_D("eglCreatePlatformPixmapSurface(dpy=%p, config=%p, native_pixmap=%p, attrib_list=%p)", dpy, config,
|
||||
native_pixmap, attrib_list);
|
||||
return MobileGL::MG_Impl::EGLImpl::CreatePlatformPixmapSurface(dpy, config, native_pixmap, attrib_list);
|
||||
}
|
||||
|
||||
MOBILEGL_EGL_API EGLBoolean eglWaitSync(EGLDisplay dpy, EGLSync sync, EGLint flags) {
|
||||
const MobileGL::MG_Impl::EGLImpl::StreamLockScope mglStreamLock;
|
||||
MGLOG_D("eglWaitSync(dpy=%p, sync=%p, flags=%d)", dpy, sync, flags);
|
||||
return MobileGL::MG_Impl::EGLImpl::WaitSync(dpy, sync, flags);
|
||||
}
|
||||
|
||||
@@ -32,6 +32,11 @@
|
||||
#include "../EGLImpl.h"
|
||||
#include "../EGLPlatformExtensions.h"
|
||||
|
||||
// Defined in Definitions.cpp; eglext.h declares them only under EGL_EGLEXT_PROTOTYPES.
|
||||
extern "C" EGLImage eglCreateImageKHR(EGLDisplay dpy, EGLContext ctx, EGLenum target, EGLClientBuffer buffer,
|
||||
const EGLint* attrib_list);
|
||||
extern "C" EGLBoolean eglDestroyImageKHR(EGLDisplay dpy, EGLImage image);
|
||||
|
||||
namespace {
|
||||
using namespace MobileGL;
|
||||
|
||||
@@ -221,6 +226,8 @@ namespace {
|
||||
{"eglWaitSync", reinterpret_cast<void*>(eglWaitSync)},
|
||||
{"eglCreateImage", reinterpret_cast<void*>(eglCreateImage)},
|
||||
{"eglDestroyImage", reinterpret_cast<void*>(eglDestroyImage)},
|
||||
{"eglCreateImageKHR", reinterpret_cast<void*>(eglCreateImageKHR)},
|
||||
{"eglDestroyImageKHR", reinterpret_cast<void*>(eglDestroyImageKHR)},
|
||||
{"eglCreatePlatformWindowSurface", reinterpret_cast<void*>(eglCreatePlatformWindowSurface)},
|
||||
{"eglCreatePlatformPixmapSurface", reinterpret_cast<void*>(eglCreatePlatformPixmapSurface)},
|
||||
// The platform and current-state queries: not in glvnd's required list, but the names an
|
||||
|
||||
@@ -324,6 +324,17 @@ namespace MobileGL::MG_Impl::EGLImpl::Wayland {
|
||||
return UniquePtr<WindowSurface>(new WindowSurface(std::move(impl)));
|
||||
}
|
||||
|
||||
Bool WindowSurface::TakeResize(EGLint* width, EGLint* height) {
|
||||
Impl& impl = *m_impl;
|
||||
if (impl.window == nullptr || impl.window->width <= 0 || impl.window->height <= 0) return false;
|
||||
if (impl.window->width == impl.width && impl.window->height == impl.height) return false;
|
||||
impl.width = impl.window->width;
|
||||
impl.height = impl.window->height;
|
||||
*width = impl.width;
|
||||
*height = impl.height;
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool WindowSurface::Present() {
|
||||
Impl& impl = *m_impl;
|
||||
if (impl.window == nullptr) return false;
|
||||
|
||||
@@ -61,6 +61,11 @@ namespace MobileGL::MG_Impl::EGLImpl::Wayland {
|
||||
// window; the swap itself goes on regardless.
|
||||
Bool Present();
|
||||
|
||||
// wl_egl_window_resize's effect, taken: true (with the new size) when the application gave
|
||||
// its wl_egl_window a size this presentation is not at yet. The presentation adopts it -
|
||||
// its next wl_shm buffer is that size - and the caller resizes the drawable behind it.
|
||||
Bool TakeResize(EGLint* width, EGLint* height);
|
||||
|
||||
struct Impl;
|
||||
|
||||
private:
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "../Sync/GL_Sync.h"
|
||||
#include "../Debug/GL_Debug.h"
|
||||
#include <MG_State/GLState/Core.h>
|
||||
#include <MG_Impl/EGLImpl/EGLImpl.h>
|
||||
|
||||
#define DECLARE_GL_FUNCTION_STUB_HEAD(type, name, ...) MOBILEGL_GL_API type gl##name(__VA_ARGS__) {
|
||||
|
||||
@@ -40,22 +41,26 @@
|
||||
#define DECLARE_GL_FUNCTION_END(type, name, ...) \
|
||||
ZoneScopedC(TRACY_ZONECOLOR_ENTRY); \
|
||||
MGLOG_D("Implementing function: %s(...)", __FUNCTION__); \
|
||||
const MobileGL::MG_Impl::EGLImpl::GLStreamScope mglStreamScope; \
|
||||
return MobileGL::MG_Impl::GLImpl::name(__VA_ARGS__); \
|
||||
}
|
||||
|
||||
#define DECLARE_GL_FUNCTION_END_NO_RETURN(type, name, ...) \
|
||||
ZoneScopedC(TRACY_ZONECOLOR_ENTRY); \
|
||||
MGLOG_D("Implementing function: %s(...)", __FUNCTION__); \
|
||||
const MobileGL::MG_Impl::EGLImpl::GLStreamScope mglStreamScope; \
|
||||
MobileGL::MG_Impl::GLImpl::name(__VA_ARGS__); \
|
||||
}
|
||||
#else
|
||||
#define DECLARE_GL_FUNCTION_END(type, name, ...) \
|
||||
MGLOG_D("Implementing function: %s(...)", __FUNCTION__); \
|
||||
const MobileGL::MG_Impl::EGLImpl::GLStreamScope mglStreamScope; \
|
||||
return MobileGL::MG_Impl::GLImpl::name(__VA_ARGS__); \
|
||||
}
|
||||
|
||||
#define DECLARE_GL_FUNCTION_END_NO_RETURN(type, name, ...) \
|
||||
MGLOG_D("Implementing function: %s(...)", __FUNCTION__); \
|
||||
const MobileGL::MG_Impl::EGLImpl::GLStreamScope mglStreamScope; \
|
||||
MobileGL::MG_Impl::GLImpl::name(__VA_ARGS__); \
|
||||
}
|
||||
#endif
|
||||
@@ -116,12 +121,14 @@ DECLARE_GL_FUNCTION_HEAD(void, EnableVertexAttribArray, GLuint index) DECLARE_GL
|
||||
// call really did have nothing to do; MGPipeClientFinish answers false-cheap there too.
|
||||
MOBILEGL_GL_API void glFinish() {
|
||||
MGLOG_D("Implementing function: %s(...)", __FUNCTION__);
|
||||
const MobileGL::MG_Impl::EGLImpl::GLStreamScope mglStreamScope;
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
MobileGL::MG_Pipe::MGPipeClientFinish();
|
||||
#endif
|
||||
}
|
||||
MOBILEGL_GL_API void glFlush() {
|
||||
MGLOG_D("Implementing function: %s(...)", __FUNCTION__);
|
||||
const MobileGL::MG_Impl::EGLImpl::GLStreamScope mglStreamScope;
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
MobileGL::MG_Pipe::MGPipeClientFlush();
|
||||
#endif
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
#endif
|
||||
|
||||
extern "C" EGLDisplay eglGetPlatformDisplayEXT(EGLenum platform, void* native_display, const EGLint* attrib_list);
|
||||
extern "C" EGLImage eglCreateImageKHR(EGLDisplay dpy, EGLContext ctx, EGLenum target, EGLClientBuffer buffer,
|
||||
const EGLint* attrib_list);
|
||||
extern "C" EGLBoolean eglDestroyImageKHR(EGLDisplay dpy, EGLImage image);
|
||||
|
||||
#define GETPROC(name, var) \
|
||||
if (strcmp(#name, var) == 0) { \
|
||||
@@ -64,6 +67,8 @@ namespace MobileGL::MG_Impl {
|
||||
GETPROC(eglGetSyncAttrib, name);
|
||||
GETPROC(eglCreateImage, name);
|
||||
GETPROC(eglDestroyImage, name);
|
||||
GETPROC(eglCreateImageKHR, name);
|
||||
GETPROC(eglDestroyImageKHR, name);
|
||||
GETPROC(eglGetPlatformDisplay, name);
|
||||
GETPROC(eglGetPlatformDisplayEXT, name);
|
||||
GETPROC(eglCreatePlatformWindowSurface, name);
|
||||
|
||||
@@ -303,9 +303,15 @@ namespace MobileGL::MG_Pipe {
|
||||
// A different context is a different server: nothing the tracker latched about
|
||||
// the old one says anything about this one, and the first walk on a fresh
|
||||
// context must publish a COMPLETE state rather than an increment.
|
||||
if (m_context != &ctx) {
|
||||
//
|
||||
// KEYED ON THE CONTEXT'S NEVER-REUSED ID, NOT ITS ADDRESS: a context destroyed and
|
||||
// remade at the same heap address (Qt does this) is a fresh server-side applier
|
||||
// with empty windows, and an address compare would skip the re-prime that refills
|
||||
// them.
|
||||
if (m_context != &ctx || m_contextId != ctx.GetTextureContextId()) {
|
||||
Reset();
|
||||
m_context = &ctx;
|
||||
m_contextId = ctx.GetTextureContextId();
|
||||
}
|
||||
const Bool wasPrimed = m_primed;
|
||||
|
||||
@@ -811,6 +817,7 @@ namespace MobileGL::MG_Pipe {
|
||||
m_staged = RenderStateParameters{};
|
||||
m_stagedAttribs = AttribDefaults{};
|
||||
m_context = nullptr;
|
||||
m_contextId = 0;
|
||||
m_lastDirty = 0;
|
||||
m_primed = false;
|
||||
m_freshlyPrimed = false;
|
||||
@@ -917,6 +924,7 @@ namespace MobileGL::MG_Pipe {
|
||||
AttribDefaults m_stagedAttribs{};
|
||||
|
||||
const void* m_context = nullptr;
|
||||
Uint64 m_contextId = 0;
|
||||
Uint32 m_lastDirty = 0;
|
||||
Bool m_primed = false;
|
||||
Bool m_freshlyPrimed = false;
|
||||
|
||||
@@ -367,7 +367,14 @@ namespace MobileGL::MG_Remote::Client {
|
||||
// InitCapabilities' answer comes from a snapshot the server can only publish once its
|
||||
// own InitCapabilities has run - and ServerMakeEGLCurrent is what publishes it.
|
||||
WaitForApplyBeforeEglForwarder("MakeEGLCurrent");
|
||||
if (!Server::ServerMakeEGLCurrent(dpy, draw, read, ctx)) return false;
|
||||
// The frame carries the context's CLIENT TOKEN rather than its handle (the server's
|
||||
// MakeCurrent arm binds the session to it before the native bind). Identity is all the
|
||||
// server reads from the field otherwise, and a token is as unique as a handle.
|
||||
const Uint64 contextToken =
|
||||
(ctx != EGL_NO_CONTEXT && MG_State::pEGLContext) ? MG_State::pEGLContext->GetContextClientToken(ctx) : 0;
|
||||
const EGLContext wireContext =
|
||||
contextToken != 0 ? reinterpret_cast<EGLContext>(static_cast<std::uintptr_t>(contextToken)) : ctx;
|
||||
if (!Server::ServerMakeEGLCurrent(dpy, draw, read, wireContext)) return false;
|
||||
if (!MG_Backend::BackendObject::MakeEGLCurrent(dpy, draw, read, ctx)) return false;
|
||||
|
||||
// R-12 ARM (a) ON EVERY SUCCESSFUL MAKE-CURRENT (codex 12). ServerMakeEGLCurrent above
|
||||
|
||||
Reference in New Issue
Block a user