blob: 47f7810b7c9efb92175b86ce96e15be552fe2ab3 [file] [log] [blame]
/*
* Copyright 2022 The WebRTC project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
package org.webrtc;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import androidx.annotation.GuardedBy;
import androidx.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import org.webrtc.EglBase.EglConnection;
/** EGL graphics thread that allows multiple clients to share the same underlying EGLContext. */
public class EglThread {
/** Callback for externally managed reference count. */
public interface ReleaseMonitor {
/**
* Called by EglThread when a client releases its reference. Returns true when there are no more
* references and resources should be released.
*/
boolean onRelease(EglThread eglThread);
}
public static EglThread create(@Nullable ReleaseMonitor releaseMonitor,
@Nullable final EglBase.Context sharedContext, final int[] configAttributes) {
final HandlerThread renderThread = new HandlerThread("EglThread");
renderThread.start();
HandlerWithExceptionCallbacks handler =
new HandlerWithExceptionCallbacks(renderThread.getLooper());
// Not creating the EGLContext on the thread it will be used on seems to cause issues with
// creating window surfaces on certain devices. So keep the same legacy behavior as EglRenderer
// and create the context on the render thread.
EglConnection eglConnection = ThreadUtils.invokeAtFrontUninterruptibly(handler, () -> {
// 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) {
return EglConnection.createEgl10(configAttributes);
} else {
return EglConnection.create(sharedContext, configAttributes);
}
});
return new EglThread(
releaseMonitor != null ? releaseMonitor : eglThread -> true, handler, eglConnection);
}
/**
* Handler that triggers callbacks when an uncaught exception happens when handling a message.
*/
private static class HandlerWithExceptionCallbacks extends Handler {
private final Object callbackLock = new Object();
@GuardedBy("callbackLock") private final List<Runnable> exceptionCallbacks = new ArrayList<>();
public HandlerWithExceptionCallbacks(Looper looper) {
super(looper);
}
@Override
public void dispatchMessage(Message msg) {
try {
super.dispatchMessage(msg);
} catch (Exception e) {
Logging.e("EglThread", "Exception on EglThread", e);
synchronized (callbackLock) {
for (Runnable callback : exceptionCallbacks) {
callback.run();
}
}
throw e;
}
}
public void addExceptionCallback(Runnable callback) {
synchronized (callbackLock) {
exceptionCallbacks.add(callback);
}
}
public void removeExceptionCallback(Runnable callback) {
synchronized (callbackLock) {
exceptionCallbacks.remove(callback);
}
}
}
private final ReleaseMonitor releaseMonitor;
private final HandlerWithExceptionCallbacks handler;
private final EglConnection eglConnection;
private EglThread(ReleaseMonitor releaseMonitor, HandlerWithExceptionCallbacks handler,
EglConnection eglConnection) {
this.releaseMonitor = releaseMonitor;
this.handler = handler;
this.eglConnection = eglConnection;
}
public void release() {
if (!releaseMonitor.onRelease(this)) {
// Thread is still in use, do not release yet.
return;
}
handler.post(eglConnection::release);
handler.getLooper().quitSafely();
}
/**
* Creates an EglBase instance with the EglThread's EglConnection. This method can be called on
* any thread, but the returned EglBase instance should only be used on this EglThread's Handler.
*/
public EglBase createEglBaseWithSharedConnection() {
return EglBase.create(eglConnection);
}
/**
* Returns the Handler to interact with Gl/EGL on. Callers need to make sure that their own
* EglBase is current on the handler before running any graphics operations since the EglThread
* can be shared by multiple clients.
*/
public Handler getHandler() {
return handler;
}
/**
* Adds a callback that will be called on the EGL thread if there is an exception on the thread.
*/
public void addExceptionCallback(Runnable callback) {
handler.addExceptionCallback(callback);
}
/**
* Removes a previously added exception callback.
*/
public void removeExceptionCallback(Runnable callback) {
handler.removeExceptionCallback(callback);
}
}