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
17package androidx.media.filterfw;
18
19import android.annotation.TargetApi;
20import android.graphics.SurfaceTexture;
21import android.media.MediaRecorder;
22import android.opengl.GLES20;
23import android.opengl.GLUtils;
24import android.os.Build.VERSION;
25import android.util.Log;
26import android.view.Surface;
27import android.view.SurfaceHolder;
28
29import java.nio.ByteBuffer;
30import java.util.HashMap;
31
32import javax.microedition.khronos.egl.EGL10;
33import javax.microedition.khronos.egl.EGLConfig;
34import javax.microedition.khronos.egl.EGLContext;
35import javax.microedition.khronos.egl.EGLDisplay;
36import javax.microedition.khronos.egl.EGLSurface;
37
38public 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