1/*
2* Copyright (C) 2011 The Android Open Source Project
3*
4* Licensed under the Apache License, Version 2.0 (the "License");
5* you may not use this file except in compliance with the License.
6* You may obtain a copy of the License at
7*
8* http://www.apache.org/licenses/LICENSE-2.0
9*
10* Unless required by applicable law or agreed to in writing, software
11* distributed under the License is distributed on an "AS IS" BASIS,
12* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13* See the License for the specific language governing permissions and
14* limitations under the License.
15*/
16
17//
18// WARNING -------------------------- WARNING
19// This code meant to be used for testing purposes only. It is not production
20// level quality.
21// Use on your own risk !!
22//
23
24#include <stdio.h>
25#include <stdlib.h>
26#include <string.h>
27#include <dlfcn.h>
28#include "egl_dispatch.h"
29#include "egl_ftable.h"
30#include <cutils/process_name.h>
31#include <cutils/log.h>
32#include "ServerConnection.h"
33#include "ThreadInfo.h"
34#include <pthread.h>
35#include "gl_wrapper_context.h"
36#include "gl2_wrapper_context.h"
37
38#define GLES_EMUL_TARGETS_FILE "/system/etc/gles_emul.cfg"
39// implementation libraries;
40#define GLESv1_enc_LIB "/system/lib/libGLESv1_enc.so"
41#define GLESv2_enc_LIB "/system/lib/libGLESv2_enc.so"
42#define GLES_android_LIB "/system/lib/egl/libGLES_android.so"
43// driver libraries;
44#define GLESv1_DRIVER "/system/lib/egl/libGLESv1_CM_emul.so"
45#define GLESv2_DRIVER "/system/lib/egl/libGLESv2_emul.so"
46
47
48static struct egl_dispatch *s_dispatch = NULL;
49pthread_once_t dispatchTablesInitialized = PTHREAD_ONCE_INIT;
50
51static bool s_needEncode = false;
52
53static gl_wrapper_context_t *g_gl_dispatch = NULL;
54static gl2_wrapper_context_t *g_gl2_dispatch = NULL;
55
56template <class T>
57int initApi(const char *driverLibName, const char *implLibName, T **dispatchTable, T *(*accessor)())
58{
59    void *driverLib = dlopen(driverLibName, RTLD_NOW | RTLD_LOCAL);
60    if (driverLib == NULL) {
61        ALOGE("failed to load %s : %s\n", driverLibName, dlerror());
62        return -1;
63    }
64
65    typedef T *(*createFcn_t)(void *, T *(*accessor)());
66    createFcn_t createFcn;
67    createFcn = (createFcn_t) dlsym(driverLib, "createFromLib");
68    if (createFcn == NULL) {
69        ALOGE("failed to load createFromLib constructor function\n");
70        return -1;
71    }
72
73    void *implLib = dlopen(implLibName, RTLD_NOW | RTLD_LOCAL);
74    if (implLib == NULL) {
75        ALOGE("couldn't open %s", implLibName);
76        return -2;
77    }
78    *dispatchTable = createFcn(implLib, accessor);
79    if (*dispatchTable == NULL) {
80        return -3;
81    }
82
83    // XXX - we do close the impl library since it doesn't have data, as far as we concern.
84    dlclose(implLib);
85
86    // XXX - we do not dlclose the driver library, so its not initialized when
87    // later loaded by android - is this required?
88    ALOGD("loading %s into %s complete\n", implLibName, driverLibName);
89    return 0;
90
91}
92
93static gl_wrapper_context_t *getGLContext()
94{
95    return g_gl_dispatch;
96}
97
98static gl2_wrapper_context_t *getGL2Context()
99{
100    return g_gl2_dispatch;
101}
102
103const char *getProcName()
104{
105    static const char *procname = NULL;
106
107    if (procname == NULL) {
108        const char *str = get_process_name();
109        if (strcmp(str, "unknown") != 0) {
110            procname = str;
111        } else {
112            // we need to obtain our process name from the command line;
113            FILE *fp = fopen("/proc/self/cmdline", "rt");
114            if (fp == NULL) {
115                ALOGE("couldn't open /proc/self/cmdline\n");
116                return NULL;
117            }
118
119            char line[1000];
120            if (fgets(line, sizeof(line), fp) == NULL) {
121                ALOGE("couldn't read the self cmdline from \n");
122                fclose(fp);
123                return NULL;
124            }
125            fclose(fp);
126
127            if (line[0] == '\0') {
128                ALOGE("cmdline is empty\n");
129                return NULL;
130            }
131
132            //obtain the basename;
133            line[sizeof(line) - 1] = '\0';
134            char *p = line;
135            while (*p != '\0' &&
136                   *p != '\t' &&
137                   *p != ' ' &&
138                   *p != '\n') {
139                p++;
140            }
141
142            *p = '\0'; p--;
143            while (p > line && *p != '/') p--;
144            if (*p == '/') p++;
145            procname = strdup(p);
146        }
147    }
148
149    return procname;
150}
151
152
153
154bool isNeedEncode()
155{
156    const char *procname = getProcName();
157    if (procname == NULL) return false;
158    ALOGD("isNeedEncode? for %s\n", procname);
159    // check on our whitelist
160    FILE *fp = fopen(GLES_EMUL_TARGETS_FILE, "rt");
161    if (fp == NULL) {
162        ALOGE("couldn't open %s\n", GLES_EMUL_TARGETS_FILE);
163        return false;
164    }
165
166    char line[100];
167    bool found = false;
168    size_t  procnameLen = strlen(procname);
169
170    while (fgets(line, sizeof(line), fp) != NULL) {
171        if (strlen(line) >= procnameLen &&
172            !strncmp(procname, line, procnameLen)) {
173            char c = line[procnameLen];
174            if (c == '\0' || c == ' ' || c == '\t' || c == '\n') {
175                found = true;
176                ALOGD("should use encoder for %s\n", procname);
177                break;
178            }
179        }
180    }
181    fclose(fp);
182    return found;
183}
184
185void initDispatchTables()
186{
187    //
188    // Load our back-end implementation of EGL/GLES
189    //
190    ALOGD("Loading egl dispatch for %s\n", getProcName());
191
192    void *gles_android = dlopen("/system/lib/egl/libGLES_android.so", RTLD_NOW | RTLD_LOCAL);
193    if (!gles_android) {
194        fprintf(stderr,"FATAL ERROR: Could not load libGLES_android lib\n");
195        exit(-1);
196    }
197
198    //
199    // Load back-end EGL implementation library
200    //
201    s_dispatch = create_egl_dispatch( gles_android );
202    if (!s_dispatch) {
203        fprintf(stderr,"FATAL ERROR: Could not create egl dispatch\n");
204        exit(-1);
205    }
206
207    //
208    // initialize gles
209    //
210    s_needEncode = isNeedEncode();
211    void *gles_encoder = NULL;
212    if (s_needEncode) {
213        // initialize a connection to the server, and the GLESv1/v2 encoders;
214        ServerConnection * connection = ServerConnection::s_getServerConnection();
215        if (connection == NULL) {
216            ALOGE("couldn't create server connection\n");
217            s_needEncode = false;
218        }
219    }
220
221    // init dispatch tabels for GLESv1 & GLESv2
222    if (s_needEncode) {
223        // XXX - we do not check the retrun value because there isn't much we can do here on failure.
224
225        if (initApi<gl_wrapper_context_t>(GLESv1_DRIVER, GLESv1_enc_LIB, &g_gl_dispatch, getGLContext) < 0) {
226            // fallback to android on faluire
227            s_needEncode = false;
228        } else {
229            initApi<gl2_wrapper_context_t>(GLESv2_DRIVER, GLESv2_enc_LIB, &g_gl2_dispatch, getGL2Context);
230        }
231    }
232
233    if (!s_needEncode) {
234        ALOGD("Initializing native opengl for %s\n", getProcName());
235        initApi<gl_wrapper_context_t>(GLESv1_DRIVER, GLES_android_LIB, &g_gl_dispatch, getGLContext);
236        // try to initialize gl2 from GLES, though its probably going to fail
237        initApi<gl2_wrapper_context_t>(GLESv2_DRIVER, GLES_android_LIB, &g_gl2_dispatch, getGL2Context);
238    }
239}
240
241static struct egl_dispatch *getDispatch()
242{
243    pthread_once(&dispatchTablesInitialized, initDispatchTables);
244    return s_dispatch;
245}
246
247__eglMustCastToProperFunctionPointerType eglGetProcAddress(const char *procname)
248{
249
250    // search in EGL function table
251    for (int i=0; i<egl_num_funcs; i++) {
252        if (!strcmp(egl_funcs_by_name[i].name, procname)) {
253            return (__eglMustCastToProperFunctionPointerType)egl_funcs_by_name[i].proc;
254        }
255    }
256
257    // we do not support eglGetProcAddress for GLESv1 & GLESv2. The loader
258    // should be able to find this function through dynamic loading.
259    return NULL;
260}
261
262////////////////  Path through functions //////////
263
264EGLint eglGetError()
265{
266    return getDispatch()->eglGetError();
267}
268
269EGLDisplay eglGetDisplay(EGLNativeDisplayType display_id)
270{
271    return getDispatch()->eglGetDisplay(display_id);
272}
273
274EGLBoolean eglInitialize(EGLDisplay dpy, EGLint *major, EGLint *minor)
275{
276    return getDispatch()->eglInitialize(dpy, major, minor);
277}
278
279EGLBoolean eglTerminate(EGLDisplay dpy)
280{
281    return getDispatch()->eglTerminate(dpy);
282}
283
284const char* eglQueryString(EGLDisplay dpy, EGLint name)
285{
286    return getDispatch()->eglQueryString(dpy, name);
287}
288
289EGLBoolean eglGetConfigs(EGLDisplay dpy, EGLConfig *configs, EGLint config_size, EGLint *num_config)
290{
291    return getDispatch()->eglGetConfigs(dpy, configs, config_size, num_config);
292}
293
294static EGLint * filter_es2_bit(const EGLint *attrib_list, bool *isES2)
295{
296    if (attrib_list == NULL) {
297        if (isES2 != NULL) *isES2 = false;
298        return NULL;
299    }
300
301    EGLint *attribs = NULL;
302    int nAttribs = 0;
303    while(attrib_list[nAttribs] != EGL_NONE) nAttribs++;
304    nAttribs++;
305
306    attribs = new EGLint[nAttribs];
307    memcpy(attribs, attrib_list, nAttribs * sizeof(EGLint));
308    if (isES2 != NULL) *isES2 = false;
309
310    // scan the attribute list for ES2 request and replace with ES1.
311    for (int i = 0; i < nAttribs; i++) {
312        if (attribs[i] == EGL_RENDERABLE_TYPE) {
313            if (attribs[i + 1] & EGL_OPENGL_ES2_BIT) {
314                attribs[i + 1] &= ~EGL_OPENGL_ES2_BIT;
315                attribs[i + 1] |= EGL_OPENGL_ES_BIT;
316                ALOGD("removing ES2 bit 0x%x\n", attribs[i + 1]);
317                if (isES2 != NULL) *isES2 = true;
318            }
319        }
320    }
321    return attribs;
322}
323
324EGLBoolean eglChooseConfig(EGLDisplay dpy, const EGLint *attrib_list, EGLConfig *configs, EGLint config_size, EGLint *num_config)
325{
326    EGLBoolean res;
327    if (s_needEncode) {
328        EGLint *attribs = filter_es2_bit(attrib_list, NULL);
329        res =  getDispatch()->eglChooseConfig(dpy,
330                                              attribs,
331                                              configs,
332                                              config_size,
333                                              num_config);
334        ALOGD("eglChooseConfig: %d configs found\n", *num_config);
335        if (*num_config == 0 && attribs != NULL) {
336            ALOGD("requested attributes:\n");
337            for (int i = 0; attribs[i] != EGL_NONE; i++) {
338                ALOGD("%d: 0x%x\n", i, attribs[i]);
339            }
340        }
341
342        delete attribs;
343    } else {
344        res = getDispatch()->eglChooseConfig(dpy, attrib_list, configs, config_size, num_config);
345    }
346    return res;
347}
348
349EGLBoolean eglGetConfigAttrib(EGLDisplay dpy, EGLConfig config, EGLint attribute, EGLint *value)
350{
351    if (s_needEncode && attribute == EGL_RENDERABLE_TYPE) {
352        *value = EGL_OPENGL_ES_BIT | EGL_OPENGL_ES2_BIT;
353        return EGL_TRUE;
354    } else {
355        return getDispatch()->eglGetConfigAttrib(dpy, config, attribute, value);
356    }
357}
358
359EGLSurface eglCreateWindowSurface(EGLDisplay dpy, EGLConfig config, EGLNativeWindowType win, const EGLint *attrib_list)
360{
361    EGLSurface surface =  getDispatch()->eglCreateWindowSurface(dpy, config, win, attrib_list);
362    if (surface != EGL_NO_SURFACE) {
363        ServerConnection *server;
364        if (s_needEncode && (server = ServerConnection::s_getServerConnection()) != NULL) {
365            server->utEnc()->createSurface(server->utEnc(), getpid(), (uint32_t)surface);
366        }
367    }
368    return surface;
369}
370
371EGLSurface eglCreatePbufferSurface(EGLDisplay dpy, EGLConfig config, const EGLint *attrib_list)
372{
373    EGLSurface surface =  getDispatch()->eglCreatePbufferSurface(dpy, config, attrib_list);
374    if (surface != EGL_NO_SURFACE) {
375        ServerConnection *server;
376        if (s_needEncode && (server = ServerConnection::s_getServerConnection()) != NULL) {
377            server->utEnc()->createSurface(server->utEnc(), getpid(), (uint32_t)surface);
378        }
379    }
380    return surface;
381}
382
383EGLSurface eglCreatePixmapSurface(EGLDisplay dpy, EGLConfig config, EGLNativePixmapType pixmap, const EGLint *attrib_list)
384{
385    EGLSurface surface =  getDispatch()->eglCreatePixmapSurface(dpy, config, pixmap, attrib_list);
386    if (surface != EGL_NO_SURFACE) {
387        ServerConnection *server;
388        if (s_needEncode && (server = ServerConnection::s_getServerConnection()) != NULL) {
389            server->utEnc()->createSurface(server->utEnc(), getpid(), (uint32_t)surface);
390        }
391    }
392    return surface;
393}
394
395EGLBoolean eglDestroySurface(EGLDisplay dpy, EGLSurface surface)
396{
397    EGLBoolean res =  getDispatch()->eglDestroySurface(dpy, surface);
398    if (res && surface != EGL_NO_SURFACE) {
399        ServerConnection *server;
400        if (s_needEncode && (server = ServerConnection::s_getServerConnection()) != NULL) {
401            server->utEnc()->destroySurface(server->utEnc(), getpid(), (uint32_t)surface);
402        }
403    }
404    return res;
405}
406
407EGLBoolean eglQuerySurface(EGLDisplay dpy, EGLSurface surface, EGLint attribute, EGLint *value)
408{
409    EGLBoolean res = getDispatch()->eglQuerySurface(dpy, surface, attribute, value);
410    if (res && attribute == EGL_RENDERABLE_TYPE) {
411        *value |= EGL_OPENGL_ES2_BIT;
412    }
413    return res;
414}
415
416EGLBoolean eglBindAPI(EGLenum api)
417{
418    return getDispatch()->eglBindAPI(api);
419}
420
421EGLenum eglQueryAPI()
422{
423    return getDispatch()->eglQueryAPI();
424}
425
426EGLBoolean eglWaitClient()
427{
428    return getDispatch()->eglWaitClient();
429}
430
431EGLBoolean eglReleaseThread()
432{
433    return getDispatch()->eglReleaseThread();
434}
435
436EGLSurface eglCreatePbufferFromClientBuffer(EGLDisplay dpy, EGLenum buftype, EGLClientBuffer buffer, EGLConfig config, const EGLint *attrib_list)
437{
438    return getDispatch()->eglCreatePbufferFromClientBuffer(dpy, buftype, buffer, config, attrib_list);
439}
440
441EGLBoolean eglSurfaceAttrib(EGLDisplay dpy, EGLSurface surface, EGLint attribute, EGLint value)
442{
443    return getDispatch()->eglSurfaceAttrib(dpy, surface, attribute, value);
444}
445
446EGLBoolean eglBindTexImage(EGLDisplay dpy, EGLSurface surface, EGLint buffer)
447{
448    return getDispatch()->eglBindTexImage(dpy, surface, buffer);
449}
450
451EGLBoolean eglReleaseTexImage(EGLDisplay dpy, EGLSurface surface, EGLint buffer)
452{
453    return getDispatch()->eglReleaseTexImage(dpy, surface, buffer);
454}
455
456EGLBoolean eglSwapInterval(EGLDisplay dpy, EGLint interval)
457{
458    return getDispatch()->eglSwapInterval(dpy, interval);
459}
460
461EGLContext eglCreateContext(EGLDisplay dpy, EGLConfig config, EGLContext share_context, const EGLint *attrib_list)
462{
463
464    EGLContext share = share_context;
465    if (share) share = ((EGLWrapperContext *)share_context)->aglContext;
466
467    // check if are ES2, and convert it to ES1.
468    int nAttribs = 0;
469    if (attrib_list != NULL) {
470        while(attrib_list[nAttribs] != EGL_NONE) {
471            nAttribs++;
472        }
473        nAttribs++;
474    }
475
476    EGLint *attrib = NULL;
477    if (nAttribs > 0) {
478        attrib = new EGLint[nAttribs];
479        memcpy(attrib, attrib_list, nAttribs * sizeof(EGLint));
480    }
481
482    int  version  = 1;
483    for (int i = 0; i < nAttribs; i++) {
484        if (attrib[i] == EGL_CONTEXT_CLIENT_VERSION &&
485            attrib[i + 1] == 2) {
486            version = 2;
487            attrib[i + 1] = 1; // replace to version 1
488        }
489    }
490
491    EGLContext ctx =  getDispatch()->eglCreateContext(dpy, config, share, attrib);
492    delete attrib;
493    EGLWrapperContext *wctx = new EGLWrapperContext(ctx, version);
494    if (ctx != EGL_NO_CONTEXT) {
495        ServerConnection *server;
496        if (s_needEncode && (server = ServerConnection::s_getServerConnection()) != NULL) {
497            wctx->clientState = new GLClientState();
498            server->utEnc()->createContext(server->utEnc(), getpid(),
499                                           (uint32_t)wctx,
500                                           (uint32_t)(share_context == EGL_NO_CONTEXT ? 0 : share_context), wctx->version);
501        }
502    }
503    return (EGLContext)wctx;
504}
505
506EGLBoolean eglDestroyContext(EGLDisplay dpy, EGLContext ctx)
507{
508    EGLWrapperContext *wctx = (EGLWrapperContext *)ctx;
509    EGLBoolean res = EGL_FALSE;
510
511    if (ctx && ctx != EGL_NO_CONTEXT) {
512        res = getDispatch()->eglDestroyContext(dpy, wctx->aglContext);
513        if (res) {
514            EGLThreadInfo *ti = getEGLThreadInfo();
515            ServerConnection *server;
516            if (s_needEncode && (server = ServerConnection::s_getServerConnection())) {
517                server->utEnc()->destroyContext(ti->serverConn->utEnc(), getpid(), (uint32_t)ctx);
518            }
519            if (ti->currentContext == wctx) ti->currentContext = NULL;
520            delete wctx;
521        }
522    }
523
524    return res;
525}
526
527EGLBoolean eglMakeCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx)
528{
529    EGLWrapperContext *wctx = (EGLWrapperContext *)ctx;
530    EGLContext aglContext = (ctx == EGL_NO_CONTEXT ? EGL_NO_CONTEXT : wctx->aglContext);
531    EGLThreadInfo *ti = getEGLThreadInfo();
532    EGLBoolean res = getDispatch()->eglMakeCurrent(dpy, draw, read, aglContext);
533    if (res ) {
534        // NOTE - we do get a pointer to the server connection, (rather then using ti->serverConn)
535        // for cases that this is the first egl call of the current thread.
536
537        ServerConnection *server;
538        if (s_needEncode && (server = ServerConnection::s_getServerConnection())) {
539            server->utEnc()->makeCurrentContext(server->utEnc(), getpid(),
540                                                (uint32_t) (draw == EGL_NO_SURFACE ? 0 : draw),
541                                                (uint32_t) (read == EGL_NO_SURFACE ? 0 : read),
542                                                (uint32_t) (ctx == EGL_NO_CONTEXT ? 0 : ctx));
543            server->glEncoder()->setClientState( wctx ? wctx->clientState : NULL );
544            server->gl2Encoder()->setClientState( wctx ? wctx->clientState : NULL );
545        }
546
547        // set current context in our thread info
548        ti->currentContext = wctx;
549    }
550    return res;
551
552}
553
554EGLContext eglGetCurrentContext()
555{
556    EGLThreadInfo *ti = getEGLThreadInfo();
557    return (ti->currentContext ? ti->currentContext : EGL_NO_CONTEXT);
558}
559
560EGLSurface eglGetCurrentSurface(EGLint readdraw)
561{
562    return getDispatch()->eglGetCurrentSurface(readdraw);
563}
564
565EGLDisplay eglGetCurrentDisplay()
566{
567    return getDispatch()->eglGetCurrentDisplay();
568}
569
570EGLBoolean eglQueryContext(EGLDisplay dpy, EGLContext ctx, EGLint attribute, EGLint *value)
571{
572    EGLWrapperContext *wctx = (EGLWrapperContext *)ctx;
573    if (wctx) {
574        if (attribute == EGL_CONTEXT_CLIENT_VERSION) {
575            *value = wctx->version;
576            return EGL_TRUE;
577        } else {
578            return getDispatch()->eglQueryContext(dpy, wctx->aglContext, attribute, value);
579        }
580    }
581    else {
582        return EGL_BAD_CONTEXT;
583    }
584}
585
586EGLBoolean eglWaitGL()
587{
588    return getDispatch()->eglWaitGL();
589}
590
591EGLBoolean eglWaitNative(EGLint engine)
592{
593    return getDispatch()->eglWaitNative(engine);
594}
595
596EGLBoolean eglSwapBuffers(EGLDisplay dpy, EGLSurface surface)
597{
598    ServerConnection *server;
599    if (s_needEncode && (server = ServerConnection::s_getServerConnection()) != NULL) {
600        server->utEnc()->swapBuffers(server->utEnc(), getpid(), (uint32_t)surface);
601        server->glEncoder()->flush();
602        server->gl2Encoder()->flush();
603        return 1;
604    }
605    return getDispatch()->eglSwapBuffers(dpy, surface);
606}
607
608EGLBoolean eglCopyBuffers(EGLDisplay dpy, EGLSurface surface, EGLNativePixmapType target)
609{
610    return getDispatch()->eglCopyBuffers(dpy, surface, target);
611}
612
613EGLBoolean eglLockSurfaceKHR(EGLDisplay display, EGLSurface surface, const EGLint *attrib_list)
614{
615    return getDispatch()->eglLockSurfaceKHR(display, surface, attrib_list);
616}
617
618EGLBoolean eglUnlockSurfaceKHR(EGLDisplay display, EGLSurface surface)
619{
620    return getDispatch()->eglUnlockSurfaceKHR(display, surface);
621}
622
623EGLImageKHR eglCreateImageKHR(EGLDisplay dpy, EGLContext ctx, EGLenum target, EGLClientBuffer buffer, const EGLint *attrib_list)
624{
625    EGLWrapperContext *wctx = (EGLWrapperContext *)ctx;
626    EGLContext aglContext = (wctx ? wctx->aglContext : EGL_NO_CONTEXT);
627    return getDispatch()->eglCreateImageKHR(dpy, aglContext, target, buffer, attrib_list);
628}
629
630EGLBoolean eglDestroyImageKHR(EGLDisplay dpy, EGLImageKHR image)
631{
632    return getDispatch()->eglDestroyImageKHR(dpy, image);
633}
634
635EGLSyncKHR eglCreateSyncKHR(EGLDisplay dpy, EGLenum type, const EGLint *attrib_list)
636{
637    return getDispatch()->eglCreateSyncKHR(dpy, type, attrib_list);
638}
639
640EGLBoolean eglDestroySyncKHR(EGLDisplay dpy, EGLSyncKHR sync)
641{
642    return getDispatch()->eglDestroySyncKHR(dpy, sync);
643}
644
645EGLint eglClientWaitSyncKHR(EGLDisplay dpy, EGLSyncKHR sync, EGLint flags, EGLTimeKHR timeout)
646{
647    return getDispatch()->eglClientWaitSyncKHR(dpy, sync, flags, timeout);
648}
649
650EGLBoolean eglSignalSyncKHR(EGLDisplay dpy, EGLSyncKHR sync, EGLenum mode)
651{
652    return getDispatch()->eglSignalSyncKHR(dpy, sync, mode);
653}
654
655EGLBoolean eglGetSyncAttribKHR(EGLDisplay dpy, EGLSyncKHR sync, EGLint attribute, EGLint *value)
656{
657    return getDispatch()->eglGetSyncAttribKHR(dpy, sync, attribute, value);
658}
659
660EGLBoolean eglSetSwapRectangleANDROID(EGLDisplay dpy, EGLSurface draw, EGLint left, EGLint top, EGLint width, EGLint height)
661{
662    return getDispatch()->eglSetSwapRectangleANDROID(dpy, draw, left, top, width, height);
663}
664