Merge "AttachedChoreographerTest: add new tests for layer hierarchy" into main

This commit is contained in:
Ady Abraham
2023-07-11 21:01:14 +00:00
committed by Android (Google) Code Review

View File

@@ -22,9 +22,9 @@ import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
import android.Manifest;
import android.app.compat.CompatChanges;
import android.hardware.display.DisplayManager;
import android.os.Looper;
import android.support.test.uiautomator.UiDevice;
@@ -34,6 +34,7 @@ import android.view.Surface;
import android.view.SurfaceControl;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.WindowManager;
import androidx.lifecycle.Lifecycle;
import androidx.test.core.app.ActivityScenario;
@@ -52,7 +53,7 @@ import java.util.concurrent.TimeUnit;
public class AttachedChoreographerTest {
private static final String TAG = "AttachedChoreographerTest";
private static final long DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGEID = 170503758;
private static final long THRESHOLD_MS = 10;
private static final long THRESHOLD_MS = 4;
private static final int FRAME_ITERATIONS = 21;
private static final int CALLBACK_MISSED_THRESHOLD = 2;
@@ -66,7 +67,7 @@ public class AttachedChoreographerTest {
private SurfaceView mSurfaceView;
private SurfaceHolder mSurfaceHolder;
private Choreographer mChoreographer;
private boolean mIsFirstCallback = true;
private long mExpectedPresentTime;
private int mCallbackMissedCounter = 0;
@Before
@@ -74,6 +75,13 @@ public class AttachedChoreographerTest {
mScenario = ActivityScenario.launch(GraphicsActivity.class);
mScenario.moveToState(Lifecycle.State.CREATED);
mScenario.onActivity(activity -> {
// Lock the display frame rate. This will not allow SurfaceFlinger to use the frame rate
// override feature that throttles down the global choreographer for this test.
float refreshRate = activity.getDisplay().getMode().getRefreshRate();
WindowManager.LayoutParams attrs = activity.getWindow().getAttributes();
attrs.preferredRefreshRate = refreshRate;
activity.getWindow().setAttributes(attrs);
mSurfaceView = activity.findViewById(R.id.surface);
mSurfaceHolder = mSurfaceView.getHolder();
mSurfaceHolder.addCallback(new SurfaceHolder.Callback() {
@@ -95,6 +103,12 @@ public class AttachedChoreographerTest {
});
UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
// TODO(b/290634611): clean this up once SF new front end is enabled by default
boolean sfNewFeEnabled = uiDevice.executeShellCommand("dumpsys SurfaceFlinger")
.indexOf("SurfaceFlinger New Frontend Enabled:true") != -1;
assumeTrue(sfNewFeEnabled);
uiDevice.wakeUp();
uiDevice.executeShellCommand("wm dismiss-keyguard");
mScenario.moveToState(Lifecycle.State.RESUMED);
@@ -112,18 +126,16 @@ public class AttachedChoreographerTest {
mDisplayManager.setRefreshRateSwitchingType(
DisplayManager.SWITCHING_TYPE_RENDER_FRAME_RATE_ONLY);
mDisplayManager.setShouldAlwaysRespectAppRequestedMode(true);
boolean changeIsEnabled =
CompatChanges.isChangeEnabled(
DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGEID);
Log.i(TAG, "DISPLAY_MODE_RETURNS_PHYSICAL_REFRESH_RATE_CHANGE_ID is "
+ (changeIsEnabled ? "enabled" : "disabled"));
});
}
@After
public void tearDown() {
mDisplayManager.setRefreshRateSwitchingType(mInitialMatchContentFrameRate);
mDisplayManager.setShouldAlwaysRespectAppRequestedMode(false);
if (mDisplayManager != null) {
mDisplayManager.setRefreshRateSwitchingType(mInitialMatchContentFrameRate);
mDisplayManager.setShouldAlwaysRespectAppRequestedMode(false);
}
InstrumentationRegistry.getInstrumentation().getUiAutomation()
.dropShellPermissionIdentity();
}
@@ -407,18 +419,141 @@ public class AttachedChoreographerTest {
}
}
@Test
public void testChoreographerAttachedAfterSetFrameRate() {
Log.i(TAG, "adyabr: starting testChoreographerAttachedAfterSetFrameRate");
class TransactionGenerator implements SurfaceControl.TransactionCommittedListener {
private SurfaceControl mSc;
private int mFrame;
private static final int NUM_FRAMES = 50;
private final CountDownLatch mCompleteLatch = new CountDownLatch(1);
TransactionGenerator(SurfaceControl sc) {
mSc = sc;
}
@Override
public void onTransactionCommitted() {
mFrame++;
if (mFrame >= NUM_FRAMES) {
mCompleteLatch.countDown();
return;
}
SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
transaction.setAlpha(mSc, 1.0f / mFrame)
.addTransactionCommittedListener(Runnable::run, this)
.apply();
}
public void startAndWaitForCompletion() {
onTransactionCommitted();
if (waitForCountDown(mCompleteLatch, /* timeoutInSeconds */ 10L)) {
fail("failed to send new transactions");
}
}
}
for (int divisor : new int[]{2, 3}) {
CountDownLatch choreographerLatch = new CountDownLatch(1);
mScenario.onActivity(activity -> {
if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) {
fail("Unable to create surface within 1 Second");
}
SurfaceControl sc = mSurfaceView.getSurfaceControl();
SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
float displayRefreshRate = activity.getDisplay().getMode().getRefreshRate();
float fps = displayRefreshRate / divisor;
long callbackDurationMs = Math.round(1000 / fps);
mCallbackMissedCounter = 0;
transaction.setFrameRate(sc, fps, Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE)
.apply();
new TransactionGenerator(sc).startAndWaitForCompletion();
Choreographer choreographer = sc.getChoreographer();
verifyVsyncCallbacks(choreographer, callbackDurationMs,
choreographerLatch, FRAME_ITERATIONS);
});
// wait for the previous callbacks to finish before moving to the next divisor
if (waitForCountDown(choreographerLatch, /* timeoutInSeconds */ 5L)) {
fail("Test not finished in 5 Seconds");
}
}
}
@Test
public void testChoreographerNonDivisorFixedSourceRefreshRate() {
CountDownLatch continueLatch = new CountDownLatch(1);
mScenario.onActivity(activity -> {
if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) {
fail("Unable to create surface within 1 Second");
}
SurfaceControl sc = mSurfaceView.getSurfaceControl();
Choreographer choreographer = sc.getChoreographer();
SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
float displayRefreshRate = activity.getDisplay().getMode().getRefreshRate();
float fps = 61.7f; // hopefully not a divisor
long callbackDurationMs = Math.round(1000 / displayRefreshRate);
mCallbackMissedCounter = 0;
transaction.setFrameRate(sc, fps, Surface.FRAME_RATE_COMPATIBILITY_FIXED_SOURCE)
.addTransactionCommittedListener(Runnable::run,
() -> verifyVsyncCallbacks(choreographer,
callbackDurationMs, continueLatch, FRAME_ITERATIONS))
.apply();
});
// wait for the previous callbacks to finish before moving to the next divisor
if (waitForCountDown(continueLatch, /* timeoutInSeconds */ 5L)) {
fail("Test not finished in 5 Seconds");
}
}
@Test
public void testChoreographerNonDivisorRefreshRate() {
CountDownLatch continueLatch = new CountDownLatch(1);
mScenario.onActivity(activity -> {
if (waitForCountDown(mSurfaceCreationCountDown, /* timeoutInSeconds */ 1L)) {
fail("Unable to create surface within 1 Second");
}
SurfaceControl sc = mSurfaceView.getSurfaceControl();
Choreographer choreographer = sc.getChoreographer();
SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
float displayRefreshRate = activity.getDisplay().getMode().getRefreshRate();
float fps = 61.7f; // hopefully not a divisor
float expectedFps = displayRefreshRate / Math.round(displayRefreshRate / fps);
long callbackDurationMs = Math.round(1000 / expectedFps);
mCallbackMissedCounter = 0;
transaction.setFrameRate(sc, fps, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT)
.addTransactionCommittedListener(Runnable::run,
() -> verifyVsyncCallbacks(choreographer,
callbackDurationMs, continueLatch, FRAME_ITERATIONS))
.apply();
});
// wait for the previous callbacks to finish before moving to the next divisor
if (waitForCountDown(continueLatch, /* timeoutInSeconds */ 5L)) {
fail("Test not finished in 5 Seconds");
}
}
private void verifyVsyncCallbacks(Choreographer choreographer, long callbackDurationMs,
CountDownLatch continueLatch, int frameCount) {
long callbackRequestedTimeNs = System.nanoTime();
choreographer.postVsyncCallback(frameData -> {
long expectedPresentTime =
frameData.getPreferredFrameTimeline().getExpectedPresentationTimeNanos();
if (frameCount > 0) {
if (!mIsFirstCallback) {
// Skip the first callback as it takes 1 frame
// to reflect the new refresh rate
long callbackDurationDiffMs = getCallbackDurationDiffInMs(
frameData.getFrameTimeNanos(),
callbackRequestedTimeNs, callbackDurationMs);
if (callbackDurationDiffMs < 0 || callbackDurationDiffMs > THRESHOLD_MS) {
if (mExpectedPresentTime > 0) {
long callbackDurationDiffMs =
TimeUnit.NANOSECONDS.toMillis(
expectedPresentTime - mExpectedPresentTime);
if (callbackDurationDiffMs < 0
|| Math.abs(callbackDurationMs - callbackDurationDiffMs)
> THRESHOLD_MS) {
mCallbackMissedCounter++;
Log.e(TAG, "Frame #" + Math.abs(frameCount - FRAME_ITERATIONS)
+ " vsync callback failed, expected callback in "
@@ -427,25 +562,19 @@ public class AttachedChoreographerTest {
+ " but actual duration difference is " + callbackDurationDiffMs);
}
}
mIsFirstCallback = false;
mExpectedPresentTime = expectedPresentTime;
verifyVsyncCallbacks(choreographer, callbackDurationMs,
continueLatch, frameCount - 1);
} else {
assertTrue("Missed timeline for " + mCallbackMissedCounter + " callbacks, while "
+ CALLBACK_MISSED_THRESHOLD + " missed callbacks are allowed",
assertTrue("Missed timeline for " + mCallbackMissedCounter
+ " callbacks, while " + CALLBACK_MISSED_THRESHOLD
+ " missed callbacks are allowed",
mCallbackMissedCounter <= CALLBACK_MISSED_THRESHOLD);
continueLatch.countDown();
}
});
}
private long getCallbackDurationDiffInMs(long callbackTimeNs, long requestedTimeNs,
long expectedCallbackMs) {
long actualTimeMs = TimeUnit.NANOSECONDS.toMillis(callbackTimeNs)
- TimeUnit.NANOSECONDS.toMillis(requestedTimeNs);
return Math.abs(expectedCallbackMs - actualTimeMs);
}
private boolean waitForCountDown(CountDownLatch countDownLatch, long timeoutInSeconds) {
try {
return !countDownLatch.await(timeoutInSeconds, TimeUnit.SECONDS);