eaffa7bc12
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.
1388 lines
42 KiB
C
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();
|
|
}
|
|
|