1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
|
/*
* 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.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.webrtc.EglBase.EglConnection;
/** EGL graphics thread that allows multiple clients to share the same underlying EGLContext. */
public class EglThread implements RenderSynchronizer.Listener {
/** 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);
}
/** 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) {
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,
renderSynchronizer);
}
public static EglThread create(
@Nullable ReleaseMonitor releaseMonitor,
@Nullable final EglBase.Context sharedContext,
final int[] configAttributes) {
return create(releaseMonitor, sharedContext, configAttributes, /* renderSynchronizer= */ null);
}
/**
* 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 final RenderSynchronizer renderSynchronizer;
// 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<>();
private boolean renderWindowOpen = true;
private EglThread(
ReleaseMonitor releaseMonitor,
HandlerWithExceptionCallbacks handler,
EglConnection eglConnection,
RenderSynchronizer renderSynchronizer) {
this.releaseMonitor = releaseMonitor;
this.handler = handler;
this.eglConnection = eglConnection;
this.renderSynchronizer = renderSynchronizer;
if (renderSynchronizer != null) {
renderSynchronizer.registerListener(this);
}
}
public void release() {
if (!releaseMonitor.onRelease(this)) {
// Thread is still in use, do not release yet.
return;
}
if (renderSynchronizer != null) {
renderSynchronizer.removeListener(this);
}
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);
}
/**
* 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.
*
* @param id a unique id of the renderer that scheduled this render update.
*/
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
public void scheduleRenderUpdate(RenderUpdate update) {
if (renderWindowOpen) {
update.update(/* runsInline = */ true);
} else {
pendingRenderUpdatesQueued.add(update);
}
}
@Override
public void onRenderWindowOpen() {
handler.post(
() -> {
renderWindowOpen = true;
for (RenderUpdate update : pendingRenderUpdates.values()) {
update.update(/* runsInline = */ false);
}
pendingRenderUpdates.clear();
for (RenderUpdate update: pendingRenderUpdatesQueued) {
update.update(/* runsInline = */ false);
}
pendingRenderUpdatesQueued.clear();
});
}
@Override
public void onRenderWindowClose() {
handler.post(() -> renderWindowOpen = false);
}
}
|