libglvnd/src/EGL/libegl.c
Kyle Brenneman eaffa7bc12 EGL: Add a dispatch stub for eglQueryDisplayAttribEXT.
Add a dispatch stub for eglQueryDisplayAttribEXT, which updates the device
hashtable if the application queries EGL_DEVICE_EXT.

It's possible that eglQueryDisplayAttribEXT could return an EGLDeviceEXT handle
that wasn't returned by eglQueryDevicesEXT, so the dispatch stub needs to add
the handle to the device hashtable.
2021-11-25 09:51:20 -07:00

1388 lines
42 KiB
C

/*
* Copyright (c) 2013, NVIDIA CORPORATION.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and/or associated documentation files (the
* "Materials"), to deal in the Materials without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Materials, and to
* permit persons to whom the Materials are furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* unaltered in all copies or substantial portions of the Materials.
* Any additions, deletions, or changes to the original source files
* must be clearly indicated in accompanying documentation.
*
* THE MATERIALS ARE 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
* MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
*/
/* For RTLD_DEFAULT on x86 systems */
#define _GNU_SOURCE 1
#include <dlfcn.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/mman.h>
#if defined(ENABLE_EGL_X11)
#include <X11/Xlib.h>
#endif
#include "glvnd_pthread.h"
#include "libeglabipriv.h"
#include "libeglmapping.h"
#include "libeglcurrent.h"
#include "libeglerror.h"
#include "utils_misc.h"
#include "trace.h"
#include "egldispatchstubs.h"
#include "compiler.h"
#include "utils_misc.h"
#include "lkdhash.h"
#if !defined(HAVE_RTLD_NOLOAD)
#define RTLD_NOLOAD 0
#endif
/*!
* This is the set of client extensions that libglvnd will support, if at least
* one vendor library supports them.
*/
static const char *SUPPORTED_CLIENT_EXTENSIONS =
"EGL_EXT_platform_base"
" EGL_EXT_device_base"
" EGL_EXT_device_enumeration"
" EGL_EXT_device_query"
" EGL_EXT_platform_device"
// FIXME: Include the other platform extensions here to make testing
// easier. These should be split off into a separate string in the vendor
// library.
" EGL_KHR_platform_android"
" EGL_KHR_platform_gbm"
" EGL_KHR_platform_wayland"
" EGL_KHR_platform_x11"
" EGL_EXT_platform_x11"
" EGL_EXT_platform_wayland"
" EGL_MESA_platform_gbm"
;
/*!
* This is the set of client extensions that libglvnd will support, regardless
* of whether any vendor library claims to support them.
*/
static const char *ALWAYS_SUPPORTED_CLIENT_EXTENSIONS =
"EGL_KHR_client_get_all_proc_addresses"
" EGL_EXT_client_extensions"
" EGL_KHR_debug"
;
static const char *GLVND_EGL_VERSION_STRING = "1.5 libglvnd";
/*!
* This is a list of platforms that the user can specify by name to override
* the platform that eglGetDisplay selects.
*/
static const struct {
EGLenum platform;
const char *name;
} EGL_PLATFORMS_NAMES[] = {
{ EGL_PLATFORM_X11_KHR, "x11" },
{ EGL_PLATFORM_WAYLAND_KHR, "wayland" },
{ EGL_PLATFORM_ANDROID_KHR, "android" },
{ EGL_PLATFORM_GBM_KHR, "gbm" },
{ EGL_PLATFORM_GBM_KHR, "drm" },
{ EGL_PLATFORM_DEVICE_EXT, "device" },
{ EGL_NONE, NULL }
};
static char *clientExtensionString = NULL;
glvnd_mutex_t clientExtensionStringMutex = GLVND_MUTEX_INITIALIZER;
void __eglEntrypointCommon(void)
{
__eglThreadInitialize();
__eglSetError(EGL_SUCCESS);
}
static EGLBoolean _eglPointerIsDereferencable(void *p)
{
#if defined(HAVE_MINCORE)
uintptr_t addr = (uintptr_t) p;
unsigned char unused;
const long page_size = getpagesize();
if (p == NULL) {
return EGL_FALSE;
}
/* align addr to page_size */
addr &= ~(page_size - 1);
/*
* mincore() returns 0 on success, and -1 on failure. The last parameter
* is a vector of bytes with one entry for each page queried. mincore
* returns page residency information in the first bit of each byte in the
* vector.
*
* Residency doesn't actually matter when determining whether a pointer is
* dereferenceable, so the output vector can be ignored. What matters is
* whether mincore succeeds. It will fail with ENOMEM if the range
* [addr, addr + length) is not mapped into the process, so all that needs
* to be checked there is whether the mincore call succeeds or not, as it
* can only succeed on dereferenceable memory ranges.
*
* Also note that the third parameter might be char or unsigned char
* depending on what system we're building on. Since we don't actually need
* that result, just cast it to a void* so that it works either way.
*/
return (mincore((void *) addr, page_size, (void *) &unused) >= 0);
#else
return EGL_FALSE;
#endif
}
static void *SafeDereference(void *ptr)
{
if (_eglPointerIsDereferencable(ptr))
return *((void **)ptr);
return NULL;
}
static EGLBoolean IsGbmDisplay(void *native_display)
{
void *first_pointer = SafeDereference(native_display);
Dl_info info;
if (dladdr(first_pointer, &info) == 0) {
return EGL_FALSE;
}
if (!info.dli_sname) {
return EGL_FALSE;
}
return !strcmp(info.dli_sname, "gbm_create_device");
}
static EGLBoolean IsX11Display(void *dpy)
{
#if defined(ENABLE_EGL_X11)
void *alloc;
void *handle;
void *XAllocID = NULL;
alloc = SafeDereference(&((_XPrivDisplay)dpy)->resource_alloc);
if (alloc == NULL) {
return EGL_FALSE;
}
handle = dlopen("libX11.so.6", RTLD_LOCAL | RTLD_LAZY | RTLD_NOLOAD);
if (handle != NULL) {
XAllocID = dlsym(handle, "_XAllocID");
dlclose(handle);
}
return (XAllocID != NULL && XAllocID == alloc);
#else // defined(ENABLE_EGL_X11)
return EGL_FALSE;
#endif // defined(ENABLE_EGL_X11)
}
static EGLBoolean IsWaylandDisplay(void *native_display)
{
void *first_pointer = SafeDereference(native_display);
Dl_info info;
if (dladdr(first_pointer, &info) == 0) {
return EGL_FALSE;
}
if (!info.dli_sname) {
return EGL_FALSE;
}
return !strcmp(info.dli_sname, "wl_display_interface");
}
/*!
* This is a helper function for eglGetDisplay to try to guess the platform
* type to use.
*/
static EGLenum GuessPlatformType(EGLNativeDisplayType display_id)
{
EGLBoolean gbmSupported = EGL_FALSE;
EGLBoolean waylandSupported = EGL_FALSE;
EGLBoolean x11Supported = EGL_FALSE;
struct glvnd_list *vendorList = __eglLoadVendors();
__EGLvendorInfo *vendor;
// First, see if any of the vendor libraries can identify the display.
glvnd_list_for_each_entry(vendor, vendorList, entry) {
if (vendor->eglvc.findNativeDisplayPlatform != NULL) {
EGLenum platform = vendor->eglvc.findNativeDisplayPlatform((void *) display_id);
if (platform != EGL_NONE) {
return platform;
}
}
}
// Next, see if this is a valid EGLDisplayEXT handle.
if (__eglGetVendorFromDevice((EGLDeviceEXT) display_id)) {
return EGL_PLATFORM_DEVICE_EXT;
}
// Check if any vendor supports EGL_KHR_platform_wayland.
glvnd_list_for_each_entry(vendor, vendorList, entry) {
if (vendor->supportsPlatformGbm) {
gbmSupported = EGL_TRUE;
}
if (vendor->supportsPlatformWayland) {
waylandSupported = EGL_TRUE;
}
if (vendor->supportsPlatformX11) {
x11Supported = EGL_TRUE;
}
}
if (gbmSupported && IsGbmDisplay(display_id)) {
return EGL_PLATFORM_GBM_KHR;
}
if (waylandSupported && IsWaylandDisplay(display_id)) {
return EGL_PLATFORM_WAYLAND_KHR;
}
if (x11Supported && IsX11Display(display_id)) {
return EGL_PLATFORM_X11_KHR;
}
return EGL_NONE;
};
static EGLDisplay GetPlatformDisplayCommon(EGLenum platform,
void *native_display, const EGLAttrib *attrib_list,
const char *funcName)
{
__EGLdisplayInfo *dpyInfo = NULL;
EGLint errorCode = EGL_SUCCESS;
EGLBoolean anyVendorSuccess = EGL_FALSE;
struct glvnd_list *vendorList;
vendorList = __eglLoadVendors();
if (glvnd_list_is_empty(vendorList)) {
// If there are no vendor libraries, then no platforms are supported.
__eglReportError(EGL_BAD_PARAMETER, funcName, __eglGetThreadLabel(),
"No EGL drivers found.");
return EGL_NO_DISPLAY;
}
if (platform == EGL_PLATFORM_DEVICE_EXT
&& native_display != (void *) EGL_DEFAULT_DISPLAY) {
EGLDeviceEXT dev = (EGLDeviceEXT) native_display;
EGLDisplay dpy;
__EGLvendorInfo *vendor = __eglGetVendorFromDevice(dev);
if (vendor == NULL) {
__eglReportError(EGL_BAD_PARAMETER, funcName, __eglGetThreadLabel(),
"Invalid EGLDevice handle %p", dev);
return EGL_NO_DISPLAY;
}
dpy = vendor->eglvc.getPlatformDisplay(platform, native_display, attrib_list);
if (dpy == EGL_NO_DISPLAY) {
return EGL_NO_DISPLAY;
}
dpyInfo = __eglAddDisplay(dpy, vendor);
if (dpyInfo == NULL) {
__eglReportCritical(EGL_BAD_ALLOC, funcName, __eglGetThreadLabel(),
"Can't allocate display");
return EGL_NO_DISPLAY;
}
}
// Note that if multiple threads try to call eglGetPlatformDisplay with the
// same arguments, then the same vendor library should return the same
// EGLDisplay handle. In that case, __eglAddDisplay will return the same
// __EGLdisplayInfo structure for both threads.
// TODO: How should this deal with EGL_KHR_debug messages? We don't want
// one vendor library to report an error only for another vendor to
// succeed. Maybe just require vendors to only use WARN or INFO level
// messages, and then report an error later on based on the error code?
if (dpyInfo == NULL) {
__EGLvendorInfo *vendor;
glvnd_list_for_each_entry(vendor, vendorList, entry) {
EGLDisplay dpy = vendor->eglvc.getPlatformDisplay(platform, native_display, attrib_list);
if (dpy != EGL_NO_DISPLAY) {
dpyInfo = __eglAddDisplay(dpy, vendor);
break;
} else {
EGLint vendorError = vendor->staticDispatch.getError();
if (vendorError == EGL_SUCCESS) {
anyVendorSuccess = EGL_TRUE;
} else if (errorCode == EGL_SUCCESS) {
errorCode = vendorError;
}
}
}
}
if (dpyInfo != NULL) {
// We got a valid EGLDisplay, so the function succeeded.
__eglSetError(EGL_SUCCESS);
return dpyInfo->dpy;
} else {
if (anyVendorSuccess) {
// We didn't get an EGLDisplay, but at least one vendor library
// returned an error code of EGL_SUCCESS. Assume that the
// parameters are valid, and that the display was unavailable for
// some other reason.
__eglSetError(EGL_SUCCESS);
} else {
// Every vendor library returned an error code, so return one of
// them to the application.
__eglReportError(errorCode, funcName, __eglGetThreadLabel(),
"Could not create EGLDisplay");
}
return EGL_NO_DISPLAY;
}
}
PUBLIC EGLDisplay EGLAPIENTRY eglGetDisplay(EGLNativeDisplayType display_id)
{
EGLenum platform = EGL_NONE;
const char *name;
__eglEntrypointCommon();
// First, see if the user specified a platform to use.
name = getenv("EGL_PLATFORM");
if (name != NULL && name[0] != '\x00') {
int i;
for (i=0; EGL_PLATFORMS_NAMES[i].name != NULL; i++) {
if (strcmp(name, EGL_PLATFORMS_NAMES[i].name) == 0) {
platform = EGL_PLATFORMS_NAMES[i].platform;
break;
}
}
// Since libglvnd might not know about every possible platform name,
// allow the user to specify a platform by the enum value.
if (platform == EGL_NONE) {
char *end;
long value = strtol(name, &end, 0);
if (*end == '\x00') {
platform = (EGLenum) value;
}
}
if (platform != EGL_NONE) {
return GetPlatformDisplayCommon(platform, display_id, NULL, "eglGetDisplay");
}
}
// For EGL_DEFAULT_DISPLAY, we can let the vendor libraries figure out a
// default.
if (display_id == EGL_DEFAULT_DISPLAY) {
return GetPlatformDisplayCommon(EGL_NONE, display_id, NULL, "eglGetDisplay");
}
// Otherwise, try to guess a platform type.
platform = GuessPlatformType(display_id);
if (platform == EGL_NONE) {
return EGL_NO_DISPLAY;
}
return GetPlatformDisplayCommon(platform, display_id, NULL, "eglGetDisplay");
}
PUBLIC EGLDisplay EGLAPIENTRY eglGetPlatformDisplay(EGLenum platform, void *native_display, const EGLAttrib *attrib_list)
{
__eglEntrypointCommon();
if (platform == EGL_NONE) {
__eglReportError(EGL_BAD_PARAMETER, "eglGetPlatformDisplay", __eglGetThreadLabel(),
"platform may not be EGL_NONE.");
return EGL_NO_DISPLAY;
}
return GetPlatformDisplayCommon(platform, native_display, attrib_list, "eglGetPlatformDisplay");
}
EGLDisplay EGLAPIENTRY eglGetPlatformDisplayEXT(EGLenum platform, void *native_display, const EGLint *attrib_list)
{
__eglEntrypointCommon();
if (platform == EGL_NONE) {
__eglReportError(EGL_BAD_PARAMETER, "eglGetPlatformDisplayEXT", __eglGetThreadLabel(),
"platform may not be EGL_NONE.");
return EGL_NO_DISPLAY;
}
if (sizeof(EGLAttrib) == sizeof(EGLint) || attrib_list == NULL) {
return GetPlatformDisplayCommon(platform, native_display,
(const EGLAttrib *) attrib_list, "eglGetPlatformDisplayEXT");
} else {
EGLAttrib *attribs = NULL;
EGLDisplay dpy;
int count = 0;
int i;
while (attrib_list[count] != EGL_NONE) {
count += 2;
}
count++;
attribs = malloc(count * sizeof(EGLAttrib));
if (attribs == NULL) {
__eglReportCritical(EGL_BAD_ALLOC, "eglGetPlatformDisplayEXT",
__eglGetThreadLabel(), NULL);
return EGL_NO_DISPLAY;
}
for (i=0; i<count; i++) {
attribs[i] = (EGLAttrib) attrib_list[i];
}
dpy = GetPlatformDisplayCommon(platform, native_display, attribs,
"eglGetPlatformDisplayEXT");
free(attribs);
return dpy;
}
}
PUBLIC EGLBoolean EGLAPIENTRY eglBindAPI(EGLenum api)
{
EGLBoolean supported = EGL_FALSE;
__EGLThreadAPIState *state;
struct glvnd_list *vendorList;
__EGLvendorInfo *vendor;
// We only support GL and GLES right now.
if (api != EGL_OPENGL_API && api != EGL_OPENGL_ES_API) {
__eglReportError(EGL_BAD_PARAMETER, "eglBindAPI", __eglGetThreadLabel(),
"Unsupported rendering API 0x%04x", api);
return EGL_FALSE;
}
if (api == eglQueryAPI()) {
// Nothing to do.
return EGL_TRUE;
}
// First, check if any vendor library supports the requested API.
vendorList = __eglLoadVendors();
glvnd_list_for_each_entry(vendor, vendorList, entry) {
if ((api == EGL_OPENGL_API && vendor->supportsGL)
|| (api == EGL_OPENGL_ES_API && vendor->supportsGLES)) {
supported = EGL_TRUE;
break;
}
}
if (!supported) {
__eglReportError(EGL_BAD_PARAMETER, "eglBindAPI", __eglGetThreadLabel(),
"Unsupported rendering API 0x%04x", api);
return EGL_FALSE;
}
// Note: We do not call into the vendor library here. The vendor is
// responsible for looking up the current API instead.
state = __eglGetCurrentThreadAPIState(EGL_TRUE);
if (state == NULL) {
// Probably out of memory. Not much else we can do here.
return EGL_FALSE;
}
state->currentClientApi = api;
glvnd_list_for_each_entry(vendor, vendorList, entry) {
if (vendor->staticDispatch.bindAPI != NULL) {
vendor->staticDispatch.bindAPI(api);
}
}
return EGL_TRUE;
}
PUBLIC EGLenum EGLAPIENTRY eglQueryAPI(void)
{
__eglEntrypointCommon();
return __eglQueryAPI();
}
PUBLIC EGLDisplay EGLAPIENTRY eglGetCurrentDisplay(void)
{
__eglEntrypointCommon();
return __eglGetCurrentDisplay();
}
PUBLIC EGLContext EGLAPIENTRY eglGetCurrentContext(void)
{
__eglEntrypointCommon();
return __eglGetCurrentContext();
}
PUBLIC EGLSurface EGLAPIENTRY eglGetCurrentSurface(EGLint readdraw)
{
__eglEntrypointCommon();
if (readdraw != EGL_DRAW && readdraw != EGL_READ) {
__eglReportError(EGL_BAD_PARAMETER, "eglGetCurrentSurface", __eglGetThreadLabel(),
"Invalid enum 0x%04x\n", readdraw);
}
return __eglGetCurrentSurface(readdraw);
}
static EGLBoolean InternalLoseCurrent(void)
{
__EGLdispatchThreadState *apiState = __eglGetCurrentAPIState();
EGLBoolean ret;
if (apiState == NULL) {
return EGL_TRUE;
}
__eglSetLastVendor(apiState->currentVendor);
ret = apiState->currentVendor->staticDispatch.makeCurrent(
apiState->currentDisplay->dpy,
EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
if (!ret) {
return EGL_FALSE;
}
__glDispatchLoseCurrent();
__eglDestroyAPIState(apiState);
return EGL_TRUE;
}
/**
* Calls into the vendor library to set the current context, and then updates
* the API state fields to match.
*
* This function does *not* call into libGLdispatch, so it can only switch
* to another context with the same vendor.
*
* If this function succeeds, then it will update the current display, context,
* and drawables in \p apiState.
*
* If it fails, then it will leave \p apiState unmodified. It's up to the
* vendor library to ensure that the old context is still current in that case.
*/
static EGLBoolean InternalMakeCurrentVendor(
__EGLdisplayInfo *dpy, EGLSurface draw, EGLSurface read,
EGLContext context,
__EGLdispatchThreadState *apiState,
__EGLvendorInfo *vendor)
{
EGLBoolean ret;
assert(apiState->currentVendor == vendor);
__eglSetLastVendor(dpy->vendor);
ret = dpy->vendor->staticDispatch.makeCurrent(dpy->dpy, draw, read, context);
if (ret) {
apiState->currentDisplay = dpy;
apiState->currentDraw = draw;
apiState->currentRead = read;
apiState->currentContext = context;
}
return ret;
}
/**
* Makes a context current. This function handles both the vendor library and
* libGLdispatch.
*
* There must not be a current API state in libGLdispatch when this function is
* called.
*
* If this function fails, then it will release the context and dispatch state
* before returning.
*/
static EGLBoolean InternalMakeCurrentDispatch(
__EGLdisplayInfo *dpy, EGLSurface draw, EGLSurface read,
EGLContext context,
__EGLvendorInfo *vendor)
{
__EGLdispatchThreadState *apiState;
EGLBoolean ret;
assert(__eglGetCurrentAPIState() == NULL);
apiState = __eglCreateAPIState();
if (apiState == NULL) {
return EGL_FALSE;
}
ret = __glDispatchMakeCurrent(
&apiState->glas,
vendor->glDispatch,
vendor->vendorID,
(vendor->patchSupported ? &vendor->patchCallbacks : NULL)
);
if (ret) {
apiState->currentVendor = vendor;
ret = InternalMakeCurrentVendor(dpy, draw, read, context,
apiState, vendor);
if (!ret) {
__glDispatchLoseCurrent();
}
}
if (!ret) {
__eglDestroyAPIState(apiState);
}
return ret;
}
PUBLIC EGLBoolean EGLAPIENTRY eglMakeCurrent(EGLDisplay dpy,
EGLSurface draw, EGLSurface read, EGLContext context)
{
__GLdispatchThreadState *glas;
__EGLdispatchThreadState *apiState;
__EGLvendorInfo *oldVendor, *newVendor;
__EGLdisplayInfo *oldDpy, *newDpy;
EGLSurface oldDraw, oldRead;
EGLContext oldContext;
EGLBoolean ret;
__eglEntrypointCommon();
// According to the EGL spec, the display handle must be valid, even if
// the context is NULL.
newDpy = __eglLookupDisplay(dpy);
if (newDpy == NULL) {
__eglReportError(EGL_BAD_DISPLAY, "eglMakeCurrent", NULL,
"Invalid display %p", dpy);
return EGL_FALSE;
}
if (context == EGL_NO_CONTEXT && (draw != EGL_NO_SURFACE || read != EGL_NO_SURFACE)) {
__eglReportError(EGL_BAD_MATCH, "eglMakeCurrent", NULL,
"Got an EGLSurface but no EGLContext");
return EGL_FALSE;
}
glas = __glDispatchGetCurrentThreadState();
if (glas != NULL) {
if (glas->tag != GLDISPATCH_API_EGL) {
// Another API (probably GLX) already has a current context. Just
// return failure.
// TODO: What error should this generate?
__eglReportError(EGL_BAD_ACCESS, "eglMakeCurrent", NULL,
"Another window API already has a current context");
return EGL_FALSE;
}
apiState = (__EGLdispatchThreadState *) glas;
oldVendor = apiState->currentVendor;
oldDpy = apiState->currentDisplay;
oldDraw = apiState->currentDraw;
oldRead = apiState->currentRead;
oldContext = apiState->currentContext;
assert(oldContext != EGL_NO_CONTEXT);
if (dpy == oldDpy->dpy && context == oldContext
&& draw == oldDraw && read == oldRead) {
// The current display, context, and drawables are the same, so just
// return.
return EGL_TRUE;
}
} else {
// We don't have a current context already.
if (context == EGL_NO_CONTEXT) {
// If both contexts are NULL, then ignore the other parameters and
// return early.
return EGL_TRUE;
}
apiState = NULL;
oldVendor = NULL;
oldDpy = NULL;
oldDraw = oldRead = EGL_NO_SURFACE;
oldContext = EGL_NO_CONTEXT;
}
if (context != EGL_NO_CONTEXT) {
newVendor = newDpy->vendor;
} else {
newVendor = NULL;
}
if (oldVendor == newVendor) {
/*
* We're switching between two contexts that use the same vendor. That
* means the dispatch table is also the same, which is the only thing
* that libGLdispatch cares about. Call into the vendor library to
* switch contexts, but don't call into libGLdispatch.
*/
ret = InternalMakeCurrentVendor(newDpy, draw, read, context,
apiState, newVendor);
} else if (newVendor == NULL) {
/*
* We have a current context and we're releasing it.
*/
assert(context == EGL_NO_CONTEXT);
ret = InternalLoseCurrent();
} else if (oldVendor == NULL) {
/*
* We don't have a current context, so we only need to make the new one
* current.
*/
ret = InternalMakeCurrentDispatch(newDpy, draw, read, context,
newVendor);
} else {
/*
* We're switching between contexts with different vendors.
*
* This gets tricky because we have to call into both vendor libraries
* and libGLdispatch. Any of those can fail, and if it does, then we
* have to make sure libEGL, libGLdispatch, and the vendor libraries
* all agree on what the current context is.
*
* To do that, we'll first release the current context, and then make
* the new context current.
*/
ret = InternalLoseCurrent();
if (ret) {
ret = InternalMakeCurrentDispatch(newDpy, draw, read, context,
newVendor);
/*
* Ideally, we should try to restore the old context if we fail,
* but we need to deal with the case where the old context was
* flagged for deletion, and thus is now deleted. We don't want to
* pass an invalid context to the vendor library.
*
* We could avoid that using a current context hashtable like GLX
* has. That would allow us to restore the old context when it
* still exists, but we'd still be left with no context if it was
* deleted.
*
* Note that GLX also needs that hashtable to keep its
* context-to-screen mapping up to date, but EGL doesn't need to
* keep track of contexts at all yet.
*
* Once we add support for OpenVG, though, then we'll need to keep
* track of data for every context, not just the current ones. At
* that point, we'll be able to use that to track context deletion
* as well.
*/
}
}
return ret;
}
PUBLIC EGLBoolean EGLAPIENTRY eglReleaseThread(void)
{
__EGLThreadAPIState *threadState = __eglGetCurrentThreadAPIState(EGL_FALSE);
if (threadState != NULL) {
__EGLdispatchThreadState *apiState = __eglGetCurrentAPIState();
__EGLvendorInfo *currentVendor = NULL;
struct glvnd_list *vendorList = __eglLoadVendors();
__EGLvendorInfo *vendor;
if (apiState != NULL) {
currentVendor = apiState->currentVendor;
if (!currentVendor->staticDispatch.releaseThread()) {
threadState->lastVendor = currentVendor;
return EGL_FALSE;
}
__glDispatchLoseCurrent();
__eglDestroyAPIState(apiState);
}
glvnd_list_for_each_entry(vendor, vendorList, entry) {
// Call into the remaining vendor libraries. Aside from the current
// vendor, none of these are allowed to fail -- otherwise, we'd end
// up in an inconsistant state.
if (vendor != currentVendor) {
vendor->staticDispatch.releaseThread();
}
}
__eglDestroyCurrentThreadAPIState();
}
assert(__eglGetCurrentAPIState() == NULL);
return EGL_TRUE;
}
PUBLIC EGLint EGLAPIENTRY eglGetError(void)
{
__EGLThreadAPIState *state;
// Note: We call __eglThreadInitialize here, not __eglEntrypointCommon,
// because we have to look up the current error code before clearing it.
__eglThreadInitialize();
state = __eglGetCurrentThreadAPIState(EGL_FALSE);
EGLint ret = EGL_SUCCESS;
if (state != NULL) {
if (state->lastVendor != NULL) {
ret = state->lastVendor->staticDispatch.getError();
} else {
ret = state->lastError;
}
state->lastVendor = NULL;
state->lastError = EGL_SUCCESS;
}
return ret;
}
void __eglSetError(EGLint error)
{
__EGLThreadAPIState *state;
state = __eglGetCurrentThreadAPIState(error != EGL_SUCCESS);
if (state != NULL) {
state->lastError = error;
state->lastVendor = NULL;
}
}
EGLBoolean __eglSetLastVendor(__EGLvendorInfo *vendor)
{
__EGLThreadAPIState *state;
state = __eglGetCurrentThreadAPIState(EGL_TRUE);
if (state != NULL) {
state->lastError = EGL_SUCCESS;
state->lastVendor = vendor;
return EGL_TRUE;
} else {
return EGL_FALSE;
}
}
static char *GetClientExtensionString(void)
{
struct glvnd_list *vendorList = __eglLoadVendors();
__EGLvendorInfo *vendor;
char *result = NULL;
// First, find the union of all available vendor libraries. Start with an
// empty string, then merge the extension string from every vendor library.
result = malloc(1);
if (result == NULL) {
return NULL;
}
result[0] = '\0';
// Merge the extension string from every vendor library.
glvnd_list_for_each_entry(vendor, vendorList, entry) {
const char *vendorString = vendor->staticDispatch.queryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
if (vendorString != NULL && vendorString[0] != '\0') {
result = UnionExtensionStrings(result, vendorString);
if (result == NULL) {
return NULL;
}
}
}
// Next, take the intersection of the client extensions from the vendors
// with the client extensions that libglvnd supports.
IntersectionExtensionStrings(result, SUPPORTED_CLIENT_EXTENSIONS);
// Add the extension strings that libEGL itself provides.
result = UnionExtensionStrings(result, ALWAYS_SUPPORTED_CLIENT_EXTENSIONS);
if (result == NULL) {
return NULL;
}
glvnd_list_for_each_entry(vendor, vendorList, entry) {
const char *vendorString = NULL;
if (vendor->eglvc.getVendorString != NULL) {
vendorString = vendor->eglvc.getVendorString(__EGL_VENDOR_STRING_PLATFORM_EXTENSIONS);
}
if (vendorString != NULL && vendorString[0] != '\0') {
result = UnionExtensionStrings(result, vendorString);
if (result == NULL) {
return NULL;
}
}
}
return result;
}
PUBLIC const char *EGLAPIENTRY eglQueryString(EGLDisplay dpy, EGLint name)
{
__eglEntrypointCommon();
if (dpy == EGL_NO_DISPLAY) {
if (name == EGL_EXTENSIONS) {
const char *ret;
if (glvnd_list_is_empty(__eglLoadVendors())) {
return "";
}
__glvndPthreadFuncs.mutex_lock(&clientExtensionStringMutex);
if (clientExtensionString == NULL) {
clientExtensionString = GetClientExtensionString();
}
ret = clientExtensionString;
__glvndPthreadFuncs.mutex_unlock(&clientExtensionStringMutex);
return ret;
} else if (name == EGL_VERSION) {
return GLVND_EGL_VERSION_STRING;
} else {
__eglReportError(EGL_BAD_DISPLAY, "eglQueryString", NULL,
"Invalid enum 0x%04x without a display", name);
return NULL;
}
} else {
__EGLdisplayInfo *dpyInfo = __eglLookupDisplay(dpy);
if (dpyInfo == NULL) {
__eglReportError(EGL_BAD_DISPLAY, "eglQueryString", NULL,
"Invalid display %p", dpy);
return NULL;
}
__eglSetLastVendor(dpyInfo->vendor);
return dpyInfo->vendor->staticDispatch.queryString(dpy, name);
}
}
static EGLBoolean QueryVendorDevices(__EGLvendorInfo *vendor, EGLint max_devices,
EGLDeviceEXT *devices, EGLint *num_devices)
{
EGLDeviceEXT *vendorDevices = NULL;
EGLint vendorCount = 0;
EGLint i;
if (!vendor->supportsDevice) {
return EGL_TRUE;
}
if (!vendor->staticDispatch.queryDevicesEXT(0, NULL, &vendorCount)) {
// Even if this vendor fails, we can still return the devices from any
// other vendors
return EGL_TRUE;
}
if (vendorCount <= 0) {
return EGL_TRUE;
}
if (devices == NULL) {
// We're only getting the number of devices.
*num_devices += vendorCount;
return EGL_TRUE;
}
vendorDevices = malloc(sizeof(EGLDeviceEXT) * vendorCount);
if (vendorDevices == NULL)
{
__eglReportCritical(EGL_BAD_ALLOC, "eglQueryDevicesEXT",
__eglGetThreadLabel(), "Out of memory allocating device list");
return EGL_FALSE;
}
if (!vendor->staticDispatch.queryDevicesEXT(vendorCount, vendorDevices, &vendorCount)) {
free(vendorDevices);
return EGL_TRUE;
}
// Add or update our mapping for all of the devices, and fill in the
// caller's array.
for (i=0; i<vendorCount; i++)
{
if (!__eglAddDevice(vendorDevices[i], vendor))
{
__eglReportCritical(EGL_BAD_ALLOC, "eglQueryDevicesEXT",
__eglGetThreadLabel(), "Out of memory allocating device/vendor map");
free(vendorDevices);
return EGL_FALSE;
}
if (*num_devices < max_devices)
{
devices[*num_devices] = vendorDevices[i];
(*num_devices)++;
}
}
free(vendorDevices);
return EGL_TRUE;
}
EGLBoolean EGLAPIENTRY eglQueryDevicesEXT(EGLint max_devices,
EGLDeviceEXT *devices, EGLint *num_devices)
{
struct glvnd_list *vendorList;
__EGLvendorInfo *vendor;
__eglEntrypointCommon();
if (num_devices == NULL || (max_devices <= 0 && devices != NULL)) {
__eglReportError(EGL_BAD_PARAMETER, "eglQueryDevicesEXT", NULL,
"Missing num_devices pointer");
return EGL_FALSE;
}
vendorList = __eglLoadVendors();
// Initialize num_devices. QueryVendorDevices will update it.
*num_devices = 0;
glvnd_list_for_each_entry(vendor, vendorList, entry) {
if (!QueryVendorDevices(vendor, max_devices, devices, num_devices)) {
return EGL_FALSE;
}
}
return EGL_TRUE;
}
EGLBoolean eglQueryDisplayAttribEXT(EGLDisplay dpy, EGLint attribute, EGLAttrib *value)
{
__EGLvendorInfo *vendor;
if (value == NULL) {
__eglReportError(EGL_BAD_PARAMETER, "eglQueryDisplayAttribEXT", NULL,
"Missing value pointer");
return EGL_FALSE;
}
vendor = __eglGetVendorFromDisplay(dpy);
if (vendor == NULL) {
__eglReportError(EGL_BAD_DISPLAY, "eglQueryDisplayAttribEXT", NULL,
"Invalid EGLDisplay handle");
return EGL_FALSE;
}
if (vendor->staticDispatch.queryDisplayAttribEXT == NULL) {
__eglReportError(EGL_BAD_DISPLAY, "eglQueryDisplayAttribEXT", NULL,
"Driver does not support eglQueryDisplayAttribEXT");
return EGL_FALSE;
}
__eglSetLastVendor(vendor);
if (!vendor->staticDispatch.queryDisplayAttribEXT(dpy, attribute, value)) {
return EGL_FALSE;
}
if (attribute == EGL_DEVICE_EXT && (EGLDeviceEXT) *value != EGL_NO_DEVICE_EXT) {
if (!__eglAddDevice((EGLDeviceEXT) *value, vendor)) {
__eglReportCritical(EGL_BAD_ALLOC, "eglQueryDevicesEXT",
__eglGetThreadLabel(), "Out of memory allocating device/vendor map");
return EGL_FALSE;
}
}
return EGL_TRUE;
}
// TODO: The function hash is the same as in GLX. It should go into a common
// file.
typedef struct {
char *procName;
__eglMustCastToProperFunctionPointerType addr;
UT_hash_handle hh;
} __EGLprocAddressHash;
static DEFINE_INITIALIZED_LKDHASH(__EGLprocAddressHash, __eglProcAddressHash);
static void CacheProcAddress(const char *procName,
__eglMustCastToProperFunctionPointerType addr)
{
size_t nameLen = strlen(procName);
__EGLprocAddressHash *pEntry;
LKDHASH_WRLOCK(__eglProcAddressHash);
HASH_FIND(hh, _LH(__eglProcAddressHash), procName,
nameLen, pEntry);
if (pEntry == NULL) {
pEntry = malloc(sizeof(*pEntry) + nameLen + 1);
if (pEntry != NULL) {
pEntry->procName = (char *) (pEntry + 1);
memcpy(pEntry->procName, procName, nameLen + 1);
pEntry->addr = addr;
HASH_ADD_KEYPTR(hh, _LH(__eglProcAddressHash), pEntry->procName,
nameLen, pEntry);
}
} else {
assert(pEntry->addr == addr);
}
LKDHASH_UNLOCK(__eglProcAddressHash);
}
static __eglMustCastToProperFunctionPointerType GetCachedProcAddress(const char *procName)
{
/*
* If this is the first time GetProcAddress has been called,
* initialize the hash table with locally-exported functions.
*/
__EGLprocAddressHash *pEntry = NULL;
LKDHASH_RDLOCK(__eglProcAddressHash);
HASH_FIND(hh, _LH(__eglProcAddressHash), procName,
strlen(procName), pEntry);
LKDHASH_UNLOCK(__eglProcAddressHash);
if (pEntry) {
return pEntry->addr;
}
return NULL;
}
PUBLIC __eglMustCastToProperFunctionPointerType EGLAPIENTRY eglGetProcAddress(const char *procName)
{
__eglMustCastToProperFunctionPointerType addr = NULL;
__eglEntrypointCommon();
/*
* Easy case: First check if we already know this address from
* a previous GetProcAddress() call or by virtue of being a function
* exported by libEGL.
*/
addr = GetCachedProcAddress(procName);
if (addr) {
return addr;
}
/*
* If that doesn't work, try requesting a dispatch function
* from one of the loaded vendor libraries.
*/
if (procName[0] == 'e' && procName[1] == 'g' && procName[2] == 'l') {
addr = __eglGetEGLDispatchAddress(procName);
} else if (procName[0] == 'g' && procName[1] == 'l') {
addr = __glDispatchGetProcAddress(procName);
} else {
addr = NULL;
}
if (addr != NULL) {
CacheProcAddress(procName, addr);
}
return addr;
}
// TODO: Move the Atomic* functions to a common file.
int AtomicIncrement(int volatile *val)
{
#if defined(HAVE_SYNC_INTRINSICS)
return __sync_add_and_fetch(val, 1);
#elif defined(USE_X86_ASM) || defined(USE_X86_64_ASM)
int result;
int delta = 1;
__asm __volatile__ ("lock; xaddl %0, %1"
: "=r" (result), "=m" (*val)
: "0" (delta), "m" (*val));
return result + delta;
#else
#error "Not implemented"
#endif
}
int AtomicSwap(int volatile *val, int newVal)
{
#if defined(HAVE_SYNC_INTRINSICS)
return __sync_lock_test_and_set(val, newVal);
#elif defined(USE_X86_ASM) || defined(USE_X86_64_ASM)
int result;
__asm __volatile__ ("xchgl %0, %1"
: "=r" (result), "=m" (*val)
: "0" (newVal), "m" (*val));
return result;
#else
#error "Not implemented"
#endif
}
int AtomicCompareAndSwap(int volatile *val, int oldVal, int newVal)
{
#if defined(HAVE_SYNC_INTRINSICS)
return __sync_val_compare_and_swap(val, oldVal, newVal);
#elif defined(USE_X86_ASM) || defined(USE_X86_64_ASM)
int result;
__asm __volatile__ ("lock; cmpxchgl %2, %1"
: "=a" (result), "=m" (*val)
: "r" (newVal), "m" (*val), "0" (oldVal));
return result;
#else
#error "Not implemented"
#endif
}
int AtomicDecrementClampAtZero(int volatile *val)
{
int oldVal, newVal;
oldVal = *val;
newVal = oldVal;
do {
if (oldVal <= 0) {
assert(oldVal == 0);
} else {
newVal = oldVal - 1;
if (newVal < 0) {
newVal = 0;
}
oldVal = AtomicCompareAndSwap(val, oldVal, newVal);
}
} while ((oldVal > 0) && (newVal != oldVal - 1));
return newVal;
}
static void __eglResetOnFork(void);
/*
* Perform checks that need to occur when entering any EGL entrypoint.
* Currently, this only detects whether a fork occurred since the last
* entrypoint was called, and performs recovery as needed.
*/
void CheckFork(void)
{
volatile static int g_threadsInCheck = 0;
volatile static int g_lastPid = -1;
int lastPid;
int pid = getpid();
AtomicIncrement(&g_threadsInCheck);
lastPid = AtomicSwap(&g_lastPid, pid);
if ((lastPid != -1) &&
(lastPid != pid)) {
DBG_PRINTF(0, "Fork detected\n");
__eglResetOnFork();
// Force g_threadsInCheck to 0 to unblock other threads waiting here.
g_threadsInCheck = 0;
} else {
AtomicDecrementClampAtZero(&g_threadsInCheck);
while (g_threadsInCheck > 0) {
// Wait for other threads to finish checking for a fork.
//
// If a fork happens while g_threadsInCheck > 0 the _first_ thread
// to enter __eglThreadInitialize() will see the fork, handle it, and force
// g_threadsInCheck to 0, unblocking any other threads stuck here.
sched_yield();
}
}
}
void __eglThreadInitialize(void)
{
CheckFork();
__glDispatchCheckMultithreaded();
}
static void __eglAPITeardown(EGLBoolean doReset)
{
__eglCurrentTeardown(doReset);
if (doReset) {
/*
* XXX: We should be able to get away with just resetting the proc address
* hash lock, and not throwing away cached addresses.
*/
__glvndPthreadFuncs.rwlock_init(&__eglProcAddressHash.lock, NULL);
} else {
LKDHASH_TEARDOWN(__EGLprocAddressHash,
__eglProcAddressHash, NULL,
NULL, EGL_FALSE);
free(clientExtensionString);
clientExtensionString = NULL;
}
}
static void __eglResetOnFork(void)
{
/* Reset all EGL API state */
__eglAPITeardown(EGL_TRUE);
/* Reset all mapping state */
__eglMappingTeardown(EGL_TRUE);
/* Reset GLdispatch */
__glDispatchReset();
}
#if defined(USE_ATTRIBUTE_CONSTRUCTOR)
void __attribute__ ((constructor)) __eglInit(void)
#else
void _init(void)
#endif
{
if (__glDispatchGetABIVersion() != GLDISPATCH_ABI_VERSION) {
fprintf(stderr, "libGLdispatch ABI version is incompatible with libEGL.\n");
abort();
}
/* Initialize GLdispatch; this will also initialize our pthreads imports */
__glDispatchInit();
glvndSetupPthreads();
// Set up the mapping code, and populate the getprocaddress hashtable.
__eglMappingInit();
__eglCurrentInit();
__eglInitVendors();
/* TODO install fork handlers using __register_atfork */
DBG_PRINTF(0, "Loading EGL...\n");
}
#if defined(USE_ATTRIBUTE_CONSTRUCTOR)
void __attribute__ ((destructor)) __eglFini(void)
#else
void _fini(void)
#endif
{
/* Check for a fork before going further. */
CheckFork();
/*
* If libEGL owns the current API state, lose current
* in GLdispatch before going further.
*/
__GLdispatchThreadState *glas =
__glDispatchGetCurrentThreadState();
if (glas && glas->tag == GLDISPATCH_API_EGL) {
__glDispatchLoseCurrent();
}
/* Tear down all EGL API state */
__eglAPITeardown(EGL_FALSE);
/* Tear down all mapping state */
__eglMappingTeardown(EGL_FALSE);
__eglTeardownVendors();
/* Tear down GLdispatch if necessary */
__glDispatchFini();
}