Implement default GlTextureProcessor in SingleFrameGlTextureProcessor.

SingleFrameGlTextureProcessor is now an abstract class containing a
default implementation of the more flexible GlTextureProcessor interface
while still exposing the same simple abstract methods for single frame
processing it previously did.

FrameProcessorChain and GlEffect will be changed to use
GlTextureProcessor in follow-ups.

PiperOrigin-RevId: 453633000
This commit is contained in:
hschlueter 2022-06-08 09:44:25 +00:00 committed by Marc Baechinger
parent 2c2be2da92
commit 0b37d860d1
9 changed files with 140 additions and 37 deletions

View File

@ -44,7 +44,7 @@ import java.util.Locale;
*/ */
// TODO(b/227625365): Delete this class and use a texture processor from the Transformer library, // TODO(b/227625365): Delete this class and use a texture processor from the Transformer library,
// once overlaying a bitmap and text is supported in Transformer. // once overlaying a bitmap and text is supported in Transformer.
/* package */ final class BitmapOverlayProcessor implements SingleFrameGlTextureProcessor { /* package */ final class BitmapOverlayProcessor extends SingleFrameGlTextureProcessor {
static { static {
GlUtil.glAssertionsEnabled = true; GlUtil.glAssertionsEnabled = true;
} }
@ -147,6 +147,7 @@ import java.util.Locale;
@Override @Override
public void release() { public void release() {
super.release();
if (glProgram != null) { if (glProgram != null) {
glProgram.delete(); glProgram.delete();
} }

View File

@ -30,7 +30,7 @@ import java.io.IOException;
* A {@link SingleFrameGlTextureProcessor} that periodically dims the frames such that pixels are * A {@link SingleFrameGlTextureProcessor} that periodically dims the frames such that pixels are
* darker the further they are away from the frame center. * darker the further they are away from the frame center.
*/ */
/* package */ final class PeriodicVignetteProcessor implements SingleFrameGlTextureProcessor { /* package */ final class PeriodicVignetteProcessor extends SingleFrameGlTextureProcessor {
static { static {
GlUtil.glAssertionsEnabled = true; GlUtil.glAssertionsEnabled = true;
} }
@ -108,6 +108,7 @@ import java.io.IOException;
@Override @Override
public void release() { public void release() {
super.release();
if (glProgram != null) { if (glProgram != null) {
glProgram.delete(); glProgram.delete();
} }

View File

@ -40,7 +40,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
* Runs a MediaPipe graph on input frames. The implementation is currently limited to graphs that * Runs a MediaPipe graph on input frames. The implementation is currently limited to graphs that
* can immediately produce one output frame per input frame. * can immediately produce one output frame per input frame.
*/ */
/* package */ final class MediaPipeProcessor implements SingleFrameGlTextureProcessor { /* package */ final class MediaPipeProcessor extends SingleFrameGlTextureProcessor {
private static final LibraryLoader LOADER = private static final LibraryLoader LOADER =
new LibraryLoader("mediapipe_jni") { new LibraryLoader("mediapipe_jni") {
@ -160,6 +160,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Override @Override
public void release() { public void release() {
super.release();
checkStateNotNull(frameProcessor).close(); checkStateNotNull(frameProcessor).close();
} }
} }

View File

@ -342,6 +342,13 @@ public final class GlUtil {
} }
} }
/** Fills the pixels in the current output render target with (r=0, g=0, b=0, a=0). */
public static void clearOutputFrame() {
GLES20.glClearColor(/* red= */ 0, /* green= */ 0, /* blue= */ 0, /* alpha= */ 0);
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
GlUtil.checkGlError();
}
/** /**
* Makes the specified {@code eglSurface} the render target, using a viewport of {@code width} by * Makes the specified {@code eglSurface} the render target, using a viewport of {@code width} by
* {@code height} pixels. * {@code height} pixels.
@ -368,6 +375,22 @@ public final class GlUtil {
Api17.focusRenderTarget(eglDisplay, eglContext, eglSurface, framebuffer, width, height); Api17.focusRenderTarget(eglDisplay, eglContext, eglSurface, framebuffer, width, height);
} }
/**
* Makes the specified {@code framebuffer} the render target, using a viewport of {@code width} by
* {@code height} pixels.
*
* <p>The caller must ensure that there is a current OpenGL context before calling this method.
*
* @param framebuffer The identifier of the framebuffer object to bind as the output render
* target.
* @param width The viewport width, in pixels.
* @param height The viewport height, in pixels.
*/
@RequiresApi(17)
public static void focusFramebufferUsingCurrentContext(int framebuffer, int width, int height) {
Api17.focusFramebufferUsingCurrentContext(framebuffer, width, height);
}
/** /**
* Deletes a GL texture. * Deletes a GL texture.
* *
@ -612,14 +635,22 @@ public final class GlUtil {
int framebuffer, int framebuffer,
int width, int width,
int height) { int height) {
EGL14.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext);
checkEglException("Error making context current");
focusFramebufferUsingCurrentContext(framebuffer, width, height);
}
@DoNotInline
public static void focusFramebufferUsingCurrentContext(int framebuffer, int width, int height) {
checkEglException(
!Util.areEqual(EGL14.eglGetCurrentContext(), EGL14.EGL_NO_CONTEXT), "No current context");
int[] boundFramebuffer = new int[1]; int[] boundFramebuffer = new int[1];
GLES20.glGetIntegerv(GLES20.GL_FRAMEBUFFER_BINDING, boundFramebuffer, /* offset= */ 0); GLES20.glGetIntegerv(GLES20.GL_FRAMEBUFFER_BINDING, boundFramebuffer, /* offset= */ 0);
if (boundFramebuffer[0] != framebuffer) { if (boundFramebuffer[0] != framebuffer) {
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, framebuffer); GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, framebuffer);
} }
checkGlError(); checkGlError();
EGL14.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext);
checkEglException("Error making context current");
GLES20.glViewport(/* x= */ 0, /* y= */ 0, width, height); GLES20.glViewport(/* x= */ 0, /* y= */ 0, width, height);
checkGlError(); checkGlError();
} }

View File

@ -135,7 +135,7 @@ public final class FrameProcessorChainTest {
/* enableExperimentalHdrEditing= */ false); /* enableExperimentalHdrEditing= */ false);
} }
private static class FakeTextureProcessor implements SingleFrameGlTextureProcessor { private static class FakeTextureProcessor extends SingleFrameGlTextureProcessor {
private final Size outputSize; private final Size outputSize;
@ -150,8 +150,5 @@ public final class FrameProcessorChainTest {
@Override @Override
public void drawFrame(int inputTexId, long presentationTimeNs) {} public void drawFrame(int inputTexId, long presentationTimeNs) {}
@Override
public void release() {}
} }
} }

View File

@ -26,7 +26,7 @@ import com.google.android.exoplayer2.util.GlUtil;
import java.io.IOException; import java.io.IOException;
/** Copies frames from an external texture and applies color transformations for HDR if needed. */ /** Copies frames from an external texture and applies color transformations for HDR if needed. */
/* package */ class ExternalTextureProcessor implements SingleFrameGlTextureProcessor { /* package */ class ExternalTextureProcessor extends SingleFrameGlTextureProcessor {
static { static {
GlUtil.glAssertionsEnabled = true; GlUtil.glAssertionsEnabled = true;
@ -115,6 +115,7 @@ import java.io.IOException;
@Override @Override
public void release() { public void release() {
super.release();
if (glProgram != null) { if (glProgram != null) {
glProgram.delete(); glProgram.delete();
} }

View File

@ -29,7 +29,6 @@ import android.opengl.EGLContext;
import android.opengl.EGLDisplay; import android.opengl.EGLDisplay;
import android.opengl.EGLExt; import android.opengl.EGLExt;
import android.opengl.EGLSurface; import android.opengl.EGLSurface;
import android.opengl.GLES20;
import android.util.Size; import android.util.Size;
import android.view.Surface; import android.view.Surface;
import android.view.SurfaceHolder; import android.view.SurfaceHolder;
@ -517,12 +516,12 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
outputTexture.fboId, outputTexture.fboId,
outputTexture.width, outputTexture.width,
outputTexture.height); outputTexture.height);
clearOutputFrame(); GlUtil.clearOutputFrame();
textureProcessors.get(i).drawFrame(inputTexId, presentationTimeUs); textureProcessors.get(i).drawFrame(inputTexId, presentationTimeUs);
inputTexId = outputTexture.texId; inputTexId = outputTexture.texId;
} }
GlUtil.focusEglSurface(eglDisplay, eglContext, outputEglSurface, outputWidth, outputHeight); GlUtil.focusEglSurface(eglDisplay, eglContext, outputEglSurface, outputWidth, outputHeight);
clearOutputFrame(); GlUtil.clearOutputFrame();
getLast(textureProcessors).drawFrame(inputTexId, presentationTimeUs); getLast(textureProcessors).drawFrame(inputTexId, presentationTimeUs);
EGLExt.eglPresentationTimeANDROID(eglDisplay, outputEglSurface, inputFrameTimeNs); EGLExt.eglPresentationTimeANDROID(eglDisplay, outputEglSurface, inputFrameTimeNs);
@ -533,7 +532,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
int finalInputTexId = inputTexId; int finalInputTexId = inputTexId;
debugSurfaceViewWrapper.maybeRenderToSurfaceView( debugSurfaceViewWrapper.maybeRenderToSurfaceView(
() -> { () -> {
clearOutputFrame(); GlUtil.clearOutputFrame();
try { try {
getLast(textureProcessors).drawFrame(finalInputTexId, finalPresentationTimeUs); getLast(textureProcessors).drawFrame(finalInputTexId, finalPresentationTimeUs);
} catch (FrameProcessingException e) { } catch (FrameProcessingException e) {
@ -553,12 +552,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
} }
} }
private static void clearOutputFrame() {
GLES20.glClearColor(/* red= */ 0, /* green= */ 0, /* blue= */ 0, /* alpha= */ 0);
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
GlUtil.checkGlError();
}
/** /**
* Releases the {@link SingleFrameGlTextureProcessor SingleFrameGlTextureProcessors} and destroys * Releases the {@link SingleFrameGlTextureProcessor SingleFrameGlTextureProcessors} and destroys
* the OpenGL context. * the OpenGL context.

View File

@ -39,7 +39,7 @@ import java.util.Arrays;
* <p>The background color of the output frame will be black. * <p>The background color of the output frame will be black.
*/ */
@SuppressWarnings("FunctionalInterfaceClash") // b/228192298 @SuppressWarnings("FunctionalInterfaceClash") // b/228192298
/* package */ final class MatrixTransformationProcessor implements SingleFrameGlTextureProcessor { /* package */ final class MatrixTransformationProcessor extends SingleFrameGlTextureProcessor {
static { static {
GlUtil.glAssertionsEnabled = true; GlUtil.glAssertionsEnabled = true;
@ -169,6 +169,7 @@ import java.util.Arrays;
@Override @Override
public void release() { public void release() {
super.release();
glProgram.delete(); glProgram.delete();
} }

View File

@ -16,36 +16,40 @@
package com.google.android.exoplayer2.transformer; package com.google.android.exoplayer2.transformer;
import android.util.Size; import android.util.Size;
import androidx.annotation.CallSuper;
import com.google.android.exoplayer2.util.GlUtil;
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** /**
* Manages a GLSL shader program for processing a frame. Implementations generally copy input pixels * Manages a GLSL shader program for processing a frame. Implementations generally copy input pixels
* into an output frame, with changes to pixels specific to the implementation. * into an output frame, with changes to pixels specific to the implementation.
* *
* <p>Methods must be called in the following order: * <p>{@code SingleFrameGlTextureProcessor} implementations must produce exactly one output frame
* * per input frame with the same presentation timestamp. For more flexibility, implement {@link
* <ol> * GlTextureProcessor} directly.
* <li>{@link #configure(int, int)}, to configure the frame processor based on the input
* dimensions.
* <li>{@link #drawFrame(int, long)}, to process one frame.
* <li>{@link #release()}, upon conclusion of processing.
* </ol>
* *
* <p>All methods in this class must be called on the thread that owns the OpenGL context. * <p>All methods in this class must be called on the thread that owns the OpenGL context.
*/ */
// TODO(b/227625423): Add GlTextureProcessor interface for async texture processors and make this an public abstract class SingleFrameGlTextureProcessor implements GlTextureProcessor {
// abstract class with a default implementation of GlTextureProcessor methods.
public interface SingleFrameGlTextureProcessor { private @MonotonicNonNull Listener listener;
private int inputWidth;
private int inputHeight;
private @MonotonicNonNull TextureInfo outputTexture;
private boolean outputTextureInUse;
/** /**
* Configures the texture processor based on the input dimensions. * Configures the texture processor based on the input dimensions.
* *
* <p>This method can be called multiple times. * <p>This method must be called before {@linkplain #drawFrame(int,long) drawing} the first frame
* and before drawing subsequent frames with different input dimensions.
* *
* @param inputWidth The input width, in pixels. * @param inputWidth The input width, in pixels.
* @param inputHeight The input height, in pixels. * @param inputHeight The input height, in pixels.
* @return The output {@link Size} of frames processed through {@link #drawFrame(int, long)}. * @return The output {@link Size} of frames processed through {@link #drawFrame(int, long)}.
*/ */
Size configure(int inputWidth, int inputHeight); public abstract Size configure(int inputWidth, int inputHeight);
/** /**
* Draws one frame. * Draws one frame.
@ -61,8 +65,81 @@ public interface SingleFrameGlTextureProcessor {
* @param presentationTimeUs The presentation timestamp of the current frame, in microseconds. * @param presentationTimeUs The presentation timestamp of the current frame, in microseconds.
* @throws FrameProcessingException If an error occurs while processing or drawing the frame. * @throws FrameProcessingException If an error occurs while processing or drawing the frame.
*/ */
void drawFrame(int inputTexId, long presentationTimeUs) throws FrameProcessingException; public abstract void drawFrame(int inputTexId, long presentationTimeUs)
throws FrameProcessingException;
/** Releases all resources. */ @Override
void release(); public final void setListener(Listener listener) {
this.listener = listener;
}
@Override
public final boolean maybeQueueInputFrame(TextureInfo inputTexture, long presentationTimeUs) {
if (outputTextureInUse) {
return false;
}
try {
if (outputTexture == null
|| inputTexture.width != inputWidth
|| inputTexture.height != inputHeight) {
configureOutputTexture(inputTexture.width, inputTexture.height);
}
outputTextureInUse = true;
GlUtil.focusFramebufferUsingCurrentContext(
outputTexture.fboId, outputTexture.width, outputTexture.height);
GlUtil.clearOutputFrame();
drawFrame(inputTexture.texId, presentationTimeUs);
if (listener != null) {
listener.onInputFrameProcessed(inputTexture);
listener.onOutputFrameAvailable(outputTexture, presentationTimeUs);
}
} catch (FrameProcessingException | RuntimeException e) {
if (listener != null) {
listener.onFrameProcessingError(
e instanceof FrameProcessingException
? (FrameProcessingException) e
: new FrameProcessingException(e));
}
}
return true;
}
@EnsuresNonNull("outputTexture")
private void configureOutputTexture(int inputWidth, int inputHeight) {
this.inputWidth = inputWidth;
this.inputHeight = inputHeight;
Size outputSize = configure(inputWidth, inputHeight);
if (outputTexture == null
|| outputSize.getWidth() != outputTexture.width
|| outputSize.getHeight() != outputTexture.height) {
if (outputTexture != null) {
GlUtil.deleteTexture(outputTexture.texId);
}
int outputTexId = GlUtil.createTexture(outputSize.getWidth(), outputSize.getHeight());
int outputFboId = GlUtil.createFboForTexture(outputTexId);
outputTexture =
new TextureInfo(outputTexId, outputFboId, outputSize.getWidth(), outputSize.getHeight());
}
}
@Override
public final void releaseOutputFrame(TextureInfo outputTexture) {
outputTextureInUse = false;
}
@Override
public final void signalEndOfInputStream() {
if (listener != null) {
listener.onOutputStreamEnded();
}
}
@Override
@CallSuper
public void release() {
if (outputTexture != null) {
GlUtil.deleteTexture(outputTexture.texId);
}
}
} }