Skip to content
Snippets Groups Projects
Commit 57928e6a authored by Vishnu Nair's avatar Vishnu Nair
Browse files

Verify SurfaceView BlastBufferQueue behavior

Initial code to easily configure buffer producers to generate buffers
with different properties inorder exercise and validate
BlastBufferQueue adapter.

The test captures surface flinger traces and verifies properties of a
single buffer. This will allow us to verify buffer presentation order,
buffer rejection, buffer properties and so on.

Test: atest SurfaceViewBufferTests
Bug: 168504870
Change-Id: I9714d7b6f5ffbe5fecca5d93e8184f0e6ac2b4c1
parent 4be3ece8
No related branches found
No related tags found
No related merge requests found
......@@ -11,6 +11,7 @@ clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp
libs/input/
services/core/jni/
services/incremental/
tests/
tools/
[Hook Scripts]
......
// 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.
android_test {
name: "SurfaceViewBufferTests",
srcs: ["**/*.java","**/*.kt"],
manifest: "AndroidManifest.xml",
test_config: "AndroidTest.xml",
platform_apis: true,
certificate: "platform",
use_embedded_native_libs: true,
jni_libs: [
"libsurface_jni",
],
static_libs: [
"androidx.appcompat_appcompat",
"androidx.test.rules",
"androidx.test.runner",
"androidx.test.ext.junit",
"kotlin-stdlib",
"kotlinx-coroutines-android",
"flickerlib",
"truth-prebuilt",
],
}
cc_library_shared {
name: "libsurface_jni",
srcs: [
"cpp/SurfaceProxy.cpp",
],
shared_libs: [
"libutils",
"libgui",
"liblog",
"libandroid",
],
include_dirs: [
"system/core/include"
],
stl: "libc++_static",
cflags: [
"-Werror",
"-Wall",
],
}
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.test">
<uses-sdk android:minSdkVersion="29"
android:targetSdkVersion="29"/>
<!-- Enable / Disable tracing !-->
<uses-permission android:name="android.permission.DUMP" />
<!-- Enable / Disable sv blast adapter !-->
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<application android:allowBackup="false"
android:supportsRtl="true">
<activity android:name=".MainActivity"
android:taskAffinity="com.android.test.MainActivity"
android:theme="@style/AppTheme"
android:label="SurfaceViewBufferTestApp"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<uses-library android:name="android.test.runner"/>
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.test"
android:label="SurfaceViewBufferTests">
</instrumentation>
</manifest>
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<configuration description="Runs SurfaceView Buffer Tests">
<option name="test-tag" value="SurfaceViewBufferTests" />
<target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
<!-- keeps the screen on during tests -->
<option name="screen-always-on" value="on" />
<!-- prevents the phone from restarting -->
<option name="force-skip-system-props" value="true" />
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="false"/>
<option name="test-file-name" value="SurfaceViewBufferTests.apk"/>
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest">
<option name="package" value="com.android.test"/>
<option name="exclude-annotation" value="androidx.test.filters.FlakyTest" />
<option name="shell-timeout" value="6600s" />
<option name="test-timeout" value="6000s" />
<option name="hidden-api-checks" value="false" />
</test>
</configuration>
/*
* 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.
*/
#include <android/log.h>
#include <android/native_window.h>
#include <android/native_window_jni.h>
#include <android/window.h>
#include <gui/Surface.h>
#include <jni.h>
#include <system/window.h>
#include <utils/RefBase.h>
#include <cassert>
#include <chrono>
#include <thread>
#define TAG "SurfaceViewBufferTests"
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
extern "C" {
int i = 0;
static ANativeWindow* sAnw;
JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_setSurface(JNIEnv* env, jclass,
jobject surfaceObject) {
sAnw = ANativeWindow_fromSurface(env, surfaceObject);
assert(sAnw);
android::sp<android::Surface> surface = static_cast<android::Surface*>(sAnw);
surface->enableFrameTimestamps(true);
return 0;
}
JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_waitUntilBufferDisplayed(
JNIEnv*, jclass, jint jFrameNumber, jint timeoutSec) {
using namespace std::chrono_literals;
assert(sAnw);
android::sp<android::Surface> surface = static_cast<android::Surface*>(sAnw);
uint64_t frameNumber = static_cast<uint64_t>(jFrameNumber);
nsecs_t outRequestedPresentTime, outAcquireTime, outLatchTime, outFirstRefreshStartTime;
nsecs_t outLastRefreshStartTime, outGlCompositionDoneTime, outDequeueReadyTime;
nsecs_t outDisplayPresentTime = -1;
nsecs_t outReleaseTime;
auto start = std::chrono::steady_clock::now();
while (outDisplayPresentTime < 0) {
std::this_thread::sleep_for(8ms);
surface->getFrameTimestamps(frameNumber, &outRequestedPresentTime, &outAcquireTime,
&outLatchTime, &outFirstRefreshStartTime,
&outLastRefreshStartTime, &outGlCompositionDoneTime,
&outDisplayPresentTime, &outDequeueReadyTime, &outReleaseTime);
if (outDisplayPresentTime < 0) {
auto end = std::chrono::steady_clock::now();
if (std::chrono::duration_cast<std::chrono::seconds>(end - start).count() >
timeoutSec) {
return -1;
}
}
}
return 0;
}
JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_draw(JNIEnv*, jclass) {
assert(sAnw);
ANativeWindow_Buffer outBuffer;
ANativeWindow_lock(sAnw, &outBuffer, nullptr);
return 0;
}
JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_ANativeWindowLock(JNIEnv*, jclass) {
assert(sAnw);
ANativeWindow_Buffer outBuffer;
ANativeWindow_lock(sAnw, &outBuffer, nullptr);
return 0;
}
JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_ANativeWindowUnlockAndPost(JNIEnv*,
jclass) {
assert(sAnw);
ANativeWindow_unlockAndPost(sAnw);
return 0;
}
JNIEXPORT jint JNICALL Java_com_android_test_SurfaceProxy_ANativeWindowSetBuffersGeometry(
JNIEnv* /* env */, jclass /* clazz */, jobject /* surfaceObject */, jint w, jint h,
jint format) {
assert(sAnw);
return ANativeWindow_setBuffersGeometry(sAnw, w, h, format);
}
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 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.
-->
<resources>
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="windowNoTitle">true</item>
<item name="windowActionBar">false</item>
<item name="android:windowFullscreen">true</item>
<item name="android:windowContentOverlay">@null</item>
<item name="android:windowDisablePreview">true</item>
</style>
</resources>
/*
* 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
import android.app.Activity
import android.content.Context
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
import android.os.Bundle
import android.view.Gravity
import android.view.Surface
import android.view.SurfaceHolder
import android.view.SurfaceView
import android.widget.FrameLayout
import java.util.concurrent.CountDownLatch
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock
class MainActivity : Activity() {
val mSurfaceProxy = SurfaceProxy()
private var mSurfaceHolder: SurfaceHolder? = null
private val mDrawLock = ReentrantLock()
val surface: Surface? get() = mSurfaceHolder?.surface
public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
addSurfaceView(Rect(0, 0, 500, 200))
}
fun addSurfaceView(size: Rect): CountDownLatch {
val layout = findViewById<FrameLayout>(android.R.id.content)
val surfaceReadyLatch = CountDownLatch(1)
val surfaceView = createSurfaceView(applicationContext, size, surfaceReadyLatch)
layout.addView(surfaceView,
FrameLayout.LayoutParams(size.width(), size.height(), Gravity.TOP or Gravity.LEFT)
.also { it.setMargins(100, 100, 0, 0) })
return surfaceReadyLatch
}
private fun createSurfaceView(
context: Context,
size: Rect,
surfaceReadyLatch: CountDownLatch
): SurfaceView {
val surfaceView = SurfaceView(context)
surfaceView.setWillNotDraw(false)
surfaceView.holder.setFixedSize(size.width(), size.height())
surfaceView.holder.addCallback(object : SurfaceHolder.Callback {
override fun surfaceCreated(holder: SurfaceHolder) {
mDrawLock.withLock {
mSurfaceHolder = holder
mSurfaceProxy.setSurface(holder.surface)
}
surfaceReadyLatch.countDown()
}
override fun surfaceChanged(
holder: SurfaceHolder,
format: Int,
width: Int,
height: Int
) {
}
override fun surfaceDestroyed(holder: SurfaceHolder) {
mDrawLock.withLock {
mSurfaceHolder = null
}
}
})
return surfaceView
}
fun drawFrame(): Rect {
mDrawLock.withLock {
val holder = mSurfaceHolder ?: return Rect()
val canvas = holder.lockCanvas()
val canvasSize = Rect(0, 0, canvas.width, canvas.height)
canvas.drawColor(Color.GREEN)
val p = Paint()
p.color = Color.RED
canvas.drawRect(canvasSize, p)
holder.unlockCanvasAndPost(canvas)
return canvasSize
}
}
}
\ No newline at end of file
/*
* 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
class SurfaceProxy {
init {
System.loadLibrary("surface_jni")
}
external fun setSurface(surface: Any)
external fun waitUntilBufferDisplayed(frameNumber: Int, timeoutSec: Int)
external fun draw()
// android/native_window.h functions
external fun ANativeWindowLock()
external fun ANativeWindowUnlockAndPost()
external fun ANativeWindowSetBuffersGeometry(surface: Any, width: Int, height: Int, format: Int)
}
/*
* 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
import android.app.Instrumentation
import android.graphics.Rect
import android.provider.Settings
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.wm.flicker.monitor.LayersTraceMonitor
import com.android.server.wm.flicker.monitor.withSFTracing
import com.android.server.wm.flicker.traces.layers.LayersTraceSubject.Companion.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import java.util.concurrent.CountDownLatch
import kotlin.properties.Delegates
@RunWith(Parameterized::class)
class SurfaceViewBufferTest(val useBlastAdapter: Boolean) {
private var mInitialUseBlastConfig by Delegates.notNull<Int>()
@get:Rule
var scenarioRule: ActivityScenarioRule<MainActivity> =
ActivityScenarioRule<MainActivity>(MainActivity::class.java)
protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
val defaultBufferSize = Rect(0, 0, 640, 480)
@Before
fun setup() {
mInitialUseBlastConfig = Settings.Global.getInt(instrumentation.context.contentResolver,
"use_blast_adapter_sv", 0)
val enable = if (useBlastAdapter) 1 else 0
Settings.Global.putInt(instrumentation.context.contentResolver, "use_blast_adapter_sv",
enable)
val tmpDir = instrumentation.targetContext.dataDir.toPath()
LayersTraceMonitor(tmpDir).stop()
lateinit var surfaceReadyLatch: CountDownLatch
scenarioRule.getScenario().onActivity {
surfaceReadyLatch = it.addSurfaceView(defaultBufferSize)
}
surfaceReadyLatch.await()
}
@After
fun teardown() {
scenarioRule.getScenario().close()
Settings.Global.putInt(instrumentation.context.contentResolver,
"use_blast_adapter_sv", mInitialUseBlastConfig)
}
@Test
fun testSetBuffersGeometry_0x0_resetsBufferSize() {
val trace = withSFTracing(instrumentation, TRACE_FLAGS) {
scenarioRule.getScenario().onActivity {
it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, 0, 0,
R8G8B8A8_UNORM)
it.mSurfaceProxy.ANativeWindowLock()
it.mSurfaceProxy.ANativeWindowUnlockAndPost()
it.mSurfaceProxy.waitUntilBufferDisplayed(1, 1 /* sec */)
}
}
// verify buffer size is reset to default buffer size
assertThat(trace).layer("SurfaceView", 1).hasBufferSize(defaultBufferSize)
}
@Test
fun testSetBuffersGeometry_0x0_rejectsBuffer() {
val trace = withSFTracing(instrumentation, TRACE_FLAGS) {
scenarioRule.getScenario().onActivity {
it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, 100, 100,
R8G8B8A8_UNORM)
it.mSurfaceProxy.ANativeWindowLock()
it.mSurfaceProxy.ANativeWindowUnlockAndPost()
it.mSurfaceProxy.ANativeWindowLock()
it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, 0, 0, R8G8B8A8_UNORM)
// Submit buffer one with a different size which should be rejected
it.mSurfaceProxy.ANativeWindowUnlockAndPost()
// submit a buffer with the default buffer size
it.mSurfaceProxy.ANativeWindowLock()
it.mSurfaceProxy.ANativeWindowUnlockAndPost()
it.mSurfaceProxy.waitUntilBufferDisplayed(3, 1 /* sec */)
}
}
// Verify we reject buffers since scaling mode == NATIVE_WINDOW_SCALING_MODE_FREEZE
assertThat(trace).layer("SurfaceView", 2).doesNotExist()
// Verify the next buffer is submitted with the correct size
assertThat(trace).layer("SurfaceView", 3).also {
it.hasBufferSize(defaultBufferSize)
it.hasScalingMode(0 /* NATIVE_WINDOW_SCALING_MODE_FREEZE */)
}
}
@Test
fun testSetBuffersGeometry_smallerThanBuffer() {
val bufferSize = Rect(0, 0, 300, 200)
val trace = withSFTracing(instrumentation, TRACE_FLAGS) {
scenarioRule.getScenario().onActivity {
it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, bufferSize.width(),
bufferSize.height(), R8G8B8A8_UNORM)
it.drawFrame()
it.mSurfaceProxy.waitUntilBufferDisplayed(1, 1 /* sec */)
}
}
assertThat(trace).layer("SurfaceView", 1).also {
it.hasBufferSize(bufferSize)
it.hasLayerSize(defaultBufferSize)
it.hasScalingMode(1 /* NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW */)
}
}
@Test
fun testSetBuffersGeometry_largerThanBuffer() {
val bufferSize = Rect(0, 0, 3000, 2000)
val trace = withSFTracing(instrumentation, TRACE_FLAGS) {
scenarioRule.getScenario().onActivity {
it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, bufferSize.width(),
bufferSize.height(), R8G8B8A8_UNORM)
it.drawFrame()
it.mSurfaceProxy.waitUntilBufferDisplayed(1, 1 /* sec */)
}
}
assertThat(trace).layer("SurfaceView", 1).also {
it.hasBufferSize(bufferSize)
it.hasLayerSize(defaultBufferSize)
it.hasScalingMode(1 /* NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW */)
}
}
/** Submit buffers as fast as possible and make sure they are queued */
@Test
fun testQueueBuffers() {
val trace = withSFTracing(instrumentation, TRACE_FLAGS) {
scenarioRule.getScenario().onActivity {
it.mSurfaceProxy.ANativeWindowSetBuffersGeometry(it.surface!!, 100, 100,
R8G8B8A8_UNORM)
for (i in 0..100) {
it.mSurfaceProxy.ANativeWindowLock()
it.mSurfaceProxy.ANativeWindowUnlockAndPost()
}
it.mSurfaceProxy.waitUntilBufferDisplayed(100, 1 /* sec */)
}
}
for (frameNumber in 1..100) {
assertThat(trace).layer("SurfaceView", frameNumber.toLong())
}
}
companion object {
private const val TRACE_FLAGS = 0x1 // TRACE_CRITICAL
private const val R8G8B8A8_UNORM = 1
@JvmStatic
@Parameterized.Parameters(name = "blast={0}")
fun data(): Collection<Array<Any>> {
return listOf(
arrayOf(false), // First test: submit buffers via bufferqueue
arrayOf(true) // Second test: submit buffers via blast adapter
)
}
}
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment