diff --git a/sdk/android/api/org/webrtc/EglRenderer.java b/sdk/android/api/org/webrtc/EglRenderer.java index 202094a092..921abe66e4 100644 --- a/sdk/android/api/org/webrtc/EglRenderer.java +++ b/sdk/android/api/org/webrtc/EglRenderer.java @@ -562,6 +562,32 @@ public class EglRenderer implements VideoSink { } } + private void swapBuffersOnRenderThread(final VideoFrame frame, long swapBuffersStartTimeNs) { + synchronized (threadLock) { + if (eglThread != null) { + eglThread.scheduleRenderUpdate( + runsInline -> { + if (!runsInline) { + if (eglBase == null || !eglBase.hasSurface()) { + return; + } + eglBase.makeCurrent(); + } + + if (usePresentationTimeStamp) { + eglBase.swapBuffers(frame.getTimestampNs()); + } else { + eglBase.swapBuffers(); + } + + synchronized (statisticsLock) { + renderSwapBufferTimeNs += (System.nanoTime() - swapBuffersStartTimeNs); + } + }); + } + } + } + /** * Renders and releases `pendingFrame`. */ @@ -638,17 +664,11 @@ public class EglRenderer implements VideoSink { eglBase.surfaceWidth(), eglBase.surfaceHeight()); final long swapBuffersStartTimeNs = System.nanoTime(); - if (usePresentationTimeStamp) { - eglBase.swapBuffers(frame.getTimestampNs()); - } else { - eglBase.swapBuffers(); - } + swapBuffersOnRenderThread(frame, swapBuffersStartTimeNs); - final long currentTimeNs = System.nanoTime(); synchronized (statisticsLock) { ++framesRendered; - renderTimeNs += (currentTimeNs - startTimeNs); - renderSwapBufferTimeNs += (currentTimeNs - swapBuffersStartTimeNs); + renderTimeNs += (swapBuffersStartTimeNs - startTimeNs); } } @@ -663,8 +683,8 @@ public class EglRenderer implements VideoSink { drawer.release(); frameDrawer.release(); bitmapTextureFramebuffer.release(); - // Continue here on purpose and retry again for next frame. In worst case, this is a continous - // problem and no more frames will be drawn. + // Continue here on purpose and retry again for next frame. In worst case, this is a + // continuous problem and no more frames will be drawn. } finally { frame.release(); } diff --git a/sdk/android/api/org/webrtc/EglThread.java b/sdk/android/api/org/webrtc/EglThread.java index 47f7810b7c..73323d59c8 100644 --- a/sdk/android/api/org/webrtc/EglThread.java +++ b/sdk/android/api/org/webrtc/EglThread.java @@ -21,7 +21,7 @@ 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 { +public class EglThread implements RenderSynchronizer.Listener { /** Callback for externally managed reference count. */ public interface ReleaseMonitor { /** @@ -31,8 +31,21 @@ public class EglThread { boolean onRelease(EglThread eglThread); } - public static EglThread create(@Nullable ReleaseMonitor releaseMonitor, - @Nullable final EglBase.Context sharedContext, final int[] configAttributes) { + /** 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 = @@ -53,7 +66,17 @@ public class EglThread { }); return new EglThread( - releaseMonitor != null ? releaseMonitor : eglThread -> true, handler, eglConnection); + 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); } /** @@ -98,12 +121,22 @@ public class EglThread { private final ReleaseMonitor releaseMonitor; private final HandlerWithExceptionCallbacks handler; private final EglConnection eglConnection; + private final RenderSynchronizer renderSynchronizer; + private final List pendingRenderUpdates = new ArrayList<>(); + private boolean renderWindowOpen = true; - private EglThread(ReleaseMonitor releaseMonitor, HandlerWithExceptionCallbacks handler, - EglConnection eglConnection) { + 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() { @@ -112,6 +145,10 @@ public class EglThread { return; } + if (renderSynchronizer != null) { + renderSynchronizer.removeListener(this); + } + handler.post(eglConnection::release); handler.getLooper().quitSafely(); } @@ -146,4 +183,34 @@ public class EglThread { 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. + */ + public void scheduleRenderUpdate(RenderUpdate update) { + if (renderWindowOpen) { + update.update(/* runsInline = */true); + } else { + pendingRenderUpdates.add(update); + } + } + + @Override + public void onRenderWindowOpen() { + handler.post( + () -> { + renderWindowOpen = true; + for (RenderUpdate update : pendingRenderUpdates) { + update.update(/* runsInline = */false); + } + pendingRenderUpdates.clear(); + }); + } + + @Override + public void onRenderWindowClose() { + handler.post(() -> renderWindowOpen = false); + } }