/****************************************************************************** * 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 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 &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; }