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:
Siarhei Vishniakou
2020-11-30 10:30:52 -10:00
parent 26ca287da7
commit efed16630a
9 changed files with 210 additions and 51 deletions

View File

@@ -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

View 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();
}
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View File

@@ -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)");

View File

@@ -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 {

View File

@@ -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 --- */

View File

@@ -6,6 +6,7 @@ android_test {
static_libs: [
"androidx.test.ext.junit",
"androidx.test.rules",
"truth-prebuilt",
"ub-uiautomator",
],
test_suites: ["device-tests"],

View 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)
}
}