blob: ab0546dbbf0891bf5cd250dbd7c4f316b36972e3 [file] [log] [blame]
/*
* 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<Object, EGLSurface> mSurfaceSources = new HashMap<Object, EGLSurface>();
/** A Map for performing reference counting over shared objects across RenderTargets */
private static HashMap<Object, Integer> mRefCounts = new HashMap<Object, Integer>();
/** Stores the RenderTarget that is focused on the current thread. */
private static ThreadLocal<RenderTarget> mCurrentTarget = new ThreadLocal<RenderTarget>();
/** 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<EGLContext, ImageShader> mIdShaders
= new HashMap<EGLContext, ImageShader>();
private static HashMap<EGLContext, EGLSurface> mDisplaySurfaces
= new HashMap<EGLContext, EGLSurface>();
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());
}
}