Adopt RenderSynchronizer in EglThread and EglRenderer
This gives the option to synchronize rendering updates with the display refresh cycle and limit effective updates to a certain frame rate. go/meet-android-synchronized-rendering Bug: b/217863437 Change-Id: I4938a10f4e80d98a17e28f2e397fbb95117a3e4c Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/325061 Reviewed-by: Ranveer Aggarwal <ranvr@webrtc.org> Commit-Queue: Linus Nilsson <lnilsson@webrtc.org> Reviewed-by: Sergey Silkin <ssilkin@webrtc.org> Cr-Commit-Position: refs/heads/main@{#41012}
This commit is contained in:
parent
b9b4609747
commit
40ce7674c4
@ -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();
|
||||
}
|
||||
|
||||
@ -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<RenderUpdate> 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);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user