/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package androidx.media.filterfw; import android.annotation.TargetApi; import android.graphics.SurfaceTexture; import android.media.MediaRecorder; import android.opengl.GLES20; import android.opengl.GLUtils; import android.os.Build.VERSION; import android.util.Log; import android.view.Surface; import android.view.SurfaceHolder; import java.nio.ByteBuffer; import java.util.HashMap; import javax.microedition.khronos.egl.EGL10; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.egl.EGLContext; import javax.microedition.khronos.egl.EGLDisplay; import javax.microedition.khronos.egl.EGLSurface; public final class RenderTarget { private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098; private static final int EGL_OPENGL_ES2_BIT = 4; // Pre-HC devices do not necessarily support multiple display surfaces. private static boolean mSupportsMultipleDisplaySurfaces = (VERSION.SDK_INT >= 11); /** A Map that tracks which objects are wrapped by EGLSurfaces */ private static HashMap mSurfaceSources = new HashMap(); /** A Map for performing reference counting over shared objects across RenderTargets */ private static HashMap mRefCounts = new HashMap(); /** Stores the RenderTarget that is focused on the current thread. */ private static ThreadLocal mCurrentTarget = new ThreadLocal(); /** The source for the surface used in this target (if any) */ private Object mSurfaceSource = null; /** The cached EGLConfig instance. */ private static EGLConfig mEglConfig = null; /** The display for which the EGLConfig was chosen. We expect only one. */ private static EGLDisplay mConfiguredDisplay; private EGL10 mEgl; private EGLDisplay mDisplay; private EGLContext mContext; private EGLSurface mSurface; private int mFbo; private boolean mOwnsContext; private boolean mOwnsSurface; private static HashMap mIdShaders = new HashMap(); private static HashMap mDisplaySurfaces = new HashMap(); private static int sRedSize = 8; private static int sGreenSize = 8; private static int sBlueSize = 8; private static int sAlphaSize = 8; private static int sDepthSize = 0; private static int sStencilSize = 0; public static RenderTarget newTarget(int width, int height) { EGL10 egl = (EGL10) EGLContext.getEGL(); EGLDisplay eglDisplay = createDefaultDisplay(egl); EGLConfig eglConfig = chooseEglConfig(egl, eglDisplay); EGLContext eglContext = createContext(egl, eglDisplay, eglConfig); EGLSurface eglSurface = createSurface(egl, eglDisplay, width, height); RenderTarget result = new RenderTarget(eglDisplay, eglContext, eglSurface, 0, true, true); result.addReferenceTo(eglSurface); return result; } public static RenderTarget currentTarget() { // As RenderTargets are immutable, we can safely return the last focused instance on this // thread, as we know it cannot have changed, and therefore must be current. return mCurrentTarget.get(); } public RenderTarget forTexture(TextureSource texture, int width, int height) { // NOTE: We do not need to lookup any previous bindings of this texture to an FBO, as // multiple FBOs to a single texture is valid. int fbo = GLToolbox.generateFbo(); GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fbo); GLToolbox.checkGlError("glBindFramebuffer"); GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, texture.getTarget(), texture.getTextureId(), 0); GLToolbox.checkGlError("glFramebufferTexture2D"); return new RenderTarget(mDisplay, mContext, surface(), fbo, false, false); } public RenderTarget forSurfaceHolder(SurfaceHolder surfaceHolder) { EGLConfig eglConfig = chooseEglConfig(mEgl, mDisplay); EGLSurface eglSurf = null; synchronized (mSurfaceSources) { eglSurf = mSurfaceSources.get(surfaceHolder); if (eglSurf == null) { eglSurf = mEgl.eglCreateWindowSurface(mDisplay, eglConfig, surfaceHolder, null); mSurfaceSources.put(surfaceHolder, eglSurf); } } checkEglError(mEgl, "eglCreateWindowSurface"); checkSurface(mEgl, eglSurf); RenderTarget result = new RenderTarget(mDisplay, mContext, eglSurf, 0, false, true); result.addReferenceTo(eglSurf); result.setSurfaceSource(surfaceHolder); return result; } @TargetApi(11) public RenderTarget forSurfaceTexture(SurfaceTexture surfaceTexture) { EGLConfig eglConfig = chooseEglConfig(mEgl, mDisplay); EGLSurface eglSurf = null; synchronized (mSurfaceSources) { eglSurf = mSurfaceSources.get(surfaceTexture); if (eglSurf == null) { eglSurf = mEgl.eglCreateWindowSurface(mDisplay, eglConfig, surfaceTexture, null); mSurfaceSources.put(surfaceTexture, eglSurf); } } checkEglError(mEgl, "eglCreateWindowSurface"); checkSurface(mEgl, eglSurf); RenderTarget result = new RenderTarget(mDisplay, mContext, eglSurf, 0, false, true); result.setSurfaceSource(surfaceTexture); result.addReferenceTo(eglSurf); return result; } @TargetApi(11) public RenderTarget forSurface(Surface surface) { EGLConfig eglConfig = chooseEglConfig(mEgl, mDisplay); EGLSurface eglSurf = null; synchronized (mSurfaceSources) { eglSurf = mSurfaceSources.get(surface); if (eglSurf == null) { eglSurf = mEgl.eglCreateWindowSurface(mDisplay, eglConfig, surface, null); mSurfaceSources.put(surface, eglSurf); } } checkEglError(mEgl, "eglCreateWindowSurface"); checkSurface(mEgl, eglSurf); RenderTarget result = new RenderTarget(mDisplay, mContext, eglSurf, 0, false, true); result.setSurfaceSource(surface); result.addReferenceTo(eglSurf); return result; } public static RenderTarget forMediaRecorder(MediaRecorder mediaRecorder) { throw new RuntimeException("Not yet implemented MediaRecorder -> RenderTarget!"); } public static void setEGLConfigChooser(int redSize, int greenSize, int blueSize, int alphaSize, int depthSize, int stencilSize) { sRedSize = redSize; sGreenSize = greenSize; sBlueSize = blueSize; sAlphaSize = alphaSize; sDepthSize = depthSize; sStencilSize = stencilSize; } public void registerAsDisplaySurface() { if (!mSupportsMultipleDisplaySurfaces) { // Note that while this does in effect change RenderTarget instances (by modifying // their returned EGLSurface), breaking the immutability requirement, it does not modify // the current target. This is important so that the instance returned in // currentTarget() remains accurate. EGLSurface currentSurface = mDisplaySurfaces.get(mContext); if (currentSurface != null && !currentSurface.equals(mSurface)) { throw new RuntimeException("This device supports only a single display surface!"); } else { mDisplaySurfaces.put(mContext, mSurface); } } } public void unregisterAsDisplaySurface() { if (!mSupportsMultipleDisplaySurfaces) { mDisplaySurfaces.put(mContext, null); } } public void focus() { RenderTarget current = mCurrentTarget.get(); // We assume RenderTargets are immutable, so that we do not need to focus if the current // RenderTarget has not changed. if (current != this) { mEgl.eglMakeCurrent(mDisplay, surface(), surface(), mContext); mCurrentTarget.set(this); } if (getCurrentFbo() != mFbo) { GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, mFbo); GLToolbox.checkGlError("glBindFramebuffer"); } } public static void focusNone() { EGL10 egl = (EGL10) EGLContext.getEGL(); egl.eglMakeCurrent(egl.eglGetCurrentDisplay(), EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_CONTEXT); mCurrentTarget.set(null); checkEglError(egl, "eglMakeCurrent"); } public void swapBuffers() { mEgl.eglSwapBuffers(mDisplay, surface()); } public EGLContext getContext() { return mContext; } public static EGLContext currentContext() { RenderTarget current = RenderTarget.currentTarget(); return current != null ? current.getContext() : EGL10.EGL_NO_CONTEXT; } public void release() { if (mOwnsContext) { mEgl.eglDestroyContext(mDisplay, mContext); mContext = EGL10.EGL_NO_CONTEXT; } if (mOwnsSurface) { synchronized (mSurfaceSources) { if (removeReferenceTo(mSurface)) { mEgl.eglDestroySurface(mDisplay, mSurface); mSurface = EGL10.EGL_NO_SURFACE; mSurfaceSources.remove(mSurfaceSource); } } } if (mFbo != 0) { GLToolbox.deleteFbo(mFbo); } } public void readPixelData(ByteBuffer pixels, int width, int height) { GLToolbox.readTarget(this, pixels, width, height); } public ByteBuffer getPixelData(int width, int height) { ByteBuffer pixels = ByteBuffer.allocateDirect(width * height * 4); GLToolbox.readTarget(this, pixels, width, height); return pixels; } /** * Returns an identity shader for this context. * You must not modify this shader. Use {@link ImageShader#createIdentity()} if you need to * modify an identity shader. */ public ImageShader getIdentityShader() { ImageShader idShader = mIdShaders.get(mContext); if (idShader == null) { idShader = ImageShader.createIdentity(); mIdShaders.put(mContext, idShader); } return idShader; } @Override public String toString() { return "RenderTarget(" + mDisplay + ", " + mContext + ", " + mSurface + ", " + mFbo + ")"; } private void setSurfaceSource(Object source) { mSurfaceSource = source; } private void addReferenceTo(Object object) { Integer refCount = mRefCounts.get(object); if (refCount != null) { mRefCounts.put(object, refCount + 1); } else { mRefCounts.put(object, 1); } } private boolean removeReferenceTo(Object object) { Integer refCount = mRefCounts.get(object); if (refCount != null && refCount > 0) { --refCount; mRefCounts.put(object, refCount); return refCount == 0; } else { Log.e("RenderTarget", "Removing reference of already released: " + object + "!"); return false; } } private static EGLConfig chooseEglConfig(EGL10 egl, EGLDisplay display) { if (mEglConfig == null || !display.equals(mConfiguredDisplay)) { int[] configsCount = new int[1]; EGLConfig[] configs = new EGLConfig[1]; int[] configSpec = getDesiredConfig(); if (!egl.eglChooseConfig(display, configSpec, configs, 1, configsCount)) { throw new IllegalArgumentException("EGL Error: eglChooseConfig failed " + getEGLErrorString(egl, egl.eglGetError())); } else if (configsCount[0] > 0) { mEglConfig = configs[0]; mConfiguredDisplay = display; } } return mEglConfig; } private static int[] getDesiredConfig() { return new int[] { EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL10.EGL_RED_SIZE, sRedSize, EGL10.EGL_GREEN_SIZE, sGreenSize, EGL10.EGL_BLUE_SIZE, sBlueSize, EGL10.EGL_ALPHA_SIZE, sAlphaSize, EGL10.EGL_DEPTH_SIZE, sDepthSize, EGL10.EGL_STENCIL_SIZE, sStencilSize, EGL10.EGL_NONE }; } private RenderTarget(EGLDisplay display, EGLContext context, EGLSurface surface, int fbo, boolean ownsContext, boolean ownsSurface) { mEgl = (EGL10) EGLContext.getEGL(); mDisplay = display; mContext = context; mSurface = surface; mFbo = fbo; mOwnsContext = ownsContext; mOwnsSurface = ownsSurface; } private EGLSurface surface() { if (mSupportsMultipleDisplaySurfaces) { return mSurface; } else { EGLSurface displaySurface = mDisplaySurfaces.get(mContext); return displaySurface != null ? displaySurface : mSurface; } } private static void initEgl(EGL10 egl, EGLDisplay display) { int[] version = new int[2]; if (!egl.eglInitialize(display, version)) { throw new RuntimeException("EGL Error: eglInitialize failed " + getEGLErrorString(egl, egl.eglGetError())); } } private static EGLDisplay createDefaultDisplay(EGL10 egl) { EGLDisplay display = egl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); checkDisplay(egl, display); initEgl(egl, display); return display; } private static EGLContext createContext(EGL10 egl, EGLDisplay display, EGLConfig config) { int[] attrib_list = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE }; EGLContext ctxt = egl.eglCreateContext(display, config, EGL10.EGL_NO_CONTEXT, attrib_list); checkContext(egl, ctxt); return ctxt; } private static EGLSurface createSurface(EGL10 egl, EGLDisplay display, int width, int height) { EGLConfig eglConfig = chooseEglConfig(egl, display); int[] attribs = { EGL10.EGL_WIDTH, width, EGL10.EGL_HEIGHT, height, EGL10.EGL_NONE }; return egl.eglCreatePbufferSurface(display, eglConfig, attribs); } private static int getCurrentFbo() { int[] result = new int[1]; GLES20.glGetIntegerv(GLES20.GL_FRAMEBUFFER_BINDING, result, 0); return result[0]; } private static void checkDisplay(EGL10 egl, EGLDisplay display) { if (display == EGL10.EGL_NO_DISPLAY) { throw new RuntimeException("EGL Error: Bad display: " + getEGLErrorString(egl, egl.eglGetError())); } } private static void checkContext(EGL10 egl, EGLContext context) { if (context == EGL10.EGL_NO_CONTEXT) { throw new RuntimeException("EGL Error: Bad context: " + getEGLErrorString(egl, egl.eglGetError())); } } private static void checkSurface(EGL10 egl, EGLSurface surface) { if (surface == EGL10.EGL_NO_SURFACE) { throw new RuntimeException("EGL Error: Bad surface: " + getEGLErrorString(egl, egl.eglGetError())); } } private static void checkEglError(EGL10 egl, String command) { int error = egl.eglGetError(); if (error != EGL10.EGL_SUCCESS) { throw new RuntimeException("Error executing " + command + "! EGL error = 0x" + Integer.toHexString(error)); } } private static String getEGLErrorString(EGL10 egl, int eglError) { if (VERSION.SDK_INT >= 14) { return getEGLErrorStringICS(egl, eglError); } else { return "EGL Error 0x" + Integer.toHexString(eglError); } } @TargetApi(14) private static String getEGLErrorStringICS(EGL10 egl, int eglError) { return GLUtils.getEGLErrorString(egl.eglGetError()); } }