Files
renderdoc/renderdoc/driver/gl/egl_hooks.cpp
T
baldurk 076beab650 Intercept EGL query and remove unsupported extensions. Closes #2830
* Since EGL is an Android API and highly likely to have
  unstable/unsupported/internal extensions, and also because we for the most
  part pass through the API apart from a couple of functions that we intercept
  but don't need to fully understand or process the attributes of, we take the
  approach of removing problematic extensions rather than only allowing
  extensions we know are safe.
2023-02-06 15:06:07 +00:00

1156 lines
40 KiB
C++

/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2019-2023 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/settings.h"
#include "hooks/hooks.h"
#include "strings/string_utils.h"
#include "egl_dispatch_table.h"
#include "gl_driver.h"
RDOC_CONFIG(bool, Android_AllowAllEGLExtensions, false,
"Normally certain extensions are removed from the EGL extension string for "
"compatibility, but with this option that behaviour can be overridden and all "
"extensions will be reported.");
#if ENABLED(RDOC_POSIX)
#include <dlfcn.h>
// default to RTLD_NEXT for EGL lookups if we haven't gotten a more specific library handle
#define DEFAULT_HANDLE RTLD_NEXT
#else
#define DEFAULT_HANDLE NULL
#endif
#if ENABLED(RDOC_LINUX)
namespace Keyboard
{
void UseXlibDisplay(Display *dpy);
WindowingSystem UseUnknownDisplay(void *disp);
void UseWaylandDisplay(wl_display *disp);
}
#endif
struct SurfaceConfig
{
WindowingSystem system;
void *wnd;
};
struct DisplayConfig
{
WindowingSystem system;
};
class EGLHook : LibraryHook
{
public:
EGLHook() : driver(GetEGLPlatform()) {}
~EGLHook()
{
for(auto it : extStrings)
SAFE_DELETE(it.second);
}
void RegisterHooks();
RDCDriver activeAPI = RDCDriver::OpenGLES;
void *handle = DEFAULT_HANDLE;
WrappedOpenGL driver;
std::set<EGLContext> contexts;
std::map<EGLContext, EGLConfig> configs;
std::map<EGLSurface, SurfaceConfig> windows;
std::map<EGLDisplay, DisplayConfig> displays;
std::map<EGLDisplay, rdcstr *> extStrings;
// indicates we're in a swap function, so don't process the swap any further if we recurse - could
// happen due to driver implementation of one function calling another
bool swapping = false;
bool IsYFlipped(EGLDisplay dpy, EGLSurface surface)
{
const char *extString = EGL.QueryString(dpy, EGL_EXTENSIONS);
if(extString && strstr(extString, "ANGLE_surface_orientation"))
{
// https://github.com/google/angle/blob/master/extensions/EGL_ANGLE_surface_orientation.txt
#define EGL_SURFACE_ORIENTATION_ANGLE 0x33A8
#define EGL_SURFACE_ORIENTATION_INVERT_Y_ANGLE 0x0002
int mask = 0;
EGL.QuerySurface(dpy, surface, EGL_SURFACE_ORIENTATION_ANGLE, &mask);
return (mask & EGL_SURFACE_ORIENTATION_INVERT_Y_ANGLE) != 0;
}
return false;
}
void RefreshWindowParameters(const GLWindowingData &data)
{
EGLDisplay display = data.egl_dpy;
EGLContext ctx = data.egl_ctx;
EGLSurface draw = data.egl_wnd;
EGLConfig config = data.egl_cfg;
if(ctx && draw)
{
GLInitParams &params = driver.GetInitParams(data);
int height, width;
EGL.QuerySurface(display, draw, EGL_HEIGHT, &height);
EGL.QuerySurface(display, draw, EGL_WIDTH, &width);
int colorspace = 0;
EGL.QuerySurface(display, draw, EGL_GL_COLORSPACE, &colorspace);
// GL_SRGB8_ALPHA8 is specified as color-renderable, unlike GL_SRGB8.
bool isSRGB = params.colorBits == 32 && colorspace == EGL_GL_COLORSPACE_SRGB;
bool isYFlipped = IsYFlipped(display, draw);
int multiSamples;
EGL.GetConfigAttrib(display, config, EGL_SAMPLES, &multiSamples);
if(multiSamples != 1 && multiSamples != 2 && multiSamples != 4 && multiSamples != 8)
{
multiSamples = 1;
}
params.width = width;
params.height = height;
params.isSRGB = isSRGB;
params.isYFlipped = isYFlipped;
params.multiSamples = multiSamples;
}
}
} eglhook;
// On linux if a user doesn't link to libEGL or try to dlopen it, but just calls dlsym with
// RTLD_NEXT it might successfully one of our functions without anything ever loading libEGL. Then
// our attempts to call onwards will fail. When any of our functions are called we check to see if
// DEFAULT_HANDLE is RTLD_NEXT and if so we manually load the library. This will trigger our hook
// callback and we'll get a specific library handle.
//
// On other platforms this is not needed because we know the real library will be loaded before any
// of our hooks can be called
static void EnsureRealLibraryLoaded()
{
#if ENABLED(RDOC_LINUX)
if(eglhook.handle == DEFAULT_HANDLE)
{
if(!RenderDoc::Inst().IsReplayApp())
RDCLOG("Loading libEGL at the last second");
void *handle = Process::LoadModule("libEGL.so.1");
if(!handle)
handle = Process::LoadModule("libEGL.so");
if(RenderDoc::Inst().IsReplayApp())
eglhook.handle = handle;
}
#endif
}
HOOK_EXPORT EGLDisplay EGLAPIENTRY eglGetDisplay_renderdoc_hooked(EGLNativeDisplayType display)
{
if(RenderDoc::Inst().IsReplayApp())
{
if(!EGL.GetDisplay)
EGL.PopulateForReplay();
return EGL.GetDisplay(display);
}
EnsureRealLibraryLoaded();
#if ENABLED(RDOC_LINUX)
// display can be EGL_DEFAULT_DISPLAY which is NULL, and unfortunately we don't have anything then
if(display)
Keyboard::UseUnknownDisplay((void *)display);
// if xlib is compiled we can try to get the default display (which is what this will do)
#if ENABLED(RDOC_XLIB)
else
Keyboard::UseUnknownDisplay(XOpenDisplay(NULL));
#endif
#endif
return EGL.GetDisplay(display);
}
HOOK_EXPORT EGLDisplay EGLAPIENTRY eglGetPlatformDisplay_renderdoc_hooked(EGLenum platform,
void *native_display,
const EGLAttrib *attrib_list)
{
if(RenderDoc::Inst().IsReplayApp())
{
if(!EGL.GetDisplay)
EGL.PopulateForReplay();
return EGL.GetPlatformDisplay(platform, native_display, attrib_list);
}
EnsureRealLibraryLoaded();
#if ENABLED(RDOC_LINUX)
if(platform == EGL_PLATFORM_X11_KHR)
Keyboard::UseXlibDisplay((Display *)native_display);
else if(platform == EGL_PLATFORM_WAYLAND_KHR)
Keyboard::UseWaylandDisplay((wl_display *)native_display);
else
RDCWARN("Unknown platform %x in eglGetPlatformDisplay", platform);
#endif
return EGL.GetPlatformDisplay(platform, native_display, attrib_list);
}
HOOK_EXPORT EGLBoolean EGLAPIENTRY eglBindAPI_renderdoc_hooked(EGLenum api)
{
if(RenderDoc::Inst().IsReplayApp())
{
if(!EGL.GetDisplay)
EGL.PopulateForReplay();
return EGL.BindAPI(api);
}
EnsureRealLibraryLoaded();
EGLBoolean ret = EGL.BindAPI(api);
if(ret)
eglhook.activeAPI = api == EGL_OPENGL_API ? RDCDriver::OpenGL : RDCDriver::OpenGLES;
return ret;
}
HOOK_EXPORT EGLContext EGLAPIENTRY eglCreateContext_renderdoc_hooked(EGLDisplay display,
EGLConfig config,
EGLContext shareContext,
EGLint const *attribList)
{
if(RenderDoc::Inst().IsReplayApp())
{
if(!EGL.CreateContext)
EGL.PopulateForReplay();
return EGL.CreateContext(display, config, shareContext, attribList);
}
EnsureRealLibraryLoaded();
LibraryHooks::Refresh();
rdcarray<EGLint> attribs;
// modify attribList to our liking
{
bool flagsFound = false;
if(attribList)
{
const EGLint *ptr = attribList;
for(;;)
{
EGLint name = *ptr++;
if(name == EGL_NONE)
{
break;
}
EGLint value = *ptr++;
if(name == EGL_CONTEXT_FLAGS_KHR)
{
if(RenderDoc::Inst().GetCaptureOptions().apiValidation)
value |= EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR;
else
value &= ~EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR;
// remove NO_ERROR bit
value &= ~GL_CONTEXT_FLAG_NO_ERROR_BIT_KHR;
flagsFound = true;
}
if(name == EGL_CONTEXT_OPENGL_NO_ERROR_KHR)
{
// remove this attribute so that we can be more stable
continue;
}
attribs.push_back(name);
attribs.push_back(value);
}
}
if(!flagsFound && RenderDoc::Inst().GetCaptureOptions().apiValidation)
{
attribs.push_back(EGL_CONTEXT_FLAGS_KHR);
attribs.push_back(EGL_CONTEXT_OPENGL_DEBUG_BIT_KHR);
}
attribs.push_back(EGL_NONE);
}
RDCDEBUG("eglCreateContext:");
if(attribList)
{
int *a = (int *)attribs.data();
while(*a != EGL_NONE)
{
RDCDEBUG("%x: %d", a[0], a[1]);
a += 2;
}
}
EGLContext ret = EGL.CreateContext(display, config, shareContext, attribs.data());
// don't continue if context creation failed
if(ret == EGL_NO_CONTEXT)
return ret;
GLInitParams init;
init.width = 0;
init.height = 0;
EGLint value;
EGL.GetConfigAttrib(display, config, EGL_BUFFER_SIZE, &value);
init.colorBits = value;
EGL.GetConfigAttrib(display, config, EGL_DEPTH_SIZE, &value);
init.depthBits = value;
EGL.GetConfigAttrib(display, config, EGL_STENCIL_SIZE, &value);
init.stencilBits = value;
// We will set isSRGB when we see the surface.
init.isSRGB = 0;
EGLint rgbSize[3] = {};
EGL.GetConfigAttrib(display, config, EGL_RED_SIZE, &rgbSize[0]);
EGL.GetConfigAttrib(display, config, EGL_GREEN_SIZE, &rgbSize[1]);
EGL.GetConfigAttrib(display, config, EGL_BLUE_SIZE, &rgbSize[2]);
if(rgbSize[0] == rgbSize[1] && rgbSize[1] == rgbSize[2] && rgbSize[2] == 10)
init.colorBits = 10;
GLWindowingData data;
data.egl_dpy = display;
data.wnd = 0;
data.egl_wnd = (EGLSurface)NULL;
data.egl_ctx = ret;
data.egl_cfg = config;
eglhook.configs[ret] = config;
EnableGLHooks();
eglhook.driver.SetDriverType(eglhook.activeAPI);
{
SCOPED_LOCK(glLock);
eglhook.driver.CreateContext(data, shareContext, init, true, true);
}
return ret;
}
HOOK_EXPORT EGLBoolean EGLAPIENTRY eglDestroyContext_renderdoc_hooked(EGLDisplay dpy, EGLContext ctx)
{
if(RenderDoc::Inst().IsReplayApp())
{
if(!EGL.DestroyContext)
EGL.PopulateForReplay();
return EGL.DestroyContext(dpy, ctx);
}
EnsureRealLibraryLoaded();
eglhook.driver.SetDriverType(eglhook.activeAPI);
{
SCOPED_LOCK(glLock);
eglhook.driver.DeleteContext(ctx);
eglhook.contexts.erase(ctx);
}
return EGL.DestroyContext(dpy, ctx);
}
HOOK_EXPORT EGLSurface EGLAPIENTRY eglCreateWindowSurface_renderdoc_hooked(EGLDisplay dpy,
EGLConfig config,
EGLNativeWindowType win,
const EGLint *attrib_list)
{
if(RenderDoc::Inst().IsReplayApp())
{
if(!EGL.CreateWindowSurface)
EGL.PopulateForReplay();
return EGL.CreateWindowSurface(dpy, config, win, attrib_list);
}
EnsureRealLibraryLoaded();
EGLSurface ret = EGL.CreateWindowSurface(dpy, config, win, attrib_list);
if(ret)
{
SCOPED_LOCK(glLock);
// spec says it's implementation dependent what happens, so we assume that we're using the same
// window system as the display
eglhook.windows[ret] = {eglhook.displays[dpy].system, (void *)win};
}
return ret;
}
HOOK_EXPORT EGLSurface EGLAPIENTRY eglCreatePlatformWindowSurface_renderdoc_hooked(
EGLDisplay dpy, EGLConfig config, void *native_window, const EGLAttrib *attrib_list)
{
if(RenderDoc::Inst().IsReplayApp())
{
if(!EGL.CreatePlatformWindowSurface)
EGL.PopulateForReplay();
return EGL.CreatePlatformWindowSurface(dpy, config, native_window, attrib_list);
}
EnsureRealLibraryLoaded();
EGLSurface ret = EGL.CreatePlatformWindowSurface(dpy, config, native_window, attrib_list);
if(ret)
{
SCOPED_LOCK(glLock);
// spec guarantees that we're using the same window system as the display
eglhook.windows[ret] = {eglhook.displays[dpy].system, native_window};
}
return ret;
}
HOOK_EXPORT EGLBoolean EGLAPIENTRY eglMakeCurrent_renderdoc_hooked(EGLDisplay display,
EGLSurface draw, EGLSurface read,
EGLContext ctx)
{
if(RenderDoc::Inst().IsReplayApp())
{
if(!EGL.MakeCurrent || !EGL.GetProcAddress)
EGL.PopulateForReplay();
// populate GL function pointers now in case linked functions are called
if(EGL.GetProcAddress)
{
GL.PopulateWithCallback(
[](const char *funcName) -> void * { return (void *)EGL.GetProcAddress(funcName); });
}
return EGL.MakeCurrent(display, draw, read, ctx);
}
EnsureRealLibraryLoaded();
EGLBoolean ret = EGL.MakeCurrent(display, draw, read, ctx);
if(ret)
{
SCOPED_LOCK(glLock);
SetDriverForHooks(&eglhook.driver);
if(ctx && eglhook.contexts.find(ctx) == eglhook.contexts.end())
{
eglhook.contexts.insert(ctx);
if(FetchEnabledExtensions())
{
// see gl_emulated.cpp
GL.EmulateUnsupportedFunctions();
GL.EmulateRequiredExtensions();
GL.DriverForEmulation(&eglhook.driver);
}
}
SurfaceConfig cfg = eglhook.windows[draw];
GLWindowingData data;
data.egl_dpy = display;
data.egl_wnd = draw;
data.egl_ctx = ctx;
data.wnd = (decltype(data.wnd))cfg.wnd;
if(!data.wnd)
{
// could be a pbuffer surface or other offscreen rendering. We want a valid wnd, so set it to
// a dummy value
data.wnd = (decltype(data.wnd))(void *)(uintptr_t(0xdeadbeef) + uintptr_t(draw));
cfg.system = WindowingSystem::Headless;
}
// we could query this out technically but it's easier to keep a map
data.egl_cfg = eglhook.configs[ctx];
eglhook.driver.SetDriverType(eglhook.activeAPI);
eglhook.driver.ActivateContext(data);
eglhook.RefreshWindowParameters(data);
}
return ret;
}
HOOK_EXPORT EGLBoolean EGLAPIENTRY eglSwapBuffers_renderdoc_hooked(EGLDisplay dpy, EGLSurface surface)
{
if(RenderDoc::Inst().IsReplayApp())
{
if(!EGL.SwapBuffers)
EGL.PopulateForReplay();
return EGL.SwapBuffers(dpy, surface);
}
EnsureRealLibraryLoaded();
SCOPED_LOCK(glLock);
eglhook.driver.SetDriverType(eglhook.activeAPI);
if(!eglhook.driver.UsesVRFrameMarkers() && !eglhook.swapping)
{
GLWindowingData data;
data.egl_dpy = dpy;
data.egl_wnd = surface;
data.egl_ctx = EGL.GetCurrentContext();
eglhook.RefreshWindowParameters(data);
SurfaceConfig cfg = eglhook.windows[surface];
gl_CurChunk = GLChunk::eglSwapBuffers;
eglhook.driver.SwapBuffers(cfg.system, cfg.wnd);
}
{
eglhook.swapping = true;
EGLBoolean ret = EGL.SwapBuffers(dpy, surface);
eglhook.swapping = false;
return ret;
}
}
HOOK_EXPORT const char *EGLAPIENTRY eglQueryString_renderdoc_hooked(EGLDisplay dpy, EGLint name)
{
if(RenderDoc::Inst().IsReplayApp())
{
if(!EGL.QueryString)
EGL.PopulateForReplay();
return EGL.QueryString(dpy, name);
}
EnsureRealLibraryLoaded();
SCOPED_LOCK(glLock);
if(name == EGL_EXTENSIONS && !Android_AllowAllEGLExtensions())
{
rdcstr *extStr = eglhook.extStrings[dpy];
if(extStr == NULL)
extStr = eglhook.extStrings[dpy] = new rdcstr;
const rdcstr implExtStr = EGL.QueryString(dpy, name);
rdcarray<rdcstr> exts;
split(implExtStr, exts, ' ');
// We take the unusual approach here of explicitly _disallowing_ extensions only when we know
// they are unsupported. The main reason for this is because EGL is the android platform API and
// it may well be that undocumented internal or private extensions are important and should not
// be filtered out. Also since we have minimal interaction with the API as long as they don't
// affect the functions we care about for context management and swapping most extensions can be
// allowed silently.
exts.removeOne("EGL_KHR_no_config_context");
merge(exts, *extStr, ' ');
}
return EGL.QueryString(dpy, name);
}
HOOK_EXPORT EGLBoolean EGLAPIENTRY eglPostSubBufferNV_renderdoc_hooked(EGLDisplay dpy,
EGLSurface surface, EGLint x,
EGLint y, EGLint width,
EGLint height)
{
if(RenderDoc::Inst().IsReplayApp())
{
if(!EGL.PostSubBufferNV)
EGL.PopulateForReplay();
return EGL.PostSubBufferNV(dpy, surface, x, y, width, height);
}
EnsureRealLibraryLoaded();
SCOPED_LOCK(glLock);
eglhook.driver.SetDriverType(eglhook.activeAPI);
if(!eglhook.driver.UsesVRFrameMarkers() && !eglhook.swapping)
{
SurfaceConfig cfg = eglhook.windows[surface];
gl_CurChunk = GLChunk::eglPostSubBufferNV;
eglhook.driver.SwapBuffers(cfg.system, cfg.wnd);
}
{
eglhook.swapping = true;
EGLBoolean ret = EGL.PostSubBufferNV(dpy, surface, x, y, width, height);
eglhook.swapping = false;
return ret;
}
}
HOOK_EXPORT EGLBoolean EGLAPIENTRY eglSwapBuffersWithDamageEXT_renderdoc_hooked(EGLDisplay dpy,
EGLSurface surface,
EGLint *rects,
EGLint n_rects)
{
if(RenderDoc::Inst().IsReplayApp())
{
if(!EGL.SwapBuffersWithDamageEXT)
EGL.PopulateForReplay();
return EGL.SwapBuffersWithDamageEXT(dpy, surface, rects, n_rects);
}
EnsureRealLibraryLoaded();
SCOPED_LOCK(glLock);
eglhook.driver.SetDriverType(eglhook.activeAPI);
if(!eglhook.driver.UsesVRFrameMarkers() && !eglhook.swapping)
{
SurfaceConfig cfg = eglhook.windows[surface];
gl_CurChunk = GLChunk::eglSwapBuffersWithDamageEXT;
eglhook.driver.SwapBuffers(cfg.system, cfg.wnd);
}
{
eglhook.swapping = true;
EGLBoolean ret = EGL.SwapBuffersWithDamageEXT(dpy, surface, rects, n_rects);
eglhook.swapping = false;
return ret;
}
}
HOOK_EXPORT EGLBoolean EGLAPIENTRY eglSwapBuffersWithDamageKHR_renderdoc_hooked(EGLDisplay dpy,
EGLSurface surface,
EGLint *rects,
EGLint n_rects)
{
if(RenderDoc::Inst().IsReplayApp())
{
if(!EGL.SwapBuffersWithDamageKHR)
EGL.PopulateForReplay();
return EGL.SwapBuffersWithDamageKHR(dpy, surface, rects, n_rects);
}
EnsureRealLibraryLoaded();
SCOPED_LOCK(glLock);
eglhook.driver.SetDriverType(eglhook.activeAPI);
if(!eglhook.driver.UsesVRFrameMarkers() && !eglhook.swapping)
{
SurfaceConfig cfg = eglhook.windows[surface];
gl_CurChunk = GLChunk::eglSwapBuffersWithDamageKHR;
eglhook.driver.SwapBuffers(cfg.system, cfg.wnd);
}
{
eglhook.swapping = true;
EGLBoolean ret = EGL.SwapBuffersWithDamageKHR(dpy, surface, rects, n_rects);
eglhook.swapping = false;
return ret;
}
}
HOOK_EXPORT __eglMustCastToProperFunctionPointerType EGLAPIENTRY
eglGetProcAddress_renderdoc_hooked(const char *func)
{
if(RenderDoc::Inst().IsReplayApp())
{
if(!EGL.GetProcAddress)
EGL.PopulateForReplay();
return EGL.GetProcAddress(func);
}
EnsureRealLibraryLoaded();
__eglMustCastToProperFunctionPointerType realFunc = NULL;
{
ScopedSuppressHooking suppress;
realFunc = EGL.GetProcAddress(func);
}
// if the real context doesn't support this function, and we don't provide an implementation fully
// ourselves, return NULL
if(realFunc == NULL && !FullyImplementedFunction(func))
return realFunc;
// return our egl hooks
#define GPA_FUNCTION(name, isext, replayrequired) \
if(!strcmp(func, "egl" STRINGIZE(name))) \
return (__eglMustCastToProperFunctionPointerType)&CONCAT(egl, CONCAT(name, _renderdoc_hooked));
EGL_HOOKED_SYMBOLS(GPA_FUNCTION)
#undef GPA_FUNCTION
// any other egl functions are safe to pass through unchanged
if(!strncmp(func, "egl", 3))
return realFunc;
// otherwise, consult our database of hooks
return (__eglMustCastToProperFunctionPointerType)HookedGetProcAddress(func, (void *)realFunc);
}
// on posix systems, someone might declare a global variable with the same name as a function. When
// doing this, it might mean that our code for "&eglSwapBuffers" looking up that global symbol will
// instead find the location fo the function pointer instead of our hook function. For this reason
// we always refer to the _renderdoc_hooked name, but we still must export the functions under their
// real names and just forward to the hook implementation.
HOOK_EXPORT EGLBoolean EGLAPIENTRY eglBindAPI(EGLenum api)
{
return eglBindAPI_renderdoc_hooked(api);
}
HOOK_EXPORT EGLDisplay EGLAPIENTRY eglGetDisplay(EGLNativeDisplayType display)
{
return eglGetDisplay_renderdoc_hooked(display);
}
HOOK_EXPORT EGLDisplay EGLAPIENTRY eglGetPlatformDisplay(EGLenum platform, void *native_display,
const EGLAttrib *attrib_list)
{
return eglGetPlatformDisplay_renderdoc_hooked(platform, native_display, attrib_list);
}
HOOK_EXPORT EGLContext EGLAPIENTRY eglCreateContext(EGLDisplay display, EGLConfig config,
EGLContext shareContext, EGLint const *attribList)
{
return eglCreateContext_renderdoc_hooked(display, config, shareContext, attribList);
}
HOOK_EXPORT EGLBoolean EGLAPIENTRY eglDestroyContext(EGLDisplay dpy, EGLContext ctx)
{
return eglDestroyContext_renderdoc_hooked(dpy, ctx);
}
HOOK_EXPORT EGLSurface EGLAPIENTRY eglCreateWindowSurface(EGLDisplay dpy, EGLConfig config,
EGLNativeWindowType win,
const EGLint *attrib_list)
{
return eglCreateWindowSurface_renderdoc_hooked(dpy, config, win, attrib_list);
}
HOOK_EXPORT EGLSurface EGLAPIENTRY eglCreatePlatformWindowSurface(EGLDisplay dpy, EGLConfig config,
void *native_window,
const EGLAttrib *attrib_list)
{
return eglCreatePlatformWindowSurface_renderdoc_hooked(dpy, config, native_window, attrib_list);
}
HOOK_EXPORT EGLBoolean EGLAPIENTRY eglMakeCurrent(EGLDisplay display, EGLSurface draw,
EGLSurface read, EGLContext ctx)
{
return eglMakeCurrent_renderdoc_hooked(display, draw, read, ctx);
}
HOOK_EXPORT EGLBoolean EGLAPIENTRY eglSwapBuffers(EGLDisplay dpy, EGLSurface surface)
{
return eglSwapBuffers_renderdoc_hooked(dpy, surface);
}
HOOK_EXPORT const char *EGLAPIENTRY eglQueryString(EGLDisplay dpy, EGLint name)
{
return eglQueryString_renderdoc_hooked(dpy, name);
}
HOOK_EXPORT EGLBoolean EGLAPIENTRY eglPostSubBufferNV(EGLDisplay dpy, EGLSurface surface, EGLint x,
EGLint y, EGLint width, EGLint height)
{
return eglPostSubBufferNV_renderdoc_hooked(dpy, surface, x, y, width, height);
}
HOOK_EXPORT EGLBoolean EGLAPIENTRY eglSwapBuffersWithDamageEXT(EGLDisplay dpy, EGLSurface surface,
EGLint *rects, EGLint n_rects)
{
return eglSwapBuffersWithDamageEXT_renderdoc_hooked(dpy, surface, rects, n_rects);
}
HOOK_EXPORT EGLBoolean EGLAPIENTRY eglSwapBuffersWithDamageKHR(EGLDisplay dpy, EGLSurface surface,
EGLint *rects, EGLint n_rects)
{
return eglSwapBuffersWithDamageKHR_renderdoc_hooked(dpy, surface, rects, n_rects);
}
HOOK_EXPORT __eglMustCastToProperFunctionPointerType EGLAPIENTRY eglGetProcAddress(const char *func)
{
return eglGetProcAddress_renderdoc_hooked(func);
}
// on posix systems we need to export the whole of the EGL API, since we will have redirected any
// dlopen() for libEGL.so to ourselves, and dlsym() for any of these entry points must return a
// valid function. We don't need to intercept them, so we just pass it along
#define EGL_PASSTHRU_0(ret, function) \
typedef ret (*CONCAT(function, _hooktype))(); \
HOOK_EXPORT ret EGLAPIENTRY function() \
{ \
EnsureRealLibraryLoaded(); \
CONCAT(function, _hooktype) \
real = (CONCAT(function, _hooktype))Process::GetFunctionAddress(eglhook.handle, \
STRINGIZE(function)); \
return real(); \
}
#define EGL_PASSTHRU_1(ret, function, t1, p1) \
typedef ret (*CONCAT(function, _hooktype))(t1); \
HOOK_EXPORT ret EGLAPIENTRY function(t1 p1) \
{ \
EnsureRealLibraryLoaded(); \
CONCAT(function, _hooktype) \
real = (CONCAT(function, _hooktype))Process::GetFunctionAddress(eglhook.handle, \
STRINGIZE(function)); \
return real(p1); \
}
#define EGL_PASSTHRU_2(ret, function, t1, p1, t2, p2) \
typedef ret (*CONCAT(function, _hooktype))(t1, t2); \
HOOK_EXPORT ret EGLAPIENTRY function(t1 p1, t2 p2) \
{ \
EnsureRealLibraryLoaded(); \
CONCAT(function, _hooktype) \
real = (CONCAT(function, _hooktype))Process::GetFunctionAddress(eglhook.handle, \
STRINGIZE(function)); \
return real(p1, p2); \
}
#define EGL_PASSTHRU_3(ret, function, t1, p1, t2, p2, t3, p3) \
typedef ret (*CONCAT(function, _hooktype))(t1, t2, t3); \
HOOK_EXPORT ret EGLAPIENTRY function(t1 p1, t2 p2, t3 p3) \
{ \
EnsureRealLibraryLoaded(); \
CONCAT(function, _hooktype) \
real = (CONCAT(function, _hooktype))Process::GetFunctionAddress(eglhook.handle, \
STRINGIZE(function)); \
return real(p1, p2, p3); \
}
#define EGL_PASSTHRU_4(ret, function, t1, p1, t2, p2, t3, p3, t4, p4) \
typedef ret (*CONCAT(function, _hooktype))(t1, t2, t3, t4); \
HOOK_EXPORT ret EGLAPIENTRY function(t1 p1, t2 p2, t3 p3, t4 p4) \
{ \
EnsureRealLibraryLoaded(); \
CONCAT(function, _hooktype) \
real = (CONCAT(function, _hooktype))Process::GetFunctionAddress(eglhook.handle, \
STRINGIZE(function)); \
return real(p1, p2, p3, p4); \
}
#define EGL_PASSTHRU_5(ret, function, t1, p1, t2, p2, t3, p3, t4, p4, t5, p5) \
typedef ret (*CONCAT(function, _hooktype))(t1, t2, t3, t4, t5); \
HOOK_EXPORT ret EGLAPIENTRY function(t1 p1, t2 p2, t3 p3, t4 p4, t5 p5) \
{ \
EnsureRealLibraryLoaded(); \
CONCAT(function, _hooktype) \
real = (CONCAT(function, _hooktype))Process::GetFunctionAddress(eglhook.handle, \
STRINGIZE(function)); \
return real(p1, p2, p3, p4, p5); \
}
/* EGL 1.0 */
EGL_PASSTHRU_5(EGLBoolean, eglChooseConfig, EGLDisplay, dpy, const EGLint *, attrib_list,
EGLConfig *, configs, EGLint, config_size, EGLint *, num_config)
EGL_PASSTHRU_3(EGLBoolean, eglCopyBuffers, EGLDisplay, dpy, EGLSurface, surface,
EGLNativePixmapType, target)
EGL_PASSTHRU_3(EGLSurface, eglCreatePbufferSurface, EGLDisplay, dpy, EGLConfig, config,
const EGLint *, attrib_list)
EGL_PASSTHRU_4(EGLSurface, eglCreatePixmapSurface, EGLDisplay, dpy, EGLConfig, config,
EGLNativePixmapType, pixmap, const EGLint *, attrib_list)
EGL_PASSTHRU_2(EGLBoolean, eglDestroySurface, EGLDisplay, dpy, EGLSurface, surface)
EGL_PASSTHRU_4(EGLBoolean, eglGetConfigAttrib, EGLDisplay, dpy, EGLConfig, config, EGLint,
attribute, EGLint *, value)
EGL_PASSTHRU_4(EGLBoolean, eglGetConfigs, EGLDisplay, dpy, EGLConfig *, configs, EGLint,
config_size, EGLint *, num_config)
EGL_PASSTHRU_0(EGLDisplay, eglGetCurrentDisplay)
EGL_PASSTHRU_1(EGLSurface, eglGetCurrentSurface, EGLint, readdraw)
EGL_PASSTHRU_0(EGLint, eglGetError)
EGL_PASSTHRU_3(EGLBoolean, eglInitialize, EGLDisplay, dpy, EGLint *, major, EGLint *, minor)
EGL_PASSTHRU_4(EGLBoolean, eglQueryContext, EGLDisplay, dpy, EGLContext, ctx, EGLint, attribute,
EGLint *, value)
EGL_PASSTHRU_4(EGLBoolean, eglQuerySurface, EGLDisplay, dpy, EGLSurface, surface, EGLint, attribute,
EGLint *, value)
EGL_PASSTHRU_1(EGLBoolean, eglTerminate, EGLDisplay, dpy)
EGL_PASSTHRU_0(EGLBoolean, eglWaitGL)
EGL_PASSTHRU_1(EGLBoolean, eglWaitNative, EGLint, engine)
/* EGL 1.1 */
EGL_PASSTHRU_3(EGLBoolean, eglBindTexImage, EGLDisplay, dpy, EGLSurface, surface, EGLint, buffer)
EGL_PASSTHRU_3(EGLBoolean, eglReleaseTexImage, EGLDisplay, dpy, EGLSurface, surface, EGLint, buffer)
EGL_PASSTHRU_4(EGLBoolean, eglSurfaceAttrib, EGLDisplay, dpy, EGLSurface, surface, EGLint,
attribute, EGLint, value)
EGL_PASSTHRU_2(EGLBoolean, eglSwapInterval, EGLDisplay, dpy, EGLint, interval)
/* EGL 1.2 */
EGL_PASSTHRU_0(EGLenum, eglQueryAPI)
EGL_PASSTHRU_5(EGLSurface, eglCreatePbufferFromClientBuffer, EGLDisplay, dpy, EGLenum, buftype,
EGLClientBuffer, buffer, EGLConfig, config, const EGLint *, attrib_list)
EGL_PASSTHRU_0(EGLBoolean, eglReleaseThread)
EGL_PASSTHRU_0(EGLBoolean, eglWaitClient)
/* EGL 1.4 */
EGL_PASSTHRU_0(EGLContext, eglGetCurrentContext)
/* EGL 1.5 */
EGL_PASSTHRU_3(EGLSync, eglCreateSync, EGLDisplay, dpy, EGLenum, type, const EGLAttrib *, attrib_list)
EGL_PASSTHRU_2(EGLBoolean, eglDestroySync, EGLDisplay, dpy, EGLSync, sync)
EGL_PASSTHRU_4(EGLint, eglClientWaitSync, EGLDisplay, dpy, EGLSync, sync, EGLint, flags, EGLTime,
timeout)
EGL_PASSTHRU_4(EGLBoolean, eglGetSyncAttrib, EGLDisplay, dpy, EGLSync, sync, EGLint, attribute,
EGLAttrib *, value)
EGL_PASSTHRU_5(EGLImage, eglCreateImage, EGLDisplay, dpy, EGLContext, ctx, EGLenum, target,
EGLClientBuffer, buffer, const EGLAttrib *, attrib_list)
EGL_PASSTHRU_2(EGLBoolean, eglDestroyImage, EGLDisplay, dpy, EGLImage, image)
EGL_PASSTHRU_4(EGLSurface, eglCreatePlatformPixmapSurface, EGLDisplay, dpy, EGLConfig, config,
void *, native_pixmap, const EGLAttrib *, attrib_list)
EGL_PASSTHRU_3(EGLBoolean, eglWaitSync, EGLDisplay, dpy, EGLSync, sync, EGLint, flags)
static void EGLHooked(void *handle)
{
RDCDEBUG("EGL library hooked");
DisableWGLHooksForEGL();
// store the handle for any pass-through implementations that need to look up their onward
// pointers
eglhook.handle = handle;
// as a hook callback this is only called while capturing
RDCASSERT(!RenderDoc::Inst().IsReplayApp());
// fetch non-hooked functions into our dispatch table
#define EGL_FETCH(func, isext, replayrequired) \
EGL.func = (CONCAT(PFN_egl, func))Process::GetFunctionAddress(handle, "egl" STRINGIZE(func)); \
if(!EGL.func && CheckConstParam(isext)) \
EGL.func = (CONCAT(PFN_egl, func))EGL.GetProcAddress("egl" STRINGIZE(func));
EGL_NONHOOKED_SYMBOLS(EGL_FETCH)
#undef EGL_FETCH
// fetch any hooked extension functions into our dispatch table since they're not necessarily
// exported
#define EGL_FETCH(func, isext, replayrequired) \
if(!EGL.func) \
EGL.func = (CONCAT(PFN_egl, func))EGL.GetProcAddress("egl" STRINGIZE(func));
EGL_HOOKED_SYMBOLS(EGL_FETCH)
#undef EGL_FETCH
// on systems where EGL isn't the primary/only way to get GL function pointers, we need to ensure we
// re-fetch all function pointers through eglGetProcAddress and don't try to use any through the
// primary system library (opengl32.dll/libGL.so), since they may not work correctly.
#if DISABLED(RDOC_ANDROID)
RDCEraseEl(GL);
#endif
// Now that libEGL is loaded, we can immediately fill out any missing functions that weren't
// library hooked by calling eglGetProcAddress.
GL.PopulateWithCallback([](const char *funcName) {
// on some android devices we need to hook dlsym, but eglGetProcAddress might call dlsym so we
// need to ensure we return the 'real' pointers
ScopedSuppressHooking suppress;
return (void *)EGL.GetProcAddress(funcName);
});
}
#if ENABLED(RDOC_WIN32)
bool ShouldHookEGL()
{
rdcstr toggle = Process::GetEnvVariable("RENDERDOC_HOOK_EGL");
// if the var is set to 0, then don't hook EGL
if(toggle.size() >= 1 && toggle[0] == '0')
{
RDCLOG(
"EGL hooks disabled by RENDERDOC_HOOK_EGL environment variable - "
"if GLES emulator is in use, underlying API will be captured");
return false;
}
return true;
}
#elif ENABLED(RDOC_ANDROID)
bool ShouldHookEGL()
{
void *egl_handle = dlopen("libEGL.so", RTLD_LAZY);
PFN_eglQueryString query_string = (PFN_eglQueryString)dlsym(egl_handle, "eglQueryString");
if(!query_string)
{
RDCERR("Unable to find eglQueryString entry point, enabling EGL hooking");
return true;
}
rdcstr ignore_layers = Process::GetEnvVariable("IGNORE_LAYERS");
// if we set IGNORE_LAYERS externally that means the layers are broken or can't be configured, so
// hook EGL in spite of the layers being present
if(ignore_layers.size() >= 1 && ignore_layers[0] == '1')
return true;
const char *eglExts = query_string(EGL_NO_DISPLAY, EGL_EXTENSIONS);
if(eglExts && strstr(eglExts, "EGL_ANDROID_GLES_layers"))
{
RDCLOG("EGL_ANDROID_GLES_layers detected, disabling EGL hooks - GLES layering in effect");
return false;
}
return true;
}
#else
bool ShouldHookEGL()
{
return true;
}
#endif
void EGLHook::RegisterHooks()
{
if(!ShouldHookEGL())
return;
RDCLOG("Registering EGL hooks");
#if ENABLED(RDOC_WIN32)
#define LIBSUFFIX ".dll"
#else
#define LIBSUFFIX ".so"
#endif
// register library hooks
LibraryHooks::RegisterLibraryHook("libEGL" LIBSUFFIX, &EGLHooked);
LibraryHooks::RegisterLibraryHook("libEGL" LIBSUFFIX ".1", &EGLHooked);
// we have to specify these with the most preferred library first. If the same function is
// exported in multiple libraries, the function we call into will be the first one found
LibraryHooks::RegisterLibraryHook("libGLESv3" LIBSUFFIX, NULL);
LibraryHooks::RegisterLibraryHook("libGLESv2" LIBSUFFIX ".2", NULL);
LibraryHooks::RegisterLibraryHook("libGLESv2" LIBSUFFIX, NULL);
LibraryHooks::RegisterLibraryHook("libGLESv1_CM" LIBSUFFIX, NULL);
#if ENABLED(RDOC_WIN32)
// on windows, we want to ignore any GLES libraries to ensure we capture the GLES calls, not the
// underlying GL calls
LibraryHooks::IgnoreLibrary("libEGL.dll");
LibraryHooks::IgnoreLibrary("libGLES_CM.dll");
LibraryHooks::IgnoreLibrary("libGLESv1_CM.dll");
LibraryHooks::IgnoreLibrary("libGLESv2.dll");
LibraryHooks::IgnoreLibrary("libGLESv3.dll");
#endif
// register EGL hooks
#define EGL_REGISTER(func, isext, replayrequired) \
LibraryHooks::RegisterFunctionHook( \
"libEGL" LIBSUFFIX, FunctionHook("egl" STRINGIZE(func), (void **)&EGL.func, \
(void *)&CONCAT(egl, CONCAT(func, _renderdoc_hooked))));
EGL_HOOKED_SYMBOLS(EGL_REGISTER)
#undef EGL_REGISTER
}
// Android GLES layering support
#if ENABLED(RDOC_ANDROID)
typedef __eglMustCastToProperFunctionPointerType(EGLAPIENTRY *PFNEGLGETNEXTLAYERPROCADDRESSPROC)(
void *, const char *funcName);
HOOK_EXPORT void AndroidGLESLayer_Initialize(void *layer_id,
PFNEGLGETNEXTLAYERPROCADDRESSPROC next_gpa)
{
RDCLOG("Initialising Android GLES layer with ID %p", layer_id);
// as a hook callback this is only called while capturing
RDCASSERT(!RenderDoc::Inst().IsReplayApp());
// populate EGL dispatch table with the next layer's function pointers. Fetch all 'hooked' and
// non-hooked functions
#define EGL_FETCH(func, isext, replayrequired) \
EGL.func = (CONCAT(PFN_egl, func))next_gpa(layer_id, "egl" STRINGIZE(func)); \
if(!EGL.func) \
RDCWARN("Couldn't fetch function pointer for egl" STRINGIZE(func));
EGL_HOOKED_SYMBOLS(EGL_FETCH)
EGL_NONHOOKED_SYMBOLS(EGL_FETCH)
#undef EGL_FETCH
// populate GL dispatch table with the next layer's function pointers
GL.PopulateWithCallback(
[layer_id, next_gpa](const char *f) { return (void *)next_gpa(layer_id, f); });
}
HOOK_EXPORT void *AndroidGLESLayer_GetProcAddress(const char *funcName,
__eglMustCastToProperFunctionPointerType next)
{
// return our egl hooks
#define GPA_FUNCTION(name, isext, replayrequired) \
if(!strcmp(funcName, "egl" STRINGIZE(name))) \
return (void *)&CONCAT(egl, CONCAT(name, _renderdoc_hooked));
EGL_HOOKED_SYMBOLS(GPA_FUNCTION)
#undef GPA_FUNCTION
// otherwise, consult our database of hooks
// Android GLES layer spec expects us to return next unmodified for functions we don't support
return HookedGetProcAddress(funcName, (void *)next);
}
#endif // ENABLED(RDOC_ANDROID)