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