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

Commit 62ce65d6 authored by Michael Wright's avatar Michael Wright
Browse files

Speculatively schedule input consumption

With the new tuned vsync offset, vsyncs are likely to occur shortly
after the input is received, meaning we will empty the input queue,
and thus won't schedule input consumption until more input is
received. If an application then speculatively posts draw commands to
the main looper faster than 60 hz, it will eventually end up blocking
in eglSwapBuffers. Since we're blocking in eglSwapBuffers, we won't
even schedule consumption until after the current frame (8-16ms), and
it's entirely likely we won't actually get around to consuming input
until after the next frame (another 16 ms of latency). This means we
can often go 16-32ms without processing any input events, causing
very noticeable amounts of jank.

Rather than waiting for the next input event to schedule input
consumption, speculatively schedule it every frame as long as we've
consumed some motion batch during this frame.

Bug: 11398045
Change-Id: I25e46308e00e9f9de00a1d8906f6b0e0f2e845b4
parent 7c2a2ef2
Loading
Loading
Loading
Loading
+6 −3
Original line number Diff line number Diff line
@@ -48,7 +48,7 @@ public abstract class InputEventReceiver {
            InputChannel inputChannel, MessageQueue messageQueue);
    private static native void nativeDispose(int receiverPtr);
    private static native void nativeFinishInputEvent(int receiverPtr, int seq, boolean handled);
    private static native void nativeConsumeBatchedInputEvents(int receiverPtr,
    private static native boolean nativeConsumeBatchedInputEvents(int receiverPtr,
            long frameTimeNanos);

    /**
@@ -165,14 +165,17 @@ public abstract class InputEventReceiver {
     *
     * @param frameTimeNanos The time in the {@link System#nanoTime()} time base
     * when the current display frame started rendering, or -1 if unknown.
     *
     * @return Whether a batch was consumed
     */
    public final void consumeBatchedInputEvents(long frameTimeNanos) {
    public final boolean consumeBatchedInputEvents(long frameTimeNanos) {
        if (mReceiverPtr == 0) {
            Log.w(TAG, "Attempted to consume batched input events but the input event "
                    + "receiver has already been disposed.");
        } else {
            nativeConsumeBatchedInputEvents(mReceiverPtr, frameTimeNanos);
            return nativeConsumeBatchedInputEvents(mReceiverPtr, frameTimeNanos);
        }
        return false;
    }

    // Called from native code.
+7 −1
Original line number Diff line number Diff line
@@ -5635,7 +5635,13 @@ public final class ViewRootImpl implements ViewParent,
        if (mConsumeBatchedInputScheduled) {
            mConsumeBatchedInputScheduled = false;
            if (mInputEventReceiver != null) {
                mInputEventReceiver.consumeBatchedInputEvents(frameTimeNanos);
                if (mInputEventReceiver.consumeBatchedInputEvents(frameTimeNanos)) {
                    // If we consumed a batch here, we want to go ahead and schedule the
                    // consumption of batched input events on the next frame. Otherwise, we would
                    // wait until we have more input events pending and might get starved by other
                    // things occurring in the process.
                    scheduleConsumeBatchedInput();
                }
            }
            doProcessInputEvents();
        }
+21 −9
Original line number Diff line number Diff line
@@ -56,7 +56,8 @@ public:
    status_t initialize();
    void dispose();
    status_t finishInputEvent(uint32_t seq, bool handled);
    status_t consumeEvents(JNIEnv* env, bool consumeBatches, nsecs_t frameTime);
    status_t consumeEvents(JNIEnv* env, bool consumeBatches, nsecs_t frameTime,
            bool* outConsumedBatch);

protected:
    virtual ~NativeInputEventReceiver();
@@ -167,7 +168,7 @@ int NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data)

    if (events & ALOOPER_EVENT_INPUT) {
        JNIEnv* env = AndroidRuntime::getJNIEnv();
        status_t status = consumeEvents(env, false /*consumeBatches*/, -1);
        status_t status = consumeEvents(env, false /*consumeBatches*/, -1, NULL);
        mMessageQueue->raiseAndClearException(env, "handleReceiveCallback");
        return status == OK || status == NO_MEMORY ? 1 : 0;
    }
@@ -214,7 +215,7 @@ int NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data)
}

status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,
        bool consumeBatches, nsecs_t frameTime) {
        bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) {
#if DEBUG_DISPATCH_CYCLE
    ALOGD("channel '%s' ~ Consuming input events, consumeBatches=%s, frameTime=%lld.",
            getInputChannelName(), consumeBatches ? "true" : "false", frameTime);
@@ -223,6 +224,9 @@ status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,
    if (consumeBatches) {
        mBatchedInputEventPending = false;
    }
    if (outConsumedBatch) {
        *outConsumedBatch = false;
    }

    ScopedLocalRef<jobject> receiverObj(env, NULL);
    bool skipCallbacks = false;
@@ -285,13 +289,17 @@ status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,
                        static_cast<KeyEvent*>(inputEvent));
                break;

            case AINPUT_EVENT_TYPE_MOTION:
            case AINPUT_EVENT_TYPE_MOTION: {
#if DEBUG_DISPATCH_CYCLE
                ALOGD("channel '%s' ~ Received motion event.", getInputChannelName());
#endif
                inputEventObj = android_view_MotionEvent_obtainAsCopy(env,
                        static_cast<MotionEvent*>(inputEvent));
                MotionEvent* motionEvent = static_cast<MotionEvent*>(inputEvent);
                if ((motionEvent->getAction() & AMOTION_EVENT_ACTION_MOVE) && outConsumedBatch) {
                    *outConsumedBatch = true;
                }
                inputEventObj = android_view_MotionEvent_obtainAsCopy(env, motionEvent);
                break;
            }

            default:
                assert(false); // InputConsumer should prevent this from ever happening
@@ -370,16 +378,20 @@ static void nativeFinishInputEvent(JNIEnv* env, jclass clazz, jint receiverPtr,
    }
}

static void nativeConsumeBatchedInputEvents(JNIEnv* env, jclass clazz, jint receiverPtr,
static bool nativeConsumeBatchedInputEvents(JNIEnv* env, jclass clazz, jint receiverPtr,
        jlong frameTimeNanos) {
    sp<NativeInputEventReceiver> receiver =
            reinterpret_cast<NativeInputEventReceiver*>(receiverPtr);
    status_t status = receiver->consumeEvents(env, true /*consumeBatches*/, frameTimeNanos);
    bool consumedBatch;
    status_t status = receiver->consumeEvents(env, true /*consumeBatches*/, frameTimeNanos,
            &consumedBatch);
    if (status && status != DEAD_OBJECT && !env->ExceptionCheck()) {
        String8 message;
        message.appendFormat("Failed to consume batched input event.  status=%d", status);
        jniThrowRuntimeException(env, message.string());
        return false;
    }
    return consumedBatch;
}


@@ -392,7 +404,7 @@ static JNINativeMethod gMethods[] = {
            (void*)nativeDispose },
    { "nativeFinishInputEvent", "(IIZ)V",
            (void*)nativeFinishInputEvent },
    { "nativeConsumeBatchedInputEvents", "(IJ)V",
    { "nativeConsumeBatchedInputEvents", "(IJ)Z",
            (void*)nativeConsumeBatchedInputEvents },
};