| /* | |
| * 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); | |
| } | |
| } |