libglvnd/src/EGL/libeglvendor.c

525 lines
17 KiB
C

#include "libeglvendor.h"
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dlfcn.h>
#include <string.h>
#include <unistd.h>
#include <fnmatch.h>
#include <dirent.h>
#include "glvnd_pthread.h"
#include "libeglcurrent.h"
#include "libeglmapping.h"
#include "utils_misc.h"
#include "glvnd_list.h"
#include "cJSON.h"
#include "egldispatchstubs.h"
#define FILE_FORMAT_VERSION_MAJOR 1
#define FILE_FORMAT_VERSION_MINOR 0
static void LoadVendors(void);
static void TeardownVendor(__EGLvendorInfo *vendor);
static __EGLvendorInfo *LoadVendor(const char *filename);
static void LoadVendorsFromConfigDir(const char *dirName);
static __EGLvendorInfo *LoadVendorFromConfigFile(const char *filename);
static cJSON *ReadJSONFile(const char *filename);
static glvnd_once_t loadVendorsOnceControl = GLVND_ONCE_INIT;
static struct glvnd_list __eglVendorList;
void LoadVendors(void)
{
const char *env = NULL;
char **tokens;
int i;
// First, check to see if a list of vendors was specified.
if (getuid() == geteuid() && getgid() == getegid()) {
env = getenv("__EGL_VENDOR_LIBRARY_FILENAMES");
}
if (env != NULL) {
tokens = SplitString(env, NULL, ":");
if (tokens != NULL) {
for (i=0; tokens[i] != NULL; i++) {
LoadVendorFromConfigFile(tokens[i]);
}
free(tokens);
}
return;
}
// We didn't get a list of vendors, so look through the vendor config
// directories.
if (getuid() == geteuid() && getgid() == getegid()) {
env = getenv("__EGL_VENDOR_LIBRARY_DIRS");
}
if (env == NULL) {
env = DEFAULT_EGL_VENDOR_CONFIG_DIRS;
}
tokens = SplitString(env, NULL, ":");
if (tokens != NULL) {
for (i=0; tokens[i] != NULL; i++) {
LoadVendorsFromConfigDir(tokens[i]);
}
free(tokens);
}
}
static int ScandirFilter(const struct dirent *ent)
{
// Ignore the entry if we know that it's not a regular file or symlink.
if (ent->d_type != DT_REG && ent->d_type != DT_LNK && ent->d_type != DT_UNKNOWN) {
return 0;
}
// Otherwise, select any JSON files.
if (fnmatch("*.json", ent->d_name, 0) == 0) {
return 1;
} else {
return 0;
}
}
static int CompareFilenames(const struct dirent **ent1, const struct dirent **ent2)
{
return strcmp((*ent1)->d_name, (*ent2)->d_name);
}
void LoadVendorsFromConfigDir(const char *dirName)
{
struct dirent **entries = NULL;
size_t dirnameLen;
const char *pathSep;
int count;
int i;
count = scandir(dirName, &entries, ScandirFilter, CompareFilenames);
if (count <= 0) {
return;
}
// Check if dirName ends with a "/" character. If it doesn't, then we need
// to add one when we construct the full file paths below.
dirnameLen = strlen(dirName);
if (dirName > 0 && dirName[dirnameLen - 1] != '/') {
pathSep = "/";
} else {
pathSep = "";
}
for (i=0; i<count; i++) {
char *path = NULL;
if (glvnd_asprintf(&path, "%s%s%s", dirName, pathSep, entries[i]->d_name) > 0) {
LoadVendorFromConfigFile(path);
free(path);
} else {
fprintf(stderr, "ERROR: Could not allocate vendor library path name\n");
}
}
free(entries);
}
void __eglInitVendors(void)
{
glvnd_list_init(&__eglVendorList);
}
struct glvnd_list *__eglLoadVendors(void)
{
__glvndPthreadFuncs.once(&loadVendorsOnceControl, LoadVendors);
return &__eglVendorList;
}
void __eglTeardownVendors(void)
{
__EGLvendorInfo *vendor;
__EGLvendorInfo *vendorTemp;
glvnd_list_for_each_entry_safe(vendor, vendorTemp, &__eglVendorList, entry) {
glvnd_list_del(&vendor->entry);
TeardownVendor(vendor);
}
}
const __EGLapiExports __eglExportsTable = {
__eglThreadInitialize, // threadInit
__eglQueryAPI, // getCurrentApi
__eglGetCurrentVendor, // getCurrentVendor
__eglGetCurrentContext, // getCurrentContext
__eglGetCurrentDisplay, // getCurrentDisplay
__eglGetCurrentSurface, // getCurrentSurface
__eglFetchDispatchEntry, // fetchDispatchEntry
__eglSetError, // setEGLError
__eglSetLastVendor, // setLastVendor
__eglGetVendorFromDisplay, // getVendorFromDisplay
__eglGetVendorFromDevice, // getVendorFromDevice
};
void TeardownVendor(__EGLvendorInfo *vendor)
{
if (vendor->glDispatch) {
__glDispatchDestroyTable(vendor->glDispatch);
}
/* Clean up the dynamic dispatch table */
if (vendor->dynDispatch != NULL) {
__glvndWinsysVendorDispatchDestroy(vendor->dynDispatch);
vendor->dynDispatch = NULL;
}
if (vendor->dlhandle != NULL) {
dlclose(vendor->dlhandle);
}
free(vendor);
}
static GLboolean LookupVendorEntrypoints(__EGLvendorInfo *vendor)
{
memset(&vendor->staticDispatch, 0, sizeof(vendor->staticDispatch));
// TODO: A lot of these should be implemented (and probably generated) as
// normal EGL dispatch functions, instead of having to special-case them.
#define LOADENTRYPOINT(ptr, name) do { \
vendor->staticDispatch.ptr = vendor->eglvc.getProcAddress(name); \
if (vendor->staticDispatch.ptr == NULL) { return GL_FALSE; } \
} while(0)
LOADENTRYPOINT(initialize, "eglInitialize" );
LOADENTRYPOINT(chooseConfig, "eglChooseConfig" );
LOADENTRYPOINT(copyBuffers, "eglCopyBuffers" );
LOADENTRYPOINT(createContext, "eglCreateContext" );
LOADENTRYPOINT(createPbufferSurface, "eglCreatePbufferSurface" );
LOADENTRYPOINT(createPixmapSurface, "eglCreatePixmapSurface" );
LOADENTRYPOINT(createWindowSurface, "eglCreateWindowSurface" );
LOADENTRYPOINT(destroyContext, "eglDestroyContext" );
LOADENTRYPOINT(destroySurface, "eglDestroySurface" );
LOADENTRYPOINT(getConfigAttrib, "eglGetConfigAttrib" );
LOADENTRYPOINT(getConfigs, "eglGetConfigs" );
LOADENTRYPOINT(makeCurrent, "eglMakeCurrent" );
LOADENTRYPOINT(queryContext, "eglQueryContext" );
LOADENTRYPOINT(queryString, "eglQueryString" );
LOADENTRYPOINT(querySurface, "eglQuerySurface" );
LOADENTRYPOINT(swapBuffers, "eglSwapBuffers" );
LOADENTRYPOINT(terminate, "eglTerminate" );
LOADENTRYPOINT(waitGL, "eglWaitGL" );
LOADENTRYPOINT(waitNative, "eglWaitNative" );
LOADENTRYPOINT(bindTexImage, "eglBindTexImage" );
LOADENTRYPOINT(releaseTexImage, "eglReleaseTexImage" );
LOADENTRYPOINT(surfaceAttrib, "eglSurfaceAttrib" );
LOADENTRYPOINT(swapInterval, "eglSwapInterval" );
LOADENTRYPOINT(createPbufferFromClientBuffer, "eglCreatePbufferFromClientBuffer" );
LOADENTRYPOINT(releaseThread, "eglReleaseThread" );
LOADENTRYPOINT(waitClient, "eglWaitClient" );
LOADENTRYPOINT(getError, "eglGetError" );
#undef LOADENTRYPOINT
// The remaining functions here are optional.
#define LOADENTRYPOINT(ptr, name) \
vendor->staticDispatch.ptr = vendor->eglvc.getProcAddress(name);
LOADENTRYPOINT(bindAPI, "eglBindAPI" );
LOADENTRYPOINT(createSync, "eglCreateSync" );
LOADENTRYPOINT(destroySync, "eglDestroySync" );
LOADENTRYPOINT(clientWaitSync, "eglClientWaitSync" );
LOADENTRYPOINT(getSyncAttrib, "eglGetSyncAttrib" );
LOADENTRYPOINT(createImage, "eglCreateImage" );
LOADENTRYPOINT(destroyImage, "eglDestroyImage" );
LOADENTRYPOINT(createPlatformWindowSurface, "eglCreatePlatformWindowSurface" );
LOADENTRYPOINT(createPlatformPixmapSurface, "eglCreatePlatformPixmapSurface" );
LOADENTRYPOINT(waitSync, "eglWaitSync" );
LOADENTRYPOINT(queryDevicesEXT, "eglQueryDevicesEXT" );
LOADENTRYPOINT(debugMessageControlKHR, "eglDebugMessageControlKHR" );
LOADENTRYPOINT(queryDebugKHR, "eglQueryDebugKHR" );
LOADENTRYPOINT(labelObjectKHR, "eglLabelObjectKHR" );
#undef LOADENTRYPOINT
return GL_TRUE;
}
static void *VendorGetProcAddressCallback(const char *procName, void *param)
{
__EGLvendorInfo *vendor = (__EGLvendorInfo *) param;
return vendor->eglvc.getProcAddress(procName);
}
static EGLBoolean CheckFormatVersion(const char *versionStr)
{
int major, minor, rev;
int len;
major = minor = rev = -1;
len = sscanf(versionStr, "%d.%d.%d", &major, &minor, &rev);
if (len < 1) {
return EGL_FALSE;
}
if (len < 2) {
minor = 0;
}
if (len < 3) {
rev = 0;
}
if (major != FILE_FORMAT_VERSION_MAJOR) {
return EGL_FALSE;
}
// The minor version number will be incremented if we ever add an optional
// value to the JSON format that libEGL has to pay attention to. That is,
// an older vendor library will still work, but a vendor library with a
// newer format than this library understands should fail.
if (minor > FILE_FORMAT_VERSION_MINOR) {
return EGL_FALSE;
}
return EGL_TRUE;
}
static __EGLvendorInfo *LoadVendorFromConfigFile(const char *filename)
{
__EGLvendorInfo *vendor = NULL;
cJSON *root;
cJSON *node;
cJSON *icdNode;
const char *libraryPath;
root = ReadJSONFile(filename);
if (root == NULL) {
goto done;
}
node = cJSON_GetObjectItem(root, "file_format_version");
if (node == NULL || node->type != cJSON_String) {
goto done;
}
if (!CheckFormatVersion(node->valuestring)) {
goto done;
}
icdNode = cJSON_GetObjectItem(root, "ICD");
if (icdNode == NULL || icdNode->type != cJSON_Object) {
goto done;
}
node = cJSON_GetObjectItem(icdNode, "library_path");
if (node == NULL || node->type != cJSON_String) {
goto done;
}
libraryPath = node->valuestring;
vendor = LoadVendor(libraryPath);
done:
if (root != NULL) {
cJSON_Delete(root);
}
if (vendor != NULL) {
glvnd_list_append(&vendor->entry, &__eglVendorList);
}
return vendor;
}
static cJSON *ReadJSONFile(const char *filename)
{
FILE *in = NULL;
char *buf = NULL;
cJSON *root = NULL;
struct stat st;
in = fopen(filename, "r");
if (in == NULL) {
goto done;
}
if (fstat(fileno(in), &st) != 0) {
goto done;
}
buf = (char *) malloc(st.st_size + 1);
if (buf == NULL) {
goto done;
}
if (fread(buf, st.st_size, 1, in) != 1) {
goto done;
}
buf[st.st_size] = '\0';
root = cJSON_Parse(buf);
done:
if (in != NULL) {
fclose(in);
}
if (buf != NULL) {
free(buf);
}
return root;
}
static void CheckVendorExtensionString(__EGLvendorInfo *vendor, const char *str)
{
static const char NAME_DEVICE_BASE[] = "EGL_EXT_device_base";
static const char NAME_DEVICE_ENUM[] = "EGL_EXT_device_enumeration";
static const char NAME_PLATFORM_DEVICE[] = "EGL_EXT_platform_device";
static const char NAME_EXT_PLATFORM_WAYLAND[] = "EGL_EXT_platform_wayland";
static const char NAME_KHR_PLATFORM_WAYLAND[] = "EGL_KHR_platform_wayland";
static const char NAME_EXT_PLATFORM_X11[] = "EGL_EXT_platform_x11";
static const char NAME_KHR_PLATFORM_X11[] = "EGL_KHR_platform_x11";
if (str == NULL || str[0] == '\x00') {
return;
}
if (!vendor->supportsDevice) {
if (IsTokenInString(str, NAME_DEVICE_BASE, sizeof(NAME_DEVICE_BASE) - 1, " ")
|| IsTokenInString(str, NAME_DEVICE_ENUM, sizeof(NAME_DEVICE_ENUM) - 1, " ")) {
vendor->supportsDevice = EGL_TRUE;
}
}
if (!vendor->supportsPlatformDevice) {
if (IsTokenInString(str, NAME_PLATFORM_DEVICE, sizeof(NAME_PLATFORM_DEVICE) - 1, " ")) {
vendor->supportsPlatformDevice = EGL_TRUE;
}
}
if (!vendor->supportsPlatformWayland) {
if (IsTokenInString(str, NAME_EXT_PLATFORM_WAYLAND, sizeof(NAME_EXT_PLATFORM_WAYLAND) - 1, " ")
|| IsTokenInString(str, NAME_KHR_PLATFORM_WAYLAND, sizeof(NAME_KHR_PLATFORM_WAYLAND) - 1, " ")) {
vendor->supportsPlatformWayland = EGL_TRUE;
}
}
if (!vendor->supportsPlatformX11) {
if (IsTokenInString(str, NAME_EXT_PLATFORM_X11, sizeof(NAME_EXT_PLATFORM_X11) - 1, " ")
|| IsTokenInString(str, NAME_KHR_PLATFORM_X11, sizeof(NAME_KHR_PLATFORM_X11) - 1, " ")) {
vendor->supportsPlatformX11 = EGL_TRUE;
}
}
}
static void CheckVendorExtensions(__EGLvendorInfo *vendor)
{
CheckVendorExtensionString(vendor,
vendor->staticDispatch.queryString(EGL_NO_DISPLAY, EGL_EXTENSIONS));
if (vendor->eglvc.getVendorString != NULL) {
CheckVendorExtensionString(vendor,
vendor->eglvc.getVendorString(__EGL_VENDOR_STRING_PLATFORM_EXTENSIONS));
}
if (vendor->staticDispatch.queryDevicesEXT == NULL) {
vendor->supportsDevice = EGL_FALSE;
}
if (!vendor->supportsDevice) {
vendor->supportsPlatformDevice = EGL_FALSE;
}
}
static __EGLvendorInfo *LoadVendor(const char *filename)
{
__PFNEGLMAINPROC eglMainProc;
__EGLvendorInfo *vendor = NULL;
__EGLvendorInfo *otherVendor;
int i;
// Allocate the vendor structure, plus enough room for a copy of its name.
vendor = (__EGLvendorInfo *) calloc(1, sizeof(__EGLvendorInfo));
if (vendor == NULL) {
return NULL;
}
vendor->dlhandle = dlopen(filename, RTLD_LAZY);
if (vendor->dlhandle == NULL) {
goto fail;
}
// Check if this vendor was already loaded under a different name.
glvnd_list_for_each_entry(otherVendor, &__eglVendorList, entry) {
if (otherVendor->dlhandle == vendor->dlhandle) {
goto fail;
}
}
eglMainProc = dlsym(vendor->dlhandle, __EGL_MAIN_PROTO_NAME);
if (!eglMainProc) {
goto fail;
}
if (!(*eglMainProc)(EGL_VENDOR_ABI_VERSION,
&__eglExportsTable,
vendor, &vendor->eglvc)) {
goto fail;
}
// Make sure all the required functions are there.
if (vendor->eglvc.getPlatformDisplay == NULL
|| vendor->eglvc.getSupportsAPI == NULL
|| vendor->eglvc.getProcAddress == NULL
|| vendor->eglvc.getDispatchAddress == NULL
|| vendor->eglvc.setDispatchIndex == NULL) {
goto fail;
}
if (vendor->eglvc.isPatchSupported != NULL
&& vendor->eglvc.initiatePatch != NULL) {
vendor->patchCallbacks.isPatchSupported = vendor->eglvc.isPatchSupported;
vendor->patchCallbacks.initiatePatch = vendor->eglvc.initiatePatch;
vendor->patchCallbacks.releasePatch = vendor->eglvc.releasePatch;
vendor->patchCallbacks.threadAttach = vendor->eglvc.patchThreadAttach;
vendor->patchSupported = EGL_TRUE;
}
if (!LookupVendorEntrypoints(vendor)) {
goto fail;
}
vendor->supportsGL = vendor->eglvc.getSupportsAPI(EGL_OPENGL_API);
vendor->supportsGLES = vendor->eglvc.getSupportsAPI(EGL_OPENGL_ES_API);
if (!(vendor->supportsGL || vendor->supportsGLES)) {
goto fail;
}
vendor->vendorID = __glDispatchNewVendorID();
assert(vendor->vendorID >= 0);
// TODO: Allow per-context dispatch tables?
vendor->glDispatch = __glDispatchCreateTable(VendorGetProcAddressCallback, vendor);
if (!vendor->glDispatch) {
goto fail;
}
CheckVendorExtensions(vendor);
// Create and initialize the EGL dispatch table.
// This is called before trying to look up any vendor-supplied EGL dispatch
// functions, so we only need to add the EGL dispatch functions that are
// defined in libEGL itself.
vendor->dynDispatch = __glvndWinsysVendorDispatchCreate();
if (!vendor->dynDispatch) {
goto fail;
}
for (i=0; i<__EGL_DISPATCH_FUNC_COUNT; i++) {
vendor->eglvc.setDispatchIndex(
__EGL_DISPATCH_FUNC_NAMES[i],
__EGL_DISPATCH_FUNC_INDICES[i]);
}
return vendor;
fail:
if (vendor != NULL) {
TeardownVendor(vendor);
}
return NULL;
}