| /* |
| * Copyright (C) 2011 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| // |
| // WARNING -------------------------- WARNING |
| // This code meant to be used for testing purposes only. It is not production |
| // level quality. |
| // Use on your own risk !! |
| // |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <dlfcn.h> |
| #include "egl_dispatch.h" |
| #include "egl_ftable.h" |
| #include <log/log.h> |
| #include "ServerConnection.h" |
| #include "ThreadInfo.h" |
| #include <pthread.h> |
| #include "gl_wrapper_context.h" |
| #include "gl2_wrapper_context.h" |
| |
| #define GLES_EMUL_TARGETS_FILE "/system/etc/gles_emul.cfg" |
| // implementation libraries; |
| #define GLESv1_enc_LIB "/system/lib/libGLESv1_enc.so" |
| #define GLESv2_enc_LIB "/system/lib/libGLESv2_enc.so" |
| #define GLES_android_LIB "/system/lib/egl/libGLES_android.so" |
| // driver libraries; |
| #define GLESv1_DRIVER "/system/lib/egl/libGLESv1_CM_emul.so" |
| #define GLESv2_DRIVER "/system/lib/egl/libGLESv2_emul.so" |
| |
| |
| static struct egl_dispatch *s_dispatch = NULL; |
| pthread_once_t dispatchTablesInitialized = PTHREAD_ONCE_INIT; |
| |
| static bool s_needEncode = false; |
| |
| static gl_wrapper_context_t *g_gl_dispatch = NULL; |
| static gl2_wrapper_context_t *g_gl2_dispatch = NULL; |
| |
| template <class T> |
| int initApi(const char *driverLibName, const char *implLibName, T **dispatchTable, T *(*accessor)()) |
| { |
| void *driverLib = dlopen(driverLibName, RTLD_NOW | RTLD_LOCAL); |
| if (driverLib == NULL) { |
| ALOGE("failed to load %s : %s\n", driverLibName, dlerror()); |
| return -1; |
| } |
| |
| typedef T *(*createFcn_t)(void *, T *(*accessor)()); |
| createFcn_t createFcn; |
| createFcn = (createFcn_t) dlsym(driverLib, "createFromLib"); |
| if (createFcn == NULL) { |
| ALOGE("failed to load createFromLib constructor function\n"); |
| return -1; |
| } |
| |
| void *implLib = dlopen(implLibName, RTLD_NOW | RTLD_LOCAL); |
| if (implLib == NULL) { |
| ALOGE("couldn't open %s", implLibName); |
| return -2; |
| } |
| *dispatchTable = createFcn(implLib, accessor); |
| if (*dispatchTable == NULL) { |
| return -3; |
| } |
| |
| // XXX - we do close the impl library since it doesn't have data, as far as we concern. |
| dlclose(implLib); |
| |
| // XXX - we do not dlclose the driver library, so its not initialized when |
| // later loaded by android - is this required? |
| ALOGD("loading %s into %s complete\n", implLibName, driverLibName); |
| return 0; |
| |
| } |
| |
| static gl_wrapper_context_t *getGLContext() |
| { |
| return g_gl_dispatch; |
| } |
| |
| static gl2_wrapper_context_t *getGL2Context() |
| { |
| return g_gl2_dispatch; |
| } |
| |
| const char *getProcName() |
| { |
| static constexpr size_t kMaxProcessNameLength = 100; |
| static const char procname[kMaxProcessNameLength]{0}; |
| |
| int rc = pthread_getname_np(pthread_self(), procname, kMaxProcessNameLength); |
| |
| if (rc == 0) { |
| return procname; |
| } |
| |
| return nullptr; |
| } |
| |
| |
| |
| bool isNeedEncode() |
| { |
| const char *procname = getProcName(); |
| if (procname == NULL) return false; |
| ALOGD("isNeedEncode? for %s\n", procname); |
| // check on our whitelist |
| FILE *fp = fopen(GLES_EMUL_TARGETS_FILE, "rt"); |
| if (fp == NULL) { |
| ALOGE("couldn't open %s\n", GLES_EMUL_TARGETS_FILE); |
| return false; |
| } |
| |
| char line[100]; |
| bool found = false; |
| size_t procnameLen = strlen(procname); |
| |
| while (fgets(line, sizeof(line), fp) != NULL) { |
| if (strlen(line) >= procnameLen && |
| !strncmp(procname, line, procnameLen)) { |
| char c = line[procnameLen]; |
| if (c == '\0' || c == ' ' || c == '\t' || c == '\n') { |
| found = true; |
| ALOGD("should use encoder for %s\n", procname); |
| break; |
| } |
| } |
| } |
| fclose(fp); |
| return found; |
| } |
| |
| void initDispatchTables() |
| { |
| // |
| // Load our back-end implementation of EGL/GLES |
| // |
| ALOGD("Loading egl dispatch for %s\n", getProcName()); |
| |
| void *gles_android = dlopen("/system/lib/egl/libGLES_android.so", RTLD_NOW | RTLD_LOCAL); |
| if (!gles_android) { |
| fprintf(stderr,"FATAL ERROR: Could not load libGLES_android lib\n"); |
| exit(-1); |
| } |
| |
| // |
| // Load back-end EGL implementation library |
| // |
| s_dispatch = create_egl_dispatch( gles_android ); |
| if (!s_dispatch) { |
| fprintf(stderr,"FATAL ERROR: Could not create egl dispatch\n"); |
| exit(-1); |
| } |
| |
| // |
| // initialize gles |
| // |
| s_needEncode = isNeedEncode(); |
| void *gles_encoder = NULL; |
| if (s_needEncode) { |
| // initialize a connection to the server, and the GLESv1/v2 encoders; |
| ServerConnection * connection = ServerConnection::s_getServerConnection(); |
| if (connection == NULL) { |
| ALOGE("couldn't create server connection\n"); |
| s_needEncode = false; |
| } |
| } |
| |
| // init dispatch tabels for GLESv1 & GLESv2 |
| if (s_needEncode) { |
| // XXX - we do not check the retrun value because there isn't much we can do here on failure. |
| |
| if (initApi<gl_wrapper_context_t>(GLESv1_DRIVER, GLESv1_enc_LIB, &g_gl_dispatch, getGLContext) < 0) { |
| // fallback to android on faluire |
| s_needEncode = false; |
| } else { |
| initApi<gl2_wrapper_context_t>(GLESv2_DRIVER, GLESv2_enc_LIB, &g_gl2_dispatch, getGL2Context); |
| } |
| } |
| |
| if (!s_needEncode) { |
| ALOGD("Initializing native opengl for %s\n", getProcName()); |
| initApi<gl_wrapper_context_t>(GLESv1_DRIVER, GLES_android_LIB, &g_gl_dispatch, getGLContext); |
| // try to initialize gl2 from GLES, though its probably going to fail |
| initApi<gl2_wrapper_context_t>(GLESv2_DRIVER, GLES_android_LIB, &g_gl2_dispatch, getGL2Context); |
| } |
| } |
| |
| static struct egl_dispatch *getDispatch() |
| { |
| pthread_once(&dispatchTablesInitialized, initDispatchTables); |
| return s_dispatch; |
| } |
| |
| __eglMustCastToProperFunctionPointerType eglGetProcAddress(const char *procname) |
| { |
| |
| // search in EGL function table |
| for (int i=0; i<egl_num_funcs; i++) { |
| if (!strcmp(egl_funcs_by_name[i].name, procname)) { |
| return (__eglMustCastToProperFunctionPointerType)egl_funcs_by_name[i].proc; |
| } |
| } |
| |
| // we do not support eglGetProcAddress for GLESv1 & GLESv2. The loader |
| // should be able to find this function through dynamic loading. |
| return NULL; |
| } |
| |
| //////////////// Path through functions ////////// |
| |
| EGLint eglGetError() |
| { |
| return getDispatch()->eglGetError(); |
| } |
| |
| EGLDisplay eglGetDisplay(EGLNativeDisplayType display_id) |
| { |
| return getDispatch()->eglGetDisplay(display_id); |
| } |
| |
| EGLBoolean eglInitialize(EGLDisplay dpy, EGLint *major, EGLint *minor) |
| { |
| return getDispatch()->eglInitialize(dpy, major, minor); |
| } |
| |
| EGLBoolean eglTerminate(EGLDisplay dpy) |
| { |
| return getDispatch()->eglTerminate(dpy); |
| } |
| |
| const char* eglQueryString(EGLDisplay dpy, EGLint name) |
| { |
| return getDispatch()->eglQueryString(dpy, name); |
| } |
| |
| EGLBoolean eglGetConfigs(EGLDisplay dpy, EGLConfig *configs, EGLint config_size, EGLint *num_config) |
| { |
| return getDispatch()->eglGetConfigs(dpy, configs, config_size, num_config); |
| } |
| |
| static EGLint * filter_es2_bit(const EGLint *attrib_list, bool *isES2) |
| { |
| if (attrib_list == NULL) { |
| if (isES2 != NULL) *isES2 = false; |
| return NULL; |
| } |
| |
| EGLint *attribs = NULL; |
| int nAttribs = 0; |
| while(attrib_list[nAttribs] != EGL_NONE) nAttribs++; |
| nAttribs++; |
| |
| attribs = new EGLint[nAttribs]; |
| memcpy(attribs, attrib_list, nAttribs * sizeof(EGLint)); |
| if (isES2 != NULL) *isES2 = false; |
| |
| // scan the attribute list for ES2 request and replace with ES1. |
| for (int i = 0; i < nAttribs; i++) { |
| if (attribs[i] == EGL_RENDERABLE_TYPE) { |
| if (attribs[i + 1] & EGL_OPENGL_ES2_BIT) { |
| attribs[i + 1] &= ~EGL_OPENGL_ES2_BIT; |
| attribs[i + 1] |= EGL_OPENGL_ES_BIT; |
| ALOGD("removing ES2 bit 0x%x\n", attribs[i + 1]); |
| if (isES2 != NULL) *isES2 = true; |
| } |
| } |
| } |
| return attribs; |
| } |
| |
| EGLBoolean eglChooseConfig(EGLDisplay dpy, const EGLint *attrib_list, EGLConfig *configs, EGLint config_size, EGLint *num_config) |
| { |
| EGLBoolean res; |
| if (s_needEncode) { |
| EGLint *attribs = filter_es2_bit(attrib_list, NULL); |
| res = getDispatch()->eglChooseConfig(dpy, |
| attribs, |
| configs, |
| config_size, |
| num_config); |
| ALOGD("eglChooseConfig: %d configs found\n", *num_config); |
| if (*num_config == 0 && attribs != NULL) { |
| ALOGD("requested attributes:\n"); |
| for (int i = 0; attribs[i] != EGL_NONE; i++) { |
| ALOGD("%d: 0x%x\n", i, attribs[i]); |
| } |
| } |
| |
| delete attribs; |
| } else { |
| res = getDispatch()->eglChooseConfig(dpy, attrib_list, configs, config_size, num_config); |
| } |
| return res; |
| } |
| |
| EGLBoolean eglGetConfigAttrib(EGLDisplay dpy, EGLConfig config, EGLint attribute, EGLint *value) |
| { |
| if (s_needEncode && attribute == EGL_RENDERABLE_TYPE) { |
| *value = EGL_OPENGL_ES_BIT | EGL_OPENGL_ES2_BIT; |
| return EGL_TRUE; |
| } else { |
| return getDispatch()->eglGetConfigAttrib(dpy, config, attribute, value); |
| } |
| } |
| |
| EGLSurface eglCreateWindowSurface(EGLDisplay dpy, EGLConfig config, EGLNativeWindowType win, const EGLint *attrib_list) |
| { |
| EGLSurface surface = getDispatch()->eglCreateWindowSurface(dpy, config, win, attrib_list); |
| if (surface != EGL_NO_SURFACE) { |
| ServerConnection *server; |
| if (s_needEncode && (server = ServerConnection::s_getServerConnection()) != NULL) { |
| server->utEnc()->createSurface(server->utEnc(), getpid(), (uint32_t)surface); |
| } |
| } |
| return surface; |
| } |
| |
| EGLSurface eglCreatePbufferSurface(EGLDisplay dpy, EGLConfig config, const EGLint *attrib_list) |
| { |
| EGLSurface surface = getDispatch()->eglCreatePbufferSurface(dpy, config, attrib_list); |
| if (surface != EGL_NO_SURFACE) { |
| ServerConnection *server; |
| if (s_needEncode && (server = ServerConnection::s_getServerConnection()) != NULL) { |
| server->utEnc()->createSurface(server->utEnc(), getpid(), (uint32_t)surface); |
| } |
| } |
| return surface; |
| } |
| |
| EGLSurface eglCreatePixmapSurface(EGLDisplay dpy, EGLConfig config, EGLNativePixmapType pixmap, const EGLint *attrib_list) |
| { |
| EGLSurface surface = getDispatch()->eglCreatePixmapSurface(dpy, config, pixmap, attrib_list); |
| if (surface != EGL_NO_SURFACE) { |
| ServerConnection *server; |
| if (s_needEncode && (server = ServerConnection::s_getServerConnection()) != NULL) { |
| server->utEnc()->createSurface(server->utEnc(), getpid(), (uint32_t)surface); |
| } |
| } |
| return surface; |
| } |
| |
| EGLBoolean eglDestroySurface(EGLDisplay dpy, EGLSurface surface) |
| { |
| EGLBoolean res = getDispatch()->eglDestroySurface(dpy, surface); |
| if (res && surface != EGL_NO_SURFACE) { |
| ServerConnection *server; |
| if (s_needEncode && (server = ServerConnection::s_getServerConnection()) != NULL) { |
| server->utEnc()->destroySurface(server->utEnc(), getpid(), (uint32_t)surface); |
| } |
| } |
| return res; |
| } |
| |
| EGLBoolean eglQuerySurface(EGLDisplay dpy, EGLSurface surface, EGLint attribute, EGLint *value) |
| { |
| EGLBoolean res = getDispatch()->eglQuerySurface(dpy, surface, attribute, value); |
| if (res && attribute == EGL_RENDERABLE_TYPE) { |
| *value |= EGL_OPENGL_ES2_BIT; |
| } |
| return res; |
| } |
| |
| EGLBoolean eglBindAPI(EGLenum api) |
| { |
| return getDispatch()->eglBindAPI(api); |
| } |
| |
| EGLenum eglQueryAPI() |
| { |
| return getDispatch()->eglQueryAPI(); |
| } |
| |
| EGLBoolean eglWaitClient() |
| { |
| return getDispatch()->eglWaitClient(); |
| } |
| |
| EGLBoolean eglReleaseThread() |
| { |
| return getDispatch()->eglReleaseThread(); |
| } |
| |
| EGLSurface eglCreatePbufferFromClientBuffer(EGLDisplay dpy, EGLenum buftype, EGLClientBuffer buffer, EGLConfig config, const EGLint *attrib_list) |
| { |
| return getDispatch()->eglCreatePbufferFromClientBuffer(dpy, buftype, buffer, config, attrib_list); |
| } |
| |
| EGLBoolean eglSurfaceAttrib(EGLDisplay dpy, EGLSurface surface, EGLint attribute, EGLint value) |
| { |
| return getDispatch()->eglSurfaceAttrib(dpy, surface, attribute, value); |
| } |
| |
| EGLBoolean eglBindTexImage(EGLDisplay dpy, EGLSurface surface, EGLint buffer) |
| { |
| return getDispatch()->eglBindTexImage(dpy, surface, buffer); |
| } |
| |
| EGLBoolean eglReleaseTexImage(EGLDisplay dpy, EGLSurface surface, EGLint buffer) |
| { |
| return getDispatch()->eglReleaseTexImage(dpy, surface, buffer); |
| } |
| |
| EGLBoolean eglSwapInterval(EGLDisplay dpy, EGLint interval) |
| { |
| return getDispatch()->eglSwapInterval(dpy, interval); |
| } |
| |
| EGLContext eglCreateContext(EGLDisplay dpy, EGLConfig config, EGLContext share_context, const EGLint *attrib_list) |
| { |
| |
| EGLContext share = share_context; |
| if (share) share = ((EGLWrapperContext *)share_context)->aglContext; |
| |
| // check if are ES2, and convert it to ES1. |
| int nAttribs = 0; |
| if (attrib_list != NULL) { |
| while(attrib_list[nAttribs] != EGL_NONE) { |
| nAttribs++; |
| } |
| nAttribs++; |
| } |
| |
| EGLint *attrib = NULL; |
| if (nAttribs > 0) { |
| attrib = new EGLint[nAttribs]; |
| memcpy(attrib, attrib_list, nAttribs * sizeof(EGLint)); |
| } |
| |
| int version = 1; |
| for (int i = 0; i < nAttribs; i++) { |
| if (attrib[i] == EGL_CONTEXT_CLIENT_VERSION && |
| attrib[i + 1] == 2) { |
| version = 2; |
| attrib[i + 1] = 1; // replace to version 1 |
| } |
| } |
| |
| EGLContext ctx = getDispatch()->eglCreateContext(dpy, config, share, attrib); |
| delete[] attrib; |
| EGLWrapperContext *wctx = new EGLWrapperContext(ctx, version); |
| if (ctx != EGL_NO_CONTEXT) { |
| ServerConnection *server; |
| if (s_needEncode && (server = ServerConnection::s_getServerConnection()) != NULL) { |
| wctx->clientState = new GLClientState(); |
| server->utEnc()->createContext(server->utEnc(), getpid(), |
| (uint32_t)wctx, |
| (uint32_t)(share_context == EGL_NO_CONTEXT ? 0 : share_context), wctx->version); |
| } |
| } |
| return (EGLContext)wctx; |
| } |
| |
| EGLBoolean eglDestroyContext(EGLDisplay dpy, EGLContext ctx) |
| { |
| EGLWrapperContext *wctx = (EGLWrapperContext *)ctx; |
| EGLBoolean res = EGL_FALSE; |
| |
| if (ctx && ctx != EGL_NO_CONTEXT) { |
| res = getDispatch()->eglDestroyContext(dpy, wctx->aglContext); |
| if (res) { |
| EGLThreadInfo *ti = getEGLThreadInfo(); |
| ServerConnection *server; |
| if (s_needEncode && (server = ServerConnection::s_getServerConnection())) { |
| server->utEnc()->destroyContext(ti->serverConn->utEnc(), getpid(), (uint32_t)ctx); |
| } |
| if (ti->currentContext == wctx) ti->currentContext = NULL; |
| delete wctx; |
| } |
| } |
| |
| return res; |
| } |
| |
| EGLBoolean eglMakeCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) |
| { |
| EGLWrapperContext *wctx = (EGLWrapperContext *)ctx; |
| EGLContext aglContext = (ctx == EGL_NO_CONTEXT ? EGL_NO_CONTEXT : wctx->aglContext); |
| EGLThreadInfo *ti = getEGLThreadInfo(); |
| EGLBoolean res = getDispatch()->eglMakeCurrent(dpy, draw, read, aglContext); |
| if (res ) { |
| // NOTE - we do get a pointer to the server connection, (rather then using ti->serverConn) |
| // for cases that this is the first egl call of the current thread. |
| |
| ServerConnection *server; |
| if (s_needEncode && (server = ServerConnection::s_getServerConnection())) { |
| server->utEnc()->makeCurrentContext(server->utEnc(), getpid(), |
| (uint32_t) (draw == EGL_NO_SURFACE ? 0 : draw), |
| (uint32_t) (read == EGL_NO_SURFACE ? 0 : read), |
| (uint32_t) (ctx == EGL_NO_CONTEXT ? 0 : ctx)); |
| server->glEncoder()->setClientState( wctx ? wctx->clientState : NULL ); |
| server->gl2Encoder()->setClientState( wctx ? wctx->clientState : NULL ); |
| } |
| |
| // set current context in our thread info |
| ti->currentContext = wctx; |
| } |
| return res; |
| |
| } |
| |
| EGLContext eglGetCurrentContext() |
| { |
| EGLThreadInfo *ti = getEGLThreadInfo(); |
| return (ti->currentContext ? ti->currentContext : EGL_NO_CONTEXT); |
| } |
| |
| EGLSurface eglGetCurrentSurface(EGLint readdraw) |
| { |
| return getDispatch()->eglGetCurrentSurface(readdraw); |
| } |
| |
| EGLDisplay eglGetCurrentDisplay() |
| { |
| return getDispatch()->eglGetCurrentDisplay(); |
| } |
| |
| EGLBoolean eglQueryContext(EGLDisplay dpy, EGLContext ctx, EGLint attribute, EGLint *value) |
| { |
| EGLWrapperContext *wctx = (EGLWrapperContext *)ctx; |
| if (wctx) { |
| if (attribute == EGL_CONTEXT_CLIENT_VERSION) { |
| *value = wctx->version; |
| return EGL_TRUE; |
| } else { |
| return getDispatch()->eglQueryContext(dpy, wctx->aglContext, attribute, value); |
| } |
| } |
| else { |
| return EGL_BAD_CONTEXT; |
| } |
| } |
| |
| EGLBoolean eglWaitGL() |
| { |
| return getDispatch()->eglWaitGL(); |
| } |
| |
| EGLBoolean eglWaitNative(EGLint engine) |
| { |
| return getDispatch()->eglWaitNative(engine); |
| } |
| |
| EGLBoolean eglSwapBuffers(EGLDisplay dpy, EGLSurface surface) |
| { |
| ServerConnection *server; |
| if (s_needEncode && (server = ServerConnection::s_getServerConnection()) != NULL) { |
| server->utEnc()->swapBuffers(server->utEnc(), getpid(), (uint32_t)surface); |
| server->glEncoder()->flush(); |
| server->gl2Encoder()->flush(); |
| return 1; |
| } |
| return getDispatch()->eglSwapBuffers(dpy, surface); |
| } |
| |
| EGLBoolean eglCopyBuffers(EGLDisplay dpy, EGLSurface surface, EGLNativePixmapType target) |
| { |
| return getDispatch()->eglCopyBuffers(dpy, surface, target); |
| } |
| |
| EGLBoolean eglLockSurfaceKHR(EGLDisplay display, EGLSurface surface, const EGLint *attrib_list) |
| { |
| return getDispatch()->eglLockSurfaceKHR(display, surface, attrib_list); |
| } |
| |
| EGLBoolean eglUnlockSurfaceKHR(EGLDisplay display, EGLSurface surface) |
| { |
| return getDispatch()->eglUnlockSurfaceKHR(display, surface); |
| } |
| |
| EGLImageKHR eglCreateImageKHR(EGLDisplay dpy, EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLint *attrib_list) |
| { |
| EGLWrapperContext *wctx = (EGLWrapperContext *)ctx; |
| EGLContext aglContext = (wctx ? wctx->aglContext : EGL_NO_CONTEXT); |
| return getDispatch()->eglCreateImageKHR(dpy, aglContext, target, buffer, attrib_list); |
| } |
| |
| EGLBoolean eglDestroyImageKHR(EGLDisplay dpy, EGLImageKHR image) |
| { |
| return getDispatch()->eglDestroyImageKHR(dpy, image); |
| } |
| |
| EGLSyncKHR eglCreateSyncKHR(EGLDisplay dpy, EGLenum type, const EGLint *attrib_list) |
| { |
| return getDispatch()->eglCreateSyncKHR(dpy, type, attrib_list); |
| } |
| |
| EGLBoolean eglDestroySyncKHR(EGLDisplay dpy, EGLSyncKHR sync) |
| { |
| return getDispatch()->eglDestroySyncKHR(dpy, sync); |
| } |
| |
| EGLint eglClientWaitSyncKHR(EGLDisplay dpy, EGLSyncKHR sync, EGLint flags, EGLTimeKHR timeout) |
| { |
| return getDispatch()->eglClientWaitSyncKHR(dpy, sync, flags, timeout); |
| } |
| |
| EGLBoolean eglSignalSyncKHR(EGLDisplay dpy, EGLSyncKHR sync, EGLenum mode) |
| { |
| return getDispatch()->eglSignalSyncKHR(dpy, sync, mode); |
| } |
| |
| EGLBoolean eglGetSyncAttribKHR(EGLDisplay dpy, EGLSyncKHR sync, EGLint attribute, EGLint *value) |
| { |
| return getDispatch()->eglGetSyncAttribKHR(dpy, sync, attribute, value); |
| } |
| |
| EGLBoolean eglSetSwapRectangleANDROID(EGLDisplay dpy, EGLSurface draw, EGLint left, EGLint top, EGLint width, EGLint height) |
| { |
| return getDispatch()->eglSetSwapRectangleANDROID(dpy, draw, left, top, width, height); |
| } |