Home | History | Annotate | Download | only in filterfw
      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 package androidx.media.filterfw;
     18 
     19 import android.annotation.TargetApi;
     20 import android.graphics.SurfaceTexture;
     21 import android.media.MediaRecorder;
     22 import android.opengl.GLES20;
     23 import android.opengl.GLUtils;
     24 import android.os.Build.VERSION;
     25 import android.util.Log;
     26 import android.view.Surface;
     27 import android.view.SurfaceHolder;
     28 
     29 import java.nio.ByteBuffer;
     30 import java.util.HashMap;
     31 
     32 import javax.microedition.khronos.egl.EGL10;
     33 import javax.microedition.khronos.egl.EGLConfig;
     34 import javax.microedition.khronos.egl.EGLContext;
     35 import javax.microedition.khronos.egl.EGLDisplay;
     36 import javax.microedition.khronos.egl.EGLSurface;
     37 
     38 public final class RenderTarget {
     39 
     40     private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
     41     private static final int EGL_OPENGL_ES2_BIT = 4;
     42 
     43     // Pre-HC devices do not necessarily support multiple display surfaces.
     44     private static boolean mSupportsMultipleDisplaySurfaces = (VERSION.SDK_INT >= 11);
     45 
     46     /** A Map that tracks which objects are wrapped by EGLSurfaces */
     47     private static HashMap<Object, EGLSurface> mSurfaceSources = new HashMap<Object, EGLSurface>();
     48 
     49     /** A Map for performing reference counting over shared objects across RenderTargets */
     50     private static HashMap<Object, Integer> mRefCounts = new HashMap<Object, Integer>();
     51 
     52     /** Stores the RenderTarget that is focused on the current thread. */
     53     private static ThreadLocal<RenderTarget> mCurrentTarget = new ThreadLocal<RenderTarget>();
     54 
     55     /** The source for the surface used in this target (if any) */
     56     private Object mSurfaceSource = null;
     57 
     58     /** The cached EGLConfig instance. */
     59     private static EGLConfig mEglConfig = null;
     60 
     61     /** The display for which the EGLConfig was chosen. We expect only one. */
     62     private static EGLDisplay mConfiguredDisplay;
     63 
     64     private EGL10 mEgl;
     65     private EGLDisplay mDisplay;
     66     private EGLContext mContext;
     67     private EGLSurface mSurface;
     68     private int mFbo;
     69 
     70     private boolean mOwnsContext;
     71     private boolean mOwnsSurface;
     72 
     73     private static HashMap<EGLContext, ImageShader> mIdShaders
     74         = new HashMap<EGLContext, ImageShader>();
     75 
     76     private static HashMap<EGLContext, EGLSurface> mDisplaySurfaces
     77         = new HashMap<EGLContext, EGLSurface>();
     78 
     79     private static int sRedSize = 8;
     80     private static int sGreenSize = 8;
     81     private static int sBlueSize = 8;
     82     private static int sAlphaSize = 8;
     83     private static int sDepthSize = 0;
     84     private static int sStencilSize = 0;
     85 
     86     public static RenderTarget newTarget(int width, int height) {
     87         EGL10 egl = (EGL10) EGLContext.getEGL();
     88         EGLDisplay eglDisplay = createDefaultDisplay(egl);
     89         EGLConfig eglConfig = chooseEglConfig(egl, eglDisplay);
     90         EGLContext eglContext = createContext(egl, eglDisplay, eglConfig);
     91         EGLSurface eglSurface = createSurface(egl, eglDisplay, width, height);
     92         RenderTarget result = new RenderTarget(eglDisplay, eglContext, eglSurface, 0, true, true);
     93         result.addReferenceTo(eglSurface);
     94         return result;
     95     }
     96 
     97     public static RenderTarget currentTarget() {
     98         // As RenderTargets are immutable, we can safely return the last focused instance on this
     99         // thread, as we know it cannot have changed, and therefore must be current.
    100         return mCurrentTarget.get();
    101     }
    102 
    103     public RenderTarget forTexture(TextureSource texture, int width, int height) {
    104         // NOTE: We do not need to lookup any previous bindings of this texture to an FBO, as
    105         // multiple FBOs to a single texture is valid.
    106         int fbo = GLToolbox.generateFbo();
    107         GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fbo);
    108         GLToolbox.checkGlError("glBindFramebuffer");
    109         GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER,
    110                                       GLES20.GL_COLOR_ATTACHMENT0,
    111                                       texture.getTarget(),
    112                                       texture.getTextureId(),
    113                                       0);
    114         GLToolbox.checkGlError("glFramebufferTexture2D");
    115         return new RenderTarget(mDisplay, mContext, surface(), fbo, false, false);
    116     }
    117 
    118     public RenderTarget forSurfaceHolder(SurfaceHolder surfaceHolder) {
    119         EGLConfig eglConfig = chooseEglConfig(mEgl, mDisplay);
    120         EGLSurface eglSurf = null;
    121         synchronized (mSurfaceSources) {
    122             eglSurf = mSurfaceSources.get(surfaceHolder);
    123             if (eglSurf == null) {
    124                 eglSurf = mEgl.eglCreateWindowSurface(mDisplay, eglConfig, surfaceHolder, null);
    125                 mSurfaceSources.put(surfaceHolder, eglSurf);
    126             }
    127         }
    128         checkEglError(mEgl, "eglCreateWindowSurface");
    129         checkSurface(mEgl, eglSurf);
    130         RenderTarget result = new RenderTarget(mDisplay, mContext, eglSurf, 0, false, true);
    131         result.addReferenceTo(eglSurf);
    132         result.setSurfaceSource(surfaceHolder);
    133         return result;
    134     }
    135 
    136     @TargetApi(11)
    137     public RenderTarget forSurfaceTexture(SurfaceTexture surfaceTexture) {
    138         EGLConfig eglConfig = chooseEglConfig(mEgl, mDisplay);
    139         EGLSurface eglSurf = null;
    140         synchronized (mSurfaceSources) {
    141             eglSurf = mSurfaceSources.get(surfaceTexture);
    142             if (eglSurf == null) {
    143                 eglSurf = mEgl.eglCreateWindowSurface(mDisplay, eglConfig, surfaceTexture, null);
    144                 mSurfaceSources.put(surfaceTexture, eglSurf);
    145             }
    146         }
    147         checkEglError(mEgl, "eglCreateWindowSurface");
    148         checkSurface(mEgl, eglSurf);
    149         RenderTarget result = new RenderTarget(mDisplay, mContext, eglSurf, 0, false, true);
    150         result.setSurfaceSource(surfaceTexture);
    151         result.addReferenceTo(eglSurf);
    152         return result;
    153     }
    154 
    155     @TargetApi(11)
    156     public RenderTarget forSurface(Surface surface) {
    157         EGLConfig eglConfig = chooseEglConfig(mEgl, mDisplay);
    158         EGLSurface eglSurf = null;
    159         synchronized (mSurfaceSources) {
    160             eglSurf = mSurfaceSources.get(surface);
    161             if (eglSurf == null) {
    162                 eglSurf = mEgl.eglCreateWindowSurface(mDisplay, eglConfig, surface, null);
    163                 mSurfaceSources.put(surface, eglSurf);
    164             }
    165         }
    166         checkEglError(mEgl, "eglCreateWindowSurface");
    167         checkSurface(mEgl, eglSurf);
    168         RenderTarget result = new RenderTarget(mDisplay, mContext, eglSurf, 0, false, true);
    169         result.setSurfaceSource(surface);
    170         result.addReferenceTo(eglSurf);
    171         return result;
    172     }
    173 
    174     public static RenderTarget forMediaRecorder(MediaRecorder mediaRecorder) {
    175         throw new RuntimeException("Not yet implemented MediaRecorder -> RenderTarget!");
    176     }
    177 
    178     public static void setEGLConfigChooser(int redSize, int greenSize, int blueSize, int alphaSize,
    179             int depthSize, int stencilSize) {
    180         sRedSize = redSize;
    181         sGreenSize = greenSize;
    182         sBlueSize = blueSize;
    183         sAlphaSize = alphaSize;
    184         sDepthSize = depthSize;
    185         sStencilSize = stencilSize;
    186     }
    187 
    188     public void registerAsDisplaySurface() {
    189         if (!mSupportsMultipleDisplaySurfaces) {
    190             // Note that while this does in effect change RenderTarget instances (by modifying
    191             // their returned EGLSurface), breaking the immutability requirement, it does not modify
    192             // the current target. This is important so that the instance returned in
    193             // currentTarget() remains accurate.
    194             EGLSurface currentSurface = mDisplaySurfaces.get(mContext);
    195             if (currentSurface != null && !currentSurface.equals(mSurface)) {
    196                 throw new RuntimeException("This device supports only a single display surface!");
    197             } else {
    198                 mDisplaySurfaces.put(mContext, mSurface);
    199             }
    200         }
    201     }
    202 
    203     public void unregisterAsDisplaySurface() {
    204         if (!mSupportsMultipleDisplaySurfaces) {
    205             mDisplaySurfaces.put(mContext, null);
    206         }
    207     }
    208 
    209     public void focus() {
    210         RenderTarget current = mCurrentTarget.get();
    211         // We assume RenderTargets are immutable, so that we do not need to focus if the current
    212         // RenderTarget has not changed.
    213         if (current != this) {
    214             mEgl.eglMakeCurrent(mDisplay, surface(), surface(), mContext);
    215             mCurrentTarget.set(this);
    216         }
    217         if (getCurrentFbo() != mFbo) {
    218             GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFbo);
    219             GLToolbox.checkGlError("glBindFramebuffer");
    220         }
    221     }
    222 
    223     public static void focusNone() {
    224         EGL10 egl = (EGL10) EGLContext.getEGL();
    225         egl.eglMakeCurrent(egl.eglGetCurrentDisplay(),
    226                            EGL10.EGL_NO_SURFACE,
    227                            EGL10.EGL_NO_SURFACE,
    228                            EGL10.EGL_NO_CONTEXT);
    229         mCurrentTarget.set(null);
    230         checkEglError(egl, "eglMakeCurrent");
    231     }
    232 
    233     public void swapBuffers() {
    234         mEgl.eglSwapBuffers(mDisplay, surface());
    235     }
    236 
    237     public EGLContext getContext() {
    238         return mContext;
    239     }
    240 
    241     public static EGLContext currentContext() {
    242         RenderTarget current = RenderTarget.currentTarget();
    243         return current != null ? current.getContext() : EGL10.EGL_NO_CONTEXT;
    244     }
    245 
    246     public void release() {
    247         if (mOwnsContext) {
    248             mEgl.eglDestroyContext(mDisplay, mContext);
    249             mContext = EGL10.EGL_NO_CONTEXT;
    250         }
    251         if (mOwnsSurface) {
    252             synchronized (mSurfaceSources) {
    253                 if (removeReferenceTo(mSurface)) {
    254                     mEgl.eglDestroySurface(mDisplay, mSurface);
    255                     mSurface = EGL10.EGL_NO_SURFACE;
    256                     mSurfaceSources.remove(mSurfaceSource);
    257                 }
    258             }
    259         }
    260         if (mFbo != 0) {
    261            GLToolbox.deleteFbo(mFbo);
    262        }
    263     }
    264 
    265     public void readPixelData(ByteBuffer pixels, int width, int height) {
    266         GLToolbox.readTarget(this, pixels, width, height);
    267     }
    268 
    269     public ByteBuffer getPixelData(int width, int height) {
    270         ByteBuffer pixels = ByteBuffer.allocateDirect(width * height * 4);
    271         GLToolbox.readTarget(this, pixels, width, height);
    272         return pixels;
    273     }
    274 
    275     /**
    276      * Returns an identity shader for this context.
    277      * You must not modify this shader. Use {@link ImageShader#createIdentity()} if you need to
    278      * modify an identity shader.
    279      */
    280     public ImageShader getIdentityShader() {
    281         ImageShader idShader = mIdShaders.get(mContext);
    282         if (idShader == null) {
    283             idShader = ImageShader.createIdentity();
    284             mIdShaders.put(mContext, idShader);
    285         }
    286         return idShader;
    287     }
    288 
    289     @Override
    290     public String toString() {
    291         return "RenderTarget(" + mDisplay + ", " + mContext + ", " + mSurface + ", " + mFbo + ")";
    292     }
    293 
    294     private void setSurfaceSource(Object source) {
    295         mSurfaceSource = source;
    296     }
    297 
    298     private void addReferenceTo(Object object) {
    299         Integer refCount = mRefCounts.get(object);
    300         if (refCount != null) {
    301             mRefCounts.put(object, refCount + 1);
    302         } else {
    303             mRefCounts.put(object, 1);
    304         }
    305     }
    306 
    307     private boolean removeReferenceTo(Object object) {
    308         Integer refCount = mRefCounts.get(object);
    309         if (refCount != null && refCount > 0) {
    310             --refCount;
    311             mRefCounts.put(object, refCount);
    312             return refCount == 0;
    313         } else {
    314             Log.e("RenderTarget", "Removing reference of already released: " + object + "!");
    315             return false;
    316         }
    317     }
    318 
    319     private static EGLConfig chooseEglConfig(EGL10 egl, EGLDisplay display) {
    320         if (mEglConfig == null || !display.equals(mConfiguredDisplay)) {
    321             int[] configsCount = new int[1];
    322             EGLConfig[] configs = new EGLConfig[1];
    323             int[] configSpec = getDesiredConfig();
    324             if (!egl.eglChooseConfig(display, configSpec, configs, 1, configsCount)) {
    325                 throw new IllegalArgumentException("EGL Error: eglChooseConfig failed " +
    326                         getEGLErrorString(egl, egl.eglGetError()));
    327             } else if (configsCount[0] > 0) {
    328                 mEglConfig = configs[0];
    329                 mConfiguredDisplay = display;
    330             }
    331         }
    332         return mEglConfig;
    333     }
    334 
    335     private static int[] getDesiredConfig() {
    336         return new int[] {
    337                 EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
    338                 EGL10.EGL_RED_SIZE, sRedSize,
    339                 EGL10.EGL_GREEN_SIZE, sGreenSize,
    340                 EGL10.EGL_BLUE_SIZE, sBlueSize,
    341                 EGL10.EGL_ALPHA_SIZE, sAlphaSize,
    342                 EGL10.EGL_DEPTH_SIZE, sDepthSize,
    343                 EGL10.EGL_STENCIL_SIZE, sStencilSize,
    344                 EGL10.EGL_NONE
    345         };
    346     }
    347 
    348     private RenderTarget(EGLDisplay display, EGLContext context, EGLSurface surface, int fbo,
    349                          boolean ownsContext, boolean ownsSurface) {
    350         mEgl = (EGL10) EGLContext.getEGL();
    351         mDisplay = display;
    352         mContext = context;
    353         mSurface = surface;
    354         mFbo = fbo;
    355         mOwnsContext = ownsContext;
    356         mOwnsSurface = ownsSurface;
    357     }
    358 
    359     private EGLSurface surface() {
    360         if (mSupportsMultipleDisplaySurfaces) {
    361             return mSurface;
    362         } else {
    363             EGLSurface displaySurface = mDisplaySurfaces.get(mContext);
    364             return displaySurface != null ? displaySurface : mSurface;
    365         }
    366     }
    367 
    368     private static void initEgl(EGL10 egl, EGLDisplay display) {
    369         int[] version = new int[2];
    370         if (!egl.eglInitialize(display, version)) {
    371             throw new RuntimeException("EGL Error: eglInitialize failed " +
    372                     getEGLErrorString(egl, egl.eglGetError()));
    373         }
    374     }
    375 
    376     private static EGLDisplay createDefaultDisplay(EGL10 egl) {
    377         EGLDisplay display = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
    378         checkDisplay(egl, display);
    379         initEgl(egl, display);
    380         return display;
    381     }
    382 
    383     private static EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig config) {
    384         int[] attrib_list = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE };
    385         EGLContext ctxt = egl.eglCreateContext(display, config, EGL10.EGL_NO_CONTEXT, attrib_list);
    386         checkContext(egl, ctxt);
    387         return ctxt;
    388     }
    389 
    390     private static EGLSurface createSurface(EGL10 egl, EGLDisplay display, int width, int height) {
    391         EGLConfig eglConfig = chooseEglConfig(egl, display);
    392         int[] attribs = { EGL10.EGL_WIDTH, width, EGL10.EGL_HEIGHT, height, EGL10.EGL_NONE };
    393         return egl.eglCreatePbufferSurface(display, eglConfig, attribs);
    394     }
    395 
    396     private static int getCurrentFbo() {
    397         int[] result = new int[1];
    398         GLES20.glGetIntegerv(GLES20.GL_FRAMEBUFFER_BINDING, result, 0);
    399         return result[0];
    400     }
    401 
    402     private static void checkDisplay(EGL10 egl, EGLDisplay display) {
    403         if (display == EGL10.EGL_NO_DISPLAY) {
    404             throw new RuntimeException("EGL Error: Bad display: "
    405                     + getEGLErrorString(egl, egl.eglGetError()));
    406         }
    407     }
    408 
    409     private static void checkContext(EGL10 egl, EGLContext context) {
    410         if (context == EGL10.EGL_NO_CONTEXT) {
    411             throw new RuntimeException("EGL Error: Bad context: "
    412                     + getEGLErrorString(egl, egl.eglGetError()));
    413         }
    414     }
    415 
    416     private static void checkSurface(EGL10 egl, EGLSurface surface) {
    417         if (surface == EGL10.EGL_NO_SURFACE) {
    418             throw new RuntimeException("EGL Error: Bad surface: "
    419                     + getEGLErrorString(egl, egl.eglGetError()));
    420         }
    421     }
    422 
    423     private static void checkEglError(EGL10 egl, String command) {
    424         int error = egl.eglGetError();
    425         if (error != EGL10.EGL_SUCCESS) {
    426             throw new RuntimeException("Error executing " + command + "! EGL error = 0x"
    427                 + Integer.toHexString(error));
    428         }
    429     }
    430 
    431     private static String getEGLErrorString(EGL10 egl, int eglError) {
    432         if (VERSION.SDK_INT >= 14) {
    433             return getEGLErrorStringICS(egl, eglError);
    434         } else {
    435             return "EGL Error 0x" + Integer.toHexString(eglError);
    436         }
    437     }
    438 
    439     @TargetApi(14)
    440     private static String getEGLErrorStringICS(EGL10 egl, int eglError) {
    441         return GLUtils.getEGLErrorString(egl.eglGetError());
    442     }
    443 }
    444 
    445