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