Track per-window information in ViewFrameInfo
FrameInfo will now be per-window; that is, per-ViewRootImpl. Some of the information should remain “global” (it remain in Choreographer), while some information is going to become ViewRootImpl-specific. Before the information gets passed to the native layer, the ViewRootImpl-specific info will be stitched together with the general Choreographer info. This change is useful in order to correctly correlate frames with a specific input event. In the unlikely scenario of a user touching two windows of the same app simultaneously, this change will allow us to correctly measure the latency of both frames produced by the windows. Design doc: https://docs.google.com/document/d/1KMpMBlOxnl7zkWBCbXZZE6ZlaHEA4efYnN6WYK8n3FE/edit?resourcekey=0-eqooVNP0SskupljlTFvtOQ Test: atest ViewFrameInfoTest Bug: 169866723 Change-Id: Ib0bf9cd51cbcc0b9b70460c929c480eb490ec322
This commit is contained in:
@@ -21,6 +21,7 @@ import android.annotation.Nullable;
|
||||
import android.app.ActivityManager;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.FrameInfo;
|
||||
import android.graphics.HardwareRenderer;
|
||||
import android.graphics.Picture;
|
||||
import android.graphics.Point;
|
||||
@@ -610,8 +611,7 @@ public final class ThreadedRenderer extends HardwareRenderer {
|
||||
* @param attachInfo AttachInfo tied to the specified view.
|
||||
*/
|
||||
void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {
|
||||
final Choreographer choreographer = attachInfo.mViewRootImpl.mChoreographer;
|
||||
choreographer.mFrameInfo.markDrawStart();
|
||||
attachInfo.mViewRootImpl.mViewFrameInfo.markDrawStart();
|
||||
|
||||
updateRootDisplayList(view, callbacks);
|
||||
|
||||
@@ -629,7 +629,9 @@ public final class ThreadedRenderer extends HardwareRenderer {
|
||||
attachInfo.mPendingAnimatingRenderNodes = null;
|
||||
}
|
||||
|
||||
int syncResult = syncAndDrawFrame(choreographer.mFrameInfo);
|
||||
final FrameInfo frameInfo = attachInfo.mViewRootImpl.getUpdatedFrameInfo();
|
||||
|
||||
int syncResult = syncAndDrawFrame(frameInfo);
|
||||
if ((syncResult & SYNC_LOST_SURFACE_REWARD_IF_FOUND) != 0) {
|
||||
Log.w("OpenGLRenderer", "Surface lost, forcing relayout");
|
||||
// We lost our surface. For a relayout next frame which should give us a new
|
||||
|
||||
81
core/java/android/view/ViewFrameInfo.java
Normal file
81
core/java/android/view/ViewFrameInfo.java
Normal file
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.view;
|
||||
|
||||
import android.graphics.FrameInfo;
|
||||
|
||||
/**
|
||||
* The timing information of events taking place in ViewRootImpl
|
||||
* @hide
|
||||
*/
|
||||
public class ViewFrameInfo {
|
||||
public long drawStart;
|
||||
public long oldestInputEventTime; // the time of the oldest input event consumed for this frame
|
||||
public long newestInputEventTime; // the time of the newest input event consumed for this frame
|
||||
// Various flags set to provide extra metadata about the current frame. See flag definitions
|
||||
// inside FrameInfo.
|
||||
// @see android.graphics.FrameInfo.FLAG_WINDOW_LAYOUT_CHANGED
|
||||
public long flags;
|
||||
|
||||
/**
|
||||
* Update the oldest event time.
|
||||
* @param eventTime the time of the input event
|
||||
*/
|
||||
public void updateOldestInputEvent(long eventTime) {
|
||||
if (oldestInputEventTime == 0 || eventTime < oldestInputEventTime) {
|
||||
oldestInputEventTime = eventTime;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the newest event time.
|
||||
* @param eventTime the time of the input event
|
||||
*/
|
||||
public void updateNewestInputEvent(long eventTime) {
|
||||
if (newestInputEventTime == 0 || eventTime > newestInputEventTime) {
|
||||
newestInputEventTime = eventTime;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate the missing fields using the data from ViewFrameInfo
|
||||
* @param frameInfo : the structure FrameInfo object to populate
|
||||
*/
|
||||
public void populateFrameInfo(FrameInfo frameInfo) {
|
||||
frameInfo.frameInfo[FrameInfo.FLAGS] |= flags;
|
||||
frameInfo.frameInfo[FrameInfo.DRAW_START] = drawStart;
|
||||
frameInfo.frameInfo[FrameInfo.OLDEST_INPUT_EVENT] = oldestInputEventTime;
|
||||
frameInfo.frameInfo[FrameInfo.NEWEST_INPUT_EVENT] = newestInputEventTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset this data. Should typically be invoked after calling "populateFrameInfo".
|
||||
*/
|
||||
public void reset() {
|
||||
drawStart = 0;
|
||||
oldestInputEventTime = 0;
|
||||
newestInputEventTime = 0;
|
||||
flags = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Record the current time, and store it in 'drawStart'
|
||||
*/
|
||||
public void markDrawStart() {
|
||||
drawStart = System.nanoTime();
|
||||
}
|
||||
}
|
||||
@@ -423,7 +423,7 @@ public final class ViewRootImpl implements ViewParent,
|
||||
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||||
int mHeight;
|
||||
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||||
Rect mDirty;
|
||||
private Rect mDirty;
|
||||
public boolean mIsAnimating;
|
||||
|
||||
private boolean mUseMTRenderer;
|
||||
@@ -446,6 +446,23 @@ public final class ViewRootImpl implements ViewParent,
|
||||
@UnsupportedAppUsage
|
||||
FallbackEventHandler mFallbackEventHandler;
|
||||
final Choreographer mChoreographer;
|
||||
protected final ViewFrameInfo mViewFrameInfo = new ViewFrameInfo();
|
||||
|
||||
/**
|
||||
* Update the Choreographer's FrameInfo object with the timing information for the current
|
||||
* ViewRootImpl instance. Erase the data in the current ViewFrameInfo to prepare for the next
|
||||
* frame.
|
||||
* @return the updated FrameInfo object
|
||||
*/
|
||||
protected @NonNull FrameInfo getUpdatedFrameInfo() {
|
||||
// Since Choreographer is a thread-local singleton while we can have multiple
|
||||
// ViewRootImpl's, populate the frame information from the current viewRootImpl before
|
||||
// starting the draw
|
||||
FrameInfo frameInfo = mChoreographer.mFrameInfo;
|
||||
mViewFrameInfo.populateFrameInfo(frameInfo);
|
||||
mViewFrameInfo.reset();
|
||||
return frameInfo;
|
||||
}
|
||||
|
||||
// used in relayout to get SurfaceControl size
|
||||
// for BLAST adapter surface setup
|
||||
@@ -2675,7 +2692,7 @@ public final class ViewRootImpl implements ViewParent,
|
||||
// to resume them
|
||||
mDirty.set(0, 0, mWidth, mHeight);
|
||||
}
|
||||
mChoreographer.mFrameInfo.addFlags(FrameInfo.FLAG_WINDOW_LAYOUT_CHANGED);
|
||||
mViewFrameInfo.flags |= FrameInfo.FLAG_WINDOW_LAYOUT_CHANGED;
|
||||
}
|
||||
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
|
||||
|
||||
@@ -8138,7 +8155,8 @@ public final class ViewRootImpl implements ViewParent,
|
||||
oldestEventTime = me.getHistoricalEventTimeNano(0);
|
||||
}
|
||||
}
|
||||
mChoreographer.mFrameInfo.updateInputEventTime(eventTime, oldestEventTime);
|
||||
mViewFrameInfo.updateOldestInputEvent(oldestEventTime);
|
||||
mViewFrameInfo.updateNewestInputEvent(eventTime);
|
||||
|
||||
deliverInputEvent(q);
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ public final class FrameInfo {
|
||||
public long[] frameInfo = new long[FRAME_INFO_SIZE];
|
||||
|
||||
// Various flags set to provide extra metadata about the current frame
|
||||
private static final int FLAGS = 0;
|
||||
public static final int FLAGS = 0;
|
||||
|
||||
// Is this the first-draw following a window layout?
|
||||
public static final long FLAG_WINDOW_LAYOUT_CHANGED = 1;
|
||||
@@ -60,35 +60,35 @@ public final class FrameInfo {
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface FrameInfoFlags {}
|
||||
|
||||
private static final int FRAME_TIMELINE_VSYNC_ID = 1;
|
||||
public static final int FRAME_TIMELINE_VSYNC_ID = 1;
|
||||
|
||||
// The intended vsync time, unadjusted by jitter
|
||||
private static final int INTENDED_VSYNC = 2;
|
||||
public static final int INTENDED_VSYNC = 2;
|
||||
|
||||
// Jitter-adjusted vsync time, this is what was used as input into the
|
||||
// animation & drawing system
|
||||
private static final int VSYNC = 3;
|
||||
public static final int VSYNC = 3;
|
||||
|
||||
// The time of the oldest input event
|
||||
private static final int OLDEST_INPUT_EVENT = 4;
|
||||
public static final int OLDEST_INPUT_EVENT = 4;
|
||||
|
||||
// The time of the newest input event
|
||||
private static final int NEWEST_INPUT_EVENT = 5;
|
||||
public static final int NEWEST_INPUT_EVENT = 5;
|
||||
|
||||
// When input event handling started
|
||||
private static final int HANDLE_INPUT_START = 6;
|
||||
public static final int HANDLE_INPUT_START = 6;
|
||||
|
||||
// When animation evaluations started
|
||||
private static final int ANIMATION_START = 7;
|
||||
public static final int ANIMATION_START = 7;
|
||||
|
||||
// When ViewRootImpl#performTraversals() started
|
||||
private static final int PERFORM_TRAVERSALS_START = 8;
|
||||
public static final int PERFORM_TRAVERSALS_START = 8;
|
||||
|
||||
// When View:draw() started
|
||||
private static final int DRAW_START = 9;
|
||||
public static final int DRAW_START = 9;
|
||||
|
||||
// When the frame needs to be ready by
|
||||
private static final int FRAME_DEADLINE = 10;
|
||||
public static final int FRAME_DEADLINE = 10;
|
||||
|
||||
// Must be the last one
|
||||
private static final int FRAME_INFO_SIZE = FRAME_DEADLINE + 1;
|
||||
@@ -99,22 +99,10 @@ public final class FrameInfo {
|
||||
frameInfo[FRAME_TIMELINE_VSYNC_ID] = frameTimelineVsyncId;
|
||||
frameInfo[INTENDED_VSYNC] = intendedVsync;
|
||||
frameInfo[VSYNC] = usedVsync;
|
||||
frameInfo[OLDEST_INPUT_EVENT] = Long.MAX_VALUE;
|
||||
frameInfo[NEWEST_INPUT_EVENT] = 0;
|
||||
frameInfo[FLAGS] = 0;
|
||||
frameInfo[FRAME_DEADLINE] = frameDeadline;
|
||||
}
|
||||
|
||||
/** checkstyle */
|
||||
public void updateInputEventTime(long inputEventTime, long inputEventOldestTime) {
|
||||
if (inputEventOldestTime < frameInfo[OLDEST_INPUT_EVENT]) {
|
||||
frameInfo[OLDEST_INPUT_EVENT] = inputEventOldestTime;
|
||||
}
|
||||
if (inputEventTime > frameInfo[NEWEST_INPUT_EVENT]) {
|
||||
frameInfo[NEWEST_INPUT_EVENT] = inputEventTime;
|
||||
}
|
||||
}
|
||||
|
||||
/** checkstyle */
|
||||
public void markInputHandlingStart() {
|
||||
frameInfo[HANDLE_INPUT_START] = System.nanoTime();
|
||||
@@ -130,14 +118,8 @@ public final class FrameInfo {
|
||||
frameInfo[PERFORM_TRAVERSALS_START] = System.nanoTime();
|
||||
}
|
||||
|
||||
/** checkstyle */
|
||||
public void markDrawStart() {
|
||||
frameInfo[DRAW_START] = System.nanoTime();
|
||||
}
|
||||
|
||||
/** checkstyle */
|
||||
public void addFlags(@FrameInfoFlags long flags) {
|
||||
frameInfo[FLAGS] |= flags;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
namespace android {
|
||||
namespace uirenderer {
|
||||
|
||||
const std::string FrameInfoNames[] = {
|
||||
const std::array<std::string, static_cast<int>(FrameInfoIndex::NumIndexes)> FrameInfoNames = {
|
||||
"Flags",
|
||||
"FrameTimelineVsyncId",
|
||||
"IntendedVsync",
|
||||
@@ -42,10 +42,6 @@ const std::string FrameInfoNames[] = {
|
||||
"GpuCompleted",
|
||||
};
|
||||
|
||||
static_assert((sizeof(FrameInfoNames) / sizeof(FrameInfoNames[0])) ==
|
||||
static_cast<int>(FrameInfoIndex::NumIndexes),
|
||||
"size mismatch: FrameInfoNames doesn't match the enum!");
|
||||
|
||||
static_assert(static_cast<int>(FrameInfoIndex::NumIndexes) == 19,
|
||||
"Must update value in FrameMetrics.java#FRAME_STATS_COUNT (and here)");
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <cutils/compiler.h>
|
||||
#include <utils/Timers.h>
|
||||
|
||||
#include <array>
|
||||
#include <memory.h>
|
||||
#include <string>
|
||||
|
||||
@@ -60,7 +61,7 @@ enum class FrameInfoIndex {
|
||||
NumIndexes
|
||||
};
|
||||
|
||||
extern const std::string FrameInfoNames[];
|
||||
extern const std::array<std::string, static_cast<int>(FrameInfoIndex::NumIndexes)> FrameInfoNames;
|
||||
|
||||
namespace FrameInfoFlags {
|
||||
enum {
|
||||
|
||||
@@ -238,16 +238,16 @@ public:
|
||||
|
||||
/* --- InputReaderPolicyInterface implementation --- */
|
||||
|
||||
virtual void getReaderConfiguration(InputReaderConfiguration* outConfig);
|
||||
virtual std::shared_ptr<PointerControllerInterface> obtainPointerController(int32_t deviceId);
|
||||
virtual void notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices);
|
||||
virtual std::shared_ptr<KeyCharacterMap> getKeyboardLayoutOverlay(
|
||||
const InputDeviceIdentifier& identifier);
|
||||
virtual std::string getDeviceAlias(const InputDeviceIdentifier& identifier);
|
||||
virtual TouchAffineTransformation getTouchAffineTransformation(JNIEnv *env,
|
||||
jfloatArray matrixArr);
|
||||
virtual TouchAffineTransformation getTouchAffineTransformation(
|
||||
const std::string& inputDeviceDescriptor, int32_t surfaceRotation);
|
||||
void getReaderConfiguration(InputReaderConfiguration* outConfig) override;
|
||||
std::shared_ptr<PointerControllerInterface> obtainPointerController(int32_t deviceId) override;
|
||||
void notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices) override;
|
||||
std::shared_ptr<KeyCharacterMap> getKeyboardLayoutOverlay(
|
||||
const InputDeviceIdentifier& identifier) override;
|
||||
std::string getDeviceAlias(const InputDeviceIdentifier& identifier) override;
|
||||
TouchAffineTransformation getTouchAffineTransformation(const std::string& inputDeviceDescriptor,
|
||||
int32_t surfaceRotation) override;
|
||||
|
||||
TouchAffineTransformation getTouchAffineTransformation(JNIEnv* env, jfloatArray matrixArr);
|
||||
|
||||
/* --- InputDispatcherPolicyInterface implementation --- */
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ android_test {
|
||||
static_libs: [
|
||||
"androidx.test.ext.junit",
|
||||
"androidx.test.rules",
|
||||
"truth-prebuilt",
|
||||
"ub-uiautomator",
|
||||
],
|
||||
test_suites: ["device-tests"],
|
||||
|
||||
78
tests/Input/src/com/android/test/input/ViewFrameInfoTest.kt
Normal file
78
tests/Input/src/com/android/test/input/ViewFrameInfoTest.kt
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.test.input
|
||||
|
||||
import android.graphics.FrameInfo
|
||||
import android.os.SystemClock
|
||||
import android.view.ViewFrameInfo
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
class ViewFrameInfoTest {
|
||||
companion object {
|
||||
private const val TAG = "ViewFrameInfoTest"
|
||||
}
|
||||
private val mViewFrameInfo = ViewFrameInfo()
|
||||
private var mTimeStarted: Long = 0
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
mViewFrameInfo.reset()
|
||||
mViewFrameInfo.updateOldestInputEvent(10)
|
||||
mViewFrameInfo.updateNewestInputEvent(20)
|
||||
mViewFrameInfo.flags = mViewFrameInfo.flags or FrameInfo.FLAG_WINDOW_LAYOUT_CHANGED
|
||||
mTimeStarted = SystemClock.uptimeNanos()
|
||||
mViewFrameInfo.markDrawStart()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPopulateFields() {
|
||||
assertThat(mViewFrameInfo.drawStart).isGreaterThan(mTimeStarted)
|
||||
assertThat(mViewFrameInfo.oldestInputEventTime).isEqualTo(10)
|
||||
assertThat(mViewFrameInfo.newestInputEventTime).isEqualTo(20)
|
||||
assertThat(mViewFrameInfo.flags).isEqualTo(FrameInfo.FLAG_WINDOW_LAYOUT_CHANGED)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testReset() {
|
||||
mViewFrameInfo.reset()
|
||||
// Ensure that the original object is reset correctly
|
||||
assertThat(mViewFrameInfo.drawStart).isEqualTo(0)
|
||||
assertThat(mViewFrameInfo.oldestInputEventTime).isEqualTo(0)
|
||||
assertThat(mViewFrameInfo.newestInputEventTime).isEqualTo(0)
|
||||
assertThat(mViewFrameInfo.flags).isEqualTo(0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testUpdateFrameInfoFromViewFrameInfo() {
|
||||
val frameInfo = FrameInfo()
|
||||
// By default, all values should be zero
|
||||
assertThat(frameInfo.frameInfo[FrameInfo.OLDEST_INPUT_EVENT]).isEqualTo(0)
|
||||
assertThat(frameInfo.frameInfo[FrameInfo.NEWEST_INPUT_EVENT]).isEqualTo(0)
|
||||
assertThat(frameInfo.frameInfo[FrameInfo.FLAGS]).isEqualTo(0)
|
||||
assertThat(frameInfo.frameInfo[FrameInfo.DRAW_START]).isEqualTo(0)
|
||||
|
||||
// The values inside FrameInfo should match those from ViewFrameInfo after we update them
|
||||
mViewFrameInfo.populateFrameInfo(frameInfo)
|
||||
assertThat(frameInfo.frameInfo[FrameInfo.OLDEST_INPUT_EVENT]).isEqualTo(10)
|
||||
assertThat(frameInfo.frameInfo[FrameInfo.NEWEST_INPUT_EVENT]).isEqualTo(20)
|
||||
assertThat(frameInfo.frameInfo[FrameInfo.FLAGS]).isEqualTo(
|
||||
FrameInfo.FLAG_WINDOW_LAYOUT_CHANGED)
|
||||
assertThat(frameInfo.frameInfo[FrameInfo.DRAW_START]).isGreaterThan(mTimeStarted)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user