Files
renderdoc/renderdoc/driver/gl/egl_platform.cpp
T
baldurk 856c838def Update copyright years to 2026 and fix copyright ranges
* In a previous update in 2021 many copyright ranges were truncated
  accidentally, and some files have been copy-pasted with wrong years. These
  dates have been fixed based on git history and original copyright messages.
2026-01-05 14:17:28 +00:00

497 lines
16 KiB
C++

/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2018-2026 Baldur Karlsson
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
******************************************************************************/
#include "core/plugins.h"
#include "strings/string_utils.h"
#include "egl_dispatch_table.h"
#include "gl_common.h"
static void *GetEGLHandle()
{
#if ENABLED(RDOC_WIN32)
rdcstr libEGL = LocatePluginFile("gles", "libEGL.dll");
// refuse to load libEGL.dll globally, as this is too likely to pick up ANGLE from some program
// with poor PATH control. Instead try to explicitly load it from next to the DLL in case someone
// has put it there.
if(libEGL == "libEGL.dll")
{
rdcstr libpath;
FileIO::GetLibraryFilename(libpath);
libpath = get_dirname(libpath);
libEGL = libpath + "/libEGL.dll";
}
return Process::LoadModule(libEGL);
#else
void *handle = Process::LoadModule("libEGL.so.1");
if(!handle)
handle = Process::LoadModule("libEGL.so");
return handle;
#endif
}
class EGLPlatform : public GLPlatform
{
RDCDriver m_API = RDCDriver::OpenGLES;
bool m_Debug = false;
bool MakeContextCurrent(GLWindowingData data)
{
if(EGL.MakeCurrent)
return EGL.MakeCurrent(data.egl_dpy, data.egl_wnd, data.egl_wnd, data.egl_ctx) == EGL_TRUE;
return false;
}
GLWindowingData CloneTemporaryContext(GLWindowingData share)
{
GLWindowingData ret = share;
ret.egl_ctx = NULL;
if(EGL.CreateContext)
{
EGLint baseAttribs[] = {EGL_CONTEXT_CLIENT_VERSION, 3, EGL_CONTEXT_FLAGS_KHR,
m_Debug ? EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR : 0, EGL_NONE};
ret.egl_ctx = EGL.CreateContext(share.egl_dpy, share.egl_cfg, share.egl_ctx, baseAttribs);
if(ret.egl_ctx == EGL_NO_CONTEXT)
{
EGLint err = EGL.GetError();
EGL.QueryContext(share.egl_dpy, share.egl_ctx, eEGL_CONTEXT_CLIENT_VERSION, &baseAttribs[1]);
RDCWARN(
"Creating cloned context failed (%x). Trying again with queried old EGL client "
"version: %d",
err, baseAttribs[1]);
ret.egl_ctx = EGL.CreateContext(share.egl_dpy, share.egl_cfg, share.egl_ctx, baseAttribs);
err = EGL.GetError();
if(ret.egl_ctx == EGL_NO_CONTEXT)
RDCERR("Cloned context failed again (%x). Capture will likely fail", err);
}
}
return ret;
}
void DeleteClonedContext(GLWindowingData context)
{
if(context.ctx && EGL.DestroyContext)
EGL.DestroyContext(context.egl_dpy, context.egl_ctx);
}
void DeleteReplayContext(GLWindowingData context)
{
if(EGL.DestroyContext)
{
EGL.MakeCurrent(context.egl_dpy, 0L, 0L, NULL);
EGL.DestroySurface(context.egl_dpy, context.egl_wnd);
EGL.DestroyContext(context.egl_dpy, context.egl_ctx);
}
}
void SwapBuffers(GLWindowingData context) { EGL.SwapBuffers(context.egl_dpy, context.egl_wnd); }
void WindowResized(GLWindowingData context) {}
void GetOutputWindowDimensions(GLWindowingData context, int32_t &w, int32_t &h)
{
// On some Linux systems the surface seems to be context dependant.
// Thus we need to switch to that context where the surface was created.
// To avoid any problems because of the context change we'll save the old
// context information so we can switch back to it after the surface query is done.
GLWindowingData oldContext;
oldContext.egl_ctx = EGL.GetCurrentContext();
oldContext.egl_dpy = EGL.GetCurrentDisplay();
oldContext.egl_wnd = EGL.GetCurrentSurface(EGL_READ);
MakeContextCurrent(context);
EGLBoolean width_ok = EGL.QuerySurface(context.egl_dpy, context.egl_wnd, EGL_WIDTH, &w);
EGLBoolean height_ok = EGL.QuerySurface(context.egl_dpy, context.egl_wnd, EGL_HEIGHT, &h);
if(!width_ok || !height_ok)
{
RDCGLenum error_code = (RDCGLenum)EGL.GetError();
RDCWARN("Unable to query the surface size. Error: (0x%x) %s", error_code,
ToStr(error_code).c_str());
}
MakeContextCurrent(oldContext);
}
bool IsOutputWindowVisible(GLWindowingData context) { return true; }
GLWindowingData MakeOutputWindow(WindowingData window, bool depth, GLWindowingData share_context)
{
EGLNativeDisplayType display = EGL_DEFAULT_DISPLAY;
EGLNativeWindowType win = 0;
switch(window.system)
{
#if ENABLED(RDOC_WIN32)
case WindowingSystem::Win32: win = window.win32.window; break;
#elif ENABLED(RDOC_ANDROID)
case WindowingSystem::Android: win = window.android.window; break;
#elif ENABLED(RDOC_LINUX)
case WindowingSystem::Xlib:
{
Display *xlibDisplay = RenderDoc::Inst().GetGlobalEnvironment().xlibDisplay;
display = (EGLNativeDisplayType)window.xlib.display;
win = (EGLNativeWindowType)window.xlib.window;
// ensure we're using the same display as the share context, and the same as our global
// display that we used at init time to create the share context's display
RDCASSERT((void *)display == (void *)xlibDisplay && display != NULL, (void *)display,
(void *)xlibDisplay);
break;
}
case WindowingSystem::Wayland:
{
wl_display *waylandDisplay = RenderDoc::Inst().GetGlobalEnvironment().waylandDisplay;
display = (EGLNativeDisplayType)window.wayland.display;
win = (EGLNativeWindowType)window.wayland.window;
// ensure we're using the same display as the share context, and the same as our global
// display
RDCASSERT((void *)display == (void *)waylandDisplay && display != NULL, (void *)display,
(void *)waylandDisplay);
break;
}
#endif
case WindowingSystem::Unknown:
case WindowingSystem::Headless:
// allow Unknown and Headless so that internally we can create a window-less context
break;
default: RDCERR("Unexpected window system %u", window.system); break;
}
EGLDisplay eglDisplay = NULL;
if(share_context.egl_dpy)
eglDisplay = share_context.egl_dpy;
else
eglDisplay = EGL.GetDisplay(display);
RDCASSERT(eglDisplay);
int major, minor;
EGL.Initialize(eglDisplay, &major, &minor);
return CreateWindowingData(eglDisplay, share_context.ctx, win, false);
}
GLWindowingData CreateWindowingData(EGLDisplay eglDisplay, EGLContext share_ctx,
EGLNativeWindowType window, bool debug)
{
GLWindowingData ret;
ret.egl_dpy = eglDisplay;
ret.egl_ctx = NULL;
ret.egl_wnd = NULL;
EGLint surfaceType = (window == 0) ? EGL_PBUFFER_BIT : EGL_WINDOW_BIT;
const EGLint configAttribs[] = {
EGL_RED_SIZE,
8,
EGL_GREEN_SIZE,
8,
EGL_BLUE_SIZE,
8,
EGL_RENDERABLE_TYPE,
m_API == RDCDriver::OpenGLES ? EGL_OPENGL_ES3_BIT : EGL_OPENGL_BIT,
EGL_CONFORMANT,
m_API == RDCDriver::OpenGLES ? EGL_OPENGL_ES3_BIT : EGL_OPENGL_BIT,
EGL_SURFACE_TYPE,
surfaceType,
EGL_COLOR_BUFFER_TYPE,
EGL_RGB_BUFFER,
EGL_NONE,
};
EGLint numConfigs = 0;
EGLBoolean success = EGL.ChooseConfig(eglDisplay, configAttribs, &ret.egl_cfg, 1, &numConfigs);
if(!success || numConfigs == 0)
{
RDCERR("Couldn't find a suitable EGL config");
return ret;
}
// we try and create the highest versioned context we can, but we need at least ES3.0 (and
// extensions) to function.
EGLContext ctx = NULL;
// first we try with the debug bit set, then if that fails we try without debug
for(int debugPass = 0; debugPass < 2; debugPass++)
{
if(!debug && debugPass == 0)
continue;
// don't change this ar ray without changing indices in the loop below
EGLint verAttribs[] = {
EGL_CONTEXT_MAJOR_VERSION_KHR,
3,
EGL_CONTEXT_MINOR_VERSION_KHR,
1,
debugPass == 0 ? EGL_CONTEXT_FLAGS_KHR : EGL_NONE,
EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR,
EGL_NONE,
};
rdcarray<GLVersion> versions = GetReplayVersions(RDCDriver::OpenGLES);
for(GLVersion v : versions)
{
verAttribs[1] = v.major;
verAttribs[3] = v.minor;
ctx = EGL.CreateContext(eglDisplay, ret.egl_cfg, share_ctx, verAttribs);
if(ctx)
break;
}
// if we got it using the major/minor pattern, then stop
if(ctx)
break;
// if none of the above worked, try just creating with the client version as 3
const EGLint baseAttribs[] = {
EGL_CONTEXT_CLIENT_VERSION,
3,
debugPass == 0 ? EGL_CONTEXT_FLAGS_KHR : EGL_NONE,
EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR,
EGL_NONE,
};
ctx = EGL.CreateContext(eglDisplay, ret.egl_cfg, share_ctx, baseAttribs);
// if it succeeded, stop
if(ctx)
break;
}
// if after that loop we still don't have a context, bail completely
if(ctx == NULL)
{
RDCERR("Couldn't create GLES3 context");
return ret;
}
ret.egl_ctx = ctx;
EGLSurface surface = 0;
if(window != 0)
{
const char *exts = EGL.QueryString(eglDisplay, EGL_EXTENSIONS);
// create an sRGB surface if possible
const EGLint *attrib_list = NULL;
const EGLint srgb_attribs[] = {EGL_GL_COLORSPACE_KHR, EGL_GL_COLORSPACE_SRGB_KHR, EGL_NONE};
if(exts && strstr(exts, "KHR_gl_colorspace"))
attrib_list = srgb_attribs;
surface = EGL.CreateWindowSurface(eglDisplay, ret.egl_cfg, window, attrib_list);
// if the sRGB surface request failed, fall back to linear
if(surface == NULL && attrib_list != NULL)
surface = EGL.CreateWindowSurface(eglDisplay, ret.egl_cfg, window, NULL);
if(surface == NULL)
RDCERR("Couldn't create surface for window");
}
else
{
static const EGLint pbAttribs[] = {EGL_WIDTH, 32, EGL_HEIGHT, 32, EGL_NONE};
surface = EGL.CreatePbufferSurface(eglDisplay, ret.egl_cfg, pbAttribs);
if(surface == NULL)
RDCERR("Couldn't create a suitable PBuffer");
}
ret.wnd = (decltype(ret.wnd))window;
ret.egl_wnd = surface;
return ret;
}
bool CanCreateGLContext()
{
#if ENABLED(RDOC_ANDROID)
// we don't trust the EGL API query to work reliably on Android, so treat
// it as special case
return false;
#else
bool success = EGL.PopulateForReplay();
// if we can't populate our functions we bail now.
if(!success)
return false;
EGLenum previousAPI = EGL.QueryAPI();
EGLBoolean supportsGL = EGL.BindAPI(EGL_OPENGL_API);
// restore previous API
if(previousAPI != EGL_NONE)
EGL.BindAPI(previousAPI);
return supportsGL == EGL_TRUE;
#endif
}
bool CanCreateGLESContext()
{
// as long as we can get libEGL we're fine
return GetEGLHandle() != NULL;
}
bool PopulateForReplay() { return EGL.PopulateForReplay(); }
void SetDriverType(RDCDriver api) { m_API = api; }
RDResult InitialiseAPI(GLWindowingData &replayContext, RDCDriver api, bool debug)
{
Display *xlibDisplay = RenderDoc::Inst().GetGlobalEnvironment().xlibDisplay;
wl_display *waylandDisplay = RenderDoc::Inst().GetGlobalEnvironment().waylandDisplay;
// we support replaying both GLES and GL through EGL
RDCASSERT(api == RDCDriver::OpenGLES || api == RDCDriver::OpenGL);
m_API = api;
m_Debug = debug;
if(api == RDCDriver::OpenGLES)
EGL.BindAPI(EGL_OPENGL_ES_API);
else
EGL.BindAPI(EGL_OPENGL_API);
EGLNativeDisplayType display = EGL_DEFAULT_DISPLAY;
if(waylandDisplay)
display = (EGLNativeDisplayType)waylandDisplay;
else if(xlibDisplay)
display = (EGLNativeDisplayType)xlibDisplay;
EGLDisplay eglDisplay = EGL.GetDisplay(display);
if(!eglDisplay)
{
RETURN_ERROR_RESULT(ResultCode::APIInitFailed, "Couldn't open default EGL display");
}
int major, minor;
EGL.Initialize(eglDisplay, &major, &minor);
replayContext = CreateWindowingData(eglDisplay, EGL_NO_CONTEXT, 0, debug);
if(!replayContext.ctx)
{
DeleteReplayContext(replayContext);
RDCEraseEl(replayContext);
RETURN_ERROR_RESULT(ResultCode::APIHardwareUnsupported,
"Couldn't create OpenGL ES 3.x replay context - required for replay");
}
return ResultCode::Succeeded;
}
void *GetReplayFunction(const char *funcname)
{
void *ret = (void *)EGL.GetProcAddress(funcname);
if(ret)
return ret;
ret = Process::GetFunctionAddress(GetEGLHandle(), funcname);
if(ret)
return ret;
#if ENABLED(RDOC_WIN32)
#define LIBSUFFIX ".dll"
#else
#define LIBSUFFIX ".so"
#endif
const char *libs[] = {
"libGLESv3" LIBSUFFIX,
"libGLESv2" LIBSUFFIX ".2",
"libGLESv2" LIBSUFFIX,
"libGLESv1_CM" LIBSUFFIX,
};
for(size_t i = 0; i < ARRAY_COUNT(libs); i++)
{
ret = Process::GetFunctionAddress(Process::LoadModule(libs[i]), funcname);
if(ret)
return ret;
}
return NULL;
}
void DrawQuads(float width, float height, const rdcarray<Vec4f> &vertices)
{
// legacy quad rendering is not supported on GLES
}
} eglPlatform;
GLPlatform &GetEGLPlatform()
{
return eglPlatform;
}
EGLDispatchTable EGL = {};
bool EGLDispatchTable::PopulateForReplay()
{
RDCASSERT(RenderDoc::Inst().IsReplayApp());
void *handle = GetEGLHandle();
if(!handle)
{
RDCERR("Can't load libEGL");
return false;
}
RDCDEBUG("Initialising EGL function pointers");
bool symbols_ok = true;
#define LOAD_FUNC(func, isext, replayrequired) \
if(!this->func) \
this->func = (CONCAT(PFN_egl, func))Process::GetFunctionAddress(handle, "egl" STRINGIZE(func)); \
if(!this->func && CheckConstParam(isext)) \
this->func = (CONCAT(PFN_egl, func))this->GetProcAddress("egl" STRINGIZE(func)); \
\
if(!this->func && !CheckConstParam(isext)) \
{ \
if(CheckConstParam(replayrequired)) \
symbols_ok = false; \
RDCWARN("Unable to load '%s'", STRINGIZE(func)); \
}
EGL_HOOKED_SYMBOLS(LOAD_FUNC)
EGL_NONHOOKED_SYMBOLS(LOAD_FUNC)
#undef LOAD_FUNC
return symbols_ok;
}