diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/filter/BaseFilterTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/filter/BaseFilterTest.java index a179b68e..a3a7b672 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/filter/BaseFilterTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/filter/BaseFilterTest.java @@ -8,7 +8,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.otaliastudios.cameraview.BaseEglTest; -import com.otaliastudios.cameraview.internal.egl.EglViewport; +import com.otaliastudios.cameraview.internal.GlTextureDrawer; import com.otaliastudios.opengl.program.GlTextureProgram; import org.junit.Test; @@ -111,18 +111,18 @@ public class BaseFilterTest extends BaseEglTest { @Test public void testDraw() { - // Use an EglViewport which cares about GL setup. + // Use a drawer which cares about GL setup. filter = spy(new TestFilter()); - EglViewport viewport = new EglViewport(filter); - int texture = viewport.createTexture(); + GlTextureDrawer drawer = new GlTextureDrawer(); + drawer.setFilter(filter); - float[] matrix = new float[16]; - viewport.draw(0L, texture, matrix); + float[] matrix = drawer.getTextureTransform(); + drawer.draw(0L); verify(filter, times(1)).onPreDraw(0L, matrix); verify(filter, times(1)).onDraw(0L); verify(filter, times(1)).onPostDraw(0L); - viewport.release(); + drawer.release(); } @Test(expected = RuntimeException.class) diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/filter/MultiFilterTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/filter/MultiFilterTest.java index 4530f016..60dced46 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/filter/MultiFilterTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/filter/MultiFilterTest.java @@ -11,7 +11,7 @@ import com.otaliastudios.cameraview.filters.AutoFixFilter; import com.otaliastudios.cameraview.filters.BrightnessFilter; import com.otaliastudios.cameraview.filters.DuotoneFilter; import com.otaliastudios.cameraview.filters.VignetteFilter; -import com.otaliastudios.cameraview.internal.egl.EglViewport; +import com.otaliastudios.cameraview.internal.GlTextureDrawer; import com.otaliastudios.opengl.program.GlProgram; import org.junit.Test; @@ -142,11 +142,11 @@ public class MultiFilterTest extends BaseEglTest { DuotoneFilter filter = spy(new DuotoneFilter()); MultiFilter multiFilter = new MultiFilter(filter); multiFilter.setSize(WIDTH, HEIGHT); - EglViewport viewport = new EglViewport(multiFilter); - int texture = viewport.createTexture(); - float[] matrix = new float[16]; - viewport.draw(0L, texture, matrix); - viewport.release(); + GlTextureDrawer drawer = new GlTextureDrawer(); + drawer.setFilter(multiFilter); + float[] matrix = drawer.getTextureTransform(); + drawer.draw(0L); + drawer.release(); // The child should have experienced the whole lifecycle. verify(filter, atLeastOnce()).getVertexShader(); @@ -165,7 +165,9 @@ public class MultiFilterTest extends BaseEglTest { final DuotoneFilter filter2 = spy(new DuotoneFilter()); final MultiFilter multiFilter = new MultiFilter(filter1, filter2); multiFilter.setSize(WIDTH, HEIGHT); - float[] matrix = new float[16]; + GlTextureDrawer drawer = new GlTextureDrawer(); + drawer.setFilter(multiFilter); + float[] matrix = drawer.getTextureTransform(); final int[] result = new int[1]; doAnswer(new Answer() { @@ -199,10 +201,8 @@ public class MultiFilterTest extends BaseEglTest { } }).when(filter2).draw(eq(0L), any(float[].class)); - EglViewport viewport = new EglViewport(multiFilter); - int texture = viewport.createTexture(); - viewport.draw(0L, texture, matrix); - viewport.release(); + drawer.draw(0L); + drawer.release(); // Verify that both are drawn. verify(filter1, times(1)).draw(0L, matrix); diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/overlay/OverlayDrawerTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/overlay/OverlayDrawerTest.java index de3d3674..f3ccb5ba 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/overlay/OverlayDrawerTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/overlay/OverlayDrawerTest.java @@ -7,7 +7,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.otaliastudios.cameraview.BaseEglTest; -import com.otaliastudios.cameraview.internal.egl.EglViewport; import com.otaliastudios.cameraview.size.Size; import org.hamcrest.BaseMatcher; @@ -60,22 +59,19 @@ public class OverlayDrawerTest extends BaseEglTest { @Test public void testRender() { OverlayDrawer drawer = new OverlayDrawer(mock(Overlay.class), new Size(WIDTH, HEIGHT)); - drawer.mViewport = spy(drawer.mViewport); + drawer.mTextureDrawer = spy(drawer.mTextureDrawer); + drawer.draw(Overlay.Target.PICTURE_SNAPSHOT); drawer.render(0L); - verify(drawer.mViewport, times(1)).draw( - 0L, - drawer.mTextureId, - drawer.getTransform() - ); + verify(drawer.mTextureDrawer, times(1)).draw(0L); } @Test public void testRelease() { OverlayDrawer drawer = new OverlayDrawer(mock(Overlay.class), new Size(WIDTH, HEIGHT)); - EglViewport viewport = spy(drawer.mViewport); - drawer.mViewport = viewport; + drawer.mTextureDrawer = spy(drawer.mTextureDrawer); + drawer.release(); - verify(viewport, times(1)).release(); + verify(drawer.mTextureDrawer, times(1)).release(); } } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/Issue514Workaround.java b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/Issue514Workaround.java index 828a5aff..f5e35725 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/Issue514Workaround.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/Issue514Workaround.java @@ -6,7 +6,6 @@ import android.graphics.SurfaceTexture; import android.opengl.GLES11Ext; import android.opengl.GLES20; import android.view.Surface; -import com.otaliastudios.cameraview.internal.egl.EglViewport; import com.otaliastudios.cameraview.preview.RendererThread; @@ -84,7 +83,7 @@ import com.otaliastudios.cameraview.preview.RendererThread; * * This makes no sense, since overlaySurfaceTexture.updateTexImage() is setting it to * overlayTextureId anyway, but it fixes the issue. Specifically, after any draw operation with - * {@link EglViewport}, the bound texture is reset to 0 so this must be undone here. We offer: + * {@link GlTextureDrawer}, the bound texture is reset to 0 so this must be undone here. We offer: * * - {@link #beforeOverlayUpdateTexImage()} to be called before the * {@link SurfaceTexture#updateTexImage()} call @@ -92,7 +91,7 @@ import com.otaliastudios.cameraview.preview.RendererThread; * * Since updating and rendering can happen on different threads with a shared EGL context, * in case they do, the {@link #beforeOverlayUpdateTexImage()}, the actual updateTexImage() and - * finally the {@link EglViewport} drawing operations should be synchronized with a lock. + * finally the {@link GlTextureDrawer} drawing operations should be synchronized with a lock. * * REFERENCES * https://github.com/natario1/CameraView/issues/514 diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/egl/EglViewport.java b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/egl/EglViewport.java deleted file mode 100644 index 47302cff..00000000 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/egl/EglViewport.java +++ /dev/null @@ -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.opengl.program.GlProgram; - - -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 = GlProgram.create(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); - Egloo.checkGlError("glGenTextures"); - - int texId = textures[0]; - GLES20.glActiveTexture(mTextureUnit); - GLES20.glBindTexture(mTextureTarget, texId); - Egloo.checkGlError("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); - Egloo.checkGlError("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 draw(long timestampUs, int textureId, float[] textureMatrix) { - if (mPendingFilter != null) { - release(); - mFilter = mPendingFilter; - mPendingFilter = null; - createProgram(); - } - - Egloo.checkGlError("draw start"); - - // Select the program and the active texture. - GLES20.glUseProgram(mProgramHandle); - Egloo.checkGlError("glUseProgram"); - GLES20.glActiveTexture(mTextureUnit); - GLES20.glBindTexture(mTextureTarget, textureId); - - // Draw. - mFilter.draw(timestampUs, textureMatrix); - - // Release. - GLES20.glBindTexture(mTextureTarget, 0); - GLES20.glUseProgram(0); - } -} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/overlay/OverlayDrawer.java b/cameraview/src/main/java/com/otaliastudios/cameraview/overlay/OverlayDrawer.java index bbce8af0..88f72668 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/overlay/OverlayDrawer.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/overlay/OverlayDrawer.java @@ -12,8 +12,8 @@ import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import com.otaliastudios.cameraview.CameraLogger; +import com.otaliastudios.cameraview.internal.GlTextureDrawer; import com.otaliastudios.cameraview.internal.Issue514Workaround; -import com.otaliastudios.cameraview.internal.egl.EglViewport; import com.otaliastudios.cameraview.size.Size; import java.nio.Buffer; @@ -27,7 +27,7 @@ import java.nio.Buffer; * - Renders this into the current EGL window: {@link #render(long)} * - Applies the {@link Issue514Workaround} the correct way * - * In the future we might want to use a different approach than {@link EglViewport}, + * In the future we might want to use a different approach than {@link GlTextureDrawer}, * {@link SurfaceTexture} and {@link GLES11Ext#GL_TEXTURE_EXTERNAL_OES}, * for example by using a regular {@link GLES20#GL_TEXTURE_2D} that might * be filled through {@link GLES20#glTexImage2D(int, int, int, int, int, int, int, int, Buffer)}. @@ -40,22 +40,19 @@ public class OverlayDrawer { private static final CameraLogger LOG = CameraLogger.create(TAG); private Overlay mOverlay; - @VisibleForTesting int mTextureId; private SurfaceTexture mSurfaceTexture; private Surface mSurface; - private float[] mTransform = new float[16]; - @VisibleForTesting EglViewport mViewport; + @VisibleForTesting GlTextureDrawer mTextureDrawer; private Issue514Workaround mIssue514Workaround; private final Object mIssue514WorkaroundLock = new Object(); public OverlayDrawer(@NonNull Overlay overlay, @NonNull Size size) { mOverlay = overlay; - mViewport = new EglViewport(); - mTextureId = mViewport.createTexture(); - mSurfaceTexture = new SurfaceTexture(mTextureId); + mTextureDrawer = new GlTextureDrawer(); + mSurfaceTexture = new SurfaceTexture(mTextureDrawer.getTexture().getId()); mSurfaceTexture.setDefaultBufferSize(size.getWidth(), size.getHeight()); mSurface = new Surface(mSurfaceTexture); - mIssue514Workaround = new Issue514Workaround(mTextureId); + mIssue514Workaround = new Issue514Workaround(mTextureDrawer.getTexture().getId()); } /** @@ -77,7 +74,7 @@ public class OverlayDrawer { mIssue514Workaround.beforeOverlayUpdateTexImage(); mSurfaceTexture.updateTexImage(); } - mSurfaceTexture.getTransformMatrix(mTransform); + mSurfaceTexture.getTransformMatrix(mTextureDrawer.getTextureTransform()); } /** @@ -86,7 +83,7 @@ public class OverlayDrawer { * @return the transform matrix */ public float[] getTransform() { - return mTransform; + return mTextureDrawer.getTextureTransform(); } /** @@ -105,7 +102,7 @@ public class OverlayDrawer { GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA); synchronized (mIssue514WorkaroundLock) { - mViewport.draw(timestampUs, mTextureId, mTransform); + mTextureDrawer.draw(timestampUs); } } @@ -125,9 +122,9 @@ public class OverlayDrawer { mSurface.release(); mSurface = null; } - if (mViewport != null) { - mViewport.release(); - mViewport = null; + if (mTextureDrawer != null) { + mTextureDrawer.release(); + mTextureDrawer = null; } } } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/preview/GlCameraPreview.java b/cameraview/src/main/java/com/otaliastudios/cameraview/preview/GlCameraPreview.java index 2e25e25d..58b6f065 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/preview/GlCameraPreview.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/preview/GlCameraPreview.java @@ -14,7 +14,7 @@ import android.view.View; import android.view.ViewGroup; import com.otaliastudios.cameraview.R; -import com.otaliastudios.cameraview.internal.egl.EglViewport; +import com.otaliastudios.cameraview.internal.GlTextureDrawer; import com.otaliastudios.cameraview.filter.Filter; import com.otaliastudios.cameraview.filter.NoFilter; import com.otaliastudios.cameraview.size.AspectRatio; @@ -63,10 +63,8 @@ import javax.microedition.khronos.opengles.GL10; public class GlCameraPreview extends FilterCameraPreview { private boolean mDispatched; - private final float[] mTransformMatrix = new float[16]; - private int mOutputTextureId = 0; private SurfaceTexture mInputSurfaceTexture; - private EglViewport mOutputViewport; + private GlTextureDrawer mOutputTextureDrawer; // A synchronized set was not enough to avoid crashes, probably due to external classes // removing the callback while this set is being iterated. CopyOnWriteArraySet solves this. private final Set mRendererFrameCallbacks = new CopyOnWriteArraySet<>(); @@ -146,14 +144,15 @@ public class GlCameraPreview extends FilterCameraPreview