• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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