Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7e53d0e9 authored by Robert Carr's avatar Robert Carr
Browse files

ViewRootImpl/SurfaceView: Listen for queue stalls

Native machinery now reports queue stalls from native layer
up to java layer, which can more appropriately handle errors.
The first case we handle is the case of "stuck fences", generally
indicating GPU hangs. In this case we trigger a bespoke ANR rather
than waiting for an ANR in dequeueBuffers later. dequeueBuffers
ANR could have any number of causes, and this large cluster
is difficult to debug.

Bug: 216160569
Test: Existing tests pass
Change-Id: I7b4429ce96d0bbfa1b74534ddf2b447facb22d10
parent 73a728ed
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -1200,8 +1200,10 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall
        }
        mTransformHint = viewRoot.getBufferTransformHint();
        mBlastSurfaceControl.setTransformHint(mTransformHint);

        mBlastBufferQueue = new BLASTBufferQueue(name, false /* updateDestinationFrame */);
        mBlastBufferQueue.update(mBlastSurfaceControl, mSurfaceWidth, mSurfaceHeight, mFormat);
        mBlastBufferQueue.setTransactionHangCallback(ViewRootImpl.sTransactionHangCallback);
    }

    private void onDrawFinished() {
+23 −0
Original line number Diff line number Diff line
@@ -851,6 +851,28 @@ public final class ViewRootImpl implements ViewParent,
     */
    private Bundle mRelayoutBundle = new Bundle();

    private static volatile boolean sAnrReported = false;
    static BLASTBufferQueue.TransactionHangCallback sTransactionHangCallback =
        new BLASTBufferQueue.TransactionHangCallback() {
            @Override
            public void onTransactionHang(boolean isGPUHang) {
                if (isGPUHang && !sAnrReported) {
                    sAnrReported = true;
                    try {
                        ActivityManager.getService().appNotResponding(
                            "Buffer processing hung up due to stuck fence. Indicates GPU hang");
                    } catch (RemoteException e) {
                        // We asked the system to crash us, but the system
                        // already crashed. Unfortunately things may be
                        // out of control.
                    }
                } else {
                    // TODO: Do something with this later. For now we just ANR
                    // in dequeue buffer later like we always have.
                }
            }
        };

    private String mTag = TAG;

    public ViewRootImpl(Context context, Display display) {
@@ -2086,6 +2108,7 @@ public final class ViewRootImpl implements ViewParent,
        }
        mBlastBufferQueue = new BLASTBufferQueue(mTag, mSurfaceControl,
                mSurfaceSize.x, mSurfaceSize.y, mWindowAttributes.format);
        mBlastBufferQueue.setTransactionHangCallback(sTransactionHangCallback);
        Surface blastSurface = mBlastBufferQueue.createSurface();
        // Only call transferFrom if the surface has changed to prevent inc the generation ID and
        // causing EGL resources to be recreated.
+60 −1
Original line number Diff line number Diff line
@@ -47,6 +47,43 @@ static JNIEnv* getenv(JavaVM* vm) {
    return env;
}

  struct {
    jmethodID onTransactionHang;
} gTransactionHangCallback;

class TransactionHangCallbackWrapper : public LightRefBase<TransactionHangCallbackWrapper> {
public:
    explicit TransactionHangCallbackWrapper(JNIEnv* env, jobject jobject) {
        env->GetJavaVM(&mVm);
        mTransactionHangObject = env->NewGlobalRef(jobject);
        LOG_ALWAYS_FATAL_IF(!mTransactionHangObject, "Failed to make global ref");
    }

    ~TransactionHangCallbackWrapper() {
        if (mTransactionHangObject) {
            getenv()->DeleteGlobalRef(mTransactionHangObject);
            mTransactionHangObject = nullptr;
        }
    }

    void onTransactionHang(bool isGpuHang) {
        if (mTransactionHangObject) {
            getenv()->CallVoidMethod(mTransactionHangObject,
                                     gTransactionHangCallback.onTransactionHang, isGpuHang);
        }
    }

private:
    JavaVM* mVm;
    jobject mTransactionHangObject;

    JNIEnv* getenv() {
        JNIEnv* env;
        mVm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6);
        return env;
    }
};

static jlong nativeCreate(JNIEnv* env, jclass clazz, jstring jName,
                          jboolean updateDestinationFrame) {
    ScopedUtfChars name(env, jName);
@@ -142,6 +179,20 @@ static bool nativeIsSameSurfaceControl(JNIEnv* env, jclass clazz, jlong ptr, jlo
    return queue->isSameSurfaceControl(reinterpret_cast<SurfaceControl*>(surfaceControl));
}
  
static void nativeSetTransactionHangCallback(JNIEnv* env, jclass clazz, jlong ptr,
                                             jobject transactionHangCallback) {
    sp<BLASTBufferQueue> queue = reinterpret_cast<BLASTBufferQueue*>(ptr);
    if (transactionHangCallback == nullptr) {
        queue->setTransactionHangCallback(nullptr);
    } else {
        sp<TransactionHangCallbackWrapper> wrapper =
                new TransactionHangCallbackWrapper{env, transactionHangCallback};
        queue->setTransactionHangCallback([wrapper](bool isGpuHang) {
            wrapper->onTransactionHang(isGpuHang);
        });
    }
}

static jobject nativeGatherPendingTransactions(JNIEnv* env, jclass clazz, jlong ptr,
                                               jlong frameNum) {
    sp<BLASTBufferQueue> queue = reinterpret_cast<BLASTBufferQueue*>(ptr);
@@ -163,7 +214,10 @@ static const JNINativeMethod gMethods[] = {
        {"nativeGetLastAcquiredFrameNum", "(J)J", (void*)nativeGetLastAcquiredFrameNum},
        {"nativeApplyPendingTransactions", "(JJ)V", (void*)nativeApplyPendingTransactions},
        {"nativeIsSameSurfaceControl", "(JJ)Z", (void*)nativeIsSameSurfaceControl},
        {"nativeGatherPendingTransactions", "(JJ)Landroid/view/SurfaceControl$Transaction;", (void*)nativeGatherPendingTransactions}
        {"nativeGatherPendingTransactions", "(JJ)Landroid/view/SurfaceControl$Transaction;", (void*)nativeGatherPendingTransactions},
        {"nativeSetTransactionHangCallback",
         "(JLandroid/graphics/BLASTBufferQueue$TransactionHangCallback;)V",
         (void*)nativeSetTransactionHangCallback},
        // clang-format on
};

@@ -180,6 +234,11 @@ int register_android_graphics_BLASTBufferQueue(JNIEnv* env) {
    jclass consumer = FindClassOrDie(env, "java/util/function/Consumer");
    gTransactionConsumer.accept =
            GetMethodIDOrDie(env, consumer, "accept", "(Ljava/lang/Object;)V");
    jclass transactionHangClass =
            FindClassOrDie(env, "android/graphics/BLASTBufferQueue$TransactionHangCallback");
    gTransactionHangCallback.onTransactionHang =
            GetMethodIDOrDie(env, transactionHangClass, "onTransactionHang", "(Z)V");

    return 0;
}

+10 −0
Original line number Diff line number Diff line
@@ -43,6 +43,12 @@ public final class BLASTBufferQueue {
    private static native boolean nativeIsSameSurfaceControl(long ptr, long surfaceControlPtr);
    private static native SurfaceControl.Transaction nativeGatherPendingTransactions(long ptr,
            long frameNumber);
    private static native void nativeSetTransactionHangCallback(long ptr,
            TransactionHangCallback callback);

    public interface TransactionHangCallback {
        void onTransactionHang(boolean isGpuHang);
    }

    /** Create a new connection with the surface flinger. */
    public BLASTBufferQueue(String name, SurfaceControl sc, int width, int height,
@@ -184,4 +190,8 @@ public final class BLASTBufferQueue {
    public SurfaceControl.Transaction gatherPendingTransactions(long frameNumber) {
        return nativeGatherPendingTransactions(mNativeObject, frameNumber);
    }

    public void setTransactionHangCallback(TransactionHangCallback hangCallback) {
        nativeSetTransactionHangCallback(mNativeObject, hangCallback);
    }
}