From ecae9cd1a7753e9f8025d0a3898a70fac07b954c Mon Sep 17 00:00:00 2001 From: Magnus Jedvert Date: Fri, 5 Jul 2019 14:33:12 +0200 Subject: [PATCH] Android: Add error callback for GL_OUT_OF_MEMORY in EglRenderer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Encountering GL_OUT_OF_MEMORY is relatively common and we should give clients a chance to deal with it in a non-fatal way. Bug: webrtc:8154 Change-Id: Ifa9ca74392f21083692b02a5144dc5632a88d34d Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/144561 Commit-Queue: Magnus Jedvert Reviewed-by: Sami Kalliomäki Cr-Commit-Position: refs/heads/master@{#28495} --- sdk/android/api/org/webrtc/EglRenderer.java | 68 +++++++++++++++------ sdk/android/api/org/webrtc/GlUtil.java | 10 ++- 2 files changed, 59 insertions(+), 19 deletions(-) diff --git a/sdk/android/api/org/webrtc/EglRenderer.java b/sdk/android/api/org/webrtc/EglRenderer.java index 2ab2779b15..950f0b593f 100644 --- a/sdk/android/api/org/webrtc/EglRenderer.java +++ b/sdk/android/api/org/webrtc/EglRenderer.java @@ -37,6 +37,12 @@ public class EglRenderer implements VideoSink { public interface FrameListener { void onFrame(Bitmap frame); } + /** Callback for clients to be notified about errors encountered during rendering. */ + public static interface ErrorCallback { + /** Called if GLES20.GL_OUT_OF_MEMORY is encountered during rendering. */ + void onGlOutOfMemory(); + } + private static class FrameListenerAndParams { public final FrameListener listener; public final float scale; @@ -112,6 +118,8 @@ public class EglRenderer implements VideoSink { private final ArrayList frameListeners = new ArrayList<>(); + private volatile ErrorCallback errorCallback; + // Variables for fps reduction. private final Object fpsReductionLock = new Object(); // Time for when next frame should be rendered. @@ -485,6 +493,11 @@ public class EglRenderer implements VideoSink { ThreadUtils.awaitUninterruptibly(latch); } + /** Can be set in order to be notified about errors encountered during rendering. */ + public void setErrorCallback(ErrorCallback errorCallback) { + this.errorCallback = errorCallback; + } + // VideoSink interface. @Override public void onFrame(VideoFrame frame) { @@ -642,29 +655,44 @@ public class EglRenderer implements VideoSink { drawMatrix.preScale(scaleX, scaleY); drawMatrix.preTranslate(-0.5f, -0.5f); - if (shouldRenderFrame) { - GLES20.glClearColor(0 /* red */, 0 /* green */, 0 /* blue */, 0 /* alpha */); - GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); - frameDrawer.drawFrame(frame, drawer, drawMatrix, 0 /* viewportX */, 0 /* viewportY */, - eglBase.surfaceWidth(), eglBase.surfaceHeight()); + try { + if (shouldRenderFrame) { + GLES20.glClearColor(0 /* red */, 0 /* green */, 0 /* blue */, 0 /* alpha */); + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); + frameDrawer.drawFrame(frame, drawer, drawMatrix, 0 /* viewportX */, 0 /* viewportY */, + eglBase.surfaceWidth(), eglBase.surfaceHeight()); - final long swapBuffersStartTimeNs = System.nanoTime(); - if (usePresentationTimeStamp) { - eglBase.swapBuffers(frame.getTimestampNs()); - } else { - eglBase.swapBuffers(); + final long swapBuffersStartTimeNs = System.nanoTime(); + if (usePresentationTimeStamp) { + eglBase.swapBuffers(frame.getTimestampNs()); + } else { + eglBase.swapBuffers(); + } + + final long currentTimeNs = System.nanoTime(); + synchronized (statisticsLock) { + ++framesRendered; + renderTimeNs += (currentTimeNs - startTimeNs); + renderSwapBufferTimeNs += (currentTimeNs - swapBuffersStartTimeNs); + } } - final long currentTimeNs = System.nanoTime(); - synchronized (statisticsLock) { - ++framesRendered; - renderTimeNs += (currentTimeNs - startTimeNs); - renderSwapBufferTimeNs += (currentTimeNs - swapBuffersStartTimeNs); + notifyCallbacks(frame, shouldRenderFrame); + } catch (GlUtil.GlOutOfMemoryException e) { + logE("Error while drawing frame", e); + final ErrorCallback errorCallback = this.errorCallback; + if (errorCallback != null) { + errorCallback.onGlOutOfMemory(); } + // Attempt to free up some resources. + 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. + } finally { + frame.release(); } - - notifyCallbacks(frame, shouldRenderFrame); - frame.release(); } private void notifyCallbacks(VideoFrame frame, boolean wasRendered) { @@ -743,6 +771,10 @@ public class EglRenderer implements VideoSink { } } + private void logE(String string, Throwable e) { + Logging.e(TAG, name + string, e); + } + private void logD(String string) { Logging.d(TAG, name + string); } diff --git a/sdk/android/api/org/webrtc/GlUtil.java b/sdk/android/api/org/webrtc/GlUtil.java index 6f5e60541a..bdafe81fd8 100644 --- a/sdk/android/api/org/webrtc/GlUtil.java +++ b/sdk/android/api/org/webrtc/GlUtil.java @@ -22,11 +22,19 @@ import java.nio.FloatBuffer; public class GlUtil { private GlUtil() {} + public static class GlOutOfMemoryException extends RuntimeException { + public GlOutOfMemoryException(String msg) { + super(msg); + } + } + // Assert that no OpenGL ES 2.0 error has been raised. public static void checkNoGLES2Error(String msg) { int error = GLES20.glGetError(); if (error != GLES20.GL_NO_ERROR) { - throw new RuntimeException(msg + ": GLES20 error: " + error); + throw error == GLES20.GL_OUT_OF_MEMORY + ? new GlOutOfMemoryException(msg) + : new RuntimeException(msg + ": GLES20 error: " + error); } }