Files
renderdoc/renderdoc/driver/gl/gl_hooks_egl.cpp
T
baldurk 6a30e63a46 LibraryHook classes self-register, without library name
* The libName parameter to CreateHooks isn't yet deleted since it's used as a
  hack in the GL hooks to have multiple paths through a function.
2018-07-06 22:44:19 +01:00

488 lines
14 KiB
C++

/******************************************************************************
* The MIT License (MIT)
*
* Copyright (c) 2017-2018 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 <dlfcn.h>
#include <stdio.h>
#include "common/threading.h"
#include "driver/gl/gl_common.h"
#include "driver/gl/gl_driver.h"
#include "hooks/hooks.h"
#include "strings/string_utils.h"
#include "gl_hooks_linux_shared.h"
#include "gl_library_egl.h"
class EGLHook : LibraryHook, public GLPlatform
{
public:
EGLHook()
{
m_HasHooks = false;
m_GLDriver = NULL;
m_PopulatedHooks = false;
}
~EGLHook()
{
delete m_GLDriver;
m_GLDriver = NULL;
}
static void libHooked(void *realLib);
bool CreateHooks(const char *libName);
void PopulateEGLFunctions()
{
if(!m_PopulatedHooks)
{
m_PopulatedHooks = PopulateHooks();
SharedCheckContext();
}
}
void SetupExportedFunctions()
{
// in the replay application we need to call SetupHooks to ensure that all of our exported
// functions like glXCreateContext etc have the 'real' pointers to call into, otherwise even the
// replay app will resolve to our hooks first before the real libGL and call in.
if(RenderDoc::Inst().IsReplayApp())
SetupHooks();
}
void MakeContextCurrent(GLWindowingData data)
{
if(real.MakeCurrent)
real.MakeCurrent(data.egl_dpy, data.egl_wnd, data.egl_wnd, data.egl_ctx);
}
GLWindowingData MakeContext(GLWindowingData share)
{
GLWindowingData ret;
if(real.CreateContext && real.ChooseConfig && real.CreatePbufferSurface)
{
ret = CreateWindowingData(real, share.egl_dpy, share.ctx, 0);
}
return ret;
}
void DeleteContext(GLWindowingData context)
{
if(context.wnd && real.DestroySurface)
real.DestroySurface(context.egl_dpy, context.egl_wnd);
if(context.ctx && real.DestroyContext)
real.DestroyContext(context.egl_dpy, context.egl_ctx);
}
void DeleteReplayContext(GLWindowingData context)
{
if(real.DestroyContext)
{
real.MakeCurrent(context.egl_dpy, 0L, 0L, NULL);
real.DestroyContext(context.egl_dpy, context.egl_ctx);
}
}
void SwapBuffers(GLWindowingData context) { real.SwapBuffers(context.egl_dpy, context.egl_wnd); }
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 = real.GetCurrentContext();
oldContext.egl_dpy = real.GetCurrentDisplay();
oldContext.egl_wnd = real.GetCurrentSurface(EGL_READ);
MakeContextCurrent(context);
EGLBoolean width_ok = real.QuerySurface(context.egl_dpy, context.egl_wnd, EGL_WIDTH, &w);
EGLBoolean height_ok = real.QuerySurface(context.egl_dpy, context.egl_wnd, EGL_HEIGHT, &h);
if(!width_ok || !height_ok)
{
RDCGLenum error_code = (RDCGLenum)real.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)
{
EGLNativeWindowType win = 0;
switch(window.system)
{
#if ENABLED(RDOC_ANDROID)
case WindowingSystem::Android: win = window.android.window; break;
#elif ENABLED(RDOC_LINUX)
case WindowingSystem::Xlib: win = window.xlib.window; break;
#endif
case WindowingSystem::Unknown:
// allow WindowingSystem::Unknown so that internally we can create a window-less context
break;
default: RDCERR("Unexpected window system %u", system); break;
}
EGLDisplay eglDisplay = real.GetDisplay(EGL_DEFAULT_DISPLAY);
RDCASSERT(eglDisplay);
return CreateWindowingData(real, eglDisplay, share_context.ctx, win);
}
bool DrawQuads(float width, float height, const std::vector<Vec4f> &vertices);
WrappedOpenGL *GetDriver()
{
if(m_GLDriver == NULL)
{
m_GLDriver = new WrappedOpenGL(*this);
m_GLDriver->SetDriverType(RDCDriver::OpenGLES);
}
return m_GLDriver;
}
EGLPointers real;
set<EGLContext> m_Contexts;
bool m_PopulatedHooks;
bool m_HasHooks;
bool SetupHooks()
{
bool success = true;
if(!real.IsInitialized())
{
bool symbols_ok = real.LoadSymbolsFrom(libGLdlsymHandle);
if(!symbols_ok)
{
RDCWARN("Unable to load some of the EGL API functions, may cause problems");
success = false;
}
}
return success;
}
bool PopulateHooks();
} eglhooks;
// everything below here needs to have C linkage
extern "C" {
__attribute__((visibility("default"))) EGLDisplay eglGetDisplay(EGLNativeDisplayType display)
{
if(eglhooks.real.GetDisplay == NULL)
eglhooks.SetupExportedFunctions();
#if DISABLED(RDOC_ANDROID)
Keyboard::CloneDisplay(display);
#endif
return eglhooks.real.GetDisplay(display);
}
__attribute__((visibility("default"))) EGLContext eglCreateContext(EGLDisplay display,
EGLConfig config,
EGLContext shareContext,
EGLint const *attribList)
{
PosixHookReapply();
vector<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;
flagsFound = true;
}
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);
}
if(eglhooks.real.CreateContext == NULL)
eglhooks.SetupExportedFunctions();
EGLContext ret = eglhooks.real.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;
eglhooks.real.GetConfigAttrib(display, config, EGL_BUFFER_SIZE, &value);
init.colorBits = value;
eglhooks.real.GetConfigAttrib(display, config, EGL_DEPTH_SIZE, &value);
init.depthBits = value;
eglhooks.real.GetConfigAttrib(display, config, EGL_STENCIL_SIZE, &value);
init.stencilBits = value;
// We will set isSRGB when we see the surface.
init.isSRGB = 0;
GLWindowingData data;
data.egl_dpy = display;
data.egl_wnd = (EGLSurface)NULL;
data.egl_ctx = ret;
eglhooks.GetDriver()->SetDriverType(RDCDriver::OpenGLES);
{
SCOPED_LOCK(glLock);
eglhooks.GetDriver()->CreateContext(data, shareContext, init, true, true);
}
return ret;
}
__attribute__((visibility("default"))) EGLBoolean eglDestroyContext(EGLDisplay dpy, EGLContext ctx)
{
if(eglhooks.real.DestroyContext == NULL)
eglhooks.SetupExportedFunctions();
eglhooks.GetDriver()->SetDriverType(RDCDriver::OpenGLES);
{
SCOPED_LOCK(glLock);
eglhooks.GetDriver()->DeleteContext(ctx);
}
return eglhooks.real.DestroyContext(dpy, ctx);
}
__attribute__((visibility("default"))) EGLBoolean eglMakeCurrent(EGLDisplay display, EGLSurface draw,
EGLSurface read, EGLContext ctx)
{
if(eglhooks.real.MakeCurrent == NULL)
eglhooks.SetupExportedFunctions();
EGLBoolean ret = eglhooks.real.MakeCurrent(display, draw, read, ctx);
SCOPED_LOCK(glLock);
if(ctx && eglhooks.m_Contexts.find(ctx) == eglhooks.m_Contexts.end())
{
eglhooks.m_Contexts.insert(ctx);
SharedCheckContext();
}
GLWindowingData data;
data.egl_dpy = display;
data.egl_wnd = draw;
data.egl_ctx = ctx;
eglhooks.GetDriver()->SetDriverType(RDCDriver::OpenGLES);
eglhooks.GetDriver()->ActivateContext(data);
return ret;
}
__attribute__((visibility("default"))) EGLBoolean eglSwapBuffers(EGLDisplay dpy, EGLSurface surface)
{
if(eglhooks.real.SwapBuffers == NULL)
eglhooks.SetupExportedFunctions();
SCOPED_LOCK(glLock);
int height, width;
eglhooks.real.QuerySurface(dpy, surface, EGL_HEIGHT, &height);
eglhooks.real.QuerySurface(dpy, surface, EGL_WIDTH, &width);
GLInitParams &init = eglhooks.GetDriver()->GetInitParams();
int colorspace = 0;
eglhooks.real.QuerySurface(dpy, surface, EGL_GL_COLORSPACE, &colorspace);
// GL_SRGB8_ALPHA8 is specified as color-renderable, unlike GL_SRGB8.
init.isSRGB = init.colorBits == 32 && colorspace == EGL_GL_COLORSPACE_SRGB;
eglhooks.GetDriver()->SetDriverType(RDCDriver::OpenGLES);
eglhooks.GetDriver()->WindowSize(surface, width, height);
if(!eglhooks.GetDriver()->UsesVRFrameMarkers())
eglhooks.GetDriver()->SwapBuffers(surface);
return eglhooks.real.SwapBuffers(dpy, surface);
}
__attribute__((visibility("default"))) __eglMustCastToProperFunctionPointerType eglGetProcAddress(
const char *func)
{
if(eglhooks.real.GetProcAddress == NULL)
eglhooks.SetupExportedFunctions();
__eglMustCastToProperFunctionPointerType realFunc = NULL;
{
PosixScopedSuppressHooking suppress;
realFunc = eglhooks.real.GetProcAddress(func);
}
if(!strcmp(func, "eglCreateContext"))
return (__eglMustCastToProperFunctionPointerType)&eglCreateContext;
if(!strcmp(func, "eglGetDisplay"))
return (__eglMustCastToProperFunctionPointerType)&eglGetDisplay;
if(!strcmp(func, "eglDestroyContext"))
return (__eglMustCastToProperFunctionPointerType)&eglDestroyContext;
if(!strcmp(func, "eglMakeCurrent"))
return (__eglMustCastToProperFunctionPointerType)&eglMakeCurrent;
if(!strcmp(func, "eglSwapBuffers"))
return (__eglMustCastToProperFunctionPointerType)&eglSwapBuffers;
if(!strncmp(func, "egl", 3))
return realFunc;
// if the real RC doesn't support this function, don't bother hooking
if(realFunc == NULL)
return realFunc;
return (__eglMustCastToProperFunctionPointerType)SharedLookupFuncPtr(func, (void *)realFunc);
}
}; // extern "C"
void EGLHook::libHooked(void *realLib)
{
libGLdlsymHandle = realLib;
eglhooks.CreateHooks(NULL);
eglhooks.GetDriver()->SetDriverType(RDCDriver::OpenGLES);
}
bool EGLHook::CreateHooks(const char *libName)
{
if(libName)
{
// register our hooked functions for PLT hooking on android
PosixHookFunctions();
PosixHookFunction("eglCreateContext", (void *)&eglCreateContext);
PosixHookFunction("eglGetDisplay", (void *)&eglGetDisplay);
PosixHookFunction("eglDestroyContext", (void *)&eglDestroyContext);
PosixHookFunction("eglMakeCurrent", (void *)&eglMakeCurrent);
PosixHookFunction("eglSwapBuffers", (void *)&eglSwapBuffers);
PosixHookFunction("eglGetProcAddress", (void *)&eglGetProcAddress);
// load the libEGL.so library and when loaded call libHooked which initialises GLES capture
PosixHookLibrary("libEGL.so", &libHooked);
PosixHookLibrary("libEGL.so.1", NULL);
PosixHookLibrary("libGL.so.1", NULL);
PosixHookLibrary("libGLESv1_CM.so", NULL);
PosixHookLibrary("libGLESv2.so", NULL);
PosixHookLibrary("libGLESv2.so.2", NULL);
PosixHookLibrary("libGLESv3.so", NULL);
#if ENABLED(RDOC_ANDROID)
return true;
#endif
}
bool success = PopulateHooks();
if(!success)
return false;
m_HasHooks = true;
return true;
}
bool EGLHook::PopulateHooks()
{
SetupHooks();
if(m_PopulatedHooks)
return true;
// dlsym can return GL symbols during a GLES context
bool dlsymFirst = false;
// however on android we want to use it to look up our trampoline map
#if ENABLED(RDOC_ANDROID)
dlsymFirst = true;
#endif
m_PopulatedHooks = SharedPopulateHooks(dlsymFirst, [](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
PosixScopedSuppressHooking suppress;
return (void *)eglGetProcAddress(funcName);
});
return m_PopulatedHooks;
}
void PopulateEGLFunctions()
{
eglhooks.PopulateEGLFunctions();
}
GLPlatform &GetGLPlatformEGL()
{
return eglhooks;
}
// All old style things are disabled in EGL mode
bool EGLHook::DrawQuads(float width, float height, const std::vector<Vec4f> &vertices)
{
return false;
}