2023-05-17 22:07:42 +02:00
|
|
|
/*
|
|
|
|
|
* 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;
|
2023-07-11 14:12:58 +02:00
|
|
|
import android.os.Looper;
|
|
|
|
|
import android.os.Message;
|
|
|
|
|
import androidx.annotation.GuardedBy;
|
2023-05-17 22:07:42 +02:00
|
|
|
import androidx.annotation.Nullable;
|
2024-08-07 14:32:54 +02:00
|
|
|
|
2023-07-11 14:12:58 +02:00
|
|
|
import java.util.ArrayList;
|
2024-08-07 14:32:54 +02:00
|
|
|
import java.util.HashMap;
|
2023-07-11 14:12:58 +02:00
|
|
|
import java.util.List;
|
2024-08-07 14:32:54 +02:00
|
|
|
import java.util.Map;
|
|
|
|
|
import java.util.UUID;
|
|
|
|
|
|
2023-05-17 22:07:42 +02:00
|
|
|
import org.webrtc.EglBase.EglConnection;
|
|
|
|
|
|
|
|
|
|
/** EGL graphics thread that allows multiple clients to share the same underlying EGLContext. */
|
2023-10-26 09:47:21 +02:00
|
|
|
public class EglThread implements RenderSynchronizer.Listener {
|
2023-05-17 22:07:42 +02:00
|
|
|
/** 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);
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-26 09:47:21 +02:00
|
|
|
/** Interface for clients to schedule rendering updates that will run synchronized. */
|
|
|
|
|
public interface RenderUpdate {
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Called by EglThread when the rendering window is open. `runsInline` is true when the update
|
|
|
|
|
* is executed directly while the client schedules the update.
|
|
|
|
|
*/
|
|
|
|
|
void update(boolean runsInline);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static EglThread create(
|
|
|
|
|
@Nullable ReleaseMonitor releaseMonitor,
|
|
|
|
|
@Nullable final EglBase.Context sharedContext,
|
|
|
|
|
final int[] configAttributes,
|
|
|
|
|
@Nullable RenderSynchronizer renderSynchronizer) {
|
2023-05-17 22:07:42 +02:00
|
|
|
final HandlerThread renderThread = new HandlerThread("EglThread");
|
|
|
|
|
renderThread.start();
|
2023-07-11 14:12:58 +02:00
|
|
|
HandlerWithExceptionCallbacks handler =
|
|
|
|
|
new HandlerWithExceptionCallbacks(renderThread.getLooper());
|
2023-05-17 22:07:42 +02:00
|
|
|
|
2023-07-10 11:24:21 +02:00
|
|
|
// 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);
|
|
|
|
|
}
|
|
|
|
|
});
|
2023-05-17 22:07:42 +02:00
|
|
|
|
|
|
|
|
return new EglThread(
|
2023-10-26 09:47:21 +02:00
|
|
|
releaseMonitor != null ? releaseMonitor : eglThread -> true,
|
|
|
|
|
handler,
|
|
|
|
|
eglConnection,
|
|
|
|
|
renderSynchronizer);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static EglThread create(
|
|
|
|
|
@Nullable ReleaseMonitor releaseMonitor,
|
|
|
|
|
@Nullable final EglBase.Context sharedContext,
|
|
|
|
|
final int[] configAttributes) {
|
|
|
|
|
return create(releaseMonitor, sharedContext, configAttributes, /* renderSynchronizer= */ null);
|
2023-05-17 22:07:42 +02:00
|
|
|
}
|
|
|
|
|
|
2023-07-11 14:12:58 +02:00
|
|
|
/**
|
|
|
|
|
* 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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-17 22:07:42 +02:00
|
|
|
private final ReleaseMonitor releaseMonitor;
|
2023-07-11 14:12:58 +02:00
|
|
|
private final HandlerWithExceptionCallbacks handler;
|
2023-05-17 22:07:42 +02:00
|
|
|
private final EglConnection eglConnection;
|
2023-10-26 09:47:21 +02:00
|
|
|
private final RenderSynchronizer renderSynchronizer;
|
2024-08-07 14:32:54 +02:00
|
|
|
// Pending render updates if they're overwritten per renderer.
|
|
|
|
|
private final Map<UUID, RenderUpdate> pendingRenderUpdates = new HashMap<>();
|
|
|
|
|
// Pending render updates if they're in a global queue.
|
|
|
|
|
private final List<RenderUpdate> pendingRenderUpdatesQueued = new ArrayList<>();
|
2023-10-26 09:47:21 +02:00
|
|
|
private boolean renderWindowOpen = true;
|
|
|
|
|
|
|
|
|
|
private EglThread(
|
|
|
|
|
ReleaseMonitor releaseMonitor,
|
|
|
|
|
HandlerWithExceptionCallbacks handler,
|
|
|
|
|
EglConnection eglConnection,
|
|
|
|
|
RenderSynchronizer renderSynchronizer) {
|
2023-05-17 22:07:42 +02:00
|
|
|
this.releaseMonitor = releaseMonitor;
|
|
|
|
|
this.handler = handler;
|
|
|
|
|
this.eglConnection = eglConnection;
|
2023-10-26 09:47:21 +02:00
|
|
|
this.renderSynchronizer = renderSynchronizer;
|
|
|
|
|
if (renderSynchronizer != null) {
|
|
|
|
|
renderSynchronizer.registerListener(this);
|
|
|
|
|
}
|
2023-05-17 22:07:42 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void release() {
|
|
|
|
|
if (!releaseMonitor.onRelease(this)) {
|
|
|
|
|
// Thread is still in use, do not release yet.
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2023-10-26 09:47:21 +02:00
|
|
|
if (renderSynchronizer != null) {
|
|
|
|
|
renderSynchronizer.removeListener(this);
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-17 22:07:42 +02:00
|
|
|
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;
|
|
|
|
|
}
|
2023-07-11 14:12:58 +02:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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);
|
|
|
|
|
}
|
2023-10-26 09:47:21 +02:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Schedules a render update (like swapBuffers) to be run in sync with other updates on the next
|
|
|
|
|
* open render window. If the render window is currently open the update will run immediately.
|
|
|
|
|
* This method must be called on the EglThread during a render pass.
|
2024-08-07 14:32:54 +02:00
|
|
|
*
|
|
|
|
|
* @param id a unique id of the renderer that scheduled this render update.
|
2023-10-26 09:47:21 +02:00
|
|
|
*/
|
2024-08-07 14:32:54 +02:00
|
|
|
public void scheduleRenderUpdate(UUID id, RenderUpdate update) {
|
|
|
|
|
if (renderWindowOpen) {
|
|
|
|
|
update.update(/* runsInline = */ true);
|
|
|
|
|
} else {
|
|
|
|
|
pendingRenderUpdates.put(id, update);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The same as above, except that the ids are randomly generated for each frame.
|
|
|
|
|
// So this essentially becomes a queue of frame updates.
|
|
|
|
|
@Deprecated
|
2023-10-26 09:47:21 +02:00
|
|
|
public void scheduleRenderUpdate(RenderUpdate update) {
|
|
|
|
|
if (renderWindowOpen) {
|
2024-08-07 14:32:54 +02:00
|
|
|
update.update(/* runsInline = */ true);
|
2023-10-26 09:47:21 +02:00
|
|
|
} else {
|
2024-08-07 14:32:54 +02:00
|
|
|
pendingRenderUpdatesQueued.add(update);
|
2023-10-26 09:47:21 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void onRenderWindowOpen() {
|
|
|
|
|
handler.post(
|
|
|
|
|
() -> {
|
|
|
|
|
renderWindowOpen = true;
|
2024-08-07 14:32:54 +02:00
|
|
|
for (RenderUpdate update : pendingRenderUpdates.values()) {
|
|
|
|
|
update.update(/* runsInline = */ false);
|
2024-07-30 16:07:00 +00:00
|
|
|
}
|
|
|
|
|
pendingRenderUpdates.clear();
|
2024-08-07 14:32:54 +02:00
|
|
|
for (RenderUpdate update: pendingRenderUpdatesQueued) {
|
|
|
|
|
update.update(/* runsInline = */ false);
|
|
|
|
|
}
|
|
|
|
|
pendingRenderUpdatesQueued.clear();
|
2023-10-26 09:47:21 +02:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void onRenderWindowClose() {
|
|
|
|
|
handler.post(() -> renderWindowOpen = false);
|
|
|
|
|
}
|
2023-05-17 22:07:42 +02:00
|
|
|
}
|