diff --git a/cameraview/build.gradle b/cameraview/build.gradle index 5b4ec368..c561ffc0 100644 --- a/cameraview/build.gradle +++ b/cameraview/build.gradle @@ -48,6 +48,7 @@ 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 'com.otaliastudios.opengl:egloo:0.4.0' } //endregion diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/BaseEglTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/BaseEglTest.java index 0bb74e0d..18865899 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/BaseEglTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/BaseEglTest.java @@ -1,32 +1,13 @@ package com.otaliastudios.cameraview; +import android.opengl.EGL14; -import android.graphics.Canvas; +import com.otaliastudios.opengl.core.EglCore; +import com.otaliastudios.opengl.surface.EglOffscreenSurface; +import com.otaliastudios.opengl.surface.EglSurface; -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.filters.SmallTest; - -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.overlay.Overlay; -import com.otaliastudios.cameraview.overlay.OverlayDrawer; -import com.otaliastudios.cameraview.size.Size; - -import org.hamcrest.BaseMatcher; -import org.hamcrest.Description; import org.junit.After; import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import static org.junit.Assert.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; @SuppressWarnings("WeakerAccess") @@ -36,19 +17,18 @@ public abstract class BaseEglTest extends BaseTest { protected final static int HEIGHT = 100; protected EglCore eglCore; - protected EglBaseSurface eglSurface; + protected EglSurface eglSurface; @Before public void setUp() { - eglCore = new EglCore(null, EglCore.FLAG_RECORDABLE); - eglSurface = new EglBaseSurface(eglCore); - eglSurface.createOffscreenSurface(WIDTH, HEIGHT); + eglCore = new EglCore(EGL14.EGL_NO_CONTEXT, EglCore.FLAG_RECORDABLE); + eglSurface = new EglOffscreenSurface(eglCore, WIDTH, HEIGHT); eglSurface.makeCurrent(); } @After public void tearDown() { - eglSurface.releaseEglSurface(); + eglSurface.release(); eglSurface = null; eglCore.release(); eglCore = null; diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/CameraIntegrationTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/CameraIntegrationTest.java index 3866dd70..e10f608e 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/CameraIntegrationTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/engine/CameraIntegrationTest.java @@ -31,7 +31,7 @@ import com.otaliastudios.cameraview.frame.FrameProcessor; import com.otaliastudios.cameraview.size.SizeSelectors; import com.otaliastudios.cameraview.tools.Emulator; import com.otaliastudios.cameraview.tools.Op; -import com.otaliastudios.cameraview.internal.utils.WorkerHandler; +import com.otaliastudios.cameraview.internal.WorkerHandler; import com.otaliastudios.cameraview.overlay.Overlay; import com.otaliastudios.cameraview.size.Size; import com.otaliastudios.cameraview.tools.Retry; @@ -45,7 +45,6 @@ import androidx.test.rule.GrantPermissionRule; import org.junit.After; import org.junit.Before; -import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; import org.mockito.ArgumentMatcher; 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..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,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.cameraview.internal.GlTextureDrawer; +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); } @@ -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.drawFrame(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 09591425..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,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.cameraview.internal.GlTextureDrawer; +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()); @@ -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.drawFrame(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() { @@ -173,7 +175,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 +191,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); @@ -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.drawFrame(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/internal/utils/CamcorderProfilesTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/CamcorderProfilesTest.java similarity index 95% rename from cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/utils/CamcorderProfilesTest.java rename to cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/CamcorderProfilesTest.java index 169e1c48..31057235 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/utils/CamcorderProfilesTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/CamcorderProfilesTest.java @@ -1,4 +1,4 @@ -package com.otaliastudios.cameraview.internal.utils; +package com.otaliastudios.cameraview.internal; import android.media.CamcorderProfile; @@ -8,6 +8,7 @@ import androidx.test.filters.SmallTest; import com.otaliastudios.cameraview.BaseTest; import com.otaliastudios.cameraview.CameraUtils; +import com.otaliastudios.cameraview.internal.CamcorderProfiles; import com.otaliastudios.cameraview.tools.SdkExclude; import com.otaliastudios.cameraview.size.Size; diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/utils/CropHelperTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/CropHelperTest.java similarity index 94% rename from cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/utils/CropHelperTest.java rename to cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/CropHelperTest.java index da016339..bb7291ba 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/utils/CropHelperTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/CropHelperTest.java @@ -1,9 +1,10 @@ -package com.otaliastudios.cameraview.internal.utils; +package com.otaliastudios.cameraview.internal; import android.graphics.Rect; import com.otaliastudios.cameraview.BaseTest; +import com.otaliastudios.cameraview.internal.CropHelper; import com.otaliastudios.cameraview.size.AspectRatio; import com.otaliastudios.cameraview.size.Size; diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/utils/OrientationHelperTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/OrientationHelperTest.java similarity index 97% rename from cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/utils/OrientationHelperTest.java rename to cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/OrientationHelperTest.java index b5fde579..5d20ae15 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/utils/OrientationHelperTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/OrientationHelperTest.java @@ -1,4 +1,4 @@ -package com.otaliastudios.cameraview.internal.utils; +package com.otaliastudios.cameraview.internal; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -6,6 +6,7 @@ import androidx.test.filters.SmallTest; import android.view.OrientationEventListener; import com.otaliastudios.cameraview.BaseTest; +import com.otaliastudios.cameraview.internal.OrientationHelper; import org.junit.After; import org.junit.Before; diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/utils/RotationHelperTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/RotationHelperTest.java similarity index 94% rename from cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/utils/RotationHelperTest.java rename to cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/RotationHelperTest.java index b2be77bc..caaea54c 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/utils/RotationHelperTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/RotationHelperTest.java @@ -1,4 +1,4 @@ -package com.otaliastudios.cameraview.internal.utils; +package com.otaliastudios.cameraview.internal; import android.graphics.ImageFormat; @@ -8,6 +8,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.otaliastudios.cameraview.BaseTest; +import com.otaliastudios.cameraview.internal.RotationHelper; import com.otaliastudios.cameraview.size.Size; import org.junit.Test; diff --git a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/utils/WorkerHandlerTest.java b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/WorkerHandlerTest.java similarity index 98% rename from cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/utils/WorkerHandlerTest.java rename to cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/WorkerHandlerTest.java index 57d531c4..40dcbc69 100644 --- a/cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/utils/WorkerHandlerTest.java +++ b/cameraview/src/androidTest/java/com/otaliastudios/cameraview/internal/WorkerHandlerTest.java @@ -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.Tasks; import com.otaliastudios.cameraview.BaseTest; +import com.otaliastudios.cameraview.internal.WorkerHandler; import com.otaliastudios.cameraview.tools.Op; import androidx.annotation.NonNull; 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..eea3def7 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.internal.GlTextureDrawer; 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 @@ -79,22 +60,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)).drawFrame( - 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; + GlTextureDrawer textureDrawer = spy(drawer.mTextureDrawer); + drawer.mTextureDrawer = textureDrawer; drawer.release(); - verify(viewport, times(1)).release(); + verify(textureDrawer, times(1)).release(); } } diff --git a/cameraview/src/main/AndroidManifest.xml b/cameraview/src/main/AndroidManifest.xml index 5da7cf09..ce05aa56 100644 --- a/cameraview/src/main/AndroidManifest.xml +++ b/cameraview/src/main/AndroidManifest.xml @@ -1,4 +1,5 @@ @@ -20,6 +21,8 @@ android:name="android.hardware.microphone" android:required="false"/> + + \ No newline at end of file diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraOptions.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraOptions.java index c6b9f15a..a8380ef1 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraOptions.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraOptions.java @@ -2,16 +2,6 @@ package com.otaliastudios.cameraview; import android.graphics.ImageFormat; -import android.hardware.Camera; -import android.hardware.camera2.CameraAccessException; -import android.hardware.camera2.CameraCharacteristics; -import android.hardware.camera2.CameraManager; -import android.hardware.camera2.params.StreamConfigurationMap; -import android.media.CamcorderProfile; -import android.media.MediaRecorder; -import android.os.Build; -import android.util.Range; -import android.util.Rational; import com.otaliastudios.cameraview.controls.Audio; import com.otaliastudios.cameraview.controls.Control; @@ -20,26 +10,21 @@ import com.otaliastudios.cameraview.controls.Facing; import com.otaliastudios.cameraview.controls.Flash; import com.otaliastudios.cameraview.controls.PictureFormat; import com.otaliastudios.cameraview.controls.Preview; -import com.otaliastudios.cameraview.engine.mappers.Camera1Mapper; -import com.otaliastudios.cameraview.engine.mappers.Camera2Mapper; import com.otaliastudios.cameraview.gesture.GestureAction; import com.otaliastudios.cameraview.controls.Grid; import com.otaliastudios.cameraview.controls.Hdr; import com.otaliastudios.cameraview.controls.Mode; import com.otaliastudios.cameraview.controls.VideoCodec; import com.otaliastudios.cameraview.controls.WhiteBalance; -import com.otaliastudios.cameraview.internal.utils.CamcorderProfiles; import com.otaliastudios.cameraview.size.AspectRatio; import com.otaliastudios.cameraview.size.Size; import androidx.annotation.NonNull; -import androidx.annotation.RequiresApi; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; -import java.util.List; import java.util.Set; /** diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraUtils.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraUtils.java index b12b12b6..17e06cf2 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraUtils.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraUtils.java @@ -11,8 +11,8 @@ import android.os.Handler; import com.otaliastudios.cameraview.controls.Facing; import com.otaliastudios.cameraview.engine.mappers.Camera1Mapper; -import com.otaliastudios.cameraview.internal.utils.ExifHelper; -import com.otaliastudios.cameraview.internal.utils.WorkerHandler; +import com.otaliastudios.cameraview.internal.ExifHelper; +import com.otaliastudios.cameraview.internal.WorkerHandler; import androidx.annotation.NonNull; import androidx.annotation.Nullable; diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java index dec8b883..5dbd656b 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/CameraView.java @@ -66,8 +66,8 @@ import com.otaliastudios.cameraview.gesture.PinchGestureFinder; import com.otaliastudios.cameraview.gesture.ScrollGestureFinder; import com.otaliastudios.cameraview.gesture.TapGestureFinder; import com.otaliastudios.cameraview.internal.GridLinesLayout; -import com.otaliastudios.cameraview.internal.utils.CropHelper; -import com.otaliastudios.cameraview.internal.utils.OrientationHelper; +import com.otaliastudios.cameraview.internal.CropHelper; +import com.otaliastudios.cameraview.internal.OrientationHelper; import com.otaliastudios.cameraview.markers.AutoFocusMarker; import com.otaliastudios.cameraview.markers.AutoFocusTrigger; import com.otaliastudios.cameraview.markers.MarkerLayout; diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera1Engine.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera1Engine.java index 069864c3..95549c64 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera1Engine.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera1Engine.java @@ -37,7 +37,7 @@ import com.otaliastudios.cameraview.gesture.Gesture; import com.otaliastudios.cameraview.controls.Hdr; import com.otaliastudios.cameraview.controls.Mode; import com.otaliastudios.cameraview.controls.WhiteBalance; -import com.otaliastudios.cameraview.internal.utils.CropHelper; +import com.otaliastudios.cameraview.internal.CropHelper; import com.otaliastudios.cameraview.metering.MeteringRegions; import com.otaliastudios.cameraview.metering.MeteringTransform; import com.otaliastudios.cameraview.picture.Full1PictureRecorder; diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java index a3313c85..7e09f588 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/Camera2Engine.java @@ -60,7 +60,7 @@ import com.otaliastudios.cameraview.frame.Frame; import com.otaliastudios.cameraview.frame.FrameManager; import com.otaliastudios.cameraview.frame.ImageFrameManager; import com.otaliastudios.cameraview.gesture.Gesture; -import com.otaliastudios.cameraview.internal.utils.CropHelper; +import com.otaliastudios.cameraview.internal.CropHelper; import com.otaliastudios.cameraview.metering.MeteringRegions; import com.otaliastudios.cameraview.picture.Full2PictureRecorder; import com.otaliastudios.cameraview.picture.Snapshot2PictureRecorder; diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java index e2c4733d..2817bbdb 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/CameraEngine.java @@ -2,7 +2,6 @@ package com.otaliastudios.cameraview.engine; import android.content.Context; import android.graphics.PointF; -import android.graphics.RectF; import android.location.Location; @@ -29,7 +28,7 @@ import com.otaliastudios.cameraview.engine.offset.Angles; import com.otaliastudios.cameraview.engine.offset.Reference; import com.otaliastudios.cameraview.frame.Frame; import com.otaliastudios.cameraview.frame.FrameManager; -import com.otaliastudios.cameraview.internal.utils.WorkerHandler; +import com.otaliastudios.cameraview.internal.WorkerHandler; import com.otaliastudios.cameraview.picture.PictureRecorder; import com.otaliastudios.cameraview.preview.CameraPreview; import com.otaliastudios.cameraview.controls.Audio; diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/options/Camera1Options.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/options/Camera1Options.java index af629578..40266e84 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/options/Camera1Options.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/options/Camera1Options.java @@ -13,7 +13,7 @@ import com.otaliastudios.cameraview.controls.Hdr; import com.otaliastudios.cameraview.controls.PictureFormat; import com.otaliastudios.cameraview.controls.WhiteBalance; import com.otaliastudios.cameraview.engine.mappers.Camera1Mapper; -import com.otaliastudios.cameraview.internal.utils.CamcorderProfiles; +import com.otaliastudios.cameraview.internal.CamcorderProfiles; import com.otaliastudios.cameraview.size.AspectRatio; import com.otaliastudios.cameraview.size.Size; diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/options/Camera2Options.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/options/Camera2Options.java index 91a37a0d..43c99876 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/options/Camera2Options.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/options/Camera2Options.java @@ -21,7 +21,7 @@ import com.otaliastudios.cameraview.controls.Hdr; import com.otaliastudios.cameraview.controls.PictureFormat; import com.otaliastudios.cameraview.controls.WhiteBalance; import com.otaliastudios.cameraview.engine.mappers.Camera2Mapper; -import com.otaliastudios.cameraview.internal.utils.CamcorderProfiles; +import com.otaliastudios.cameraview.internal.CamcorderProfiles; import com.otaliastudios.cameraview.size.AspectRatio; import com.otaliastudios.cameraview.size.Size; diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/orchestrator/CameraOrchestrator.java b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/orchestrator/CameraOrchestrator.java index 45166288..d703eabf 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/engine/orchestrator/CameraOrchestrator.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/engine/orchestrator/CameraOrchestrator.java @@ -8,7 +8,7 @@ import com.google.android.gms.tasks.Task; import com.google.android.gms.tasks.TaskCompletionSource; import com.google.android.gms.tasks.Tasks; import com.otaliastudios.cameraview.CameraLogger; -import com.otaliastudios.cameraview.internal.utils.WorkerHandler; +import com.otaliastudios.cameraview.internal.WorkerHandler; import java.util.ArrayDeque; import java.util.ArrayList; 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..b7aa2cc7 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,13 @@ 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; /** * A base implementation of {@link Filter} that just leaves the fragment shader to subclasses. @@ -90,26 +88,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 GlDrawable programDrawable = null; @VisibleForTesting Size size; @SuppressWarnings("WeakerAccess") @@ -141,28 +121,23 @@ 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, + program = new GlTextureProgram(programHandle, + vertexPositionName, + vertexModelViewProjectionMatrixName, + vertexTextureCoordinateName, vertexTransformMatrixName); - GlUtils.checkLocation(vertexTransformMatrixLocation, vertexTransformMatrixName); + programDrawable = new GlRect(); } @Override public void onDestroy() { - programHandle = -1; - vertexPositionLocation = -1; - vertexTextureCoordinateLocation = -1; - vertexModelViewProjectionMatrixLocation = -1; - vertexTransformMatrixLocation = -1; + // Since we used the handle constructor of GlTextureProgram, calling release here + // will NOT destroy the GL program. This is important because Filters are not supposed + // to have ownership of programs. Creation and deletion happen outside, and deleting twice + // would cause an error. + program.release(); + program = null; + programDrawable = 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 @@ -244,6 +194,7 @@ public abstract class BaseFilter implements Filter { return copy; } + @NonNull protected BaseFilter onCopy() { try { return getClass().newInstance(); 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..5742336c 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,12 @@ 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 com.otaliastudios.opengl.texture.GlFramebuffer; +import com.otaliastudios.opengl.texture.GlTexture; import java.util.ArrayList; import java.util.Arrays; @@ -42,13 +46,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 GlFramebuffer outputFramebuffer = null; + private GlTexture outputTexture = null; } @VisibleForTesting final List filters = new ArrayList<>(); @@ -62,7 +65,6 @@ public class MultiFilter implements Filter, OneParameterFilter, TwoParameterFilt * Creates a new group with the given filters. * @param filters children */ - @SuppressWarnings("WeakerAccess") public MultiFilter(@NonNull Filter... filters) { this(Arrays.asList(filters)); } @@ -105,86 +107,54 @@ 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; + state.outputTexture = new GlTexture(GLES20.GL_TEXTURE0, + GLES20.GL_TEXTURE_2D, + state.size.getWidth(), + state.size.getHeight()); + state.outputFramebuffer = new GlFramebuffer(); + state.outputFramebuffer.attach(state.outputTexture); } 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; + state.outputFramebuffer.release(); + state.outputFramebuffer = null; + state.outputTexture.release(); + state.outputTexture = null; } private void maybeSetSize(@NonNull Filter filter) { @@ -196,24 +166,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 +191,7 @@ public class MultiFilter implements Filter, OneParameterFilter, TwoParameterFilt synchronized (lock) { for (Filter filter : filters) { maybeDestroyFramebuffer(filter); - maybeDestroy(filter); + maybeDestroyProgram(filter); } } } @@ -244,9 +214,10 @@ public class MultiFilter implements Filter, OneParameterFilter, TwoParameterFilt boolean isLast = i == filters.size() - 1; 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 +226,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); + state.outputFramebuffer.bind(); GLES20.glClearColor(0, 0, 0, 0); } else { GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0); @@ -267,16 +238,17 @@ 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); + state.outputTexture.bind(); } else { GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0); + GLES20.glActiveTexture(GLES20.GL_TEXTURE0); } GLES20.glUseProgram(0); @@ -289,6 +261,9 @@ public class MultiFilter implements Filter, OneParameterFilter, TwoParameterFilt public Filter copy() { synchronized (lock) { MultiFilter copy = new MultiFilter(); + if (size != null) { + copy.setSize(size.getWidth(), size.getHeight()); + } for (Filter filter : filters) { copy.addFilter(filter.copy()); } diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filter/SimpleFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filter/SimpleFilter.java index eda5ec3e..ca8d4b15 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/filter/SimpleFilter.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filter/SimpleFilter.java @@ -33,6 +33,7 @@ public final class SimpleFilter extends BaseFilter { return fragmentShader; } + @NonNull @Override protected BaseFilter onCopy() { return new SimpleFilter(fragmentShader); 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/utils/CamcorderProfiles.java b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/CamcorderProfiles.java similarity index 98% rename from cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/CamcorderProfiles.java rename to cameraview/src/main/java/com/otaliastudios/cameraview/internal/CamcorderProfiles.java index 93ec596e..9ea2f933 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/CamcorderProfiles.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/CamcorderProfiles.java @@ -1,4 +1,4 @@ -package com.otaliastudios.cameraview.internal.utils; +package com.otaliastudios.cameraview.internal; import android.annotation.SuppressLint; import android.media.CamcorderProfile; diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/CropHelper.java b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/CropHelper.java similarity index 96% rename from cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/CropHelper.java rename to cameraview/src/main/java/com/otaliastudios/cameraview/internal/CropHelper.java index f7f71ee7..649acc80 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/CropHelper.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/CropHelper.java @@ -1,4 +1,4 @@ -package com.otaliastudios.cameraview.internal.utils; +package com.otaliastudios.cameraview.internal; import android.graphics.Rect; diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/ExifHelper.java b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/ExifHelper.java similarity index 96% rename from cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/ExifHelper.java rename to cameraview/src/main/java/com/otaliastudios/cameraview/internal/ExifHelper.java index a890124f..8a150419 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/ExifHelper.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/ExifHelper.java @@ -1,4 +1,4 @@ -package com.otaliastudios.cameraview.internal.utils; +package com.otaliastudios.cameraview.internal; import androidx.exifinterface.media.ExifInterface; 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..24c956d5 --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/GlTextureDrawer.java @@ -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; + } +} 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/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/utils/OrientationHelper.java b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/OrientationHelper.java similarity index 99% rename from cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/OrientationHelper.java rename to cameraview/src/main/java/com/otaliastudios/cameraview/internal/OrientationHelper.java index 36c5f1d5..de47526a 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/OrientationHelper.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/OrientationHelper.java @@ -1,4 +1,4 @@ -package com.otaliastudios.cameraview.internal.utils; +package com.otaliastudios.cameraview.internal; import android.content.Context; import android.hardware.SensorManager; diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/Pool.java b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/Pool.java similarity index 98% rename from cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/Pool.java rename to cameraview/src/main/java/com/otaliastudios/cameraview/internal/Pool.java index d7c8b57c..5010b15b 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/Pool.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/Pool.java @@ -1,4 +1,4 @@ -package com.otaliastudios.cameraview.internal.utils; +package com.otaliastudios.cameraview.internal; import com.otaliastudios.cameraview.CameraLogger; diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/RotationHelper.java b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/RotationHelper.java similarity index 97% rename from cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/RotationHelper.java rename to cameraview/src/main/java/com/otaliastudios/cameraview/internal/RotationHelper.java index bed0f90c..88225e25 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/RotationHelper.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/RotationHelper.java @@ -1,4 +1,4 @@ -package com.otaliastudios.cameraview.internal.utils; +package com.otaliastudios.cameraview.internal; import com.otaliastudios.cameraview.size.Size; diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/WorkerHandler.java b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/WorkerHandler.java similarity index 99% rename from cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/WorkerHandler.java rename to cameraview/src/main/java/com/otaliastudios/cameraview/internal/WorkerHandler.java index 81743f8d..e5cb9702 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/utils/WorkerHandler.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/WorkerHandler.java @@ -1,4 +1,4 @@ -package com.otaliastudios.cameraview.internal.utils; +package com.otaliastudios.cameraview.internal; import android.os.Handler; import android.os.HandlerThread; 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 deleted file mode 100644 index f9ceec53..00000000 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/egl/EglBaseSurface.java +++ /dev/null @@ -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. - *

- * 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. - *

- * @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. - *

- * 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. - *

- * 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. - *

- * 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(); - } -} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/egl/EglCore.java b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/egl/EglCore.java deleted file mode 100644 index 3d9c6b65..00000000 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/egl/EglCore.java +++ /dev/null @@ -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). - *

- * 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. - *

- * Equivalent to EglCore(null, 0). - */ - public EglCore() { - this(null, 0); - } - - /** - * Prepares EGL display and context. - *

- * @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. - *

- * 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. - *

- * 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)); - } - } -} 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 685b00ca..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.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); - } -} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/egl/EglWindowSurface.java b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/egl/EglWindowSurface.java deleted file mode 100644 index 0cb49776..00000000 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/egl/EglWindowSurface.java +++ /dev/null @@ -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. - *

- * 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. - *

- * 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). - *

- * Does not require that the surface's EGL context be current. - */ - public void release() { - releaseEglSurface(); - if (mSurface != null) { - if (mReleaseSurface) { - mSurface.release(); - } - mSurface = null; - } - } -} 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..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.drawFrame(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/picture/Full1PictureRecorder.java b/cameraview/src/main/java/com/otaliastudios/cameraview/picture/Full1PictureRecorder.java index 7690419b..1260015a 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/picture/Full1PictureRecorder.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/picture/Full1PictureRecorder.java @@ -4,10 +4,9 @@ import android.hardware.Camera; import com.otaliastudios.cameraview.PictureResult; import com.otaliastudios.cameraview.engine.Camera1Engine; -import com.otaliastudios.cameraview.internal.utils.ExifHelper; +import com.otaliastudios.cameraview.internal.ExifHelper; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; import androidx.exifinterface.media.ExifInterface; import java.io.ByteArrayInputStream; diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/picture/Full2PictureRecorder.java b/cameraview/src/main/java/com/otaliastudios/cameraview/picture/Full2PictureRecorder.java index f4ed71c7..10ea5775 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/picture/Full2PictureRecorder.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/picture/Full2PictureRecorder.java @@ -15,8 +15,8 @@ import com.otaliastudios.cameraview.engine.Camera2Engine; import com.otaliastudios.cameraview.engine.action.Action; import com.otaliastudios.cameraview.engine.action.ActionHolder; import com.otaliastudios.cameraview.engine.action.BaseAction; -import com.otaliastudios.cameraview.internal.utils.ExifHelper; -import com.otaliastudios.cameraview.internal.utils.WorkerHandler; +import com.otaliastudios.cameraview.internal.ExifHelper; +import com.otaliastudios.cameraview.internal.WorkerHandler; import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/picture/Snapshot1PictureRecorder.java b/cameraview/src/main/java/com/otaliastudios/cameraview/picture/Snapshot1PictureRecorder.java index 71104f4e..ab4533d3 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/picture/Snapshot1PictureRecorder.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/picture/Snapshot1PictureRecorder.java @@ -7,9 +7,9 @@ import android.hardware.Camera; import com.otaliastudios.cameraview.PictureResult; import com.otaliastudios.cameraview.engine.Camera1Engine; import com.otaliastudios.cameraview.engine.offset.Reference; -import com.otaliastudios.cameraview.internal.utils.CropHelper; -import com.otaliastudios.cameraview.internal.utils.RotationHelper; -import com.otaliastudios.cameraview.internal.utils.WorkerHandler; +import com.otaliastudios.cameraview.internal.CropHelper; +import com.otaliastudios.cameraview.internal.RotationHelper; +import com.otaliastudios.cameraview.internal.WorkerHandler; import com.otaliastudios.cameraview.size.AspectRatio; import com.otaliastudios.cameraview.size.Size; 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..7b2dca41 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/picture/SnapshotGlPictureRecorder.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/picture/SnapshotGlPictureRecorder.java @@ -10,17 +10,14 @@ import android.opengl.Matrix; import android.os.Build; import com.otaliastudios.cameraview.PictureResult; -import com.otaliastudios.cameraview.internal.egl.EglBaseSurface; +import com.otaliastudios.cameraview.internal.GlTextureDrawer; import com.otaliastudios.cameraview.overlay.Overlay; import com.otaliastudios.cameraview.controls.Facing; import com.otaliastudios.cameraview.engine.CameraEngine; import com.otaliastudios.cameraview.engine.offset.Axis; import com.otaliastudios.cameraview.engine.offset.Reference; -import com.otaliastudios.cameraview.internal.egl.EglCore; -import com.otaliastudios.cameraview.internal.egl.EglViewport; -import com.otaliastudios.cameraview.internal.egl.EglWindowSurface; -import com.otaliastudios.cameraview.internal.utils.CropHelper; -import com.otaliastudios.cameraview.internal.utils.WorkerHandler; +import com.otaliastudios.cameraview.internal.CropHelper; +import com.otaliastudios.cameraview.internal.WorkerHandler; import com.otaliastudios.cameraview.overlay.OverlayDrawer; import com.otaliastudios.cameraview.preview.GlCameraPreview; import com.otaliastudios.cameraview.preview.RendererFrameCallback; @@ -28,6 +25,9 @@ import com.otaliastudios.cameraview.preview.RendererThread; import com.otaliastudios.cameraview.filter.Filter; import com.otaliastudios.cameraview.size.AspectRatio; import com.otaliastudios.cameraview.size.Size; +import com.otaliastudios.opengl.core.EglCore; +import com.otaliastudios.opengl.surface.EglSurface; +import com.otaliastudios.opengl.surface.EglWindowSurface; import androidx.annotation.NonNull; import androidx.annotation.WorkerThread; @@ -45,7 +45,7 @@ import android.view.Surface; * - We move to another thread, and create a new EGL surface for that EGL context. * - We make this new surface current, and re-draw the textureId on it * - [Optional: fill the overlayTextureId and draw it on the same surface] - * - We use glReadPixels (through {@link EglBaseSurface#saveFrameTo(Bitmap.CompressFormat)}) + * - We use glReadPixels (through {@link EglSurface#toByteArray(Bitmap.CompressFormat)}) * and save to file. * * We create a new EGL surface and redraw the frame because: @@ -62,12 +62,7 @@ public class SnapshotGlPictureRecorder extends SnapshotPictureRecorder { private Overlay mOverlay; private boolean mHasOverlay; private OverlayDrawer mOverlayDrawer; - - private int mTextureId; - private float[] mTransform; - - - private EglViewport mViewport; + private GlTextureDrawer mTextureDrawer; public SnapshotGlPictureRecorder( @NonNull PictureResult.Stub stub, @@ -114,14 +109,10 @@ public class SnapshotGlPictureRecorder extends SnapshotPictureRecorder { @RendererThread @TargetApi(Build.VERSION_CODES.KITKAT) protected void onRendererTextureCreated(int textureId) { - mTextureId = textureId; - mViewport = new EglViewport(); + mTextureDrawer = new GlTextureDrawer(textureId); // Need to crop the size. Rect crop = CropHelper.computeCrop(mResult.size, mOutputRatio); mResult.size = new Size(crop.width(), crop.height()); - mTransform = new float[16]; - Matrix.setIdentityM(mTransform, 0); - if (mHasOverlay) { mOverlayDrawer = new OverlayDrawer(mOverlay, mResult.size); } @@ -131,7 +122,7 @@ public class SnapshotGlPictureRecorder extends SnapshotPictureRecorder { @RendererThread @TargetApi(Build.VERSION_CODES.KITKAT) protected void onRendererFilterChanged(@NonNull Filter filter) { - mViewport.setFilter(filter.copy()); + mTextureDrawer.setFilter(filter.copy()); } @SuppressWarnings("WeakerAccess") @@ -194,8 +185,9 @@ public class SnapshotGlPictureRecorder extends SnapshotPictureRecorder { // 1. Create an EGL surface final EglCore core = new EglCore(eglContext, EglCore.FLAG_RECORDABLE); - final EglBaseSurface eglSurface = new EglWindowSurface(core, fakeOutputSurface); + final EglSurface eglSurface = new EglWindowSurface(core, fakeOutputSurface); eglSurface.makeCurrent(); + final float[] transform = mTextureDrawer.getTextureTransform(); // 2. Apply scale and crop boolean flip = mEngine.getAngles().flip(Reference.VIEW, Reference.SENSOR); @@ -203,17 +195,17 @@ public class SnapshotGlPictureRecorder extends SnapshotPictureRecorder { float realScaleY = flip ? scaleX : scaleY; float scaleTranslX = (1F - realScaleX) / 2F; float scaleTranslY = (1F - realScaleY) / 2F; - Matrix.translateM(mTransform, 0, scaleTranslX, scaleTranslY, 0); - Matrix.scaleM(mTransform, 0, realScaleX, realScaleY, 1); + Matrix.translateM(transform, 0, scaleTranslX, scaleTranslY, 0); + Matrix.scaleM(transform, 0, realScaleX, realScaleY, 1); // 3. Apply rotation and flip - Matrix.translateM(mTransform, 0, 0.5F, 0.5F, 0); // Go back to 0,0 - Matrix.rotateM(mTransform, 0, -mResult.rotation, 0, 0, 1); // Rotate (not sure why we need the minus) + Matrix.translateM(transform, 0, 0.5F, 0.5F, 0); // Go back to 0,0 + Matrix.rotateM(transform, 0, -mResult.rotation, 0, 0, 1); // Rotate (not sure why we need the minus) mResult.rotation = 0; if (mResult.facing == Facing.FRONT) { // 5. Flip horizontally for front camera - Matrix.scaleM(mTransform, 0, -1, 1, 1); + Matrix.scaleM(transform, 0, -1, 1, 1); } - Matrix.translateM(mTransform, 0, -0.5F, -0.5F, 0); // Go back to old position + Matrix.translateM(transform, 0, -0.5F, -0.5F, 0); // Go back to old position // 4. Do pretty much the same for overlays if (mHasOverlay) { @@ -232,13 +224,13 @@ public class SnapshotGlPictureRecorder extends SnapshotPictureRecorder { // 5. Draw and save long timestampUs = surfaceTexture.getTimestamp() / 1000L; LOG.i("takeFrame:", "timestampUs:", timestampUs); - mViewport.drawFrame(timestampUs, mTextureId, mTransform); + mTextureDrawer.draw(timestampUs); if (mHasOverlay) mOverlayDrawer.render(timestampUs); - mResult.data = eglSurface.saveFrameTo(Bitmap.CompressFormat.JPEG); + mResult.data = eglSurface.toByteArray(Bitmap.CompressFormat.JPEG); // 6. Cleanup - eglSurface.releaseEglSurface(); - mViewport.release(); + eglSurface.release(); + mTextureDrawer.release(); fakeOutputSurface.release(); if (mHasOverlay) mOverlayDrawer.release(); core.release(); 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..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 { private int mTransformRotation; private EglCore mEglCore; private EglWindowSurface mWindow; - private EglViewport mViewport; + private GlTextureDrawer mDrawer; private Pool mFramePool = new Pool<>(Integer.MAX_VALUE, new Pool.Factory() { @Override public Frame create() { @@ -99,7 +99,7 @@ public class TextureMediaEncoder extends VideoMediaEncoder { mEglCore = new EglCore(mConfig.eglContext, EglCore.FLAG_RECORDABLE); mWindow = new EglWindowSurface(mEglCore, mSurface, true); mWindow.makeCurrent(); - mViewport = new EglViewport(); + mDrawer = new GlTextureDrawer(mConfig.textureId); } /** @@ -149,7 +149,7 @@ public class TextureMediaEncoder extends VideoMediaEncoder { } private void onFilter(@NonNull Filter filter) { - mViewport.setFilter(filter); + mDrawer.setFilter(filter); } private void onFrame(@NonNull Frame frame) { @@ -229,7 +229,8 @@ public class TextureMediaEncoder extends VideoMediaEncoder { "hasReachedMaxLength:", hasReachedMaxLength(), "thread:", Thread.currentThread(), "- gl rendering."); - mViewport.drawFrame(frame.timestampUs(), mConfig.textureId, transform); + mDrawer.setTextureTransform(transform); + mDrawer.draw(frame.timestampUs()); if (mConfig.hasOverlay()) { mConfig.overlayDrawer.render(frame.timestampUs()); } @@ -252,9 +253,9 @@ public class TextureMediaEncoder extends VideoMediaEncoder { mWindow.release(); mWindow = null; } - if (mViewport != null) { - mViewport.release(); - mViewport = null; + if (mDrawer != null) { + mDrawer.release(); + mDrawer = null; } if (mEglCore != null) { mEglCore.release(); diff --git a/cameraview/src/test/java/com/otaliastudios/cameraview/internal/utils/ExifHelperTest.java b/cameraview/src/test/java/com/otaliastudios/cameraview/internal/ExifHelperTest.java similarity index 93% rename from cameraview/src/test/java/com/otaliastudios/cameraview/internal/utils/ExifHelperTest.java rename to cameraview/src/test/java/com/otaliastudios/cameraview/internal/ExifHelperTest.java index 319f2096..eab0e6ec 100644 --- a/cameraview/src/test/java/com/otaliastudios/cameraview/internal/utils/ExifHelperTest.java +++ b/cameraview/src/test/java/com/otaliastudios/cameraview/internal/ExifHelperTest.java @@ -1,8 +1,10 @@ -package com.otaliastudios.cameraview.internal.utils; +package com.otaliastudios.cameraview.internal; import androidx.exifinterface.media.ExifInterface; +import com.otaliastudios.cameraview.internal.ExifHelper; + import org.junit.Test; import static junit.framework.Assert.assertNotNull; diff --git a/cameraview/src/test/java/com/otaliastudios/cameraview/internal/utils/PoolTest.java b/cameraview/src/test/java/com/otaliastudios/cameraview/internal/PoolTest.java similarity index 97% rename from cameraview/src/test/java/com/otaliastudios/cameraview/internal/utils/PoolTest.java rename to cameraview/src/test/java/com/otaliastudios/cameraview/internal/PoolTest.java index 90fb874c..3fa5b1fb 100644 --- a/cameraview/src/test/java/com/otaliastudios/cameraview/internal/utils/PoolTest.java +++ b/cameraview/src/test/java/com/otaliastudios/cameraview/internal/PoolTest.java @@ -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.Before; import org.junit.Test; diff --git a/demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java b/demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java index 1ee1891e..bf5a4c52 100644 --- a/demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java +++ b/demo/src/main/java/com/otaliastudios/cameraview/demo/CameraActivity.java @@ -48,7 +48,7 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis private final static CameraLogger LOG = CameraLogger.create("DemoApp"); private final static boolean USE_FRAME_PROCESSOR = true; - private final static boolean DECODE_BITMAP = true; + private final static boolean DECODE_BITMAP = false; private CameraView camera; private ViewGroup controlPanel; @@ -76,7 +76,7 @@ public class CameraActivity extends AppCompatActivity implements View.OnClickLis long newTime = frame.getTime(); long delay = newTime - lastTime; lastTime = newTime; - LOG.e("Frame delayMillis:", delay, "FPS:", 1000 / delay); + LOG.v("Frame delayMillis:", delay, "FPS:", 1000 / delay); if (DECODE_BITMAP) { if (frame.getFormat() == ImageFormat.NV21 && frame.getDataClass() == byte[].class) {