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 
newTarget(int width, int height)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 
currentTarget()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 
forTexture(TextureSource texture, int width, int height)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 
forSurfaceHolder(SurfaceHolder surfaceHolder)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)
forSurfaceTexture(SurfaceTexture surfaceTexture)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)
forSurface(Surface surface)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 
forMediaRecorder(MediaRecorder mediaRecorder)174     public static RenderTarget forMediaRecorder(MediaRecorder mediaRecorder) {
175         throw new RuntimeException("Not yet implemented MediaRecorder -> RenderTarget!");
176     }
177 
setEGLConfigChooser(int redSize, int greenSize, int blueSize, int alphaSize, int depthSize, int stencilSize)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 
registerAsDisplaySurface()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 
unregisterAsDisplaySurface()203     public void unregisterAsDisplaySurface() {
204         if (!mSupportsMultipleDisplaySurfaces) {
205             mDisplaySurfaces.put(mContext, null);
206         }
207     }
208 
focus()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 
focusNone()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 
swapBuffers()233     public void swapBuffers() {
234         mEgl.eglSwapBuffers(mDisplay, surface());
235     }
236 
getContext()237     public EGLContext getContext() {
238         return mContext;
239     }
240 
currentContext()241     public static EGLContext currentContext() {
242         RenderTarget current = RenderTarget.currentTarget();
243         return current != null ? current.getContext() : EGL10.EGL_NO_CONTEXT;
244     }
245 
release()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 
readPixelData(ByteBuffer pixels, int width, int height)265     public void readPixelData(ByteBuffer pixels, int width, int height) {
266         GLToolbox.readTarget(this, pixels, width, height);
267     }
268 
getPixelData(int width, int height)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      */
getIdentityShader()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
toString()290     public String toString() {
291         return "RenderTarget(" + mDisplay + ", " + mContext + ", " + mSurface + ", " + mFbo + ")";
292     }
293 
setSurfaceSource(Object source)294     private void setSurfaceSource(Object source) {
295         mSurfaceSource = source;
296     }
297 
addReferenceTo(Object object)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 
removeReferenceTo(Object object)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 
chooseEglConfig(EGL10 egl, EGLDisplay display)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 
getDesiredConfig()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 
RenderTarget(EGLDisplay display, EGLContext context, EGLSurface surface, int fbo, boolean ownsContext, boolean ownsSurface)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 
surface()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 
initEgl(EGL10 egl, EGLDisplay display)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 
createDefaultDisplay(EGL10 egl)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 
createContext(EGL10 egl, EGLDisplay display, EGLConfig config)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 
createSurface(EGL10 egl, EGLDisplay display, int width, int height)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 
getCurrentFbo()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 
checkDisplay(EGL10 egl, EGLDisplay display)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 
checkContext(EGL10 egl, EGLContext context)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 
checkSurface(EGL10 egl, EGLSurface surface)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 
checkEglError(EGL10 egl, String command)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 
getEGLErrorString(EGL10 egl, int eglError)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)
getEGLErrorStringICS(EGL10 egl, int eglError)440     private static String getEGLErrorStringICS(EGL10 egl, int eglError) {
441         return GLUtils.getEGLErrorString(egl.eglGetError());
442     }
443 }
444 
445