diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/filter/MultiFilter.java b/cameraview/src/main/java/com/otaliastudios/cameraview/filter/MultiFilter.java new file mode 100644 index 00000000..4b1bb0b2 --- /dev/null +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/filter/MultiFilter.java @@ -0,0 +1,150 @@ +package com.otaliastudios.cameraview.filter; + +import android.opengl.GLES20; + +import androidx.annotation.NonNull; + +import com.otaliastudios.cameraview.internal.GlUtils; +import com.otaliastudios.cameraview.size.Size; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +public class MultiFilter implements Filter { + + private static class State { + boolean isCreated = false; + int programHandle = -1; + Size size = null; + } + + private final Map filters = new HashMap<>(); + private final Object lock = new Object(); + private Size size = null; + + @SuppressWarnings("WeakerAccess") + public MultiFilter(@NonNull Filter... filters) { + this(Arrays.asList(filters)); + } + + @SuppressWarnings("WeakerAccess") + public MultiFilter(@NonNull Collection filters) { + for (Filter filter : filters) { + addFilter(filter); + } + } + + @SuppressWarnings("WeakerAccess") + public void addFilter(@NonNull Filter filter) { + if (filter instanceof MultiFilter) { + MultiFilter multiFilter = (MultiFilter) filter; + for (Filter multiChild : multiFilter.filters.keySet()) { + addFilter(multiChild); + } + return; + } + synchronized (lock) { + if (!filters.keySet().contains(filter)) { + filters.put(filter, new State()); + } + } + } + + // We don't offer a removeFilter method since that would cause issues + // 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) { + State state = filters.get(filter); + //noinspection ConstantConditions + if (!state.isCreated) { + state.isCreated = true; + state.programHandle = GlUtils.createProgram(filter.getVertexShader(), filter.getFragmentShader()); + filter.onCreate(state.programHandle); + // TODO extra creation + } + } + + private void maybeDestroy(@NonNull Filter filter) { + State state = filters.get(filter); + //noinspection ConstantConditions + if (state.isCreated) { + state.isCreated = false; + filter.onDestroy(); + GLES20.glDeleteProgram(state.programHandle); + state.programHandle = -1; + // TODO extra cleanup. + } + } + + private void maybeSetSize(@NonNull Filter filter) { + State state = filters.get(filter); + //noinspection ConstantConditions + if (size != null && !size.equals(state.size)) { + state.size = size; + filter.setSize(size.getWidth(), size.getHeight()); + } + } + + @NonNull + @Override + public String getVertexShader() { + return new NoFilter().getVertexShader(); + } + + @NonNull + @Override + public String getFragmentShader() { + 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. + } + + @Override + public void onDestroy() { + synchronized (lock) { + for (Filter filter : filters.keySet()) { + maybeDestroy(filter); + } + } + } + + @Override + public void setSize(int width, int height) { + size = new Size(width, height); + synchronized (lock) { + for (Filter filter : filters.keySet()) { + maybeSetSize(filter); + } + } + } + + @Override + public void draw(float[] transformMatrix) { + synchronized (lock) { + for (Filter filter : filters.keySet()) { + maybeCreate(filter); + maybeSetSize(filter); + } + } + // TODO + } + + @NonNull + @Override + public Filter copy() { + synchronized (lock) { + MultiFilter copy = new MultiFilter(); + for (Filter filter : filters.keySet()) { + copy.addFilter(filter.copy()); + } + return copy; + } + } +} diff --git a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/egl/EglViewport.java b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/egl/EglViewport.java index c7d03a87..f6f72080 100644 --- a/cameraview/src/main/java/com/otaliastudios/cameraview/internal/egl/EglViewport.java +++ b/cameraview/src/main/java/com/otaliastudios/cameraview/internal/egl/EglViewport.java @@ -21,7 +21,7 @@ public class EglViewport { private int mTextureUnit; private Filter mFilter; - private boolean mFilterChanged = false; + private Filter mPendingFilter; public EglViewport() { this(new NoFilter()); @@ -35,7 +35,6 @@ public class EglViewport { } private void createProgram() { - release(); // Release old program if present. mProgramHandle = GlUtils.createProgram(mFilter.getVertexShader(), mFilter.getFragmentShader()); mFilter.onCreate(mProgramHandle); } @@ -67,15 +66,16 @@ public class EglViewport { return texId; } - public void setFilter(@NonNull Filter filter){ - this.mFilter = filter; - mFilterChanged = true; + public void setFilter(@NonNull Filter filter) { + mPendingFilter = filter; } public void drawFrame(int textureId, float[] textureMatrix) { - if (mFilterChanged) { + if (mPendingFilter != null) { + release(); + mFilter = mPendingFilter; + mPendingFilter = null; createProgram(); - mFilterChanged = false; } GlUtils.checkError("draw start");