libglvnd/src/EGL/libegl.c
Kyle Brenneman db5ca3370c EGL: Add support for eglQueryDisplayAttribKHR and NV.
There are KHR, EXT, and NV versions of eglQueryDisplayAttrib, so add
support for all of them.

Define separate eglQueryDisplayAttribEXT, eglQueryDisplayAttribKHR, and
eglQueryDisplayAttribNV functions. They all call into the same common
function, with the only difference being the name passed to an error
callback.

When loading a vendor library, check for the vendor's
eglQueryDisplayAttrib using all three suffixes, and use whichever one is
available.
2021-11-29 07:26:39 -07:00

1401 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;
}
static EGLBoolean CommonQueryDisplayAttrib(const char *name, EGLDisplay dpy, EGLint attribute, EGLAttrib *value)
{
__EGLvendorInfo *vendor;
if (value == NULL) {
__eglReportError(EGL_BAD_PARAMETER, name, NULL,
"Missing value pointer");
return EGL_FALSE;
}
vendor = __eglGetVendorFromDisplay(dpy);
if (vendor == NULL) {
__eglReportError(EGL_BAD_DISPLAY, name, NULL,
"Invalid EGLDisplay handle");
return EGL_FALSE;
}
if (vendor->staticDispatch.queryDisplayAttrib == NULL) {
__eglReportError(EGL_BAD_DISPLAY, name, NULL,
"Driver does not support eglQueryDisplayAttrib");
return EGL_FALSE;
}
__eglSetLastVendor(vendor);
if (!vendor->staticDispatch.queryDisplayAttrib(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;
}
EGLBoolean eglQueryDisplayAttribEXT(EGLDisplay dpy, EGLint attribute, EGLAttrib *value)
{
return CommonQueryDisplayAttrib("eglQueryDisplayAttribEXT", dpy, attribute, value);
}
EGLBoolean eglQueryDisplayAttribKHR(EGLDisplay dpy, EGLint attribute, EGLAttrib *value)
{
return CommonQueryDisplayAttrib("eglQueryDisplayAttribKHR", dpy, attribute, value);
}
EGLBoolean eglQueryDisplayAttribNV(EGLDisplay dpy, EGLint attribute, EGLAttrib *value)
{
return CommonQueryDisplayAttrib("eglQueryDisplayAttribNV", dpy, attribute, value);
}
// 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();
}