Home | History | Annotate | Download | only in cts
      1 /*
      2  * Copyright (C) 2014 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 package android.opengl.cts;
     18 
     19 import android.graphics.Bitmap;
     20 import android.graphics.SurfaceTexture;
     21 import android.opengl.EGL14;
     22 import android.opengl.EGLConfig;
     23 import android.opengl.EGLContext;
     24 import android.opengl.EGLDisplay;
     25 import android.opengl.EGLExt;
     26 import android.opengl.EGLSurface;
     27 import android.opengl.GLES20;
     28 import android.opengl.GLES30;
     29 import android.test.AndroidTestCase;
     30 import android.util.Log;
     31 import android.view.Surface;
     32 
     33 import java.io.BufferedOutputStream;
     34 import java.io.File;
     35 import java.io.FileOutputStream;
     36 import java.io.IOException;
     37 import java.nio.ByteBuffer;
     38 import java.nio.ByteOrder;
     39 import java.util.Arrays;
     40 
     41 /**
     42  * Test some GLES framebuffer stuff.
     43  */
     44 public class FramebufferTest extends AndroidTestCase {
     45     private static final String TAG = "FramebufferTest";
     46 
     47 
     48     /**
     49      * Tests very basic glBlitFramebuffer() features by copying from one offscreen framebuffer
     50      * to another.
     51      * <p>
     52      * Requires GLES3.
     53      */
     54     public void testBlitFramebuffer() throws Throwable {
     55         final int WIDTH = 640;
     56         final int HEIGHT = 480;
     57         final int BYTES_PER_PIXEL = 4;
     58         final int TEST_RED = 255;
     59         final int TEST_GREEN = 0;
     60         final int TEST_BLUE = 127;
     61         final int TEST_ALPHA = 255;
     62         final byte expectedBytes[] = new byte[] {
     63                 (byte) TEST_RED, (byte) TEST_GREEN, (byte) TEST_BLUE, (byte) TEST_ALPHA
     64         };
     65         EglCore eglCore = null;
     66         OffscreenSurface surface1 = null;
     67         OffscreenSurface surface2 = null;
     68 
     69         try {
     70             eglCore = new EglCore(null, EglCore.FLAG_TRY_GLES3);
     71             if (eglCore.getGlVersion() < 3) {
     72                 Log.d(TAG, "GLES3 not available, skipping test");
     73                 return;
     74             }
     75 
     76             // Create two surfaces, and clear surface1
     77             surface1 = new OffscreenSurface(eglCore, WIDTH, HEIGHT);
     78             surface2 = new OffscreenSurface(eglCore, WIDTH, HEIGHT);
     79             surface1.makeCurrent();
     80             GLES30.glClearColor(TEST_RED / 255.0f, TEST_GREEN / 255.0f, TEST_BLUE / 255.0f,
     81                     TEST_ALPHA / 255.0f);
     82             GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);
     83             checkGlError("glClear");
     84 
     85             // Set surface2 as "draw", surface1 as "read", and blit.
     86             surface2.makeCurrentReadFrom(surface1);
     87             GLES30.glBlitFramebuffer(0, 0, WIDTH, HEIGHT, 0, 0, WIDTH, HEIGHT,
     88                     GLES30.GL_COLOR_BUFFER_BIT, GLES30.GL_NEAREST);
     89             checkGlError("glBlitFramebuffer");
     90 
     91             ByteBuffer pixelBuf = ByteBuffer.allocateDirect(WIDTH * HEIGHT * BYTES_PER_PIXEL);
     92             pixelBuf.order(ByteOrder.LITTLE_ENDIAN);
     93             byte testBytes[] = new byte[4];
     94 
     95             // Confirm that surface1 has the color by testing a pixel from the center.
     96             surface1.makeCurrent();
     97             pixelBuf.clear();
     98             GLES30.glReadPixels(0, 0, WIDTH, HEIGHT, GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE,
     99                     pixelBuf);
    100             checkGlError("glReadPixels");
    101             pixelBuf.position((WIDTH * (HEIGHT / 2) + (WIDTH / 2)) * BYTES_PER_PIXEL);
    102             pixelBuf.get(testBytes, 0, 4);
    103             Log.v(TAG, "testBytes1 = " + Arrays.toString(testBytes));
    104             assertTrue(Arrays.equals(testBytes, expectedBytes));
    105 
    106             // Confirm that surface2 has the color.
    107             surface2.makeCurrent();
    108             pixelBuf.clear();
    109             GLES30.glReadPixels(0, 0, WIDTH, HEIGHT, GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE,
    110                     pixelBuf);
    111             checkGlError("glReadPixels");
    112             pixelBuf.position((WIDTH * (HEIGHT / 2) + (WIDTH / 2)) * BYTES_PER_PIXEL);
    113             pixelBuf.get(testBytes, 0, 4);
    114             Log.v(TAG, "testBytes2 = " + Arrays.toString(testBytes));
    115             assertTrue(Arrays.equals(testBytes, expectedBytes));
    116         } finally {
    117             if (surface1 != null) {
    118                 surface1.release();
    119             }
    120             if (surface2 != null) {
    121                 surface2.release();
    122             }
    123             if (eglCore != null) {
    124                 eglCore.release();
    125             }
    126         }
    127     }
    128 
    129     /**
    130      * Checks to see if a GLES error has been raised.
    131      */
    132     private static void checkGlError(String op) {
    133         int error = GLES20.glGetError();
    134         if (error != GLES20.GL_NO_ERROR) {
    135             String msg = op + ": glError 0x" + Integer.toHexString(error);
    136             Log.e(TAG, msg);
    137             throw new RuntimeException(msg);
    138         }
    139     }
    140 
    141 
    142     /**
    143      * Core EGL state (display, context, config).
    144      */
    145     private static final class EglCore {
    146         /**
    147          * Constructor flag: surface must be recordable.  This discourages EGL from using a
    148          * pixel format that cannot be converted efficiently to something usable by the video
    149          * encoder.
    150          */
    151         public static final int FLAG_RECORDABLE = 0x01;
    152 
    153         /**
    154          * Constructor flag: ask for GLES3, fall back to GLES2 if not available.  Without this
    155          * flag, GLES2 is used.
    156          */
    157         public static final int FLAG_TRY_GLES3 = 0x02;
    158 
    159         // Android-specific extension.
    160         private static final int EGL_RECORDABLE_ANDROID = 0x3142;
    161 
    162         private EGLDisplay mEGLDisplay = EGL14.EGL_NO_DISPLAY;
    163         private EGLContext mEGLContext = EGL14.EGL_NO_CONTEXT;
    164         private EGLConfig mEGLConfig = null;
    165         private int mGlVersion = -1;
    166 
    167 
    168         /**
    169          * Prepares EGL display and context.
    170          * <p>
    171          * Equivalent to EglCore(null, 0).
    172          */
    173         public EglCore() {
    174             this(null, 0);
    175         }
    176 
    177         /**
    178          * Prepares EGL display and context.
    179          * <p>
    180          * @param sharedContext The context to share, or null if sharing is not desired.
    181          * @param flags Configuration bit flags, e.g. FLAG_RECORDABLE.
    182          */
    183         public EglCore(EGLContext sharedContext, int flags) {
    184             if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) {
    185                 throw new RuntimeException("EGL already set up");
    186             }
    187 
    188             if (sharedContext == null) {
    189                 sharedContext = EGL14.EGL_NO_CONTEXT;
    190             }
    191 
    192             mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
    193             if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) {
    194                 throw new RuntimeException("unable to get EGL14 display");
    195             }
    196             int[] version = new int[2];
    197             if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) {
    198                 mEGLDisplay = null;
    199                 throw new RuntimeException("unable to initialize EGL14");
    200             }
    201 
    202             // Try to get a GLES3 context, if requested.
    203             if ((flags & FLAG_TRY_GLES3) != 0) {
    204                 //Log.d(TAG, "Trying GLES 3");
    205                 EGLConfig config = getConfig(flags, 3);
    206                 if (config != null) {
    207                     int[] attrib3_list = {
    208                             EGL14.EGL_CONTEXT_CLIENT_VERSION, 3,
    209                             EGL14.EGL_NONE
    210                     };
    211                     EGLContext context = EGL14.eglCreateContext(mEGLDisplay, config, sharedContext,
    212                             attrib3_list, 0);
    213 
    214                     if (EGL14.eglGetError() == EGL14.EGL_SUCCESS) {
    215                         //Log.d(TAG, "Got GLES 3 config");
    216                         mEGLConfig = config;
    217                         mEGLContext = context;
    218                         mGlVersion = 3;
    219                     }
    220                 }
    221             }
    222             if (mEGLContext == EGL14.EGL_NO_CONTEXT) {  // GLES 2 only, or GLES 3 attempt failed
    223                 //Log.d(TAG, "Trying GLES 2");
    224                 EGLConfig config = getConfig(flags, 2);
    225                 if (config == null) {
    226                     throw new RuntimeException("Unable to find a suitable EGLConfig");
    227                 }
    228                 int[] attrib2_list = {
    229                         EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
    230                         EGL14.EGL_NONE
    231                 };
    232                 EGLContext context = EGL14.eglCreateContext(mEGLDisplay, config, sharedContext,
    233                         attrib2_list, 0);
    234                 checkEglError("eglCreateContext");
    235                 mEGLConfig = config;
    236                 mEGLContext = context;
    237                 mGlVersion = 2;
    238             }
    239 
    240             // Confirm with query.
    241             int[] values = new int[1];
    242             EGL14.eglQueryContext(mEGLDisplay, mEGLContext, EGL14.EGL_CONTEXT_CLIENT_VERSION,
    243                     values, 0);
    244             Log.d(TAG, "EGLContext created, client version " + values[0]);
    245         }
    246 
    247         /**
    248          * Finds a suitable EGLConfig.
    249          *
    250          * @param flags Bit flags from constructor.
    251          * @param version Must be 2 or 3.
    252          */
    253         private EGLConfig getConfig(int flags, int version) {
    254             int renderableType = EGL14.EGL_OPENGL_ES2_BIT;
    255             if (version >= 3) {
    256                 renderableType |= EGLExt.EGL_OPENGL_ES3_BIT_KHR;
    257             }
    258 
    259             // The actual surface is generally RGBA or RGBX, so situationally omitting alpha
    260             // doesn't really help.  It can also lead to a huge performance hit on glReadPixels()
    261             // when reading into a GL_RGBA buffer.
    262             int[] attribList = {
    263                     EGL14.EGL_RED_SIZE, 8,
    264                     EGL14.EGL_GREEN_SIZE, 8,
    265                     EGL14.EGL_BLUE_SIZE, 8,
    266                     EGL14.EGL_ALPHA_SIZE, 8,
    267                     //EGL14.EGL_DEPTH_SIZE, 16,
    268                     //EGL14.EGL_STENCIL_SIZE, 8,
    269                     EGL14.EGL_RENDERABLE_TYPE, renderableType,
    270                     EGL14.EGL_NONE, 0,      // placeholder for recordable [@-3]
    271                     EGL14.EGL_NONE
    272             };
    273             if ((flags & FLAG_RECORDABLE) != 0) {
    274                 attribList[attribList.length - 3] = EGL_RECORDABLE_ANDROID;
    275                 attribList[attribList.length - 2] = 1;
    276             }
    277             EGLConfig[] configs = new EGLConfig[1];
    278             int[] numConfigs = new int[1];
    279             if (!EGL14.eglChooseConfig(mEGLDisplay, attribList, 0, configs, 0, configs.length,
    280                     numConfigs, 0)) {
    281                 Log.w(TAG, "unable to find RGB8888 / " + version + " EGLConfig");
    282                 return null;
    283             }
    284             return configs[0];
    285         }
    286 
    287         /**
    288          * Discard all resources held by this class, notably the EGL context.
    289          */
    290         public void release() {
    291             if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) {
    292                 // Android is unusual in that it uses a reference-counted EGLDisplay.  So for
    293                 // every eglInitialize() we need an eglTerminate().
    294                 EGL14.eglDestroyContext(mEGLDisplay, mEGLContext);
    295                 EGL14.eglReleaseThread();
    296                 EGL14.eglTerminate(mEGLDisplay);
    297             }
    298 
    299             mEGLDisplay = EGL14.EGL_NO_DISPLAY;
    300             mEGLContext = EGL14.EGL_NO_CONTEXT;
    301             mEGLConfig = null;
    302         }
    303 
    304         @Override
    305         protected void finalize() throws Throwable {
    306             try {
    307                 if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) {
    308                     // We're limited here -- finalizers don't run on the thread that holds
    309                     // the EGL state, so if a surface or context is still current on another
    310                     // thread we can't fully release it here.  Exceptions thrown from here
    311                     // are quietly discarded.  Complain in the log file.
    312                     Log.w(TAG, "WARNING: EglCore was not explicitly released -- state may be leaked");
    313                     release();
    314                 }
    315             } finally {
    316                 super.finalize();
    317             }
    318         }
    319 
    320         /**
    321          * Destroys the specified surface.  Note the EGLSurface won't actually be destroyed if it's
    322          * still current in a context.
    323          */
    324         public void releaseSurface(EGLSurface eglSurface) {
    325             EGL14.eglDestroySurface(mEGLDisplay, eglSurface);
    326         }
    327 
    328         /**
    329          * Creates an EGL surface associated with a Surface.
    330          * <p>
    331          * If this is destined for MediaCodec, the EGLConfig should have the "recordable" attribute.
    332          */
    333         public EGLSurface createWindowSurface(Object surface) {
    334             if (!(surface instanceof Surface) && !(surface instanceof SurfaceTexture)) {
    335                 throw new RuntimeException("invalid surface: " + surface);
    336             }
    337 
    338             // Create a window surface, and attach it to the Surface we received.
    339             int[] surfaceAttribs = {
    340                     EGL14.EGL_NONE
    341             };
    342             EGLSurface eglSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, mEGLConfig, surface,
    343                     surfaceAttribs, 0);
    344             checkEglError("eglCreateWindowSurface");
    345             if (eglSurface == null) {
    346                 throw new RuntimeException("surface was null");
    347             }
    348             return eglSurface;
    349         }
    350 
    351         /**
    352          * Creates an EGL surface associated with an offscreen buffer.
    353          */
    354         public EGLSurface createOffscreenSurface(int width, int height) {
    355             int[] surfaceAttribs = {
    356                     EGL14.EGL_WIDTH, width,
    357                     EGL14.EGL_HEIGHT, height,
    358                     EGL14.EGL_NONE
    359             };
    360             EGLSurface eglSurface = EGL14.eglCreatePbufferSurface(mEGLDisplay, mEGLConfig,
    361                     surfaceAttribs, 0);
    362             checkEglError("eglCreatePbufferSurface");
    363             if (eglSurface == null) {
    364                 throw new RuntimeException("surface was null");
    365             }
    366             return eglSurface;
    367         }
    368 
    369         /**
    370          * Makes our EGL context current, using the supplied surface for both "draw" and "read".
    371          */
    372         public void makeCurrent(EGLSurface eglSurface) {
    373             if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) {
    374                 // called makeCurrent() before create?
    375                 Log.d(TAG, "NOTE: makeCurrent w/o display");
    376             }
    377             if (!EGL14.eglMakeCurrent(mEGLDisplay, eglSurface, eglSurface, mEGLContext)) {
    378                 throw new RuntimeException("eglMakeCurrent failed");
    379             }
    380         }
    381 
    382         /**
    383          * Makes our EGL context current, using the supplied "draw" and "read" surfaces.
    384          */
    385         public void makeCurrent(EGLSurface drawSurface, EGLSurface readSurface) {
    386             if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) {
    387                 // called makeCurrent() before create?
    388                 Log.d(TAG, "NOTE: makeCurrent w/o display");
    389             }
    390             if (!EGL14.eglMakeCurrent(mEGLDisplay, drawSurface, readSurface, mEGLContext)) {
    391                 throw new RuntimeException("eglMakeCurrent(draw,read) failed");
    392             }
    393         }
    394 
    395         /**
    396          * Makes no context current.
    397          */
    398         public void makeNothingCurrent() {
    399             if (!EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE,
    400                     EGL14.EGL_NO_CONTEXT)) {
    401                 throw new RuntimeException("eglMakeCurrent failed");
    402             }
    403         }
    404 
    405         /**
    406          * Calls eglSwapBuffers.  Use this to "publish" the current frame.
    407          *
    408          * @return false on failure
    409          */
    410         public boolean swapBuffers(EGLSurface eglSurface) {
    411             return EGL14.eglSwapBuffers(mEGLDisplay, eglSurface);
    412         }
    413 
    414         /**
    415          * Sends the presentation time stamp to EGL.  Time is expressed in nanoseconds.
    416          */
    417         public void setPresentationTime(EGLSurface eglSurface, long nsecs) {
    418             EGLExt.eglPresentationTimeANDROID(mEGLDisplay, eglSurface, nsecs);
    419         }
    420 
    421         /**
    422          * Returns true if our context and the specified surface are current.
    423          */
    424         public boolean isCurrent(EGLSurface eglSurface) {
    425             return mEGLContext.equals(EGL14.eglGetCurrentContext()) &&
    426                     eglSurface.equals(EGL14.eglGetCurrentSurface(EGL14.EGL_DRAW));
    427         }
    428 
    429         /**
    430          * Performs a simple surface query.
    431          */
    432         public int querySurface(EGLSurface eglSurface, int what) {
    433             int[] value = new int[1];
    434             EGL14.eglQuerySurface(mEGLDisplay, eglSurface, what, value, 0);
    435             return value[0];
    436         }
    437 
    438         /**
    439          * Returns the GLES version this context is configured for (2 or 3).
    440          */
    441         public int getGlVersion() {
    442             return mGlVersion;
    443         }
    444 
    445         /**
    446          * Writes the current display, context, and surface to the log.
    447          */
    448         public static void logCurrent(String msg) {
    449             EGLDisplay display;
    450             EGLContext context;
    451             EGLSurface surface;
    452 
    453             display = EGL14.eglGetCurrentDisplay();
    454             context = EGL14.eglGetCurrentContext();
    455             surface = EGL14.eglGetCurrentSurface(EGL14.EGL_DRAW);
    456             Log.i(TAG, "Current EGL (" + msg + "): display=" + display + ", context=" + context +
    457                     ", surface=" + surface);
    458         }
    459 
    460         /**
    461          * Checks for EGL errors.
    462          */
    463         private void checkEglError(String msg) {
    464             int error;
    465             if ((error = EGL14.eglGetError()) != EGL14.EGL_SUCCESS) {
    466                 throw new RuntimeException(msg + ": EGL error: 0x" + Integer.toHexString(error));
    467             }
    468         }
    469     }
    470 
    471 
    472     /**
    473      * Common base class for EGL surfaces.
    474      * <p>
    475      * There can be multiple surfaces associated with a single context.
    476      */
    477     private static class EglSurfaceBase {
    478         // EglCore object we're associated with.  It may be associated with multiple surfaces.
    479         protected EglCore mEglCore;
    480 
    481         private EGLSurface mEGLSurface = EGL14.EGL_NO_SURFACE;
    482         private int mWidth = -1;
    483         private int mHeight = -1;
    484 
    485         protected EglSurfaceBase(EglCore eglCore) {
    486             mEglCore = eglCore;
    487         }
    488 
    489         /**
    490          * Creates a window surface.
    491          * <p>
    492          * @param surface May be a Surface or SurfaceTexture.
    493          */
    494         public void createWindowSurface(Object surface) {
    495             if (mEGLSurface != EGL14.EGL_NO_SURFACE) {
    496                 throw new IllegalStateException("surface already created");
    497             }
    498             mEGLSurface = mEglCore.createWindowSurface(surface);
    499             mWidth = mEglCore.querySurface(mEGLSurface, EGL14.EGL_WIDTH);
    500             mHeight = mEglCore.querySurface(mEGLSurface, EGL14.EGL_HEIGHT);
    501         }
    502 
    503         /**
    504          * Creates an off-screen surface.
    505          */
    506         public void createOffscreenSurface(int width, int height) {
    507             if (mEGLSurface != EGL14.EGL_NO_SURFACE) {
    508                 throw new IllegalStateException("surface already created");
    509             }
    510             mEGLSurface = mEglCore.createOffscreenSurface(width, height);
    511             mWidth = width;
    512             mHeight = height;
    513         }
    514 
    515         /**
    516          * Returns the surface's width, in pixels.
    517          */
    518         public int getWidth() {
    519             return mWidth;
    520         }
    521 
    522         /**
    523          * Returns the surface's height, in pixels.
    524          */
    525         public int getHeight() {
    526             return mHeight;
    527         }
    528 
    529         /**
    530          * Release the EGL surface.
    531          */
    532         public void releaseEglSurface() {
    533             mEglCore.releaseSurface(mEGLSurface);
    534             mEGLSurface = EGL14.EGL_NO_SURFACE;
    535             mWidth = mHeight = -1;
    536         }
    537 
    538         /**
    539          * Makes our EGL context and surface current.
    540          */
    541         public void makeCurrent() {
    542             mEglCore.makeCurrent(mEGLSurface);
    543         }
    544 
    545         /**
    546          * Makes our EGL context and surface current for drawing, using the supplied surface
    547          * for reading.
    548          */
    549         public void makeCurrentReadFrom(EglSurfaceBase readSurface) {
    550             mEglCore.makeCurrent(mEGLSurface, readSurface.mEGLSurface);
    551         }
    552 
    553         /**
    554          * Calls eglSwapBuffers.  Use this to "publish" the current frame.
    555          *
    556          * @return false on failure
    557          */
    558         public boolean swapBuffers() {
    559             boolean result = mEglCore.swapBuffers(mEGLSurface);
    560             if (!result) {
    561                 Log.d(TAG, "WARNING: swapBuffers() failed");
    562             }
    563             return result;
    564         }
    565 
    566         /**
    567          * Sends the presentation time stamp to EGL.
    568          *
    569          * @param nsecs Timestamp, in nanoseconds.
    570          */
    571         public void setPresentationTime(long nsecs) {
    572             mEglCore.setPresentationTime(mEGLSurface, nsecs);
    573         }
    574 
    575         /**
    576          * Saves the EGL surface to a file.
    577          * <p>
    578          * Expects that this object's EGL surface is current.
    579          */
    580         public void saveFrame(File file) throws IOException {
    581             if (!mEglCore.isCurrent(mEGLSurface)) {
    582                 throw new RuntimeException("Expected EGL context/surface is not current");
    583             }
    584 
    585             // glReadPixels gives us a ByteBuffer filled with what is essentially big-endian RGBA
    586             // data (i.e. a byte of red, followed by a byte of green...).  We need an int[] filled
    587             // with little-endian ARGB data to feed to Bitmap.
    588             //
    589             // If we implement this as a series of buf.get() calls, we can spend 2.5 seconds just
    590             // copying data around for a 720p frame.  It's better to do a bulk get() and then
    591             // rearrange the data in memory.  (For comparison, the PNG compress takes about 500ms
    592             // for a trivial frame.)
    593             //
    594             // So... we set the ByteBuffer to little-endian, which should turn the bulk IntBuffer
    595             // get() into a straight memcpy on most Android devices.  Our ints will hold ABGR data.
    596             // Swapping B and R gives us ARGB.
    597             //
    598             // Making this even more interesting is the upside-down nature of GL, which means
    599             // our output will look upside-down relative to what appears on screen if the
    600             // typical GL conventions are used.
    601 
    602             String filename = file.toString();
    603 
    604             ByteBuffer buf = ByteBuffer.allocateDirect(mWidth * mHeight * 4);
    605             buf.order(ByteOrder.LITTLE_ENDIAN);
    606             GLES20.glReadPixels(0, 0, mWidth, mHeight,
    607                     GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buf);
    608             checkGlError("glReadPixels");
    609             buf.rewind();
    610 
    611             int pixelCount = mWidth * mHeight;
    612             int[] colors = new int[pixelCount];
    613             buf.asIntBuffer().get(colors);
    614             for (int i = 0; i < pixelCount; i++) {
    615                 int c = colors[i];
    616                 colors[i] = (c & 0xff00ff00) | ((c & 0x00ff0000) >> 16) | ((c & 0x000000ff) << 16);
    617             }
    618 
    619             BufferedOutputStream bos = null;
    620             try {
    621                 bos = new BufferedOutputStream(new FileOutputStream(filename));
    622                 Bitmap bmp = Bitmap.createBitmap(colors, mWidth, mHeight, Bitmap.Config.ARGB_8888);
    623                 bmp.compress(Bitmap.CompressFormat.PNG, 90, bos);
    624                 bmp.recycle();
    625             } finally {
    626                 if (bos != null) bos.close();
    627             }
    628             Log.d(TAG, "Saved " + mWidth + "x" + mHeight + " frame as '" + filename + "'");
    629         }
    630     }
    631 
    632     /**
    633      * Off-screen EGL surface (pbuffer).
    634      * <p>
    635      * It's good practice to explicitly release() the surface, preferably from a "finally" block.
    636      */
    637     private static class OffscreenSurface extends EglSurfaceBase {
    638         /**
    639          * Creates an off-screen surface with the specified width and height.
    640          */
    641         public OffscreenSurface(EglCore eglCore, int width, int height) {
    642             super(eglCore);
    643             createOffscreenSurface(width, height);
    644         }
    645 
    646         /**
    647          * Releases any resources associated with the surface.
    648          */
    649         public void release() {
    650             releaseEglSurface();
    651         }
    652     }
    653 }
    654