rsdGL.cpp revision 4edf030cbb7c6ac08dc563335c2af73c20f6e2e5
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#include <ui/FramebufferNativeWindow.h> 18#include <ui/PixelFormat.h> 19 20#include <system/window.h> 21 22#include <sys/types.h> 23#include <sys/resource.h> 24#include <sched.h> 25 26#include <cutils/properties.h> 27 28#include <GLES/gl.h> 29#include <GLES/glext.h> 30#include <GLES2/gl2.h> 31#include <GLES2/gl2ext.h> 32 33#include <string.h> 34 35#include "rsdCore.h" 36#include "rsdGL.h" 37 38#include <malloc.h> 39#include "rsContext.h" 40#include "rsDevice.h" 41#include "rsdShaderCache.h" 42#include "rsdVertexArray.h" 43#include "rsdFrameBufferObj.h" 44 45#include <gui/SurfaceTextureClient.h> 46 47using namespace android; 48using namespace android::renderscript; 49 50static int32_t gGLContextCount = 0; 51 52static void checkEglError(const char* op, EGLBoolean returnVal = EGL_TRUE) { 53 struct EGLUtils { 54 static const char *strerror(EGLint err) { 55 switch (err){ 56 case EGL_SUCCESS: return "EGL_SUCCESS"; 57 case EGL_NOT_INITIALIZED: return "EGL_NOT_INITIALIZED"; 58 case EGL_BAD_ACCESS: return "EGL_BAD_ACCESS"; 59 case EGL_BAD_ALLOC: return "EGL_BAD_ALLOC"; 60 case EGL_BAD_ATTRIBUTE: return "EGL_BAD_ATTRIBUTE"; 61 case EGL_BAD_CONFIG: return "EGL_BAD_CONFIG"; 62 case EGL_BAD_CONTEXT: return "EGL_BAD_CONTEXT"; 63 case EGL_BAD_CURRENT_SURFACE: return "EGL_BAD_CURRENT_SURFACE"; 64 case EGL_BAD_DISPLAY: return "EGL_BAD_DISPLAY"; 65 case EGL_BAD_MATCH: return "EGL_BAD_MATCH"; 66 case EGL_BAD_NATIVE_PIXMAP: return "EGL_BAD_NATIVE_PIXMAP"; 67 case EGL_BAD_NATIVE_WINDOW: return "EGL_BAD_NATIVE_WINDOW"; 68 case EGL_BAD_PARAMETER: return "EGL_BAD_PARAMETER"; 69 case EGL_BAD_SURFACE: return "EGL_BAD_SURFACE"; 70 case EGL_CONTEXT_LOST: return "EGL_CONTEXT_LOST"; 71 default: return "UNKNOWN"; 72 } 73 } 74 }; 75 76 if (returnVal != EGL_TRUE) { 77 fprintf(stderr, "%s() returned %d\n", op, returnVal); 78 } 79 80 for (EGLint error = eglGetError(); error != EGL_SUCCESS; error 81 = eglGetError()) { 82 fprintf(stderr, "after %s() eglError %s (0x%x)\n", op, EGLUtils::strerror(error), 83 error); 84 } 85} 86 87static void printEGLConfiguration(EGLDisplay dpy, EGLConfig config) { 88 89#define X(VAL) {VAL, #VAL} 90 struct {EGLint attribute; const char* name;} names[] = { 91 X(EGL_BUFFER_SIZE), 92 X(EGL_ALPHA_SIZE), 93 X(EGL_BLUE_SIZE), 94 X(EGL_GREEN_SIZE), 95 X(EGL_RED_SIZE), 96 X(EGL_DEPTH_SIZE), 97 X(EGL_STENCIL_SIZE), 98 X(EGL_CONFIG_CAVEAT), 99 X(EGL_CONFIG_ID), 100 X(EGL_LEVEL), 101 X(EGL_MAX_PBUFFER_HEIGHT), 102 X(EGL_MAX_PBUFFER_PIXELS), 103 X(EGL_MAX_PBUFFER_WIDTH), 104 X(EGL_NATIVE_RENDERABLE), 105 X(EGL_NATIVE_VISUAL_ID), 106 X(EGL_NATIVE_VISUAL_TYPE), 107 X(EGL_SAMPLES), 108 X(EGL_SAMPLE_BUFFERS), 109 X(EGL_SURFACE_TYPE), 110 X(EGL_TRANSPARENT_TYPE), 111 X(EGL_TRANSPARENT_RED_VALUE), 112 X(EGL_TRANSPARENT_GREEN_VALUE), 113 X(EGL_TRANSPARENT_BLUE_VALUE), 114 X(EGL_BIND_TO_TEXTURE_RGB), 115 X(EGL_BIND_TO_TEXTURE_RGBA), 116 X(EGL_MIN_SWAP_INTERVAL), 117 X(EGL_MAX_SWAP_INTERVAL), 118 X(EGL_LUMINANCE_SIZE), 119 X(EGL_ALPHA_MASK_SIZE), 120 X(EGL_COLOR_BUFFER_TYPE), 121 X(EGL_RENDERABLE_TYPE), 122 X(EGL_CONFORMANT), 123 }; 124#undef X 125 126 for (size_t j = 0; j < sizeof(names) / sizeof(names[0]); j++) { 127 EGLint value = -1; 128 EGLBoolean returnVal = eglGetConfigAttrib(dpy, config, names[j].attribute, &value); 129 if (returnVal) { 130 ALOGV(" %s: %d (0x%x)", names[j].name, value, value); 131 } 132 } 133} 134 135static void DumpDebug(RsdHal *dc) { 136 ALOGE(" EGL ver %i %i", dc->gl.egl.majorVersion, dc->gl.egl.minorVersion); 137 ALOGE(" EGL context %p surface %p, Display=%p", dc->gl.egl.context, dc->gl.egl.surface, 138 dc->gl.egl.display); 139 ALOGE(" GL vendor: %s", dc->gl.gl.vendor); 140 ALOGE(" GL renderer: %s", dc->gl.gl.renderer); 141 ALOGE(" GL Version: %s", dc->gl.gl.version); 142 ALOGE(" GL Extensions: %s", dc->gl.gl.extensions); 143 ALOGE(" GL int Versions %i %i", dc->gl.gl.majorVersion, dc->gl.gl.minorVersion); 144 145 ALOGV("MAX Textures %i, %i %i", dc->gl.gl.maxVertexTextureUnits, 146 dc->gl.gl.maxFragmentTextureImageUnits, dc->gl.gl.maxTextureImageUnits); 147 ALOGV("MAX Attribs %i", dc->gl.gl.maxVertexAttribs); 148 ALOGV("MAX Uniforms %i, %i", dc->gl.gl.maxVertexUniformVectors, 149 dc->gl.gl.maxFragmentUniformVectors); 150 ALOGV("MAX Varyings %i", dc->gl.gl.maxVaryingVectors); 151} 152 153void rsdGLShutdown(const Context *rsc) { 154 RsdHal *dc = (RsdHal *)rsc->mHal.drv; 155 156 dc->gl.shaderCache->cleanupAll(); 157 delete dc->gl.shaderCache; 158 delete dc->gl.vertexArrayState; 159 160 if (dc->gl.egl.context != EGL_NO_CONTEXT) { 161 RSD_CALL_GL(eglMakeCurrent, dc->gl.egl.display, 162 EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); 163 RSD_CALL_GL(eglDestroySurface, dc->gl.egl.display, dc->gl.egl.surfaceDefault); 164 if (dc->gl.egl.surface != EGL_NO_SURFACE) { 165 RSD_CALL_GL(eglDestroySurface, dc->gl.egl.display, dc->gl.egl.surface); 166 } 167 RSD_CALL_GL(eglDestroyContext, dc->gl.egl.display, dc->gl.egl.context); 168 checkEglError("eglDestroyContext"); 169 } 170 171 gGLContextCount--; 172 if (!gGLContextCount) { 173 RSD_CALL_GL(eglTerminate, dc->gl.egl.display); 174 } 175} 176 177void getConfigData(const Context *rsc, 178 EGLint *configAttribs, size_t configAttribsLen, 179 uint32_t numSamples) { 180 memset(configAttribs, 0, configAttribsLen*sizeof(*configAttribs)); 181 182 EGLint *configAttribsPtr = configAttribs; 183 184 configAttribsPtr[0] = EGL_SURFACE_TYPE; 185 configAttribsPtr[1] = EGL_WINDOW_BIT; 186 configAttribsPtr += 2; 187 188 configAttribsPtr[0] = EGL_RENDERABLE_TYPE; 189 configAttribsPtr[1] = EGL_OPENGL_ES2_BIT; 190 configAttribsPtr += 2; 191 192 configAttribsPtr[0] = EGL_RED_SIZE; 193 configAttribsPtr[1] = 8; 194 configAttribsPtr += 2; 195 196 configAttribsPtr[0] = EGL_GREEN_SIZE; 197 configAttribsPtr[1] = 8; 198 configAttribsPtr += 2; 199 200 configAttribsPtr[0] = EGL_BLUE_SIZE; 201 configAttribsPtr[1] = 8; 202 configAttribsPtr += 2; 203 204 if (rsc->mUserSurfaceConfig.alphaMin > 0) { 205 configAttribsPtr[0] = EGL_ALPHA_SIZE; 206 configAttribsPtr[1] = rsc->mUserSurfaceConfig.alphaMin; 207 configAttribsPtr += 2; 208 } 209 210 if (rsc->mUserSurfaceConfig.depthMin > 0) { 211 configAttribsPtr[0] = EGL_DEPTH_SIZE; 212 configAttribsPtr[1] = rsc->mUserSurfaceConfig.depthMin; 213 configAttribsPtr += 2; 214 } 215 216 if (rsc->mDev->mForceSW) { 217 configAttribsPtr[0] = EGL_CONFIG_CAVEAT; 218 configAttribsPtr[1] = EGL_SLOW_CONFIG; 219 configAttribsPtr += 2; 220 } 221 222 if (numSamples > 1) { 223 configAttribsPtr[0] = EGL_SAMPLE_BUFFERS; 224 configAttribsPtr[1] = 1; 225 configAttribsPtr[2] = EGL_SAMPLES; 226 configAttribsPtr[3] = numSamples; 227 configAttribsPtr += 4; 228 } 229 230 configAttribsPtr[0] = EGL_NONE; 231 rsAssert(configAttribsPtr < (configAttribs + configAttribsLen)); 232} 233 234bool rsdGLInit(const Context *rsc) { 235 RsdHal *dc = (RsdHal *)rsc->mHal.drv; 236 237 dc->gl.egl.numConfigs = -1; 238 239 EGLint configAttribs[128]; 240 EGLint context_attribs2[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE }; 241 242 ALOGV("%p initEGL start", rsc); 243 rsc->setWatchdogGL("eglGetDisplay", __LINE__, __FILE__); 244 dc->gl.egl.display = eglGetDisplay(EGL_DEFAULT_DISPLAY); 245 checkEglError("eglGetDisplay"); 246 247 RSD_CALL_GL(eglInitialize, dc->gl.egl.display, 248 &dc->gl.egl.majorVersion, &dc->gl.egl.minorVersion); 249 checkEglError("eglInitialize"); 250 251 EGLBoolean ret; 252 253 EGLint numConfigs = -1, n = 0; 254 rsc->setWatchdogGL("eglChooseConfig", __LINE__, __FILE__); 255 256 // Try minding a multisample config that matches the user request 257 uint32_t minSample = rsc->mUserSurfaceConfig.samplesMin; 258 uint32_t prefSample = rsc->mUserSurfaceConfig.samplesPref; 259 for (uint32_t sampleCount = prefSample; sampleCount >= minSample; sampleCount--) { 260 getConfigData(rsc, configAttribs, (sizeof(configAttribs) / sizeof(EGLint)), sampleCount); 261 ret = eglChooseConfig(dc->gl.egl.display, configAttribs, 0, 0, &numConfigs); 262 checkEglError("eglGetConfigs", ret); 263 if (numConfigs > 0) { 264 break; 265 } 266 } 267 268 eglSwapInterval(dc->gl.egl.display, 0); 269 270 if (numConfigs) { 271 EGLConfig* const configs = new EGLConfig[numConfigs]; 272 273 rsc->setWatchdogGL("eglChooseConfig", __LINE__, __FILE__); 274 ret = eglChooseConfig(dc->gl.egl.display, 275 configAttribs, configs, numConfigs, &n); 276 if (!ret || !n) { 277 checkEglError("eglChooseConfig", ret); 278 ALOGE("%p, couldn't find an EGLConfig matching the screen format\n", rsc); 279 } 280 281 // The first config is guaranteed to over-satisfy the constraints 282 dc->gl.egl.config = configs[0]; 283 284 // go through the list and skip configs that over-satisfy our needs 285 for (int i=0 ; i<n ; i++) { 286 if (rsc->mUserSurfaceConfig.alphaMin <= 0) { 287 EGLint alphaSize; 288 eglGetConfigAttrib(dc->gl.egl.display, 289 configs[i], EGL_ALPHA_SIZE, &alphaSize); 290 if (alphaSize > 0) { 291 continue; 292 } 293 } 294 295 if (rsc->mUserSurfaceConfig.depthMin <= 0) { 296 EGLint depthSize; 297 eglGetConfigAttrib(dc->gl.egl.display, 298 configs[i], EGL_DEPTH_SIZE, &depthSize); 299 if (depthSize > 0) { 300 continue; 301 } 302 } 303 304 // Found one! 305 dc->gl.egl.config = configs[i]; 306 break; 307 } 308 309 delete [] configs; 310 } 311 312 //if (props.mLogVisual) { 313 if (0) { 314 printEGLConfiguration(dc->gl.egl.display, dc->gl.egl.config); 315 } 316 //} 317 318 rsc->setWatchdogGL("eglCreateContext", __LINE__, __FILE__); 319 dc->gl.egl.context = eglCreateContext(dc->gl.egl.display, dc->gl.egl.config, 320 EGL_NO_CONTEXT, context_attribs2); 321 checkEglError("eglCreateContext"); 322 if (dc->gl.egl.context == EGL_NO_CONTEXT) { 323 ALOGE("%p, eglCreateContext returned EGL_NO_CONTEXT", rsc); 324 rsc->setWatchdogGL(NULL, 0, NULL); 325 return false; 326 } 327 gGLContextCount++; 328 329 sp<SurfaceTexture> st(new SurfaceTexture(123)); 330 sp<SurfaceTextureClient> stc(new SurfaceTextureClient(st)); 331 dc->gl.egl.surfaceDefault = eglCreateWindowSurface(dc->gl.egl.display, dc->gl.egl.config, 332 static_cast<ANativeWindow*>(stc.get()), 333 NULL); 334 335 checkEglError("eglCreateWindowSurface"); 336 if (dc->gl.egl.surfaceDefault == EGL_NO_SURFACE) { 337 ALOGE("eglCreateWindowSurface returned EGL_NO_SURFACE"); 338 rsdGLShutdown(rsc); 339 rsc->setWatchdogGL(NULL, 0, NULL); 340 return false; 341 } 342 343 rsc->setWatchdogGL("eglMakeCurrent", __LINE__, __FILE__); 344 ret = eglMakeCurrent(dc->gl.egl.display, dc->gl.egl.surfaceDefault, 345 dc->gl.egl.surfaceDefault, dc->gl.egl.context); 346 if (ret == EGL_FALSE) { 347 ALOGE("eglMakeCurrent returned EGL_FALSE"); 348 checkEglError("eglMakeCurrent", ret); 349 rsdGLShutdown(rsc); 350 rsc->setWatchdogGL(NULL, 0, NULL); 351 return false; 352 } 353 354 dc->gl.gl.version = glGetString(GL_VERSION); 355 dc->gl.gl.vendor = glGetString(GL_VENDOR); 356 dc->gl.gl.renderer = glGetString(GL_RENDERER); 357 dc->gl.gl.extensions = glGetString(GL_EXTENSIONS); 358 359 //ALOGV("EGL Version %i %i", mEGL.mMajorVersion, mEGL.mMinorVersion); 360 //ALOGV("GL Version %s", mGL.mVersion); 361 //ALOGV("GL Vendor %s", mGL.mVendor); 362 //ALOGV("GL Renderer %s", mGL.mRenderer); 363 //ALOGV("GL Extensions %s", mGL.mExtensions); 364 365 const char *verptr = NULL; 366 if (strlen((const char *)dc->gl.gl.version) > 9) { 367 if (!memcmp(dc->gl.gl.version, "OpenGL ES-CM", 12)) { 368 verptr = (const char *)dc->gl.gl.version + 12; 369 } 370 if (!memcmp(dc->gl.gl.version, "OpenGL ES ", 10)) { 371 verptr = (const char *)dc->gl.gl.version + 9; 372 } 373 } 374 375 if (!verptr) { 376 ALOGE("Error, OpenGL ES Lite not supported"); 377 rsdGLShutdown(rsc); 378 rsc->setWatchdogGL(NULL, 0, NULL); 379 return false; 380 } else { 381 sscanf(verptr, " %i.%i", &dc->gl.gl.majorVersion, &dc->gl.gl.minorVersion); 382 } 383 384 glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &dc->gl.gl.maxVertexAttribs); 385 glGetIntegerv(GL_MAX_VERTEX_UNIFORM_VECTORS, &dc->gl.gl.maxVertexUniformVectors); 386 glGetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &dc->gl.gl.maxVertexTextureUnits); 387 388 glGetIntegerv(GL_MAX_VARYING_VECTORS, &dc->gl.gl.maxVaryingVectors); 389 glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &dc->gl.gl.maxTextureImageUnits); 390 391 glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &dc->gl.gl.maxFragmentTextureImageUnits); 392 glGetIntegerv(GL_MAX_FRAGMENT_UNIFORM_VECTORS, &dc->gl.gl.maxFragmentUniformVectors); 393 394 dc->gl.gl.OES_texture_npot = NULL != strstr((const char *)dc->gl.gl.extensions, 395 "GL_OES_texture_npot"); 396 dc->gl.gl.IMG_texture_npot = NULL != strstr((const char *)dc->gl.gl.extensions, 397 "GL_IMG_texture_npot"); 398 dc->gl.gl.NV_texture_npot_2D_mipmap = NULL != strstr((const char *)dc->gl.gl.extensions, 399 "GL_NV_texture_npot_2D_mipmap"); 400 dc->gl.gl.EXT_texture_max_aniso = 1.0f; 401 bool hasAniso = NULL != strstr((const char *)dc->gl.gl.extensions, 402 "GL_EXT_texture_filter_anisotropic"); 403 if (hasAniso) { 404 glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &dc->gl.gl.EXT_texture_max_aniso); 405 } 406 407 if (0) { 408 DumpDebug(dc); 409 } 410 411 dc->gl.shaderCache = new RsdShaderCache(); 412 dc->gl.vertexArrayState = new RsdVertexArrayState(); 413 dc->gl.vertexArrayState->init(dc->gl.gl.maxVertexAttribs); 414 dc->gl.currentFrameBuffer = NULL; 415 dc->mHasGraphics = true; 416 417 ALOGV("%p initGLThread end", rsc); 418 rsc->setWatchdogGL(NULL, 0, NULL); 419 return true; 420} 421 422 423bool rsdGLSetSurface(const Context *rsc, uint32_t w, uint32_t h, RsNativeWindow sur) { 424 RsdHal *dc = (RsdHal *)rsc->mHal.drv; 425 426 EGLBoolean ret; 427 // WAR: Some drivers fail to handle 0 size surfaces correcntly. 428 // Use the pbuffer to avoid this pitfall. 429 if ((dc->gl.egl.surface != NULL) || (w == 0) || (h == 0)) { 430 rsc->setWatchdogGL("eglMakeCurrent", __LINE__, __FILE__); 431 ret = eglMakeCurrent(dc->gl.egl.display, dc->gl.egl.surfaceDefault, 432 dc->gl.egl.surfaceDefault, dc->gl.egl.context); 433 checkEglError("eglMakeCurrent", ret); 434 435 rsc->setWatchdogGL("eglDestroySurface", __LINE__, __FILE__); 436 ret = eglDestroySurface(dc->gl.egl.display, dc->gl.egl.surface); 437 checkEglError("eglDestroySurface", ret); 438 439 dc->gl.egl.surface = NULL; 440 dc->gl.width = 1; 441 dc->gl.height = 1; 442 } 443 444 if (dc->gl.wndSurface != NULL) { 445 dc->gl.wndSurface->decStrong(NULL); 446 } 447 448 dc->gl.wndSurface = (ANativeWindow *)sur; 449 if (dc->gl.wndSurface != NULL) { 450 dc->gl.wndSurface->incStrong(NULL); 451 dc->gl.width = w; 452 dc->gl.height = h; 453 454 rsc->setWatchdogGL("eglCreateWindowSurface", __LINE__, __FILE__); 455 dc->gl.egl.surface = eglCreateWindowSurface(dc->gl.egl.display, dc->gl.egl.config, 456 dc->gl.wndSurface, NULL); 457 checkEglError("eglCreateWindowSurface"); 458 if (dc->gl.egl.surface == EGL_NO_SURFACE) { 459 ALOGE("eglCreateWindowSurface returned EGL_NO_SURFACE"); 460 } 461 462 rsc->setWatchdogGL("eglMakeCurrent", __LINE__, __FILE__); 463 ret = eglMakeCurrent(dc->gl.egl.display, dc->gl.egl.surface, 464 dc->gl.egl.surface, dc->gl.egl.context); 465 checkEglError("eglMakeCurrent", ret); 466 } 467 rsc->setWatchdogGL(NULL, 0, NULL); 468 return true; 469} 470 471void rsdGLSwap(const android::renderscript::Context *rsc) { 472 RsdHal *dc = (RsdHal *)rsc->mHal.drv; 473 RSD_CALL_GL(eglSwapBuffers, dc->gl.egl.display, dc->gl.egl.surface); 474} 475 476void rsdGLSetPriority(const Context *rsc, int32_t priority) { 477 if (priority > 0) { 478 // Mark context as low priority. 479 ALOGV("low pri"); 480 } else { 481 ALOGV("normal pri"); 482 } 483} 484 485void rsdGLCheckError(const android::renderscript::Context *rsc, 486 const char *msg, bool isFatal) { 487 GLenum err = glGetError(); 488 if (err != GL_NO_ERROR) { 489 char buf[1024]; 490 snprintf(buf, sizeof(buf), "GL Error = 0x%08x, from: %s", err, msg); 491 492 if (isFatal) { 493 rsc->setError(RS_ERROR_FATAL_DRIVER, buf); 494 } else { 495 switch (err) { 496 case GL_OUT_OF_MEMORY: 497 rsc->setError(RS_ERROR_OUT_OF_MEMORY, buf); 498 break; 499 default: 500 rsc->setError(RS_ERROR_DRIVER, buf); 501 break; 502 } 503 } 504 505 ALOGE("%p, %s", rsc, buf); 506 } 507 508} 509 510void rsdGLClearColor(const android::renderscript::Context *rsc, 511 float r, float g, float b, float a) { 512 RSD_CALL_GL(glClearColor, r, g, b, a); 513 RSD_CALL_GL(glClear, GL_COLOR_BUFFER_BIT); 514} 515 516void rsdGLClearDepth(const android::renderscript::Context *rsc, float v) { 517 RSD_CALL_GL(glClearDepthf, v); 518 RSD_CALL_GL(glClear, GL_DEPTH_BUFFER_BIT); 519} 520 521void rsdGLFinish(const android::renderscript::Context *rsc) { 522 RSD_CALL_GL(glFinish); 523} 524 525void rsdGLDrawQuadTexCoords(const android::renderscript::Context *rsc, 526 float x1, float y1, float z1, float u1, float v1, 527 float x2, float y2, float z2, float u2, float v2, 528 float x3, float y3, float z3, float u3, float v3, 529 float x4, float y4, float z4, float u4, float v4) { 530 531 float vtx[] = {x1,y1,z1, x2,y2,z2, x3,y3,z3, x4,y4,z4}; 532 const float tex[] = {u1,v1, u2,v2, u3,v3, u4,v4}; 533 534 RsdVertexArray::Attrib attribs[2]; 535 attribs[0].set(GL_FLOAT, 3, 12, false, (uint32_t)vtx, "ATTRIB_position"); 536 attribs[1].set(GL_FLOAT, 2, 8, false, (uint32_t)tex, "ATTRIB_texture0"); 537 538 RsdVertexArray va(attribs, 2); 539 va.setup(rsc); 540 541 RSD_CALL_GL(glDrawArrays, GL_TRIANGLE_FAN, 0, 4); 542} 543