mirror of
https://github.com/baldurk/renderdoc.git
synced 2026-10-14 16:19:17 +00:00
* 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.
497 lines
16 KiB
C++
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;
|
|
}
|