GL cleanup (#766)
* Egloo: create GlTextureDrawer * Remove internal/utils * Remove internal/utils (test) * Remove internal/utils (unit tests) * Remove EglCore usages * Remove EglSurface usages * Remove EglViewport usages * Fix BaseFilter bug * Add Texture.use * Fix MultiFilter bug * Use GlFramebuffer * Use GlBindable.bind() in MultiFilter * Use Egloo Jitpack (temp) * BaseFilter changes * Improve BaseFilter * Use v0.4.0 * Fix testpull/786/head
parent
7f23c309ab
commit
968d181af4
@ -1,9 +1,10 @@ |
|||||||
package com.otaliastudios.cameraview.internal.utils; |
package com.otaliastudios.cameraview.internal; |
||||||
|
|
||||||
|
|
||||||
import android.graphics.Rect; |
import android.graphics.Rect; |
||||||
|
|
||||||
import com.otaliastudios.cameraview.BaseTest; |
import com.otaliastudios.cameraview.BaseTest; |
||||||
|
import com.otaliastudios.cameraview.internal.CropHelper; |
||||||
import com.otaliastudios.cameraview.size.AspectRatio; |
import com.otaliastudios.cameraview.size.AspectRatio; |
||||||
import com.otaliastudios.cameraview.size.Size; |
import com.otaliastudios.cameraview.size.Size; |
||||||
|
|
@ -1,9 +1,10 @@ |
|||||||
package com.otaliastudios.cameraview.internal.utils; |
package com.otaliastudios.cameraview.internal; |
||||||
|
|
||||||
|
|
||||||
import com.google.android.gms.tasks.Task; |
import com.google.android.gms.tasks.Task; |
||||||
import com.google.android.gms.tasks.Tasks; |
import com.google.android.gms.tasks.Tasks; |
||||||
import com.otaliastudios.cameraview.BaseTest; |
import com.otaliastudios.cameraview.BaseTest; |
||||||
|
import com.otaliastudios.cameraview.internal.WorkerHandler; |
||||||
import com.otaliastudios.cameraview.tools.Op; |
import com.otaliastudios.cameraview.tools.Op; |
||||||
|
|
||||||
import androidx.annotation.NonNull; |
import androidx.annotation.NonNull; |
@ -1,4 +1,4 @@ |
|||||||
package com.otaliastudios.cameraview.internal.utils; |
package com.otaliastudios.cameraview.internal; |
||||||
|
|
||||||
import android.annotation.SuppressLint; |
import android.annotation.SuppressLint; |
||||||
import android.media.CamcorderProfile; |
import android.media.CamcorderProfile; |
@ -1,4 +1,4 @@ |
|||||||
package com.otaliastudios.cameraview.internal.utils; |
package com.otaliastudios.cameraview.internal; |
||||||
|
|
||||||
import android.graphics.Rect; |
import android.graphics.Rect; |
||||||
|
|
@ -1,4 +1,4 @@ |
|||||||
package com.otaliastudios.cameraview.internal.utils; |
package com.otaliastudios.cameraview.internal; |
||||||
|
|
||||||
import androidx.exifinterface.media.ExifInterface; |
import androidx.exifinterface.media.ExifInterface; |
||||||
|
|
@ -0,0 +1,97 @@ |
|||||||
|
package com.otaliastudios.cameraview.internal; |
||||||
|
|
||||||
|
|
||||||
|
import android.opengl.GLES11Ext; |
||||||
|
import android.opengl.GLES20; |
||||||
|
|
||||||
|
import androidx.annotation.NonNull; |
||||||
|
|
||||||
|
import com.otaliastudios.cameraview.CameraLogger; |
||||||
|
import com.otaliastudios.cameraview.filter.Filter; |
||||||
|
import com.otaliastudios.cameraview.filter.NoFilter; |
||||||
|
import com.otaliastudios.opengl.core.Egloo; |
||||||
|
import com.otaliastudios.opengl.program.GlProgram; |
||||||
|
import com.otaliastudios.opengl.texture.GlTexture; |
||||||
|
|
||||||
|
|
||||||
|
public class GlTextureDrawer { |
||||||
|
|
||||||
|
private final static String TAG = GlTextureDrawer.class.getSimpleName(); |
||||||
|
private final static CameraLogger LOG = CameraLogger.create(TAG); |
||||||
|
|
||||||
|
private final static int TEXTURE_TARGET = GLES11Ext.GL_TEXTURE_EXTERNAL_OES; |
||||||
|
private final static int TEXTURE_UNIT = GLES20.GL_TEXTURE0; |
||||||
|
|
||||||
|
private final GlTexture mTexture; |
||||||
|
private float[] mTextureTransform = Egloo.IDENTITY_MATRIX.clone(); |
||||||
|
|
||||||
|
@NonNull |
||||||
|
private Filter mFilter = new NoFilter(); |
||||||
|
private Filter mPendingFilter = null; |
||||||
|
private int mProgramHandle = -1; |
||||||
|
|
||||||
|
@SuppressWarnings("unused") |
||||||
|
public GlTextureDrawer() { |
||||||
|
this(new GlTexture(TEXTURE_UNIT, TEXTURE_TARGET)); |
||||||
|
} |
||||||
|
|
||||||
|
@SuppressWarnings("unused") |
||||||
|
public GlTextureDrawer(int textureId) { |
||||||
|
this(new GlTexture(TEXTURE_UNIT, TEXTURE_TARGET, textureId)); |
||||||
|
} |
||||||
|
|
||||||
|
@SuppressWarnings("WeakerAccess") |
||||||
|
public GlTextureDrawer(@NonNull GlTexture texture) { |
||||||
|
mTexture = texture; |
||||||
|
} |
||||||
|
|
||||||
|
public void setFilter(@NonNull Filter filter) { |
||||||
|
mPendingFilter = filter; |
||||||
|
} |
||||||
|
|
||||||
|
@NonNull |
||||||
|
public GlTexture getTexture() { |
||||||
|
return mTexture; |
||||||
|
} |
||||||
|
|
||||||
|
@NonNull |
||||||
|
public float[] getTextureTransform() { |
||||||
|
return mTextureTransform; |
||||||
|
} |
||||||
|
|
||||||
|
public void setTextureTransform(@NonNull float[] textureTransform) { |
||||||
|
mTextureTransform = textureTransform; |
||||||
|
} |
||||||
|
|
||||||
|
public void draw(final long timestampUs) { |
||||||
|
if (mPendingFilter != null) { |
||||||
|
release(); |
||||||
|
mFilter = mPendingFilter; |
||||||
|
mPendingFilter = null; |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
if (mProgramHandle == -1) { |
||||||
|
mProgramHandle = GlProgram.create( |
||||||
|
mFilter.getVertexShader(), |
||||||
|
mFilter.getFragmentShader()); |
||||||
|
mFilter.onCreate(mProgramHandle); |
||||||
|
Egloo.checkGlError("program creation"); |
||||||
|
} |
||||||
|
|
||||||
|
GLES20.glUseProgram(mProgramHandle); |
||||||
|
Egloo.checkGlError("glUseProgram(handle)"); |
||||||
|
mTexture.bind(); |
||||||
|
mFilter.draw(timestampUs, mTextureTransform); |
||||||
|
mTexture.unbind(); |
||||||
|
GLES20.glUseProgram(0); |
||||||
|
Egloo.checkGlError("glUseProgram(0)"); |
||||||
|
} |
||||||
|
|
||||||
|
public void release() { |
||||||
|
if (mProgramHandle == -1) return; |
||||||
|
mFilter.onDestroy(); |
||||||
|
GLES20.glDeleteProgram(mProgramHandle); |
||||||
|
mProgramHandle = -1; |
||||||
|
} |
||||||
|
} |
@ -1,96 +0,0 @@ |
|||||||
package com.otaliastudios.cameraview.internal; |
|
||||||
|
|
||||||
|
|
||||||
import android.opengl.GLES20; |
|
||||||
import android.opengl.Matrix; |
|
||||||
|
|
||||||
import androidx.annotation.NonNull; |
|
||||||
|
|
||||||
import com.otaliastudios.cameraview.CameraLogger; |
|
||||||
|
|
||||||
import java.nio.ByteBuffer; |
|
||||||
import java.nio.ByteOrder; |
|
||||||
import java.nio.FloatBuffer; |
|
||||||
|
|
||||||
public class GlUtils { |
|
||||||
|
|
||||||
private final static String TAG = GlUtils.class.getSimpleName(); |
|
||||||
private final static CameraLogger LOG = CameraLogger.create(TAG); |
|
||||||
|
|
||||||
// Identity matrix for general use.
|
|
||||||
public static final float[] IDENTITY_MATRIX = new float[16]; |
|
||||||
static { |
|
||||||
Matrix.setIdentityM(IDENTITY_MATRIX, 0); |
|
||||||
} |
|
||||||
|
|
||||||
public static void checkError(@NonNull String opName) { |
|
||||||
int error = GLES20.glGetError(); |
|
||||||
if (error != GLES20.GL_NO_ERROR) { |
|
||||||
String message = LOG.e("Error during", opName, "glError 0x", |
|
||||||
Integer.toHexString(error)); |
|
||||||
throw new RuntimeException(message); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public static void checkLocation(int location, @NonNull String name) { |
|
||||||
if (location < 0) { |
|
||||||
String message = LOG.e("Unable to locate", name, "in program"); |
|
||||||
throw new RuntimeException(message); |
|
||||||
} |
|
||||||
} |
|
||||||
// Compiles the given shader, returns a handle.
|
|
||||||
@SuppressWarnings("WeakerAccess") |
|
||||||
public static int loadShader(int shaderType, @NonNull String source) { |
|
||||||
int shader = GLES20.glCreateShader(shaderType); |
|
||||||
checkError("glCreateShader type=" + shaderType); |
|
||||||
GLES20.glShaderSource(shader, source); |
|
||||||
GLES20.glCompileShader(shader); |
|
||||||
int[] compiled = new int[1]; |
|
||||||
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0); |
|
||||||
if (compiled[0] == 0) { |
|
||||||
LOG.e("Could not compile shader", shaderType, ":", |
|
||||||
GLES20.glGetShaderInfoLog(shader)); |
|
||||||
GLES20.glDeleteShader(shader); |
|
||||||
shader = 0; |
|
||||||
} |
|
||||||
return shader; |
|
||||||
} |
|
||||||
|
|
||||||
// Creates a program with given vertex shader and pixel shader.
|
|
||||||
public static int createProgram(@NonNull String vertexSource, @NonNull String fragmentSource) { |
|
||||||
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource); |
|
||||||
if (vertexShader == 0) return 0; |
|
||||||
int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource); |
|
||||||
if (pixelShader == 0) return 0; |
|
||||||
|
|
||||||
int program = GLES20.glCreateProgram(); |
|
||||||
checkError("glCreateProgram"); |
|
||||||
if (program == 0) { |
|
||||||
LOG.e("Could not create program"); |
|
||||||
} |
|
||||||
GLES20.glAttachShader(program, vertexShader); |
|
||||||
checkError("glAttachShader"); |
|
||||||
GLES20.glAttachShader(program, pixelShader); |
|
||||||
checkError("glAttachShader"); |
|
||||||
GLES20.glLinkProgram(program); |
|
||||||
int[] linkStatus = new int[1]; |
|
||||||
GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0); |
|
||||||
if (linkStatus[0] != GLES20.GL_TRUE) { |
|
||||||
LOG.e("Could not link program:", GLES20.glGetProgramInfoLog(program)); |
|
||||||
GLES20.glDeleteProgram(program); |
|
||||||
program = 0; |
|
||||||
} |
|
||||||
return program; |
|
||||||
} |
|
||||||
|
|
||||||
// Allocates a direct float buffer, and populates it with the float array data.
|
|
||||||
public static FloatBuffer floatBuffer(@NonNull float[] coords) { |
|
||||||
// Allocate a direct ByteBuffer, using 4 bytes per float, and copy coords into it.
|
|
||||||
ByteBuffer bb = ByteBuffer.allocateDirect(coords.length * 4); |
|
||||||
bb.order(ByteOrder.nativeOrder()); |
|
||||||
FloatBuffer fb = bb.asFloatBuffer(); |
|
||||||
fb.put(coords); |
|
||||||
fb.position(0); |
|
||||||
return fb; |
|
||||||
} |
|
||||||
} |
|
@ -1,4 +1,4 @@ |
|||||||
package com.otaliastudios.cameraview.internal.utils; |
package com.otaliastudios.cameraview.internal; |
||||||
|
|
||||||
import android.content.Context; |
import android.content.Context; |
||||||
import android.hardware.SensorManager; |
import android.hardware.SensorManager; |
@ -1,4 +1,4 @@ |
|||||||
package com.otaliastudios.cameraview.internal.utils; |
package com.otaliastudios.cameraview.internal; |
||||||
|
|
||||||
import com.otaliastudios.cameraview.CameraLogger; |
import com.otaliastudios.cameraview.CameraLogger; |
||||||
|
|
@ -1,4 +1,4 @@ |
|||||||
package com.otaliastudios.cameraview.internal.utils; |
package com.otaliastudios.cameraview.internal; |
||||||
|
|
||||||
import com.otaliastudios.cameraview.size.Size; |
import com.otaliastudios.cameraview.size.Size; |
||||||
|
|
@ -1,4 +1,4 @@ |
|||||||
package com.otaliastudios.cameraview.internal.utils; |
package com.otaliastudios.cameraview.internal; |
||||||
|
|
||||||
import android.os.Handler; |
import android.os.Handler; |
||||||
import android.os.HandlerThread; |
import android.os.HandlerThread; |
@ -1,244 +0,0 @@ |
|||||||
/* |
|
||||||
* Copyright 2013 Google Inc. All rights reserved. |
|
||||||
* |
|
||||||
* 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 com.otaliastudios.cameraview.internal.egl; |
|
||||||
|
|
||||||
import android.graphics.Bitmap; |
|
||||||
import android.opengl.EGL14; |
|
||||||
import android.opengl.EGLSurface; |
|
||||||
import android.opengl.GLES20; |
|
||||||
import android.os.Build; |
|
||||||
import androidx.annotation.RequiresApi; |
|
||||||
import android.util.Log; |
|
||||||
|
|
||||||
import com.otaliastudios.cameraview.CameraLogger; |
|
||||||
import com.otaliastudios.cameraview.internal.GlUtils; |
|
||||||
|
|
||||||
import java.io.BufferedOutputStream; |
|
||||||
import java.io.ByteArrayOutputStream; |
|
||||||
import java.io.File; |
|
||||||
import java.io.FileOutputStream; |
|
||||||
import java.io.IOException; |
|
||||||
import java.nio.ByteBuffer; |
|
||||||
import java.nio.ByteOrder; |
|
||||||
|
|
||||||
/** |
|
||||||
* Common base class for EGL surfaces. |
|
||||||
* <p> |
|
||||||
* There can be multiple surfaces associated with a single context. |
|
||||||
*/ |
|
||||||
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) |
|
||||||
public class EglBaseSurface { |
|
||||||
protected static final String TAG = EglBaseSurface.class.getSimpleName(); |
|
||||||
private final static CameraLogger LOG = CameraLogger.create(TAG); |
|
||||||
|
|
||||||
// EglCore object we're associated with. It may be associated with multiple surfaces.
|
|
||||||
protected EglCore mEglCore; |
|
||||||
|
|
||||||
private EGLSurface mEGLSurface = EGL14.EGL_NO_SURFACE; |
|
||||||
private int mWidth = -1; |
|
||||||
private int mHeight = -1; |
|
||||||
|
|
||||||
public EglBaseSurface(EglCore eglCore) { |
|
||||||
mEglCore = eglCore; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Creates a window surface. |
|
||||||
* <p> |
|
||||||
* @param surface May be a Surface or SurfaceTexture. |
|
||||||
*/ |
|
||||||
public void createWindowSurface(Object surface) { |
|
||||||
if (mEGLSurface != EGL14.EGL_NO_SURFACE) { |
|
||||||
throw new IllegalStateException("surface already created"); |
|
||||||
} |
|
||||||
mEGLSurface = mEglCore.createWindowSurface(surface); |
|
||||||
|
|
||||||
// Don't cache width/height here, because the size of the underlying surface can change
|
|
||||||
// out from under us (see e.g. HardwareScalerActivity).
|
|
||||||
//mWidth = mEglCore.querySurface(mEGLSurface, EGL14.EGL_WIDTH);
|
|
||||||
//mHeight = mEglCore.querySurface(mEGLSurface, EGL14.EGL_HEIGHT);
|
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Creates an off-screen surface. |
|
||||||
*/ |
|
||||||
public void createOffscreenSurface(int width, int height) { |
|
||||||
if (mEGLSurface != EGL14.EGL_NO_SURFACE) { |
|
||||||
throw new IllegalStateException("surface already created"); |
|
||||||
} |
|
||||||
mEGLSurface = mEglCore.createOffscreenSurface(width, height); |
|
||||||
mWidth = width; |
|
||||||
mHeight = height; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Returns the surface's width, in pixels. |
|
||||||
* <p> |
|
||||||
* If this is called on a window surface, and the underlying surface is in the process |
|
||||||
* of changing size, we may not see the new size right away (e.g. in the "surfaceChanged" |
|
||||||
* callback). The size should match after the next buffer swap. |
|
||||||
*/ |
|
||||||
public int getWidth() { |
|
||||||
if (mWidth < 0) { |
|
||||||
return mEglCore.querySurface(mEGLSurface, EGL14.EGL_WIDTH); |
|
||||||
} else { |
|
||||||
return mWidth; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Returns the surface's height, in pixels. |
|
||||||
*/ |
|
||||||
public int getHeight() { |
|
||||||
if (mHeight < 0) { |
|
||||||
return mEglCore.querySurface(mEGLSurface, EGL14.EGL_HEIGHT); |
|
||||||
} else { |
|
||||||
return mHeight; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Release the EGL surface. |
|
||||||
*/ |
|
||||||
public void releaseEglSurface() { |
|
||||||
mEglCore.releaseSurface(mEGLSurface); |
|
||||||
mEGLSurface = EGL14.EGL_NO_SURFACE; |
|
||||||
mWidth = mHeight = -1; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Makes our EGL context and surface current. |
|
||||||
*/ |
|
||||||
public void makeCurrent() { |
|
||||||
mEglCore.makeCurrent(mEGLSurface); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Makes our EGL context and surface current for drawing, using the supplied surface |
|
||||||
* for reading. |
|
||||||
*/ |
|
||||||
public void makeCurrentReadFrom(EglBaseSurface readSurface) { |
|
||||||
mEglCore.makeCurrent(mEGLSurface, readSurface.mEGLSurface); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Calls eglSwapBuffers. Use this to "publish" the current frame. |
|
||||||
* |
|
||||||
* @return false on failure |
|
||||||
*/ |
|
||||||
public boolean swapBuffers() { |
|
||||||
boolean result = mEglCore.swapBuffers(mEGLSurface); |
|
||||||
if (!result) { |
|
||||||
Log.d(TAG, "WARNING: swapBuffers() failed"); |
|
||||||
} |
|
||||||
return result; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Sends the presentation time stamp to EGL. |
|
||||||
* https://www.khronos.org/registry/EGL/extensions/ANDROID/EGL_ANDROID_presentation_time.txt
|
|
||||||
* |
|
||||||
* @param nsecs Timestamp, in nanoseconds. |
|
||||||
*/ |
|
||||||
public void setPresentationTime(long nsecs) { |
|
||||||
mEglCore.setPresentationTime(mEGLSurface, nsecs); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Saves the EGL surface to a file. |
|
||||||
* <p> |
|
||||||
* Expects that this object's EGL surface is current. |
|
||||||
*/ |
|
||||||
public void saveFrameToFile(File file) throws IOException { |
|
||||||
if (!mEglCore.isCurrent(mEGLSurface)) { |
|
||||||
throw new RuntimeException("Expected EGL context/surface is not current"); |
|
||||||
} |
|
||||||
|
|
||||||
// glReadPixels fills in a "direct" ByteBuffer with what is essentially big-endian RGBA
|
|
||||||
// data (i.e. a byte of red, followed by a byte of green...). While the Bitmap
|
|
||||||
// constructor that takes an int[] wants little-endian ARGB (blue/red swapped), the
|
|
||||||
// Bitmap "copy pixels" method wants the same format GL provides.
|
|
||||||
//
|
|
||||||
// Ideally we'd have some way to re-use the ByteBuffer, especially if we're calling
|
|
||||||
// here often.
|
|
||||||
//
|
|
||||||
// Making this even more interesting is the upside-down nature of GL, which means
|
|
||||||
// our output will look upside down relative to what appears on screen if the
|
|
||||||
// typical GL conventions are used.
|
|
||||||
|
|
||||||
String filename = file.toString(); |
|
||||||
|
|
||||||
int width = getWidth(); |
|
||||||
int height = getHeight(); |
|
||||||
ByteBuffer buf = ByteBuffer.allocateDirect(width * height * 4); |
|
||||||
buf.order(ByteOrder.LITTLE_ENDIAN); |
|
||||||
GLES20.glReadPixels(0, 0, width, height, |
|
||||||
GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buf); |
|
||||||
GlUtils.checkError("glReadPixels"); |
|
||||||
buf.rewind(); |
|
||||||
|
|
||||||
BufferedOutputStream bos = null; |
|
||||||
try { |
|
||||||
bos = new BufferedOutputStream(new FileOutputStream(filename)); |
|
||||||
Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); |
|
||||||
bmp.copyPixelsFromBuffer(buf); |
|
||||||
bmp.compress(Bitmap.CompressFormat.PNG, 90, bos); |
|
||||||
bmp.recycle(); |
|
||||||
} finally { |
|
||||||
if (bos != null) bos.close(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Saves the EGL surface to given format. |
|
||||||
* <p> |
|
||||||
* Expects that this object's EGL surface is current. |
|
||||||
*/ |
|
||||||
public byte[] saveFrameTo(Bitmap.CompressFormat compressFormat) { |
|
||||||
if (!mEglCore.isCurrent(mEGLSurface)) { |
|
||||||
throw new RuntimeException("Expected EGL context/surface is not current"); |
|
||||||
} |
|
||||||
|
|
||||||
// glReadPixels fills in a "direct" ByteBuffer with what is essentially big-endian RGBA
|
|
||||||
// data (i.e. a byte of red, followed by a byte of green...). While the Bitmap
|
|
||||||
// constructor that takes an int[] wants little-endian ARGB (blue/red swapped), the
|
|
||||||
// Bitmap "copy pixels" method wants the same format GL provides.
|
|
||||||
//
|
|
||||||
// Ideally we'd have some way to re-use the ByteBuffer, especially if we're calling
|
|
||||||
// here often.
|
|
||||||
//
|
|
||||||
// Making this even more interesting is the upside-down nature of GL, which means
|
|
||||||
// our output will look upside down relative to what appears on screen if the
|
|
||||||
// typical GL conventions are used.
|
|
||||||
|
|
||||||
int width = getWidth(); |
|
||||||
int height = getHeight(); |
|
||||||
ByteBuffer buf = ByteBuffer.allocateDirect(width * height * 4); |
|
||||||
buf.order(ByteOrder.LITTLE_ENDIAN); |
|
||||||
GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, |
|
||||||
buf); |
|
||||||
GlUtils.checkError("glReadPixels"); |
|
||||||
buf.rewind(); |
|
||||||
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream(buf.array().length); |
|
||||||
Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); |
|
||||||
bmp.copyPixelsFromBuffer(buf); |
|
||||||
bmp.compress(compressFormat, 90, bos); |
|
||||||
bmp.recycle(); |
|
||||||
return bos.toByteArray(); |
|
||||||
} |
|
||||||
} |
|
@ -1,364 +0,0 @@ |
|||||||
/* |
|
||||||
* Copyright 2013 Google Inc. All rights reserved. |
|
||||||
* |
|
||||||
* 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 com.otaliastudios.cameraview.internal.egl; |
|
||||||
|
|
||||||
import android.graphics.SurfaceTexture; |
|
||||||
import android.opengl.EGL14; |
|
||||||
import android.opengl.EGLConfig; |
|
||||||
import android.opengl.EGLContext; |
|
||||||
import android.opengl.EGLDisplay; |
|
||||||
import android.opengl.EGLExt; |
|
||||||
import android.opengl.EGLSurface; |
|
||||||
import android.os.Build; |
|
||||||
import androidx.annotation.RequiresApi; |
|
||||||
import android.util.Log; |
|
||||||
import android.view.Surface; |
|
||||||
|
|
||||||
/** |
|
||||||
* -- from grafika -- |
|
||||||
* |
|
||||||
* Core EGL state (display, context, config). |
|
||||||
* <p> |
|
||||||
* The EGLContext must only be attached to one thread at a time. This class is not thread-safe. |
|
||||||
*/ |
|
||||||
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) |
|
||||||
public final class EglCore { |
|
||||||
private static final String TAG = EglCore.class.getSimpleName(); |
|
||||||
|
|
||||||
/** |
|
||||||
* Constructor flag: surface must be recordable. This discourages EGL from using a |
|
||||||
* pixel format that cannot be converted efficiently to something usable by the video |
|
||||||
* encoder. |
|
||||||
*/ |
|
||||||
public static final int FLAG_RECORDABLE = 0x01; |
|
||||||
|
|
||||||
/** |
|
||||||
* Constructor flag: ask for GLES3, fall back to GLES2 if not available. Without this |
|
||||||
* flag, GLES2 is used. |
|
||||||
*/ |
|
||||||
public static final int FLAG_TRY_GLES3 = 0x02; |
|
||||||
|
|
||||||
// Android-specific extension.
|
|
||||||
private static final int EGL_RECORDABLE_ANDROID = 0x3142; |
|
||||||
|
|
||||||
private EGLDisplay mEGLDisplay = EGL14.EGL_NO_DISPLAY; |
|
||||||
private EGLContext mEGLContext = EGL14.EGL_NO_CONTEXT; |
|
||||||
private EGLConfig mEGLConfig = null; |
|
||||||
private int mGlVersion = -1; |
|
||||||
|
|
||||||
|
|
||||||
/** |
|
||||||
* Prepares EGL display and context. |
|
||||||
* <p> |
|
||||||
* Equivalent to EglCore(null, 0). |
|
||||||
*/ |
|
||||||
public EglCore() { |
|
||||||
this(null, 0); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Prepares EGL display and context. |
|
||||||
* <p> |
|
||||||
* @param sharedContext The context to share, or null if sharing is not desired. |
|
||||||
* @param flags Configuration bit flags, e.g. FLAG_RECORDABLE. |
|
||||||
*/ |
|
||||||
public EglCore(EGLContext sharedContext, int flags) { |
|
||||||
if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) { |
|
||||||
throw new RuntimeException("EGL already set up"); |
|
||||||
} |
|
||||||
|
|
||||||
if (sharedContext == null) { |
|
||||||
sharedContext = EGL14.EGL_NO_CONTEXT; |
|
||||||
} |
|
||||||
|
|
||||||
mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); |
|
||||||
if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) { |
|
||||||
throw new RuntimeException("unable to get EGL14 display"); |
|
||||||
} |
|
||||||
int[] version = new int[2]; |
|
||||||
if (!EGL14.eglInitialize(mEGLDisplay, version, 0, version, 1)) { |
|
||||||
mEGLDisplay = null; |
|
||||||
throw new RuntimeException("unable to initialize EGL14"); |
|
||||||
} |
|
||||||
|
|
||||||
// Try to get a GLES3 context, if requested.
|
|
||||||
if ((flags & FLAG_TRY_GLES3) != 0) { |
|
||||||
//Log.d(TAG, "Trying GLES 3");
|
|
||||||
EGLConfig config = getConfig(flags, 3); |
|
||||||
if (config != null) { |
|
||||||
int[] attrib3_list = { |
|
||||||
EGL14.EGL_CONTEXT_CLIENT_VERSION, 3, |
|
||||||
EGL14.EGL_NONE |
|
||||||
}; |
|
||||||
EGLContext context = EGL14.eglCreateContext(mEGLDisplay, config, sharedContext, |
|
||||||
attrib3_list, 0); |
|
||||||
|
|
||||||
if (EGL14.eglGetError() == EGL14.EGL_SUCCESS) { |
|
||||||
//Log.d(TAG, "Got GLES 3 config");
|
|
||||||
mEGLConfig = config; |
|
||||||
mEGLContext = context; |
|
||||||
mGlVersion = 3; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
if (mEGLContext == EGL14.EGL_NO_CONTEXT) { // GLES 2 only, or GLES 3 attempt failed
|
|
||||||
//Log.d(TAG, "Trying GLES 2");
|
|
||||||
EGLConfig config = getConfig(flags, 2); |
|
||||||
if (config == null) { |
|
||||||
throw new RuntimeException("Unable to find a suitable EGLConfig"); |
|
||||||
} |
|
||||||
int[] attrib2_list = { |
|
||||||
EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, |
|
||||||
EGL14.EGL_NONE |
|
||||||
}; |
|
||||||
EGLContext context = EGL14.eglCreateContext(mEGLDisplay, config, sharedContext, |
|
||||||
attrib2_list, 0); |
|
||||||
checkEglError("eglCreateContext"); |
|
||||||
mEGLConfig = config; |
|
||||||
mEGLContext = context; |
|
||||||
mGlVersion = 2; |
|
||||||
} |
|
||||||
|
|
||||||
// Confirm with query.
|
|
||||||
int[] values = new int[1]; |
|
||||||
EGL14.eglQueryContext(mEGLDisplay, mEGLContext, EGL14.EGL_CONTEXT_CLIENT_VERSION, |
|
||||||
values, 0); |
|
||||||
// Log.d(TAG, "EGLContext created, client version " + values[0]);
|
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Finds a suitable EGLConfig. |
|
||||||
* |
|
||||||
* @param flags Bit flags from constructor. |
|
||||||
* @param version Must be 2 or 3. |
|
||||||
*/ |
|
||||||
private EGLConfig getConfig(int flags, int version) { |
|
||||||
int renderableType = EGL14.EGL_OPENGL_ES2_BIT; |
|
||||||
if (version >= 3) { |
|
||||||
renderableType |= EGLExt.EGL_OPENGL_ES3_BIT_KHR; |
|
||||||
} |
|
||||||
|
|
||||||
// The actual surface is generally RGBA or RGBX, so situationally omitting alpha
|
|
||||||
// doesn't really help. It can also lead to a huge performance hit on glReadPixels()
|
|
||||||
// when reading into a GL_RGBA buffer.
|
|
||||||
int[] attribList = { |
|
||||||
EGL14.EGL_RED_SIZE, 8, |
|
||||||
EGL14.EGL_GREEN_SIZE, 8, |
|
||||||
EGL14.EGL_BLUE_SIZE, 8, |
|
||||||
EGL14.EGL_ALPHA_SIZE, 8, |
|
||||||
//EGL14.EGL_DEPTH_SIZE, 16,
|
|
||||||
//EGL14.EGL_STENCIL_SIZE, 8,
|
|
||||||
EGL14.EGL_RENDERABLE_TYPE, renderableType, |
|
||||||
EGL14.EGL_NONE, 0, // placeholder for recordable [@-3]
|
|
||||||
EGL14.EGL_NONE |
|
||||||
}; |
|
||||||
if ((flags & FLAG_RECORDABLE) != 0) { |
|
||||||
attribList[attribList.length - 3] = EGL_RECORDABLE_ANDROID; |
|
||||||
attribList[attribList.length - 2] = 1; |
|
||||||
} |
|
||||||
EGLConfig[] configs = new EGLConfig[1]; |
|
||||||
int[] numConfigs = new int[1]; |
|
||||||
if (!EGL14.eglChooseConfig(mEGLDisplay, attribList, 0, configs, 0, |
|
||||||
configs.length, numConfigs, 0)) { |
|
||||||
Log.w(TAG, "unable to find RGB8888 / " + version + " EGLConfig"); |
|
||||||
return null; |
|
||||||
} |
|
||||||
return configs[0]; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Discards all resources held by this class, notably the EGL context. This must be |
|
||||||
* called from the thread where the context was created. |
|
||||||
* <p> |
|
||||||
* On completion, no context will be current. |
|
||||||
*/ |
|
||||||
public void release() { |
|
||||||
if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) { |
|
||||||
// Android is unusual in that it uses a reference-counted EGLDisplay. So for
|
|
||||||
// every eglInitialize() we need an eglTerminate().
|
|
||||||
EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, |
|
||||||
EGL14.EGL_NO_CONTEXT); |
|
||||||
EGL14.eglDestroyContext(mEGLDisplay, mEGLContext); |
|
||||||
EGL14.eglReleaseThread(); |
|
||||||
EGL14.eglTerminate(mEGLDisplay); |
|
||||||
} |
|
||||||
|
|
||||||
mEGLDisplay = EGL14.EGL_NO_DISPLAY; |
|
||||||
mEGLContext = EGL14.EGL_NO_CONTEXT; |
|
||||||
mEGLConfig = null; |
|
||||||
} |
|
||||||
|
|
||||||
@Override |
|
||||||
protected void finalize() throws Throwable { |
|
||||||
try { |
|
||||||
if (mEGLDisplay != EGL14.EGL_NO_DISPLAY) { |
|
||||||
// We're limited here -- finalizers don't run on the thread that holds
|
|
||||||
// the EGL state, so if a surface or context is still current on another
|
|
||||||
// thread we can't fully release it here. Exceptions thrown from here
|
|
||||||
// are quietly discarded. Complain in the log file.
|
|
||||||
Log.w(TAG, "WARNING: EglCore was not explicitly released! " + |
|
||||||
"State may be leaked"); |
|
||||||
release(); |
|
||||||
} |
|
||||||
} finally { |
|
||||||
super.finalize(); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Destroys the specified surface. Note the EGLSurface won't actually be destroyed if it's |
|
||||||
* still current in a context. |
|
||||||
*/ |
|
||||||
public void releaseSurface(EGLSurface eglSurface) { |
|
||||||
EGL14.eglDestroySurface(mEGLDisplay, eglSurface); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Creates an EGL surface associated with a Surface. |
|
||||||
* <p> |
|
||||||
* If this is destined for MediaCodec, the EGLConfig should have the "recordable" attribute. |
|
||||||
*/ |
|
||||||
public EGLSurface createWindowSurface(Object surface) { |
|
||||||
if (!(surface instanceof Surface) && !(surface instanceof SurfaceTexture)) { |
|
||||||
throw new RuntimeException("invalid surface: " + surface); |
|
||||||
} |
|
||||||
|
|
||||||
// Create a window surface, and attach it to the Surface we received.
|
|
||||||
int[] surfaceAttribs = { |
|
||||||
EGL14.EGL_NONE |
|
||||||
}; |
|
||||||
EGLSurface eglSurface = EGL14.eglCreateWindowSurface(mEGLDisplay, mEGLConfig, surface, |
|
||||||
surfaceAttribs, 0); |
|
||||||
checkEglError("eglCreateWindowSurface"); |
|
||||||
if (eglSurface == null) { |
|
||||||
throw new RuntimeException("surface was null"); |
|
||||||
} |
|
||||||
return eglSurface; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Creates an EGL surface associated with an offscreen buffer. |
|
||||||
*/ |
|
||||||
public EGLSurface createOffscreenSurface(int width, int height) { |
|
||||||
int[] surfaceAttribs = { |
|
||||||
EGL14.EGL_WIDTH, width, |
|
||||||
EGL14.EGL_HEIGHT, height, |
|
||||||
EGL14.EGL_NONE |
|
||||||
}; |
|
||||||
EGLSurface eglSurface = EGL14.eglCreatePbufferSurface(mEGLDisplay, mEGLConfig, |
|
||||||
surfaceAttribs, 0); |
|
||||||
checkEglError("eglCreatePbufferSurface"); |
|
||||||
if (eglSurface == null) { |
|
||||||
throw new RuntimeException("surface was null"); |
|
||||||
} |
|
||||||
return eglSurface; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Makes our EGL context current, using the supplied surface for both "draw" and "read". |
|
||||||
*/ |
|
||||||
public void makeCurrent(EGLSurface eglSurface) { |
|
||||||
if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) { |
|
||||||
// called makeCurrent() before create?
|
|
||||||
// Log.d(TAG, "NOTE: makeCurrent w/o display");
|
|
||||||
} |
|
||||||
if (!EGL14.eglMakeCurrent(mEGLDisplay, eglSurface, eglSurface, mEGLContext)) { |
|
||||||
throw new RuntimeException("eglMakeCurrent failed"); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Makes our EGL context current, using the supplied "draw" and "read" surfaces. |
|
||||||
*/ |
|
||||||
public void makeCurrent(EGLSurface drawSurface, EGLSurface readSurface) { |
|
||||||
if (mEGLDisplay == EGL14.EGL_NO_DISPLAY) { |
|
||||||
// called makeCurrent() before create?
|
|
||||||
Log.d(TAG, "NOTE: makeCurrent w/o display"); |
|
||||||
} |
|
||||||
if (!EGL14.eglMakeCurrent(mEGLDisplay, drawSurface, readSurface, mEGLContext)) { |
|
||||||
throw new RuntimeException("eglMakeCurrent(draw,read) failed"); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Makes no context current. |
|
||||||
*/ |
|
||||||
public void makeNothingCurrent() { |
|
||||||
if (!EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, |
|
||||||
EGL14.EGL_NO_CONTEXT)) { |
|
||||||
throw new RuntimeException("eglMakeCurrent failed"); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Calls eglSwapBuffers. Use this to "publish" the current frame. |
|
||||||
* |
|
||||||
* @return false on failure |
|
||||||
*/ |
|
||||||
public boolean swapBuffers(EGLSurface eglSurface) { |
|
||||||
return EGL14.eglSwapBuffers(mEGLDisplay, eglSurface); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Sends the presentation time stamp to EGL. Time is expressed in nanoseconds. |
|
||||||
* https://www.khronos.org/registry/EGL/extensions/ANDROID/EGL_ANDROID_presentation_time.txt
|
|
||||||
*/ |
|
||||||
public void setPresentationTime(EGLSurface eglSurface, long nsecs) { |
|
||||||
EGLExt.eglPresentationTimeANDROID(mEGLDisplay, eglSurface, nsecs); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Returns true if our context and the specified surface are current. |
|
||||||
*/ |
|
||||||
public boolean isCurrent(EGLSurface eglSurface) { |
|
||||||
return mEGLContext.equals(EGL14.eglGetCurrentContext()) && |
|
||||||
eglSurface.equals(EGL14.eglGetCurrentSurface(EGL14.EGL_DRAW)); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Performs a simple surface query. |
|
||||||
*/ |
|
||||||
public int querySurface(EGLSurface eglSurface, int what) { |
|
||||||
int[] value = new int[1]; |
|
||||||
EGL14.eglQuerySurface(mEGLDisplay, eglSurface, what, value, 0); |
|
||||||
return value[0]; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Queries a string value. |
|
||||||
*/ |
|
||||||
public String queryString(int what) { |
|
||||||
return EGL14.eglQueryString(mEGLDisplay, what); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Returns the GLES version this context is configured for (currently 2 or 3). |
|
||||||
*/ |
|
||||||
public int getGlVersion() { |
|
||||||
return mGlVersion; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Checks for EGL errors. Throws an exception if an error has been raised. |
|
||||||
*/ |
|
||||||
private void checkEglError(String msg) { |
|
||||||
int error; |
|
||||||
if ((error = EGL14.eglGetError()) != EGL14.EGL_SUCCESS) { |
|
||||||
throw new RuntimeException(msg + ": EGL error: 0x" + Integer.toHexString(error)); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,103 +0,0 @@ |
|||||||
package com.otaliastudios.cameraview.internal.egl; |
|
||||||
|
|
||||||
|
|
||||||
import android.opengl.GLES11Ext; |
|
||||||
import android.opengl.GLES20; |
|
||||||
|
|
||||||
import androidx.annotation.NonNull; |
|
||||||
|
|
||||||
import com.otaliastudios.cameraview.CameraLogger; |
|
||||||
import com.otaliastudios.cameraview.filter.Filter; |
|
||||||
import com.otaliastudios.cameraview.filter.NoFilter; |
|
||||||
import com.otaliastudios.cameraview.internal.GlUtils; |
|
||||||
|
|
||||||
|
|
||||||
public class EglViewport { |
|
||||||
|
|
||||||
private final static CameraLogger LOG = CameraLogger.create(EglViewport.class.getSimpleName()); |
|
||||||
|
|
||||||
private int mProgramHandle = -1; |
|
||||||
private int mTextureTarget; |
|
||||||
private int mTextureUnit; |
|
||||||
|
|
||||||
private Filter mFilter; |
|
||||||
private Filter mPendingFilter; |
|
||||||
|
|
||||||
public EglViewport() { |
|
||||||
this(new NoFilter()); |
|
||||||
} |
|
||||||
|
|
||||||
public EglViewport(@NonNull Filter filter) { |
|
||||||
mTextureTarget = GLES11Ext.GL_TEXTURE_EXTERNAL_OES; |
|
||||||
mTextureUnit = GLES20.GL_TEXTURE0; |
|
||||||
mFilter = filter; |
|
||||||
createProgram(); |
|
||||||
} |
|
||||||
|
|
||||||
private void createProgram() { |
|
||||||
mProgramHandle = GlUtils.createProgram(mFilter.getVertexShader(), |
|
||||||
mFilter.getFragmentShader()); |
|
||||||
mFilter.onCreate(mProgramHandle); |
|
||||||
} |
|
||||||
|
|
||||||
public void release() { |
|
||||||
if (mProgramHandle != -1) { |
|
||||||
mFilter.onDestroy(); |
|
||||||
GLES20.glDeleteProgram(mProgramHandle); |
|
||||||
mProgramHandle = -1; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
public int createTexture() { |
|
||||||
int[] textures = new int[1]; |
|
||||||
GLES20.glGenTextures(1, textures, 0); |
|
||||||
GlUtils.checkError("glGenTextures"); |
|
||||||
|
|
||||||
int texId = textures[0]; |
|
||||||
GLES20.glActiveTexture(mTextureUnit); |
|
||||||
GLES20.glBindTexture(mTextureTarget, texId); |
|
||||||
GlUtils.checkError("glBindTexture " + texId); |
|
||||||
|
|
||||||
GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, |
|
||||||
GLES20.GL_NEAREST); |
|
||||||
GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, |
|
||||||
GLES20.GL_LINEAR); |
|
||||||
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S, |
|
||||||
GLES20.GL_CLAMP_TO_EDGE); |
|
||||||
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T, |
|
||||||
GLES20.GL_CLAMP_TO_EDGE); |
|
||||||
GlUtils.checkError("glTexParameter"); |
|
||||||
|
|
||||||
return texId; |
|
||||||
} |
|
||||||
|
|
||||||
public void setFilter(@NonNull Filter filter) { |
|
||||||
// TODO see if this is needed. If setFilter is always called from the correct GL thread,
|
|
||||||
// we don't need to wait for a new draw call (which might not even happen).
|
|
||||||
mPendingFilter = filter; |
|
||||||
} |
|
||||||
|
|
||||||
public void drawFrame(long timestampUs, int textureId, float[] textureMatrix) { |
|
||||||
if (mPendingFilter != null) { |
|
||||||
release(); |
|
||||||
mFilter = mPendingFilter; |
|
||||||
mPendingFilter = null; |
|
||||||
createProgram(); |
|
||||||
} |
|
||||||
|
|
||||||
GlUtils.checkError("draw start"); |
|
||||||
|
|
||||||
// Select the program and the active texture.
|
|
||||||
GLES20.glUseProgram(mProgramHandle); |
|
||||||
GlUtils.checkError("glUseProgram"); |
|
||||||
GLES20.glActiveTexture(mTextureUnit); |
|
||||||
GLES20.glBindTexture(mTextureTarget, textureId); |
|
||||||
|
|
||||||
// Draw.
|
|
||||||
mFilter.draw(timestampUs, textureMatrix); |
|
||||||
|
|
||||||
// Release.
|
|
||||||
GLES20.glBindTexture(mTextureTarget, 0); |
|
||||||
GLES20.glUseProgram(0); |
|
||||||
} |
|
||||||
} |
|
@ -1,80 +0,0 @@ |
|||||||
/* |
|
||||||
* Copyright 2013 Google Inc. All rights reserved. |
|
||||||
* |
|
||||||
* 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 com.otaliastudios.cameraview.internal.egl; |
|
||||||
|
|
||||||
import android.graphics.SurfaceTexture; |
|
||||||
import android.os.Build; |
|
||||||
import androidx.annotation.RequiresApi; |
|
||||||
import android.view.Surface; |
|
||||||
|
|
||||||
/** |
|
||||||
* Recordable EGL window surface. |
|
||||||
* <p> |
|
||||||
* It's good practice to explicitly release() the surface, preferably from a "finally" block. |
|
||||||
*/ |
|
||||||
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2) |
|
||||||
public class EglWindowSurface extends EglBaseSurface { |
|
||||||
private Surface mSurface; |
|
||||||
private boolean mReleaseSurface; |
|
||||||
|
|
||||||
/** |
|
||||||
* Associates an EGL surface with the native window surface. |
|
||||||
* <p> |
|
||||||
* Set releaseSurface to true if you want the Surface to be released when release() is |
|
||||||
* called. This is convenient, but can interfere with framework classes that expect to |
|
||||||
* manage the Surface themselves (e.g. if you release a SurfaceView's Surface, the |
|
||||||
* surfaceDestroyed() callback won't fire). |
|
||||||
*/ |
|
||||||
public EglWindowSurface(EglCore eglCore, Surface surface, boolean releaseSurface) { |
|
||||||
super(eglCore); |
|
||||||
createWindowSurface(surface); |
|
||||||
mSurface = surface; |
|
||||||
mReleaseSurface = releaseSurface; |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Associates an EGL surface with the SurfaceTexture. |
|
||||||
*/ |
|
||||||
public EglWindowSurface(EglCore eglCore, SurfaceTexture surfaceTexture) { |
|
||||||
super(eglCore); |
|
||||||
createWindowSurface(surfaceTexture); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Associates an EGL surface with the Surface. |
|
||||||
*/ |
|
||||||
public EglWindowSurface(EglCore eglCore, Surface surface) { |
|
||||||
super(eglCore); |
|
||||||
createWindowSurface(surface); |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Releases any resources associated with the EGL surface (and, if configured to do so, |
|
||||||
* with the Surface as well). |
|
||||||
* <p> |
|
||||||
* Does not require that the surface's EGL context be current. |
|
||||||
*/ |
|
||||||
public void release() { |
|
||||||
releaseEglSurface(); |
|
||||||
if (mSurface != null) { |
|
||||||
if (mReleaseSurface) { |
|
||||||
mSurface.release(); |
|
||||||
} |
|
||||||
mSurface = null; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,8 +1,10 @@ |
|||||||
package com.otaliastudios.cameraview.internal.utils; |
package com.otaliastudios.cameraview.internal; |
||||||
|
|
||||||
|
|
||||||
import androidx.exifinterface.media.ExifInterface; |
import androidx.exifinterface.media.ExifInterface; |
||||||
|
|
||||||
|
import com.otaliastudios.cameraview.internal.ExifHelper; |
||||||
|
|
||||||
import org.junit.Test; |
import org.junit.Test; |
||||||
|
|
||||||
import static junit.framework.Assert.assertNotNull; |
import static junit.framework.Assert.assertNotNull; |
@ -1,6 +1,8 @@ |
|||||||
package com.otaliastudios.cameraview.internal.utils; |
package com.otaliastudios.cameraview.internal; |
||||||
|
|
||||||
|
|
||||||
|
import com.otaliastudios.cameraview.internal.Pool; |
||||||
|
|
||||||
import org.junit.After; |
import org.junit.After; |
||||||
import org.junit.Before; |
import org.junit.Before; |
||||||
import org.junit.Test; |
import org.junit.Test; |
Loading…
Reference in new issue