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