From 8bad417cf2399444cb1bfbf2e9f3282357c1924d Mon Sep 17 00:00:00 2001 From: Mattia Iavarone Date: Mon, 20 Jan 2020 20:50:57 +0000 Subject: [PATCH] Egloo: create GlTextureDrawer --- build.gradle | 4 + cameraview/build.gradle | 2 + .../cameraview/filter/BaseFilterTest.java | 12 +- .../cameraview/filter/MultiFilterTest.java | 12 +- .../cameraview/overlay/OverlayDrawerTest.java | 21 +-- .../cameraview/filter/BaseFilter.java | 100 +++-------- .../cameraview/filter/MultiFilter.java | 163 +++++++++--------- .../cameraview/filters/AutoFixFilter.java | 6 +- .../cameraview/filters/BrightnessFilter.java | 6 +- .../cameraview/filters/ContrastFilter.java | 6 +- .../cameraview/filters/DocumentaryFilter.java | 10 +- .../cameraview/filters/DuotoneFilter.java | 10 +- .../cameraview/filters/FillLightFilter.java | 10 +- .../cameraview/filters/GammaFilter.java | 6 +- .../cameraview/filters/GrainFilter.java | 14 +- .../cameraview/filters/HueFilter.java | 6 +- .../cameraview/filters/LomoishFilter.java | 18 +- .../cameraview/filters/SaturationFilter.java | 14 +- .../cameraview/filters/SharpnessFilter.java | 14 +- .../cameraview/filters/TemperatureFilter.java | 6 +- .../cameraview/filters/TintFilter.java | 6 +- .../cameraview/filters/VignetteFilter.java | 18 +- .../cameraview/internal/GlTextureDrawer.java | 96 +++++++++++ .../cameraview/internal/GlUtils.java | 96 ----------- .../internal/egl/EglBaseSurface.java | 5 +- .../cameraview/internal/egl/EglViewport.java | 16 +- .../cameraview/overlay/OverlayDrawer.java | 2 +- .../picture/SnapshotGlPictureRecorder.java | 2 +- .../cameraview/preview/GlCameraPreview.java | 2 +- .../video/encoding/TextureMediaEncoder.java | 2 +- settings.gradle | 2 + 31 files changed, 311 insertions(+), 376 deletions(-) create mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/internal/GlTextureDrawer.java delete mode 100644 cameraview/src/main/java/com/otaliastudios/cameraview/internal/GlUtils.java diff --git a/build.gradle b/build.gradle index 469b3eb1..3eaf82d4 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,11 @@ buildscript { classpath 'com.android.tools.build:gradle:3.5.3' classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4' + + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.31" + classpath "org.jetbrains.dokka:dokka-android-gradle-plugin:0.9.17" } + ext.kotlinVersion = '1.3.31' } allprojects { diff --git a/cameraview/build.gradle b/cameraview/build.gradle index 5b4ec368..16949f5a 100644 --- a/cameraview/build.gradle +++ b/cameraview/build.gradle @@ -48,6 +48,8 @@ dependencies { api 'androidx.lifecycle:lifecycle-common:2.1.0' api 'com.google.android.gms:play-services-tasks:17.0.0' implementation 'androidx.annotation:annotation:1.1.0' + + implementation project(':egloo') } //endregion 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 ff0474f0..a179b68e 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/filter/BaseFilterTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/filter/BaseFilterTest.java @@ -8,16 +8,16 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.otaliastudios.cameraview.BaseEglTest; -import com.otaliastudios.cameraview.internal.GlUtils; import com.otaliastudios.cameraview.internal.egl.EglViewport; +import com.otaliastudios.opengl.program.GlTextureProgram; import org.junit.Test; import org.junit.runner.RunWith; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; @@ -91,11 +91,11 @@ public class BaseFilterTest extends BaseEglTest { @Test public void testOnProgramCreate() { filter = new TestFilter(); - int handle = GlUtils.createProgram(filter.getVertexShader(), filter.getFragmentShader()); + int handle = GlTextureProgram.create(filter.getVertexShader(), filter.getFragmentShader()); filter.onCreate(handle); - assertTrue(filter.programHandle >= 0); + assertNotNull(filter.program); filter.onDestroy(); - assertTrue(filter.programHandle < 0); + assertNull(filter.program); GLES20.glDeleteProgram(handle); } @@ -117,7 +117,7 @@ public class BaseFilterTest extends BaseEglTest { int texture = viewport.createTexture(); float[] matrix = new float[16]; - viewport.drawFrame(0L, texture, matrix); + viewport.draw(0L, texture, matrix); verify(filter, times(1)).onPreDraw(0L, matrix); verify(filter, times(1)).onDraw(0L); verify(filter, times(1)).onPostDraw(0L); 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 09591425..4530f016 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/filter/MultiFilterTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/filter/MultiFilterTest.java @@ -11,8 +11,8 @@ 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.GlUtils; import com.otaliastudios.cameraview.internal.egl.EglViewport; +import com.otaliastudios.opengl.program.GlProgram; import org.junit.Test; import org.junit.runner.RunWith; @@ -127,7 +127,7 @@ public class MultiFilterTest extends BaseEglTest { DuotoneFilter filter = spy(new DuotoneFilter()); MultiFilter multiFilter = new MultiFilter(filter); - int program = GlUtils.createProgram(multiFilter.getVertexShader(), + int program = GlProgram.create(multiFilter.getVertexShader(), multiFilter.getFragmentShader()); multiFilter.onCreate(program); verify(filter, never()).onCreate(anyInt()); @@ -145,7 +145,7 @@ public class MultiFilterTest extends BaseEglTest { EglViewport viewport = new EglViewport(multiFilter); int texture = viewport.createTexture(); float[] matrix = new float[16]; - viewport.drawFrame(0L, texture, matrix); + viewport.draw(0L, texture, matrix); viewport.release(); // The child should have experienced the whole lifecycle. @@ -173,7 +173,7 @@ public class MultiFilterTest extends BaseEglTest { public Object answer(InvocationOnMock invocation) { MultiFilter.State state = multiFilter.states.get(filter1); assertNotNull(state); - assertTrue(state.isCreated); + assertTrue(state.isProgramCreated); assertTrue(state.isFramebufferCreated); GLES20.glGetIntegerv(GLES20.GL_FRAMEBUFFER_BINDING, result, 0); @@ -189,7 +189,7 @@ public class MultiFilterTest extends BaseEglTest { // The last filter has no FBO / texture. MultiFilter.State state = multiFilter.states.get(filter2); assertNotNull(state); - assertTrue(state.isCreated); + assertTrue(state.isProgramCreated); assertFalse(state.isFramebufferCreated); GLES20.glGetIntegerv(GLES20.GL_FRAMEBUFFER_BINDING, result, 0); @@ -201,7 +201,7 @@ public class MultiFilterTest extends BaseEglTest { EglViewport viewport = new EglViewport(multiFilter); int texture = viewport.createTexture(); - viewport.drawFrame(0L, texture, matrix); + viewport.draw(0L, texture, matrix); viewport.release(); // Verify that both are drawn. 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 b763c83e..de3d3674 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/overlay/OverlayDrawerTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/overlay/OverlayDrawerTest.java @@ -1,48 +1,29 @@ package com.otaliastudios.cameraview.overlay; -import android.content.res.XmlResourceParser; import android.graphics.Canvas; -import android.util.AttributeSet; -import android.util.Xml; -import android.view.View; -import android.view.ViewGroup; -import androidx.annotation.NonNull; -import androidx.test.annotation.UiThreadTest; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.otaliastudios.cameraview.BaseEglTest; -import com.otaliastudios.cameraview.BaseTest; -import com.otaliastudios.cameraview.internal.egl.EglBaseSurface; -import com.otaliastudios.cameraview.internal.egl.EglCore; import com.otaliastudios.cameraview.internal.egl.EglViewport; import com.otaliastudios.cameraview.size.Size; import org.hamcrest.BaseMatcher; import org.hamcrest.Description; -import org.hamcrest.Matcher; -import org.junit.After; -import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mockito; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyFloat; -import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; @RunWith(AndroidJUnit4.class) @SmallTest @@ -82,7 +63,7 @@ public class OverlayDrawerTest extends BaseEglTest { drawer.mViewport = spy(drawer.mViewport); drawer.draw(Overlay.Target.PICTURE_SNAPSHOT); drawer.render(0L); - verify(drawer.mViewport, times(1)).drawFrame( + verify(drawer.mViewport, times(1)).draw( 0L, drawer.mTextureId, drawer.getTransform() diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filter/BaseFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filter/BaseFilter.java index 2c3ef350..288fddbd 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/filter/BaseFilter.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filter/BaseFilter.java @@ -1,15 +1,14 @@ package com.otaliastudios.cameraview.filter; -import android.opengl.GLES20; - import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import com.otaliastudios.cameraview.CameraLogger; -import com.otaliastudios.cameraview.internal.GlUtils; import com.otaliastudios.cameraview.size.Size; - -import java.nio.FloatBuffer; +import com.otaliastudios.opengl.draw.GlDrawable; +import com.otaliastudios.opengl.draw.GlRect; +import com.otaliastudios.opengl.program.GlTextureProgram; +import com.otaliastudios.opengl.texture.GlTexture; /** * A base implementation of {@link Filter} that just leaves the fragment shader to subclasses. @@ -90,26 +89,8 @@ public abstract class BaseFilter implements Filter { + "}\n"; } - // When the model/view/projection matrix is identity, this will exactly cover the viewport. - private FloatBuffer vertexPosition = GlUtils.floatBuffer(new float[]{ - -1.0f, -1.0f, // 0 bottom left - 1.0f, -1.0f, // 1 bottom right - -1.0f, 1.0f, // 2 top left - 1.0f, 1.0f, // 3 top right - }); - - private FloatBuffer textureCoordinates = GlUtils.floatBuffer(new float[]{ - 0.0f, 0.0f, // 0 bottom left - 1.0f, 0.0f, // 1 bottom right - 0.0f, 1.0f, // 2 top left - 1.0f, 1.0f // 3 top right - }); - - private int vertexModelViewProjectionMatrixLocation = -1; - private int vertexTransformMatrixLocation = -1; - private int vertexPositionLocation = -1; - private int vertexTextureCoordinateLocation = -1; - @VisibleForTesting int programHandle = -1; + @VisibleForTesting GlTextureProgram program = null; + private final GlDrawable programDrawable = new GlRect(); @VisibleForTesting Size size; @SuppressWarnings("WeakerAccess") @@ -141,28 +122,22 @@ public abstract class BaseFilter implements Filter { @Override public void onCreate(int programHandle) { - this.programHandle = programHandle; - vertexPositionLocation = GLES20.glGetAttribLocation(programHandle, vertexPositionName); - GlUtils.checkLocation(vertexPositionLocation, vertexPositionName); - vertexTextureCoordinateLocation = GLES20.glGetAttribLocation(programHandle, - vertexTextureCoordinateName); - GlUtils.checkLocation(vertexTextureCoordinateLocation, vertexTextureCoordinateName); - vertexModelViewProjectionMatrixLocation = GLES20.glGetUniformLocation(programHandle, - vertexModelViewProjectionMatrixName); - GlUtils.checkLocation(vertexModelViewProjectionMatrixLocation, - vertexModelViewProjectionMatrixName); - vertexTransformMatrixLocation = GLES20.glGetUniformLocation(programHandle, - vertexTransformMatrixName); - GlUtils.checkLocation(vertexTransformMatrixLocation, vertexTransformMatrixName); + program = new GlTextureProgram( + programHandle, + vertexPositionName, + vertexModelViewProjectionMatrixName, + vertexTextureCoordinateName, + vertexTransformMatrixName + ); } @Override public void onDestroy() { - programHandle = -1; - vertexPositionLocation = -1; - vertexTextureCoordinateLocation = -1; - vertexModelViewProjectionMatrixLocation = -1; - vertexTransformMatrixLocation = -1; + // NOTE: this destroys the GL program, but Filter consumers (GlTextureDrawer, MultiFilter) + // will destroy it too (just like they create it). Hope this doesn't cause any issue; + // if it does, we could avoid calling release here. + program.release(); + program = null; } @NonNull @@ -178,7 +153,7 @@ public abstract class BaseFilter implements Filter { @Override public void draw(long timestampUs, @NonNull float[] transformMatrix) { - if (programHandle == -1) { + if (program == null) { LOG.w("Filter.draw() called after destroying the filter. " + "This can happen rarely because of threading."); } else { @@ -189,43 +164,18 @@ public abstract class BaseFilter implements Filter { } protected void onPreDraw(long timestampUs, @NonNull float[] transformMatrix) { - // Copy the model / view / projection matrix over. - GLES20.glUniformMatrix4fv(vertexModelViewProjectionMatrixLocation, 1, - false, GlUtils.IDENTITY_MATRIX, 0); - GlUtils.checkError("glUniformMatrix4fv"); - - // Copy the texture transformation matrix over. - GLES20.glUniformMatrix4fv(vertexTransformMatrixLocation, 1, - false, transformMatrix, 0); - GlUtils.checkError("glUniformMatrix4fv"); - - // Enable the "aPosition" vertex attribute. - // Connect vertexBuffer to "aPosition". - GLES20.glEnableVertexAttribArray(vertexPositionLocation); - GlUtils.checkError("glEnableVertexAttribArray: " + vertexPositionLocation); - GLES20.glVertexAttribPointer(vertexPositionLocation, 2, GLES20.GL_FLOAT, - false, 8, vertexPosition); - GlUtils.checkError("glVertexAttribPointer"); - - // Enable the "aTextureCoord" vertex attribute. - // Connect texBuffer to "aTextureCoord". - GLES20.glEnableVertexAttribArray(vertexTextureCoordinateLocation); - GlUtils.checkError("glEnableVertexAttribArray"); - GLES20.glVertexAttribPointer(vertexTextureCoordinateLocation, 2, GLES20.GL_FLOAT, - false, 8, textureCoordinates); - GlUtils.checkError("glVertexAttribPointer"); + program.setTextureTransform(transformMatrix); + program.onPreDraw(programDrawable, programDrawable.getModelMatrix()); } @SuppressWarnings("WeakerAccess") - protected void onDraw(long timestampUs) { - GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4); - GlUtils.checkError("glDrawArrays"); + protected void onDraw(@SuppressWarnings("unused") long timestampUs) { + program.onDraw(programDrawable); } @SuppressWarnings("WeakerAccess") - protected void onPostDraw(long timestampUs) { - GLES20.glDisableVertexAttribArray(vertexPositionLocation); - GLES20.glDisableVertexAttribArray(vertexTextureCoordinateLocation); + protected void onPostDraw(@SuppressWarnings("unused") long timestampUs) { + program.onPostDraw(programDrawable); } @NonNull diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filter/MultiFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filter/MultiFilter.java index 116f4b2b..1caa82a4 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/filter/MultiFilter.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filter/MultiFilter.java @@ -6,8 +6,10 @@ import android.opengl.GLES20; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; -import com.otaliastudios.cameraview.internal.GlUtils; import com.otaliastudios.cameraview.size.Size; +import com.otaliastudios.opengl.core.Egloo; +import com.otaliastudios.opengl.program.GlProgram; +import com.otaliastudios.opengl.program.GlTextureProgram; import java.util.ArrayList; import java.util.Arrays; @@ -42,13 +44,12 @@ public class MultiFilter implements Filter, OneParameterFilter, TwoParameterFilt @VisibleForTesting static class State { - @VisibleForTesting boolean isCreated = false; + @VisibleForTesting boolean isProgramCreated = false; @VisibleForTesting boolean isFramebufferCreated = false; @VisibleForTesting Size size = null; - private int programHandle = -1; - private int framebufferId = -1; - private int textureId = -1; + private int outputFramebufferId = -1; + private int outputTextureId = -1; } @VisibleForTesting final List filters = new ArrayList<>(); @@ -105,86 +106,82 @@ public class MultiFilter implements Filter, OneParameterFilter, TwoParameterFilt // with cleanup. Cleanup must happen on the GL thread so we'd have to wait // for new rendering call (which might not even happen). - private void maybeCreate(@NonNull Filter filter, boolean isFirst) { + private void maybeCreateProgram(@NonNull Filter filter, boolean isFirst, boolean isLast) { State state = states.get(filter); //noinspection ConstantConditions - if (!state.isCreated) { - state.isCreated = true; - String shader = filter.getFragmentShader(); - if (!isFirst) { - // The first shader actually reads from a OES texture, but the others - // will read from the 2d framebuffer texture. This is a dirty hack. - shader = shader.replace("samplerExternalOES ", "sampler2D "); - } - state.programHandle = GlUtils.createProgram(filter.getVertexShader(), shader); - filter.onCreate(state.programHandle); - } + if (state.isProgramCreated) return; + state.isProgramCreated = true; + + // The first shader actually reads from a OES texture, but the others + // will read from the 2d framebuffer texture. This is a dirty hack. + String fragmentShader = isFirst + ? filter.getFragmentShader() + : filter.getFragmentShader().replace("samplerExternalOES ", "sampler2D "); + String vertexShader = filter.getVertexShader(); + state.programHandle = GlProgram.create(vertexShader, fragmentShader); + filter.onCreate(state.programHandle); } - private void maybeDestroy(@NonNull Filter filter) { + private void maybeDestroyProgram(@NonNull Filter filter) { State state = states.get(filter); //noinspection ConstantConditions - if (state.isCreated) { - state.isCreated = false; - filter.onDestroy(); - GLES20.glDeleteProgram(state.programHandle); - state.programHandle = -1; - } + if (!state.isProgramCreated) return; + state.isProgramCreated = false; + filter.onDestroy(); + GLES20.glDeleteProgram(state.programHandle); + state.programHandle = -1; } - private void maybeCreateFramebuffer(@NonNull Filter filter, boolean isLast) { - if (isLast) return; + private void maybeCreateFramebuffer(@NonNull Filter filter, boolean isFirst, boolean isLast) { State state = states.get(filter); //noinspection ConstantConditions - if (!state.isFramebufferCreated) { - state.isFramebufferCreated = true; - - int[] framebufferArray = new int[1]; - int[] textureArray = new int[1]; - GLES20.glGenFramebuffers(1, framebufferArray, 0); - GLES20.glGenTextures(1, textureArray, 0); - state.framebufferId = framebufferArray[0]; - state.textureId = textureArray[0]; - - GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, state.textureId); - GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, - state.size.getWidth(), state.size.getHeight(), 0, GLES20.GL_RGBA, - GLES20.GL_UNSIGNED_BYTE, null); - GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, - GLES20.GL_LINEAR); - GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, - GLES20.GL_LINEAR); - GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, - GLES20.GL_CLAMP_TO_EDGE); - GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, - GLES20.GL_CLAMP_TO_EDGE); - GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, state.framebufferId); - GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, - GLES20.GL_COLOR_ATTACHMENT0, - GLES20.GL_TEXTURE_2D, - state.textureId, - 0); - - int status = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER); - if (status != GLES20.GL_FRAMEBUFFER_COMPLETE) { - throw new RuntimeException("Invalid framebuffer generation. Error:" + status); - } - - GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0); - GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); + if (state.isFramebufferCreated || isLast) return; + state.isFramebufferCreated = true; + + int[] framebufferArray = new int[1]; + int[] textureArray = new int[1]; + GLES20.glGenFramebuffers(1, framebufferArray, 0); + GLES20.glGenTextures(1, textureArray, 0); + state.outputFramebufferId = framebufferArray[0]; + state.outputTextureId = textureArray[0]; + + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, state.outputTextureId); + GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, + state.size.getWidth(), state.size.getHeight(), 0, GLES20.GL_RGBA, + GLES20.GL_UNSIGNED_BYTE, null); + GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, + GLES20.GL_LINEAR); + GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, + GLES20.GL_LINEAR); + GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, + GLES20.GL_CLAMP_TO_EDGE); + GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, + GLES20.GL_CLAMP_TO_EDGE); + GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, state.outputTextureId); + GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, + GLES20.GL_COLOR_ATTACHMENT0, + GLES20.GL_TEXTURE_2D, + state.outputTextureId, + 0); + + int status = GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER); + if (status != GLES20.GL_FRAMEBUFFER_COMPLETE) { + throw new RuntimeException("Invalid framebuffer generation. Error:" + status); } + + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0); + GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); } private void maybeDestroyFramebuffer(@NonNull Filter filter) { State state = states.get(filter); //noinspection ConstantConditions - if (state.isFramebufferCreated) { - state.isFramebufferCreated = false; - GLES20.glDeleteFramebuffers(1, new int[]{state.framebufferId}, 0); - state.framebufferId = -1; - GLES20.glDeleteTextures(1, new int[]{state.textureId}, 0); - state.textureId = -1; - } + if (!state.isFramebufferCreated) return; + state.isFramebufferCreated = false; + GLES20.glDeleteFramebuffers(1, new int[]{state.outputFramebufferId}, 0); + state.outputFramebufferId = -1; + GLES20.glDeleteTextures(1, new int[]{state.outputTextureId}, 0); + state.outputTextureId = -1; } private void maybeSetSize(@NonNull Filter filter) { @@ -196,24 +193,24 @@ public class MultiFilter implements Filter, OneParameterFilter, TwoParameterFilt } } + @Override + public void onCreate(int programHandle) { + // We'll create children during the draw() op, since some of them + // might have been added after this onCreate() is called. + } + @NonNull @Override public String getVertexShader() { // Whatever, we won't be using this. - return new NoFilter().getVertexShader(); + return GlTextureProgram.SIMPLE_VERTEX_SHADER; } @NonNull @Override public String getFragmentShader() { // Whatever, we won't be using this. - return new NoFilter().getFragmentShader(); - } - - @Override - public void onCreate(int programHandle) { - // We'll create children during the draw() op, since some of them - // might have been added after this onCreate() is called. + return GlTextureProgram.SIMPLE_FRAGMENT_SHADER; } @Override @@ -221,7 +218,7 @@ public class MultiFilter implements Filter, OneParameterFilter, TwoParameterFilt synchronized (lock) { for (Filter filter : filters) { maybeDestroyFramebuffer(filter); - maybeDestroy(filter); + maybeDestroyProgram(filter); } } } @@ -245,8 +242,8 @@ public class MultiFilter implements Filter, OneParameterFilter, TwoParameterFilt Filter filter = filters.get(i); State state = states.get(filter); maybeSetSize(filter); - maybeCreate(filter, isFirst); - maybeCreateFramebuffer(filter, isLast); + maybeCreateProgram(filter, isFirst, isLast); + maybeCreateFramebuffer(filter, isFirst, isLast); //noinspection ConstantConditions GLES20.glUseProgram(state.programHandle); @@ -255,7 +252,7 @@ public class MultiFilter implements Filter, OneParameterFilter, TwoParameterFilt // Each filter outputs into its own framebuffer object, except the // last filter, which outputs into the default framebuffer. if (!isLast) { - GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, state.framebufferId); + GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, state.outputFramebufferId); GLES20.glClearColor(0, 0, 0, 0); } else { GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); @@ -267,14 +264,14 @@ public class MultiFilter implements Filter, OneParameterFilter, TwoParameterFilt if (isFirst) { filter.draw(timestampUs, transformMatrix); } else { - filter.draw(timestampUs, GlUtils.IDENTITY_MATRIX); + filter.draw(timestampUs, Egloo.IDENTITY_MATRIX); } // Set the input for the next cycle: // It is the framebuffer texture from this cycle. If this is the last // filter, reset this value just to cleanup. if (!isLast) { - GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, state.textureId); + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, state.outputTextureId); } else { GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0); } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/AutoFixFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/AutoFixFilter.java index 2d01e658..9b6ea8da 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/AutoFixFilter.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/AutoFixFilter.java @@ -6,7 +6,7 @@ import androidx.annotation.NonNull; import com.otaliastudios.cameraview.filter.BaseFilter; import com.otaliastudios.cameraview.filter.OneParameterFilter; -import com.otaliastudios.cameraview.internal.GlUtils; +import com.otaliastudios.opengl.core.Egloo; /** * Attempts to auto-fix the frames based on histogram equalization. @@ -107,7 +107,7 @@ public class AutoFixFilter extends BaseFilter implements OneParameterFilter { public void onCreate(int programHandle) { super.onCreate(programHandle); scaleLocation = GLES20.glGetUniformLocation(programHandle, "scale"); - GlUtils.checkLocation(scaleLocation, "scale"); + Egloo.checkGlProgramLocation(scaleLocation, "scale"); } @Override @@ -120,6 +120,6 @@ public class AutoFixFilter extends BaseFilter implements OneParameterFilter { protected void onPreDraw(long timestampUs, @NonNull float[] transformMatrix) { super.onPreDraw(timestampUs, transformMatrix); GLES20.glUniform1f(scaleLocation, scale); - GlUtils.checkError("glUniform1f"); + Egloo.checkGlError("glUniform1f"); } } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/BrightnessFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/BrightnessFilter.java index 3c453326..ffef8fbe 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/BrightnessFilter.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/BrightnessFilter.java @@ -6,7 +6,7 @@ import androidx.annotation.NonNull; import com.otaliastudios.cameraview.filter.BaseFilter; import com.otaliastudios.cameraview.filter.OneParameterFilter; -import com.otaliastudios.cameraview.internal.GlUtils; +import com.otaliastudios.opengl.core.Egloo; /** * Adjusts the brightness of the frames. @@ -76,7 +76,7 @@ public class BrightnessFilter extends BaseFilter implements OneParameterFilter { public void onCreate(int programHandle) { super.onCreate(programHandle); brightnessLocation = GLES20.glGetUniformLocation(programHandle, "brightness"); - GlUtils.checkLocation(brightnessLocation, "brightness"); + Egloo.checkGlProgramLocation(brightnessLocation, "brightness"); } @Override @@ -89,6 +89,6 @@ public class BrightnessFilter extends BaseFilter implements OneParameterFilter { protected void onPreDraw(long timestampUs, @NonNull float[] transformMatrix) { super.onPreDraw(timestampUs, transformMatrix); GLES20.glUniform1f(brightnessLocation, brightness); - GlUtils.checkError("glUniform1f"); + Egloo.checkGlError("glUniform1f"); } } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/ContrastFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/ContrastFilter.java index c4054e16..cfb537d5 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/ContrastFilter.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/ContrastFilter.java @@ -6,7 +6,7 @@ import androidx.annotation.NonNull; import com.otaliastudios.cameraview.filter.BaseFilter; import com.otaliastudios.cameraview.filter.OneParameterFilter; -import com.otaliastudios.cameraview.internal.GlUtils; +import com.otaliastudios.opengl.core.Egloo; /** * Adjusts the contrast. @@ -78,7 +78,7 @@ public class ContrastFilter extends BaseFilter implements OneParameterFilter { public void onCreate(int programHandle) { super.onCreate(programHandle); contrastLocation = GLES20.glGetUniformLocation(programHandle, "contrast"); - GlUtils.checkLocation(contrastLocation, "contrast"); + Egloo.checkGlProgramLocation(contrastLocation, "contrast"); } @Override @@ -91,6 +91,6 @@ public class ContrastFilter extends BaseFilter implements OneParameterFilter { protected void onPreDraw(long timestampUs, @NonNull float[] transformMatrix) { super.onPreDraw(timestampUs, transformMatrix); GLES20.glUniform1f(contrastLocation, contrast); - GlUtils.checkError("glUniform1f"); + Egloo.checkGlError("glUniform1f"); } } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/DocumentaryFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/DocumentaryFilter.java index 9e66c8d8..bc26488e 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/DocumentaryFilter.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/DocumentaryFilter.java @@ -5,7 +5,7 @@ import android.opengl.GLES20; import androidx.annotation.NonNull; import com.otaliastudios.cameraview.filter.BaseFilter; -import com.otaliastudios.cameraview.internal.GlUtils; +import com.otaliastudios.opengl.core.Egloo; import java.util.Random; @@ -80,9 +80,9 @@ public class DocumentaryFilter extends BaseFilter { public void onCreate(int programHandle) { super.onCreate(programHandle); mScaleLocation = GLES20.glGetUniformLocation(programHandle, "scale"); - GlUtils.checkLocation(mScaleLocation, "scale"); + Egloo.checkGlProgramLocation(mScaleLocation, "scale"); mMaxDistLocation = GLES20.glGetUniformLocation(programHandle, "inv_max_dist"); - GlUtils.checkLocation(mMaxDistLocation, "inv_max_dist"); + Egloo.checkGlProgramLocation(mMaxDistLocation, "inv_max_dist"); } @Override @@ -104,12 +104,12 @@ public class DocumentaryFilter extends BaseFilter { scale[1] = 1f; } GLES20.glUniform2fv(mScaleLocation, 1, scale, 0); - GlUtils.checkError("glUniform2fv"); + Egloo.checkGlError("glUniform2fv"); float maxDist = ((float) Math.sqrt(scale[0] * scale[0] + scale[1] * scale[1])) * 0.5f; float invMaxDist = 1F / maxDist; GLES20.glUniform1f(mMaxDistLocation, invMaxDist); - GlUtils.checkError("glUniform1f"); + Egloo.checkGlError("glUniform1f"); } } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/DuotoneFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/DuotoneFilter.java index f335c019..d6c87a38 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/DuotoneFilter.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/DuotoneFilter.java @@ -8,7 +8,7 @@ import androidx.annotation.NonNull; import com.otaliastudios.cameraview.filter.BaseFilter; import com.otaliastudios.cameraview.filter.TwoParameterFilter; -import com.otaliastudios.cameraview.internal.GlUtils; +import com.otaliastudios.opengl.core.Egloo; /** * Representation of input frames using only two color tones. @@ -131,9 +131,9 @@ public class DuotoneFilter extends BaseFilter implements TwoParameterFilter { public void onCreate(int programHandle) { super.onCreate(programHandle); mFirstColorLocation = GLES20.glGetUniformLocation(programHandle, "first"); - GlUtils.checkLocation(mFirstColorLocation, "first"); + Egloo.checkGlProgramLocation(mFirstColorLocation, "first"); mSecondColorLocation = GLES20.glGetUniformLocation(programHandle, "second"); - GlUtils.checkLocation(mSecondColorLocation, "second"); + Egloo.checkGlProgramLocation(mSecondColorLocation, "second"); } @Override @@ -150,9 +150,9 @@ public class DuotoneFilter extends BaseFilter implements TwoParameterFilter { Color.blue(mSecondColor) / 255f }; GLES20.glUniform3fv(mFirstColorLocation, 1, first, 0); - GlUtils.checkError("glUniform3fv"); + Egloo.checkGlError("glUniform3fv"); GLES20.glUniform3fv(mSecondColorLocation, 1, second, 0); - GlUtils.checkError("glUniform3fv"); + Egloo.checkGlError("glUniform3fv"); } @Override diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/FillLightFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/FillLightFilter.java index bf801474..0d9d2fce 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/FillLightFilter.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/FillLightFilter.java @@ -6,7 +6,7 @@ import androidx.annotation.NonNull; import com.otaliastudios.cameraview.filter.BaseFilter; import com.otaliastudios.cameraview.filter.OneParameterFilter; -import com.otaliastudios.cameraview.internal.GlUtils; +import com.otaliastudios.opengl.core.Egloo; /** * Applies back-light filling to the frames. @@ -82,9 +82,9 @@ public class FillLightFilter extends BaseFilter implements OneParameterFilter { public void onCreate(int programHandle) { super.onCreate(programHandle); multiplierLocation = GLES20.glGetUniformLocation(programHandle, "mult"); - GlUtils.checkLocation(multiplierLocation, "mult"); + Egloo.checkGlProgramLocation(multiplierLocation, "mult"); gammaLocation = GLES20.glGetUniformLocation(programHandle, "igamma"); - GlUtils.checkLocation(gammaLocation, "igamma"); + Egloo.checkGlProgramLocation(gammaLocation, "igamma"); } @Override @@ -100,12 +100,12 @@ public class FillLightFilter extends BaseFilter implements OneParameterFilter { float amount = 1.0f - strength; float multiplier = 1.0f / (amount * 0.7f + 0.3f); GLES20.glUniform1f(multiplierLocation, multiplier); - GlUtils.checkError("glUniform1f"); + Egloo.checkGlError("glUniform1f"); float fadeGamma = 0.3f; float faded = fadeGamma + (1.0f - fadeGamma) * multiplier; float gamma = 1.0f / faded; GLES20.glUniform1f(gammaLocation, gamma); - GlUtils.checkError("glUniform1f"); + Egloo.checkGlError("glUniform1f"); } } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/GammaFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/GammaFilter.java index 4cef7017..6f320e52 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/GammaFilter.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/GammaFilter.java @@ -6,7 +6,7 @@ import androidx.annotation.NonNull; import com.otaliastudios.cameraview.filter.BaseFilter; import com.otaliastudios.cameraview.filter.OneParameterFilter; -import com.otaliastudios.cameraview.internal.GlUtils; +import com.otaliastudios.opengl.core.Egloo; /** * Applies gamma correction to the frames. @@ -73,7 +73,7 @@ public class GammaFilter extends BaseFilter implements OneParameterFilter { public void onCreate(int programHandle) { super.onCreate(programHandle); gammaLocation = GLES20.glGetUniformLocation(programHandle, "gamma"); - GlUtils.checkLocation(gammaLocation, "gamma"); + Egloo.checkGlProgramLocation(gammaLocation, "gamma"); } @Override @@ -86,6 +86,6 @@ public class GammaFilter extends BaseFilter implements OneParameterFilter { protected void onPreDraw(long timestampUs, @NonNull float[] transformMatrix) { super.onPreDraw(timestampUs, transformMatrix); GLES20.glUniform1f(gammaLocation, gamma); - GlUtils.checkError("glUniform1f"); + Egloo.checkGlError("glUniform1f"); } } \ No newline at end of file diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/GrainFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/GrainFilter.java index 92df0d5f..47092979 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/GrainFilter.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/GrainFilter.java @@ -6,7 +6,7 @@ import androidx.annotation.NonNull; import com.otaliastudios.cameraview.filter.BaseFilter; import com.otaliastudios.cameraview.filter.OneParameterFilter; -import com.otaliastudios.cameraview.internal.GlUtils; +import com.otaliastudios.opengl.core.Egloo; import java.util.Random; @@ -122,11 +122,11 @@ public class GrainFilter extends BaseFilter implements OneParameterFilter { public void onCreate(int programHandle) { super.onCreate(programHandle); strengthLocation = GLES20.glGetUniformLocation(programHandle, "scale"); - GlUtils.checkLocation(strengthLocation, "scale"); + Egloo.checkGlProgramLocation(strengthLocation, "scale"); stepXLocation = GLES20.glGetUniformLocation(programHandle, "stepX"); - GlUtils.checkLocation(stepXLocation, "stepX"); + Egloo.checkGlProgramLocation(stepXLocation, "stepX"); stepYLocation = GLES20.glGetUniformLocation(programHandle, "stepY"); - GlUtils.checkLocation(stepYLocation, "stepY"); + Egloo.checkGlProgramLocation(stepYLocation, "stepY"); } @Override @@ -141,10 +141,10 @@ public class GrainFilter extends BaseFilter implements OneParameterFilter { protected void onPreDraw(long timestampUs, @NonNull float[] transformMatrix) { super.onPreDraw(timestampUs, transformMatrix); GLES20.glUniform1f(strengthLocation, strength); - GlUtils.checkError("glUniform1f"); + Egloo.checkGlError("glUniform1f"); GLES20.glUniform1f(stepXLocation, 0.5f / width); - GlUtils.checkError("glUniform1f"); + Egloo.checkGlError("glUniform1f"); GLES20.glUniform1f(stepYLocation, 0.5f / height); - GlUtils.checkError("glUniform1f"); + Egloo.checkGlError("glUniform1f"); } } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/HueFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/HueFilter.java index 54c6d93f..22f798fd 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/HueFilter.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/HueFilter.java @@ -6,7 +6,7 @@ import androidx.annotation.NonNull; import com.otaliastudios.cameraview.filter.BaseFilter; import com.otaliastudios.cameraview.filter.OneParameterFilter; -import com.otaliastudios.cameraview.internal.GlUtils; +import com.otaliastudios.opengl.core.Egloo; /** * Applies a hue effect on the input frames. @@ -86,7 +86,7 @@ public class HueFilter extends BaseFilter implements OneParameterFilter { public void onCreate(int programHandle) { super.onCreate(programHandle); hueLocation = GLES20.glGetUniformLocation(programHandle, "hue"); - GlUtils.checkLocation(hueLocation, "hue"); + Egloo.checkGlProgramLocation(hueLocation, "hue"); } @Override @@ -101,6 +101,6 @@ public class HueFilter extends BaseFilter implements OneParameterFilter { // map it on 360 degree circle float shaderHue = ((hue - 45) / 45f + 0.5f) * -1; GLES20.glUniform1f(hueLocation, shaderHue); - GlUtils.checkError("glUniform1f"); + Egloo.checkGlError("glUniform1f"); } } \ No newline at end of file diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/LomoishFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/LomoishFilter.java index 411e1531..71cb9ec1 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/LomoishFilter.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/LomoishFilter.java @@ -5,7 +5,7 @@ import android.opengl.GLES20; import androidx.annotation.NonNull; import com.otaliastudios.cameraview.filter.BaseFilter; -import com.otaliastudios.cameraview.internal.GlUtils; +import com.otaliastudios.opengl.core.Egloo; import java.util.Random; @@ -122,13 +122,13 @@ public class LomoishFilter extends BaseFilter { public void onCreate(int programHandle) { super.onCreate(programHandle); scaleLocation = GLES20.glGetUniformLocation(programHandle, "scale"); - GlUtils.checkLocation(scaleLocation, "scale"); + Egloo.checkGlProgramLocation(scaleLocation, "scale"); maxDistLocation = GLES20.glGetUniformLocation(programHandle, "inv_max_dist"); - GlUtils.checkLocation(maxDistLocation, "inv_max_dist"); + Egloo.checkGlProgramLocation(maxDistLocation, "inv_max_dist"); stepSizeXLocation = GLES20.glGetUniformLocation(programHandle, "stepsizeX"); - GlUtils.checkLocation(stepSizeXLocation, "stepsizeX"); + Egloo.checkGlProgramLocation(stepSizeXLocation, "stepsizeX"); stepSizeYLocation = GLES20.glGetUniformLocation(programHandle, "stepsizeY"); - GlUtils.checkLocation(stepSizeYLocation, "stepsizeY"); + Egloo.checkGlProgramLocation(stepSizeYLocation, "stepsizeY"); } @Override @@ -153,12 +153,12 @@ public class LomoishFilter extends BaseFilter { } float maxDist = ((float) Math.sqrt(scale[0] * scale[0] + scale[1] * scale[1])) * 0.5f; GLES20.glUniform2fv(scaleLocation, 1, scale, 0); - GlUtils.checkError("glUniform2fv"); + Egloo.checkGlError("glUniform2fv"); GLES20.glUniform1f(maxDistLocation, 1.0F / maxDist); - GlUtils.checkError("glUniform1f"); + Egloo.checkGlError("glUniform1f"); GLES20.glUniform1f(stepSizeXLocation, 1.0F / width); - GlUtils.checkError("glUniform1f"); + Egloo.checkGlError("glUniform1f"); GLES20.glUniform1f(stepSizeYLocation, 1.0F / height); - GlUtils.checkError("glUniform1f"); + Egloo.checkGlError("glUniform1f"); } } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/SaturationFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/SaturationFilter.java index 26d1b357..2dcae76c 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/SaturationFilter.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/SaturationFilter.java @@ -6,7 +6,7 @@ import androidx.annotation.NonNull; import com.otaliastudios.cameraview.filter.BaseFilter; import com.otaliastudios.cameraview.filter.OneParameterFilter; -import com.otaliastudios.cameraview.internal.GlUtils; +import com.otaliastudios.opengl.core.Egloo; /** * Adjusts color saturation. @@ -93,9 +93,9 @@ public class SaturationFilter extends BaseFilter implements OneParameterFilter { public void onCreate(int programHandle) { super.onCreate(programHandle); scaleLocation = GLES20.glGetUniformLocation(programHandle, "scale"); - GlUtils.checkLocation(scaleLocation, "scale"); + Egloo.checkGlProgramLocation(scaleLocation, "scale"); exponentsLocation = GLES20.glGetUniformLocation(programHandle, "exponents"); - GlUtils.checkLocation(exponentsLocation, "exponents"); + Egloo.checkGlProgramLocation(exponentsLocation, "exponents"); } @Override @@ -110,18 +110,18 @@ public class SaturationFilter extends BaseFilter implements OneParameterFilter { super.onPreDraw(timestampUs, transformMatrix); if (scale > 0.0f) { GLES20.glUniform1f(scaleLocation, 0F); - GlUtils.checkError("glUniform1f"); + Egloo.checkGlError("glUniform1f"); GLES20.glUniform3f(exponentsLocation, (0.9f * scale) + 1.0f, (2.1f * scale) + 1.0f, (2.7f * scale) + 1.0f ); - GlUtils.checkError("glUniform3f"); + Egloo.checkGlError("glUniform3f"); } else { GLES20.glUniform1f(scaleLocation, 1.0F + scale); - GlUtils.checkError("glUniform1f"); + Egloo.checkGlError("glUniform1f"); GLES20.glUniform3f(exponentsLocation, 0F, 0F, 0F); - GlUtils.checkError("glUniform3f"); + Egloo.checkGlError("glUniform3f"); } } } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/SharpnessFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/SharpnessFilter.java index 5f100ebc..e9e8f9ba 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/SharpnessFilter.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/SharpnessFilter.java @@ -6,7 +6,7 @@ import androidx.annotation.NonNull; import com.otaliastudios.cameraview.filter.BaseFilter; import com.otaliastudios.cameraview.filter.OneParameterFilter; -import com.otaliastudios.cameraview.internal.GlUtils; +import com.otaliastudios.opengl.core.Egloo; /** * Sharpens the input frames. @@ -100,11 +100,11 @@ public class SharpnessFilter extends BaseFilter implements OneParameterFilter { public void onCreate(int programHandle) { super.onCreate(programHandle); scaleLocation = GLES20.glGetUniformLocation(programHandle, "scale"); - GlUtils.checkLocation(scaleLocation, "scale"); + Egloo.checkGlProgramLocation(scaleLocation, "scale"); stepSizeXLocation = GLES20.glGetUniformLocation(programHandle, "stepsizeX"); - GlUtils.checkLocation(stepSizeXLocation, "stepsizeX"); + Egloo.checkGlProgramLocation(stepSizeXLocation, "stepsizeX"); stepSizeYLocation = GLES20.glGetUniformLocation(programHandle, "stepsizeY"); - GlUtils.checkLocation(stepSizeYLocation, "stepsizeY"); + Egloo.checkGlProgramLocation(stepSizeYLocation, "stepsizeY"); } @Override @@ -119,10 +119,10 @@ public class SharpnessFilter extends BaseFilter implements OneParameterFilter { protected void onPreDraw(long timestampUs, @NonNull float[] transformMatrix) { super.onPreDraw(timestampUs, transformMatrix); GLES20.glUniform1f(scaleLocation, scale); - GlUtils.checkError("glUniform1f"); + Egloo.checkGlError("glUniform1f"); GLES20.glUniform1f(stepSizeXLocation, 1.0F / width); - GlUtils.checkError("glUniform1f"); + Egloo.checkGlError("glUniform1f"); GLES20.glUniform1f(stepSizeYLocation, 1.0F / height); - GlUtils.checkError("glUniform1f"); + Egloo.checkGlError("glUniform1f"); } } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/TemperatureFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/TemperatureFilter.java index 432b1e37..96fc32dc 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/TemperatureFilter.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/TemperatureFilter.java @@ -6,7 +6,7 @@ import androidx.annotation.NonNull; import com.otaliastudios.cameraview.filter.BaseFilter; import com.otaliastudios.cameraview.filter.OneParameterFilter; -import com.otaliastudios.cameraview.internal.GlUtils; +import com.otaliastudios.opengl.core.Egloo; /** * Adjusts color temperature. @@ -84,7 +84,7 @@ public class TemperatureFilter extends BaseFilter implements OneParameterFilter public void onCreate(int programHandle) { super.onCreate(programHandle); scaleLocation = GLES20.glGetUniformLocation(programHandle, "scale"); - GlUtils.checkLocation(scaleLocation, "scale"); + Egloo.checkGlProgramLocation(scaleLocation, "scale"); } @Override @@ -97,6 +97,6 @@ public class TemperatureFilter extends BaseFilter implements OneParameterFilter protected void onPreDraw(long timestampUs, @NonNull float[] transformMatrix) { super.onPreDraw(timestampUs, transformMatrix); GLES20.glUniform1f(scaleLocation, scale); - GlUtils.checkError("glUniform1f"); + Egloo.checkGlError("glUniform1f"); } } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/TintFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/TintFilter.java index a5f8b146..ea7fc457 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/TintFilter.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/TintFilter.java @@ -8,7 +8,7 @@ import androidx.annotation.NonNull; import com.otaliastudios.cameraview.filter.BaseFilter; import com.otaliastudios.cameraview.filter.OneParameterFilter; -import com.otaliastudios.cameraview.internal.GlUtils; +import com.otaliastudios.opengl.core.Egloo; /** @@ -81,7 +81,7 @@ public class TintFilter extends BaseFilter implements OneParameterFilter { public void onCreate(int programHandle) { super.onCreate(programHandle); tintLocation = GLES20.glGetUniformLocation(programHandle, "tint"); - GlUtils.checkLocation(tintLocation, "tint"); + Egloo.checkGlProgramLocation(tintLocation, "tint"); } @Override @@ -99,6 +99,6 @@ public class TintFilter extends BaseFilter implements OneParameterFilter { Color.blue(tint) / 255f }; GLES20.glUniform3fv(tintLocation, 1, channels, 0); - GlUtils.checkError("glUniform3fv"); + Egloo.checkGlError("glUniform3fv"); } } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/VignetteFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/VignetteFilter.java index fcdd3bf2..1ac400e2 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/filters/VignetteFilter.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filters/VignetteFilter.java @@ -6,7 +6,7 @@ import androidx.annotation.NonNull; import com.otaliastudios.cameraview.filter.BaseFilter; import com.otaliastudios.cameraview.filter.TwoParameterFilter; -import com.otaliastudios.cameraview.internal.GlUtils; +import com.otaliastudios.opengl.core.Egloo; /** @@ -126,13 +126,13 @@ public class VignetteFilter extends BaseFilter implements TwoParameterFilter { public void onCreate(int programHandle) { super.onCreate(programHandle); mRangeLocation = GLES20.glGetUniformLocation(programHandle, "range"); - GlUtils.checkLocation(mRangeLocation, "range"); + Egloo.checkGlProgramLocation(mRangeLocation, "range"); mMaxDistLocation = GLES20.glGetUniformLocation(programHandle, "inv_max_dist"); - GlUtils.checkLocation(mMaxDistLocation, "inv_max_dist"); + Egloo.checkGlProgramLocation(mMaxDistLocation, "inv_max_dist"); mShadeLocation = GLES20.glGetUniformLocation(programHandle, "shade"); - GlUtils.checkLocation(mShadeLocation, "shade"); + Egloo.checkGlProgramLocation(mShadeLocation, "shade"); mScaleLocation = GLES20.glGetUniformLocation(programHandle, "scale"); - GlUtils.checkLocation(mScaleLocation, "scale"); + Egloo.checkGlProgramLocation(mScaleLocation, "scale"); } @Override @@ -156,20 +156,20 @@ public class VignetteFilter extends BaseFilter implements TwoParameterFilter { scale[1] = 1f; } GLES20.glUniform2fv(mScaleLocation, 1, scale, 0); - GlUtils.checkError("glUniform2fv"); + Egloo.checkGlError("glUniform2fv"); float maxDist = ((float) Math.sqrt(scale[0] * scale[0] + scale[1] * scale[1])) * 0.5f; GLES20.glUniform1f(mMaxDistLocation, 1F / maxDist); - GlUtils.checkError("glUniform1f"); + Egloo.checkGlError("glUniform1f"); GLES20.glUniform1f(mShadeLocation, mShade); - GlUtils.checkError("glUniform1f"); + Egloo.checkGlError("glUniform1f"); // The 'range' is between 1.3 to 0.6. When scale is zero then range is 1.3 // which means no vignette at all because the luminousity difference is // less than 1/256 and will cause nothing. float range = (1.30f - (float) Math.sqrt(mScale) * 0.7f); GLES20.glUniform1f(mRangeLocation, range); - GlUtils.checkError("glUniform1f"); + Egloo.checkGlError("glUniform1f"); } } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/GlTextureDrawer.java b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/GlTextureDrawer.java new file mode 100644 index 00000000..8a260b37 --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/GlTextureDrawer.java @@ -0,0 +1,96 @@ +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 final 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 draw(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("draw start"); + // Select the program and the active texture. + GLES20.glUseProgram(mProgramHandle); + Egloo.checkGlError("glUseProgram"); + GLES20.glActiveTexture(mTexture.getUnit()); + GLES20.glBindTexture(mTexture.getTarget(), mTexture.getId()); + + // Draw and release. + mFilter.draw(timestampUs, mTextureTransform); + GLES20.glBindTexture(mTexture.getTarget(), 0); + GLES20.glUseProgram(0); + Egloo.checkGlError("draw end"); + } + + public void release() { + if (mProgramHandle == -1) return; + mFilter.onDestroy(); + GLES20.glDeleteProgram(mProgramHandle); + mProgramHandle = -1; + } +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/GlUtils.java b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/GlUtils.java deleted file mode 100644 index 05d5c4d8..00000000 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/GlUtils.java +++ /dev/null @@ -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; - } -} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/egl/EglBaseSurface.java b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/egl/EglBaseSurface.java index f9ceec53..be07f0b1 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/egl/EglBaseSurface.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/egl/EglBaseSurface.java @@ -25,7 +25,6 @@ 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; @@ -188,7 +187,7 @@ public class EglBaseSurface { buf.order(ByteOrder.LITTLE_ENDIAN); GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buf); - GlUtils.checkError("glReadPixels"); + Egloo.checkGlError("glReadPixels"); buf.rewind(); BufferedOutputStream bos = null; @@ -231,7 +230,7 @@ public class EglBaseSurface { buf.order(ByteOrder.LITTLE_ENDIAN); GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buf); - GlUtils.checkError("glReadPixels"); + Egloo.checkGlError("glReadPixels"); buf.rewind(); ByteArrayOutputStream bos = new ByteArrayOutputStream(buf.array().length); 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 index 685b00ca..47302cff 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/egl/EglViewport.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/egl/EglViewport.java @@ -9,7 +9,7 @@ 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; +import com.otaliastudios.opengl.program.GlProgram; public class EglViewport { @@ -35,7 +35,7 @@ public class EglViewport { } private void createProgram() { - mProgramHandle = GlUtils.createProgram(mFilter.getVertexShader(), + mProgramHandle = GlProgram.create(mFilter.getVertexShader(), mFilter.getFragmentShader()); mFilter.onCreate(mProgramHandle); } @@ -51,12 +51,12 @@ public class EglViewport { public int createTexture() { int[] textures = new int[1]; GLES20.glGenTextures(1, textures, 0); - GlUtils.checkError("glGenTextures"); + Egloo.checkGlError("glGenTextures"); int texId = textures[0]; GLES20.glActiveTexture(mTextureUnit); GLES20.glBindTexture(mTextureTarget, texId); - GlUtils.checkError("glBindTexture " + texId); + Egloo.checkGlError("glBindTexture " + texId); GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST); @@ -66,7 +66,7 @@ public class EglViewport { 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"); + Egloo.checkGlError("glTexParameter"); return texId; } @@ -77,7 +77,7 @@ public class EglViewport { mPendingFilter = filter; } - public void drawFrame(long timestampUs, int textureId, float[] textureMatrix) { + public void draw(long timestampUs, int textureId, float[] textureMatrix) { if (mPendingFilter != null) { release(); mFilter = mPendingFilter; @@ -85,11 +85,11 @@ public class EglViewport { createProgram(); } - GlUtils.checkError("draw start"); + Egloo.checkGlError("draw start"); // Select the program and the active texture. GLES20.glUseProgram(mProgramHandle); - GlUtils.checkError("glUseProgram"); + Egloo.checkGlError("glUseProgram"); GLES20.glActiveTexture(mTextureUnit); GLES20.glBindTexture(mTextureTarget, textureId); 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 03b87222..bbce8af0 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/overlay/OverlayDrawer.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/overlay/OverlayDrawer.java @@ -105,7 +105,7 @@ public class OverlayDrawer { GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA); synchronized (mIssue514WorkaroundLock) { - mViewport.drawFrame(timestampUs, mTextureId, mTransform); + mViewport.draw(timestampUs, mTextureId, mTransform); } } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/picture/SnapshotGlPictureRecorder.java b/cameraview/src/main/java/com/otaliastudios/cameraview/picture/SnapshotGlPictureRecorder.java index 35b76a37..29f359ef 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/picture/SnapshotGlPictureRecorder.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/picture/SnapshotGlPictureRecorder.java @@ -232,7 +232,7 @@ public class SnapshotGlPictureRecorder extends SnapshotPictureRecorder { // 5. Draw and save long timestampUs = surfaceTexture.getTimestamp() / 1000L; LOG.i("takeFrame:", "timestampUs:", timestampUs); - mViewport.drawFrame(timestampUs, mTextureId, mTransform); + mViewport.draw(timestampUs, mTextureId, mTransform); if (mHasOverlay) mOverlayDrawer.render(timestampUs); mResult.data = eglSurface.saveFrameTo(Bitmap.CompressFormat.JPEG); 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 c678d85b..2e25e25d 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/preview/GlCameraPreview.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/preview/GlCameraPreview.java @@ -231,7 +231,7 @@ public class GlCameraPreview extends FilterCameraPreview { "hasReachedMaxLength:", hasReachedMaxLength(), "thread:", Thread.currentThread(), "- gl rendering."); - mViewport.drawFrame(frame.timestampUs(), mConfig.textureId, transform); + mViewport.draw(frame.timestampUs(), mConfig.textureId, transform); if (mConfig.hasOverlay()) { mConfig.overlayDrawer.render(frame.timestampUs()); } diff --git a/settings.gradle b/settings.gradle index 48f809fc..a11db463 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,3 @@ include ':demo', ':cameraview' +include ':egloo' +project(':egloo').projectDir = file('../Egloo/library')