Use placeholder surface to configure OpenGL and frame processors.

The placeholder surface is either EGL_NO_SURFACE or a 1x1 pbuffer
depending on whether the device supports EGL_KHR_surfaceless_context.

PiperOrigin-RevId: 438541846
This commit is contained in:
hschlueter 2022-03-31 13:20:56 +01:00 committed by Ian Baker
parent bd257d24ed
commit 839f342e55
2 changed files with 108 additions and 29 deletions

View File

@ -147,7 +147,11 @@ public final class GlUtil {
} }
/** /**
* Returns whether creating a GL context with {@value #EXTENSION_SURFACELESS_CONTEXT} is possible. * Returns whether the {@value #EXTENSION_SURFACELESS_CONTEXT} extension is supported.
*
* <p>This extension allows passing {@link EGL14#EGL_NO_SURFACE} for both the write and read
* surfaces in a call to {@link EGL14#eglMakeCurrent(EGLDisplay, EGLSurface, EGLSurface,
* EGLContext)}.
*/ */
public static boolean isSurfacelessContextExtensionSupported() { public static boolean isSurfacelessContextExtensionSupported() {
if (Util.SDK_INT < 17) { if (Util.SDK_INT < 17) {
@ -207,6 +211,52 @@ public final class GlUtil {
EGL_WINDOW_SURFACE_ATTRIBUTES_BT2020_PQ); EGL_WINDOW_SURFACE_ATTRIBUTES_BT2020_PQ);
} }
/**
* Creates and focuses a new {@link EGLSurface} wrapping a 1x1 pixel buffer.
*
* @param eglContext The {@link EGLContext} to make current.
* @param eglDisplay The {@link EGLDisplay} to attach the surface to.
*/
@RequiresApi(17)
public static void focusPlaceholderEglSurface(EGLContext eglContext, EGLDisplay eglDisplay) {
int[] pbufferAttributes =
new int[] {
EGL14.EGL_WIDTH, /* width= */ 1,
EGL14.EGL_HEIGHT, /* height= */ 1,
EGL14.EGL_NONE
};
EGLSurface eglSurface =
Api17.createEglPbufferSurface(
eglDisplay, EGL_CONFIG_ATTRIBUTES_RGBA_8888, pbufferAttributes);
focusEglSurface(eglDisplay, eglContext, eglSurface, /* width= */ 1, /* height= */ 1);
}
/**
* Creates and focuses a new {@link EGLSurface} wrapping a 1x1 pixel buffer, for HDR rendering
* with Rec. 2020 color primaries and using the PQ transfer function.
*
* @param eglContext The {@link EGLContext} to make current.
* @param eglDisplay The {@link EGLDisplay} to attach the surface to.
*/
@RequiresApi(17)
public static void focusPlaceholderEglSurfaceBt2020Pq(
EGLContext eglContext, EGLDisplay eglDisplay) {
int[] pbufferAttributes =
new int[] {
EGL14.EGL_WIDTH,
/* width= */ 1,
EGL14.EGL_HEIGHT,
/* height= */ 1,
EGL_GL_COLORSPACE_KHR,
EGL_GL_COLORSPACE_BT2020_PQ_EXT,
EGL14.EGL_NONE
};
EGLSurface eglSurface =
Api17.createEglPbufferSurface(
eglDisplay, EGL_CONFIG_ATTRIBUTES_RGBA_1010102, pbufferAttributes);
focusEglSurface(eglDisplay, eglContext, eglSurface, /* width= */ 1, /* height= */ 1);
}
/** /**
* If there is an OpenGl error, logs the error and if {@link #glAssertionsEnabled} is true throws * If there is an OpenGl error, logs the error and if {@link #glAssertionsEnabled} is true throws
* a {@link GlException}. * a {@link GlException}.
@ -486,6 +536,19 @@ public final class GlUtil {
return eglSurface; return eglSurface;
} }
@DoNotInline
public static EGLSurface createEglPbufferSurface(
EGLDisplay eglDisplay, int[] configAttributes, int[] pbufferAttributes) {
EGLSurface eglSurface =
EGL14.eglCreatePbufferSurface(
eglDisplay,
getEglConfig(eglDisplay, configAttributes),
pbufferAttributes,
/* offset= */ 0);
checkEglException("Error creating surface");
return eglSurface;
}
@DoNotInline @DoNotInline
public static void focusRenderTarget( public static void focusRenderTarget(
EGLDisplay eglDisplay, EGLDisplay eglDisplay,

View File

@ -45,7 +45,6 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.RequiresNonNull; import org.checkerframework.checker.nullness.qual.RequiresNonNull;
@ -68,8 +67,8 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
private static final String THREAD_NAME = "Transformer:FrameProcessorChain"; private static final String THREAD_NAME = "Transformer:FrameProcessorChain";
private final boolean enableExperimentalHdrEditing; private final boolean enableExperimentalHdrEditing;
private final EGLDisplay eglDisplay; private @MonotonicNonNull EGLDisplay eglDisplay;
private final EGLContext eglContext; private @MonotonicNonNull EGLContext eglContext;
/** Some OpenGL commands may block, so all OpenGL commands are run on a background thread. */ /** Some OpenGL commands may block, so all OpenGL commands are run on a background thread. */
private final ExecutorService singleThreadExecutorService; private final ExecutorService singleThreadExecutorService;
/** Futures corresponding to the executor service's pending tasks. */ /** Futures corresponding to the executor service's pending tasks. */
@ -152,16 +151,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
this.enableExperimentalHdrEditing = enableExperimentalHdrEditing; this.enableExperimentalHdrEditing = enableExperimentalHdrEditing;
this.frameProcessors = ImmutableList.copyOf(frameProcessors); this.frameProcessors = ImmutableList.copyOf(frameProcessors);
try {
eglDisplay = GlUtil.createEglDisplay();
eglContext =
enableExperimentalHdrEditing
? GlUtil.createEglContextEs3Rgba1010102(eglDisplay)
: GlUtil.createEglContext(eglDisplay);
} catch (GlUtil.GlException e) {
throw TransformationException.createForFrameProcessorChain(
e, TransformationException.ERROR_CODE_GL_INIT_FAILED);
}
singleThreadExecutorService = Util.newSingleThreadExecutor(THREAD_NAME); singleThreadExecutorService = Util.newSingleThreadExecutor(THREAD_NAME);
futures = new ConcurrentLinkedQueue<>(); futures = new ConcurrentLinkedQueue<>();
pendingFrameCount = new AtomicInteger(); pendingFrameCount = new AtomicInteger();
@ -218,9 +207,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
try { try {
// Wait for task to finish to be able to use inputExternalTexId to create the SurfaceTexture. // Wait for task to finish to be able to use inputExternalTexId to create the SurfaceTexture.
singleThreadExecutorService singleThreadExecutorService
.submit( .submit(this::createOpenGlObjectsAndInitializeFrameProcessors)
() ->
createOpenGlObjectsAndInitializeFrameProcessors(outputSurface, debugSurfaceView))
.get(); .get();
} catch (ExecutionException e) { } catch (ExecutionException e) {
throw TransformationException.createForFrameProcessorChain( throw TransformationException.createForFrameProcessorChain(
@ -248,6 +235,10 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
} }
}); });
inputSurface = new Surface(inputSurfaceTexture); inputSurface = new Surface(inputSurfaceTexture);
futures.add(
singleThreadExecutorService.submit(
() -> createOpenGlSurfaces(outputSurface, debugSurfaceView)));
} }
/** /**
@ -344,16 +335,15 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
} }
/** /**
* Creates the OpenGL textures, framebuffers, surfaces, and initializes the {@link * Creates the OpenGL surfaces.
* GlFrameProcessor GlFrameProcessors}.
* *
* <p>This method must by executed on the same thread as {@link #processFrame()}, i.e., executed * <p>This method should only be called after {@link
* by the {@link #singleThreadExecutorService}. * #createOpenGlObjectsAndInitializeFrameProcessors()} and must be called on the background
* thread.
*/ */
@EnsuresNonNull("eglSurface") private void createOpenGlSurfaces(Surface outputSurface, @Nullable SurfaceView debugSurfaceView) {
private Void createOpenGlObjectsAndInitializeFrameProcessors(
Surface outputSurface, @Nullable SurfaceView debugSurfaceView) throws IOException {
checkState(Thread.currentThread().getName().equals(THREAD_NAME)); checkState(Thread.currentThread().getName().equals(THREAD_NAME));
checkStateNotNull(eglDisplay);
if (enableExperimentalHdrEditing) { if (enableExperimentalHdrEditing) {
// TODO(b/209404935): Don't assume BT.2020 PQ input/output. // TODO(b/209404935): Don't assume BT.2020 PQ input/output.
@ -369,7 +359,32 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
GlUtil.getEglSurface(eglDisplay, checkNotNull(debugSurfaceView.getHolder())); GlUtil.getEglSurface(eglDisplay, checkNotNull(debugSurfaceView.getHolder()));
} }
} }
GlUtil.focusEglSurface(eglDisplay, eglContext, eglSurface, outputWidth, outputHeight); }
/**
* Creates the OpenGL textures and framebuffers, and initializes the {@link GlFrameProcessor
* GlFrameProcessors}.
*
* <p>This method should only be called on the background thread.
*/
private Void createOpenGlObjectsAndInitializeFrameProcessors() throws IOException {
checkState(Thread.currentThread().getName().equals(THREAD_NAME));
eglDisplay = GlUtil.createEglDisplay();
eglContext =
enableExperimentalHdrEditing
? GlUtil.createEglContextEs3Rgba1010102(eglDisplay)
: GlUtil.createEglContext(eglDisplay);
if (GlUtil.isSurfacelessContextExtensionSupported()) {
GlUtil.focusEglSurface(
eglDisplay, eglContext, EGL14.EGL_NO_SURFACE, /* width= */ 1, /* height= */ 1);
} else if (enableExperimentalHdrEditing) {
// TODO(b/209404935): Don't assume BT.2020 PQ input/output.
GlUtil.focusPlaceholderEglSurfaceBt2020Pq(eglContext, eglDisplay);
} else {
GlUtil.focusPlaceholderEglSurface(eglContext, eglDisplay);
}
inputExternalTexId = GlUtil.createExternalTexture(); inputExternalTexId = GlUtil.createExternalTexture();
Size inputSize = inputSizes.get(0); Size inputSize = inputSizes.get(0);
@ -389,13 +404,14 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/** /**
* Processes an input frame. * Processes an input frame.
* *
* <p>This method must by executed on the same thread as {@link * <p>This method should only be called on the background thread.
* #createOpenGlObjectsAndInitializeFrameProcessors(Surface,SurfaceView)}, i.e., executed by the
* {@link #singleThreadExecutorService}.
*/ */
@RequiresNonNull({"inputSurfaceTexture", "eglSurface"}) @RequiresNonNull("inputSurfaceTexture")
private void processFrame() { private void processFrame() {
checkState(Thread.currentThread().getName().equals(THREAD_NAME)); checkState(Thread.currentThread().getName().equals(THREAD_NAME));
checkStateNotNull(eglSurface);
checkStateNotNull(eglContext);
checkStateNotNull(eglDisplay);
if (frameProcessors.isEmpty()) { if (frameProcessors.isEmpty()) {
GlUtil.focusEglSurface(eglDisplay, eglContext, eglSurface, outputWidth, outputHeight); GlUtil.focusEglSurface(eglDisplay, eglContext, eglSurface, outputWidth, outputHeight);