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