Optionally overwrite instead of queueing render updates.

Here, we overwrite the pending frames per renderer.

Bug: webrtc:351858995
Change-Id: I070219aec4e7be5f2b0c9f2371fe2c99af3e3920
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/358760
Reviewed-by: Zoé Lepaul <xalep@webrtc.org>
Reviewed-by: Magnus Jedvert <magjed@webrtc.org>
Reviewed-by: Fabian Bergmark <fabianbergmark@google.com>
Commit-Queue: Zoé Lepaul <xalep@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#42741}
This commit is contained in:
Ranveer Aggarwal 2024-08-07 14:32:54 +02:00 committed by WebRTC LUCI CQ
parent 675986ec5f
commit c7e2568457
2 changed files with 80 additions and 26 deletions

View File

@ -21,6 +21,8 @@ import java.nio.ByteBuffer;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -93,6 +95,10 @@ public class EglRenderer implements VideoSink {
protected final String name; protected final String name;
// An id to uniquely identify the renderer, used for when we're scheduling
// frames for render.
private Optional<UUID> id = Optional.empty();
// `eglThread` is used for rendering, and is synchronized on `threadLock`. // `eglThread` is used for rendering, and is synchronized on `threadLock`.
private final Object threadLock = new Object(); private final Object threadLock = new Object();
@GuardedBy("threadLock") @Nullable private EglThread eglThread; @GuardedBy("threadLock") @Nullable private EglThread eglThread;
@ -214,6 +220,23 @@ public class EglRenderer implements VideoSink {
} }
} }
/**
* Intializes this class with the given parameters.
*
* The new parameter here, `overwritePendingFrames` overwrites instead of
* queueing frames when passing them to the synchronized renderer.
*/
public void init(
EglThread eglThread,
RendererCommon.GlDrawer drawer,
boolean usePresentationTimeStamp,
boolean overwritePendingFrames) {
if (overwritePendingFrames) {
id = Optional.of(UUID.randomUUID());
}
init(eglThread, drawer, usePresentationTimeStamp);
}
/** /**
* Initialize this class, sharing resources with `sharedContext`. The custom `drawer` will be used * Initialize this class, sharing resources with `sharedContext`. The custom `drawer` will be used
* for drawing frames on the EGLSurface. This class is responsible for calling release() on * for drawing frames on the EGLSurface. This class is responsible for calling release() on
@ -444,7 +467,7 @@ public class EglRenderer implements VideoSink {
} }
/** /**
* Register a callback to be invoked when a new video frame has been rendered. * Register a callback to be invoked when a new video frame has been rendered.
* *
* @param listener The callback to be invoked. The callback will be invoked on the render thread. * @param listener The callback to be invoked. The callback will be invoked on the render thread.
* It should be lightweight and must not call removeRenderListener. * It should be lightweight and must not call removeRenderListener.
@ -611,30 +634,36 @@ public class EglRenderer implements VideoSink {
private void swapBuffersOnRenderThread(final VideoFrame frame, long swapBuffersStartTimeNs) { private void swapBuffersOnRenderThread(final VideoFrame frame, long swapBuffersStartTimeNs) {
synchronized (threadLock) { synchronized (threadLock) {
if (eglThread != null) { if (eglThread == null) {
eglThread.scheduleRenderUpdate( return;
runsInline -> { }
if (!runsInline) { EglThread.RenderUpdate renderUpdate =
if (eglBase == null || !eglBase.hasSurface()) { runsInline -> {
return; if (!runsInline) {
} if (eglBase == null || !eglBase.hasSurface()) {
eglBase.makeCurrent(); return;
} }
eglBase.makeCurrent();
}
if (usePresentationTimeStamp) { if (usePresentationTimeStamp) {
eglBase.swapBuffers(frame.getTimestampNs()); eglBase.swapBuffers(frame.getTimestampNs());
} else { } else {
eglBase.swapBuffers(); eglBase.swapBuffers();
} }
for (var listener : renderListeners) { for (var listener : renderListeners) {
listener.onRender(System.nanoTime()); listener.onRender(System.nanoTime());
} }
synchronized (statisticsLock) { synchronized (statisticsLock) {
renderSwapBufferTimeNs += (System.nanoTime() - swapBuffersStartTimeNs); renderSwapBufferTimeNs += (System.nanoTime() - swapBuffersStartTimeNs);
} }
}); };
if (id.isPresent()) {
eglThread.scheduleRenderUpdate(id.get(), renderUpdate);
} else {
eglThread.scheduleRenderUpdate(renderUpdate);
} }
} }
} }

View File

@ -16,8 +16,13 @@ import android.os.Looper;
import android.os.Message; import android.os.Message;
import androidx.annotation.GuardedBy; import androidx.annotation.GuardedBy;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.webrtc.EglBase.EglConnection; import org.webrtc.EglBase.EglConnection;
/** EGL graphics thread that allows multiple clients to share the same underlying EGLContext. */ /** EGL graphics thread that allows multiple clients to share the same underlying EGLContext. */
@ -122,7 +127,10 @@ public class EglThread implements RenderSynchronizer.Listener {
private final HandlerWithExceptionCallbacks handler; private final HandlerWithExceptionCallbacks handler;
private final EglConnection eglConnection; private final EglConnection eglConnection;
private final RenderSynchronizer renderSynchronizer; private final RenderSynchronizer renderSynchronizer;
private final List<RenderUpdate> pendingRenderUpdates = new ArrayList<>(); // 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 boolean renderWindowOpen = true;
private EglThread( private EglThread(
@ -188,12 +196,25 @@ public class EglThread implements RenderSynchronizer.Listener {
* Schedules a render update (like swapBuffers) to be run in sync with other updates on the next * 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. * 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. * 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) { public void scheduleRenderUpdate(RenderUpdate update) {
if (renderWindowOpen) { if (renderWindowOpen) {
update.update(/* runsInline = */true); update.update(/* runsInline = */ true);
} else { } else {
pendingRenderUpdates.add(update); pendingRenderUpdatesQueued.add(update);
} }
} }
@ -202,10 +223,14 @@ public class EglThread implements RenderSynchronizer.Listener {
handler.post( handler.post(
() -> { () -> {
renderWindowOpen = true; renderWindowOpen = true;
for (RenderUpdate update : pendingRenderUpdates) { for (RenderUpdate update : pendingRenderUpdates.values()) {
update.update(/* runsInline = */false); update.update(/* runsInline = */ false);
} }
pendingRenderUpdates.clear(); pendingRenderUpdates.clear();
for (RenderUpdate update: pendingRenderUpdatesQueued) {
update.update(/* runsInline = */ false);
}
pendingRenderUpdatesQueued.clear();
}); });
} }