Adopt EglThread in EglRenderer
This allows EglRenderer to be able to share render thread and EGLContext
with others.
go/meet-android-eglcontext-reduction
Bug: b/225229697
Change-Id: I896c8082ef8b64f5b544fa2eda7303fbca3985d1
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/316881
Reviewed-by: Xavier Lepaul‎ <xalep@webrtc.org>
Commit-Queue: Linus Nilsson <lnilsson@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#40574}
diff --git a/sdk/android/api/org/webrtc/EglRenderer.java b/sdk/android/api/org/webrtc/EglRenderer.java
index 5ab0868..e010ad5 100644
--- a/sdk/android/api/org/webrtc/EglRenderer.java
+++ b/sdk/android/api/org/webrtc/EglRenderer.java
@@ -14,11 +14,8 @@
import android.graphics.Matrix;
import android.graphics.SurfaceTexture;
import android.opengl.GLES20;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Message;
import android.view.Surface;
+import androidx.annotation.GuardedBy;
import androidx.annotation.Nullable;
import java.nio.ByteBuffer;
import java.text.DecimalFormat;
@@ -86,35 +83,20 @@
}
}
- /**
- * Handler that triggers a callback when an uncaught exception happens when handling a message.
- */
- private static class HandlerWithExceptionCallback extends Handler {
- private final Runnable exceptionCallback;
-
- public HandlerWithExceptionCallback(Looper looper, Runnable exceptionCallback) {
- super(looper);
- this.exceptionCallback = exceptionCallback;
- }
-
- @Override
- public void dispatchMessage(Message msg) {
- try {
- super.dispatchMessage(msg);
- } catch (Exception e) {
- Logging.e(TAG, "Exception on EglRenderer thread", e);
- exceptionCallback.run();
- throw e;
- }
- }
- }
-
protected final String name;
- // `renderThreadHandler` is a handler for communicating with `renderThread`, and is synchronized
- // on `handlerLock`.
- private final Object handlerLock = new Object();
- @Nullable private Handler renderThreadHandler;
+ // `eglThread` is used for rendering, and is synchronized on `threadLock`.
+ private final Object threadLock = new Object();
+ @GuardedBy("threadLock") @Nullable private EglThread eglThread;
+
+ private final Runnable eglExceptionCallback = new Runnable() {
+ @Override
+ public void run() {
+ synchronized (threadLock) {
+ eglThread = null;
+ }
+ }
+ };
private final ArrayList<FrameListenerAndParams> frameListeners = new ArrayList<>();
@@ -172,10 +154,10 @@
@Override
public void run() {
logStatistics();
- synchronized (handlerLock) {
- if (renderThreadHandler != null) {
- renderThreadHandler.removeCallbacks(logStatisticsRunnable);
- renderThreadHandler.postDelayed(
+ synchronized (threadLock) {
+ if (eglThread != null) {
+ eglThread.getHandler().removeCallbacks(logStatisticsRunnable);
+ eglThread.getHandler().postDelayed(
logStatisticsRunnable, TimeUnit.SECONDS.toMillis(LOG_INTERVAL_SEC));
}
}
@@ -185,8 +167,8 @@
private final EglSurfaceCreation eglSurfaceCreationRunnable = new EglSurfaceCreation();
/**
- * Standard constructor. The name will be used for the render thread name and included when
- * logging. In order to render something, you must first call init() and createEglSurface.
+ * Standard constructor. The name will be included when logging. In order to render something,
+ * you must first call init() and createEglSurface.
*/
public EglRenderer(String name) {
this(name, new VideoFrameDrawer());
@@ -197,6 +179,31 @@
this.frameDrawer = videoFrameDrawer;
}
+ public void init(
+ EglThread eglThread, RendererCommon.GlDrawer drawer, boolean usePresentationTimeStamp) {
+ synchronized (threadLock) {
+ if (this.eglThread != null) {
+ throw new IllegalStateException(name + "Already initialized");
+ }
+
+ logD("Initializing EglRenderer");
+ this.eglThread = eglThread;
+ this.drawer = drawer;
+ this.usePresentationTimeStamp = usePresentationTimeStamp;
+
+ eglThread.addExceptionCallback(eglExceptionCallback);
+
+ eglBase = eglThread.createEglBaseWithSharedConnection();
+ eglThread.getHandler().post(eglSurfaceCreationRunnable);
+
+ final long currentTimeNs = System.nanoTime();
+ resetStatistics(currentTimeNs);
+
+ eglThread.getHandler().postDelayed(
+ logStatisticsRunnable, TimeUnit.SECONDS.toMillis(LOG_INTERVAL_SEC));
+ }
+ }
+
/**
* Initialize this class, sharing resources with `sharedContext`. The custom `drawer` will be used
* for drawing frames on the EGLSurface. This class is responsible for calling release() on
@@ -207,46 +214,9 @@
*/
public void init(@Nullable final EglBase.Context sharedContext, final int[] configAttributes,
RendererCommon.GlDrawer drawer, boolean usePresentationTimeStamp) {
- synchronized (handlerLock) {
- if (renderThreadHandler != null) {
- throw new IllegalStateException(name + "Already initialized");
- }
- logD("Initializing EglRenderer");
- this.drawer = drawer;
- this.usePresentationTimeStamp = usePresentationTimeStamp;
-
- final HandlerThread renderThread = new HandlerThread(name + "EglRenderer");
- renderThread.start();
- renderThreadHandler =
- new HandlerWithExceptionCallback(renderThread.getLooper(), new Runnable() {
- @Override
- public void run() {
- synchronized (handlerLock) {
- renderThreadHandler = null;
- }
- }
- });
- // Create EGL context on the newly created render thread. It should be possibly to create the
- // context on this thread and make it current on the render thread, but this causes failure on
- // some Marvel based JB devices. https://bugs.chromium.org/p/webrtc/issues/detail?id=6350.
- ThreadUtils.invokeAtFrontUninterruptibly(renderThreadHandler, () -> {
- // If sharedContext is null, then texture frames are disabled. This is typically for old
- // devices that might not be fully spec compliant, so force EGL 1.0 since EGL 1.4 has
- // caused trouble on some weird devices.
- if (sharedContext == null) {
- logD("EglBase10.create context");
- eglBase = EglBase.createEgl10(configAttributes);
- } else {
- logD("EglBase.create shared context");
- eglBase = EglBase.create(sharedContext, configAttributes);
- }
- });
- renderThreadHandler.post(eglSurfaceCreationRunnable);
- final long currentTimeNs = System.nanoTime();
- resetStatistics(currentTimeNs);
- renderThreadHandler.postDelayed(
- logStatisticsRunnable, TimeUnit.SECONDS.toMillis(LOG_INTERVAL_SEC));
- }
+ EglThread thread =
+ EglThread.create(/* releaseMonitor= */ null, sharedContext, configAttributes);
+ init(thread, drawer, usePresentationTimeStamp);
}
/**
@@ -281,14 +251,16 @@
public void release() {
logD("Releasing.");
final CountDownLatch eglCleanupBarrier = new CountDownLatch(1);
- synchronized (handlerLock) {
- if (renderThreadHandler == null) {
+ synchronized (threadLock) {
+ if (eglThread == null) {
logD("Already released");
return;
}
- renderThreadHandler.removeCallbacks(logStatisticsRunnable);
+ eglThread.getHandler().removeCallbacks(logStatisticsRunnable);
+ eglThread.removeExceptionCallback(eglExceptionCallback);
+
// Release EGL and GL resources on render thread.
- renderThreadHandler.postAtFrontOfQueue(() -> {
+ eglThread.getHandler().postAtFrontOfQueue(() -> {
// Detach current shader program.
synchronized (EglBase.lock) {
GLES20.glUseProgram(/* program= */ 0);
@@ -299,23 +271,19 @@
}
frameDrawer.release();
bitmapTextureFramebuffer.release();
+
if (eglBase != null) {
- logD("eglBase detach and release.");
- eglBase.detachCurrent();
eglBase.release();
eglBase = null;
}
+
frameListeners.clear();
eglCleanupBarrier.countDown();
});
- final Looper renderLooper = renderThreadHandler.getLooper();
- // TODO(magjed): Replace this post() with renderLooper.quitSafely() when API support >= 18.
- renderThreadHandler.post(() -> {
- logD("Quitting render thread.");
- renderLooper.quit();
- });
+
// Don't accept any more frames or messages to the render thread.
- renderThreadHandler = null;
+ eglThread.release();
+ eglThread = null;
}
// Make sure the EGL/GL cleanup posted above is executed.
ThreadUtils.awaitUninterruptibly(eglCleanupBarrier);
@@ -343,9 +311,9 @@
}
public void printStackTrace() {
- synchronized (handlerLock) {
+ synchronized (threadLock) {
final Thread renderThread =
- (renderThreadHandler == null) ? null : renderThreadHandler.getLooper().getThread();
+ (eglThread == null) ? null : eglThread.getHandler().getLooper().getThread();
if (renderThread != null) {
final StackTraceElement[] renderStackTrace = renderThread.getStackTrace();
if (renderStackTrace.length > 0) {
@@ -475,11 +443,11 @@
*/
public void removeFrameListener(final FrameListener listener) {
final CountDownLatch latch = new CountDownLatch(1);
- synchronized (handlerLock) {
- if (renderThreadHandler == null) {
+ synchronized (threadLock) {
+ if (eglThread == null) {
return;
}
- if (Thread.currentThread() == renderThreadHandler.getLooper().getThread()) {
+ if (Thread.currentThread() == eglThread.getHandler().getLooper().getThread()) {
throw new RuntimeException("removeFrameListener must not be called on the render thread.");
}
postToRenderThread(() -> {
@@ -507,8 +475,8 @@
++framesReceived;
}
final boolean dropOldFrame;
- synchronized (handlerLock) {
- if (renderThreadHandler == null) {
+ synchronized (threadLock) {
+ if (eglThread == null) {
logD("Dropping frame - Not initialized or already released.");
return;
}
@@ -519,7 +487,7 @@
}
pendingFrame = frame;
pendingFrame.retain();
- renderThreadHandler.post(this ::renderFrameOnRenderThread);
+ eglThread.getHandler().post(this::renderFrameOnRenderThread);
}
}
if (dropOldFrame) {
@@ -536,12 +504,11 @@
// Ensure that the render thread is no longer touching the Surface before returning from this
// function.
eglSurfaceCreationRunnable.setSurface(null /* surface */);
- synchronized (handlerLock) {
- if (renderThreadHandler != null) {
- renderThreadHandler.removeCallbacks(eglSurfaceCreationRunnable);
- renderThreadHandler.postAtFrontOfQueue(() -> {
+ synchronized (threadLock) {
+ if (eglThread != null) {
+ eglThread.getHandler().removeCallbacks(eglSurfaceCreationRunnable);
+ eglThread.getHandler().postAtFrontOfQueue(() -> {
if (eglBase != null) {
- eglBase.detachCurrent();
eglBase.releaseSurface();
}
completionCallback.run();
@@ -556,9 +523,9 @@
* Private helper function to post tasks safely.
*/
private void postToRenderThread(Runnable runnable) {
- synchronized (handlerLock) {
- if (renderThreadHandler != null) {
- renderThreadHandler.post(runnable);
+ synchronized (threadLock) {
+ if (eglThread != null) {
+ eglThread.getHandler().post(runnable);
}
}
}
@@ -566,6 +533,7 @@
private void clearSurfaceOnRenderThread(float r, float g, float b, float a) {
if (eglBase != null && eglBase.hasSurface()) {
logD("clearSurface");
+ eglBase.makeCurrent();
GLES20.glClearColor(r, g, b, a);
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
eglBase.swapBuffers();
@@ -583,11 +551,11 @@
* Post a task to clear the surface to a specific color.
*/
public void clearImage(final float r, final float g, final float b, final float a) {
- synchronized (handlerLock) {
- if (renderThreadHandler == null) {
+ synchronized (threadLock) {
+ if (eglThread == null) {
return;
}
- renderThreadHandler.postAtFrontOfQueue(() -> clearSurfaceOnRenderThread(r, g, b, a));
+ eglThread.getHandler().postAtFrontOfQueue(() -> clearSurfaceOnRenderThread(r, g, b, a));
}
}
@@ -609,6 +577,8 @@
frame.release();
return;
}
+ eglBase.makeCurrent();
+
// Check if fps reduction is active.
final boolean shouldRenderFrame;
synchronized (fpsReductionLock) {