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