package com.chillingvan.canvasgl; import android.annotation.TargetApi; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.Rect; import android.opengl.EGL14; import android.opengl.GLES20; import android.os.Build; import android.os.Handler; import com.chillingvan.canvasgl.glview.GLView; import com.chillingvan.canvasgl.glview.texture.GLMultiTexProducerView; import com.chillingvan.canvasgl.glview.texture.GLTexture; import com.chillingvan.canvasgl.glview.texture.GLViewRenderer; import com.chillingvan.canvasgl.glview.texture.gles.EglContextWrapper; import com.chillingvan.canvasgl.glview.texture.gles.GLThread; import com.chillingvan.canvasgl.util.Loggers; import java.util.ArrayList; import java.util.List; import javax.microedition.khronos.egl.EGL10; import javax.microedition.khronos.egl.EGLConfig; import javax.microedition.khronos.egl.EGLDisplay; import javax.microedition.khronos.egl.EGLSurface; /** * OffScreenCanvas that support providing multiple textures to Camera or Media. * This can also consume textures from other GL zone( Should be in same GL context) */ public abstract class MultiTexOffScreenCanvas implements GLViewRenderer { private List<GLTexture> producedTextureList = new ArrayList<>(); protected List<GLTexture> consumedTextures = new ArrayList<>(); protected final GLThread mGLThread; protected int width; protected int height; protected ICanvasGL mCanvas; private GLMultiTexProducerView.SurfaceTextureCreatedListener surfaceTextureCreatedListener; private Handler handler; private boolean isStart; private int producedTextureTarget = GLES20.GL_TEXTURE_2D; private int backgroundColor = Color.TRANSPARENT; public MultiTexOffScreenCanvas() { this(0, 0, EglContextWrapper.EGL_NO_CONTEXT_WRAPPER); } public MultiTexOffScreenCanvas(int width, int height) { this(width, height, EglContextWrapper.EGL_NO_CONTEXT_WRAPPER); } public MultiTexOffScreenCanvas(Object surface) { this(0, 0, EglContextWrapper.EGL_NO_CONTEXT_WRAPPER, surface); } public MultiTexOffScreenCanvas(int width, int height, Object surface) { this(width, height, EglContextWrapper.EGL_NO_CONTEXT_WRAPPER, surface); } public MultiTexOffScreenCanvas(int width, int height, EglContextWrapper sharedEglContext, Object surface) { this.width = width; this.height = height; mGLThread = new GLThread.Builder().setRenderMode(getRenderMode()) .setSharedEglContext(sharedEglContext) .setSurface(surface) .setRenderer(this).createGLThread(); handler = new Handler(); } public MultiTexOffScreenCanvas(int width, int height, EglContextWrapper sharedEglContext) { this.width = width; this.height = height; mGLThread = new GLThread.Builder().setRenderMode(getRenderMode()) .setSharedEglContext(sharedEglContext) .setEglWindowSurfaceFactory(new SurfaceFactory()) .setRenderer(this).createGLThread(); handler = new Handler(); } /** * * @param glTexture texture from outSide. */ public void addConsumeGLTexture(GLTexture glTexture) { consumedTextures.add(glTexture); } /** * Create a new produced texture and upload it to the canvas. */ public GLTexture addProducedGLTexture(int width, int height, boolean opaque, int target) { GLTexture glTexture = GLTexture.createRaw(width, height, opaque, target, mCanvas); producedTextureList.add(glTexture); return glTexture; } /** * If it is used, it must be called before start() called. * @param producedTextureTarget GLES20.GL_TEXTURE_2D or GLES11Ext.GL_TEXTURE_EXTERNAL_OES */ public void setProducedTextureTarget(int producedTextureTarget) { this.producedTextureTarget = producedTextureTarget; } /** * If it is used, it must be called before start() called. */ public void setOnCreateGLContextListener(GLThread.OnCreateGLContextListener onCreateGLContextListener) { mGLThread.setOnCreateGLContextListener(onCreateGLContextListener); } /** */ public void setSurfaceTextureCreatedListener(GLMultiTexProducerView.SurfaceTextureCreatedListener surfaceTextureCreatedListener) { this.surfaceTextureCreatedListener = surfaceTextureCreatedListener; } public void setSize(int width, int height) { this.width = width; this.height = height; if (isStart) { mGLThread.onWindowResize(width, height); } } public void setBackgroundColor(int backgroundColor) { this.backgroundColor = backgroundColor; } public void start() { mGLThread.start(); mGLThread.surfaceCreated(); mGLThread.onWindowResize(width, height); isStart = true; } public void onResume() { if(mGLThread != null) { mGLThread.onResume(); } } public void onPause() { if(mGLThread != null) { mGLThread.onPause(); } recycleProduceTexture(); } public void end() { if (mGLThread != null) { mGLThread.requestExitAndWait(); } recycleProduceTexture(); } private void recycleProduceTexture() { for (GLTexture glTexture : producedTextureList) { if (!glTexture.getRawTexture().isRecycled()) { glTexture.getRawTexture().recycle(); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (!glTexture.getSurfaceTexture().isReleased()) { glTexture.getSurfaceTexture().release(); } } else { glTexture.getSurfaceTexture().release(); } } producedTextureList.clear(); } @Override protected void finalize() throws Throwable { try { end(); } finally { super.finalize(); } } private class SurfaceFactory implements GLThread.EGLWindowSurfaceFactory { @Override public EGLSurface createWindowSurface(EGL10 egl, EGLDisplay display, EGLConfig config, Object nativeWindow) { int[] attribList = new int[]{ EGL10.EGL_WIDTH, width, EGL10.EGL_HEIGHT, height, EGL10.EGL_NONE }; return egl.eglCreatePbufferSurface(display, config, attribList); } @Override public void destroySurface(EGL10 egl, EGLDisplay display, EGLSurface surface) { egl.eglDestroySurface(display, surface); } @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) @Override public android.opengl.EGLSurface createWindowSurface(android.opengl.EGLDisplay display, android.opengl.EGLConfig config, Object nativeWindow) { int[] attribList = new int[]{ EGL14.EGL_WIDTH, width, EGL14.EGL_HEIGHT, height, EGL14.EGL_NONE }; return EGL14.eglCreatePbufferSurface(display, config, attribList, 0); } @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) @Override public void destroySurface(android.opengl.EGLDisplay display, android.opengl.EGLSurface surface) { EGL14.eglDestroySurface(display, surface); } } /** * * @return The initial produced texture count */ protected int getInitialTexCount() { return 1; } @Override public void onSurfaceCreated() { Loggers.d("OffScreenCanvas", "onSurfaceCreated: "); mCanvas = new CanvasGL(); } @Override public void onSurfaceChanged(int width, int height) { Loggers.d("OffScreenCanvas", "onSurfaceChanged: "); mCanvas.setSize(width, height); if (producedTextureList.isEmpty()) { for (int i = 0; i < getInitialTexCount(); i++) { producedTextureList.add(GLTexture.createRaw(width, height, false, producedTextureTarget, mCanvas)); } handler.post(new Runnable() { @Override public void run() { if (surfaceTextureCreatedListener != null) { surfaceTextureCreatedListener.onCreated(producedTextureList); } } }); } else { for (GLTexture glTexture : producedTextureList) { glTexture.getRawTexture().setSize(width, height); } } } @Override public void onDrawFrame() { mCanvas.clearBuffer(backgroundColor); if (producedTextureTarget != GLES20.GL_TEXTURE_2D) { for (GLTexture glTexture : producedTextureList) { glTexture.getSurfaceTexture().updateTexImage(); glTexture.getRawTexture().setNeedInvalidate(true); } } onGLDraw(mCanvas, producedTextureList, consumedTextures); } protected int getRenderMode() { return GLThread.RENDERMODE_WHEN_DIRTY; } protected abstract void onGLDraw(ICanvasGL canvas, List<GLTexture> producedTextures, List<GLTexture> consumedTextures); public void queueEvent(Runnable r) { if (mGLThread == null) { return; } mGLThread.queueEvent(r); } public void requestRender() { if (mGLThread != null) { mGLThread.requestRender(); } } public void requestRenderAndWait() { if (mGLThread != null) { mGLThread.requestRenderAndWait(); } } public void getDrawingBitmap(final Rect rect, final GLView.GetDrawingCacheCallback getDrawingCacheCallback) { final Handler handler = new Handler(); queueEvent(new Runnable() { @Override public void run() { onDrawFrame(); onDrawFrame(); final Bitmap bitmapFromGLSurface = OpenGLUtil.createBitmapFromGLSurface(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, height); handler.post(new Runnable() { @Override public void run() { getDrawingCacheCallback.onFetch(bitmapFromGLSurface); } }); } }); requestRender(); } }