SufaceComposition performance test.
Implement set of low-level tests to measure graphics performance. Design and test result: https://docs.google.com/a/google.com/document/d/1LYlUxjjmC2JBulAIIO8UVfvjeHWEALzgyUzqMMzwiGE/edit?usp=sharing Change-Id: I48efbce5dcdac1b8caa2cd332777ce0b06d40ed2
This commit is contained in:
34
tests/SurfaceComposition/Android.mk
Normal file
34
tests/SurfaceComposition/Android.mk
Normal file
@@ -0,0 +1,34 @@
|
||||
# Copyright (C) 2015 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.
|
||||
|
||||
LOCAL_PATH:= $(call my-dir)
|
||||
|
||||
include $(CLEAR_VARS)
|
||||
|
||||
# Don't include this package in any target
|
||||
LOCAL_MODULE_TAGS := tests
|
||||
# When built, explicitly put it in the data partition.
|
||||
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
|
||||
|
||||
LOCAL_DEX_PREOPT := false
|
||||
|
||||
LOCAL_PROGUARD_ENABLED := disabled
|
||||
|
||||
LOCAL_SRC_FILES := $(call all-java-files-under, src)
|
||||
|
||||
LOCAL_PACKAGE_NAME := SurfaceComposition
|
||||
|
||||
LOCAL_SDK_VERSION := current
|
||||
|
||||
include $(BUILD_PACKAGE)
|
||||
36
tests/SurfaceComposition/AndroidManifest.xml
Normal file
36
tests/SurfaceComposition/AndroidManifest.xml
Normal file
@@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
* Copyright (C) 2015 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="android.surfacecomposition">
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<application android:theme="@style/noeffects">
|
||||
<uses-library android:name="android.test.runner" />
|
||||
<activity android:name="android.surfacecomposition.SurfaceCompositionMeasuringActivity" >
|
||||
<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>
|
||||
|
||||
<!-- self-instrumenting test package. -->
|
||||
<instrumentation android:name="android.test.InstrumentationTestRunner"
|
||||
android:targetPackage="android.surfacecomposition">
|
||||
</instrumentation>
|
||||
</manifest>
|
||||
25
tests/SurfaceComposition/res/values/themes.xml
Normal file
25
tests/SurfaceComposition/res/values/themes.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
* Copyright (C) 2015 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="noeffects" parent="@android:style/Theme.Holo.NoActionBar.Fullscreen">
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
<item name="android:windowFullscreen">true</item>
|
||||
<item name="android:fadingEdge">none</item>
|
||||
<item name="android:windowContentTransitions">false</item>
|
||||
<item name="android:windowAnimationStyle">@null</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.surfacecomposition;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
public class CustomLayout extends ViewGroup {
|
||||
public CustomLayout(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public static class LayoutParams extends ViewGroup.LayoutParams {
|
||||
private int mLeft, mTop, mRight, mBottom;
|
||||
|
||||
public LayoutParams(int left, int top, int right, int bottom) {
|
||||
super(0, 0);
|
||||
mLeft = left;
|
||||
mTop = top;
|
||||
mRight = right;
|
||||
mBottom = bottom;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int l, int t, int r, int b) {
|
||||
final int count = getChildCount();
|
||||
for (int i = 0; i < count; i++) {
|
||||
View child = getChildAt(i);
|
||||
CustomLayout.LayoutParams lp = (CustomLayout.LayoutParams) child.getLayoutParams();
|
||||
child.layout(lp.mLeft, lp.mTop, lp.mRight, lp.mBottom);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.surfacecomposition;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.view.Surface;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.SurfaceView;
|
||||
|
||||
/**
|
||||
* This provides functionality to measure Surface update frame rate. The idea is to
|
||||
* constantly invalidates Surface in a separate thread. Lowest possible way is to
|
||||
* use SurfaceView which works with Surface. This gives a very small overhead
|
||||
* and very close to Android internals. Note, that lockCanvas is blocking
|
||||
* methods and it returns once SurfaceFlinger consumes previous buffer. This
|
||||
* gives the change to measure real performance of Surface compositor.
|
||||
*/
|
||||
public class CustomSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
|
||||
private final static long DURATION_TO_WARMUP_MS = 50;
|
||||
private final static long DURATION_TO_MEASURE_ROUGH_MS = 500;
|
||||
private final static long DURATION_TO_MEASURE_PRECISE_MS = 3000;
|
||||
private final static Random mRandom = new Random();
|
||||
|
||||
private final Object mSurfaceLock = new Object();
|
||||
private Surface mSurface;
|
||||
private boolean mDrawNameOnReady = true;
|
||||
private boolean mSurfaceWasChanged = false;
|
||||
private String mName;
|
||||
private Canvas mCanvas;
|
||||
|
||||
class ValidateThread extends Thread {
|
||||
private double mFPS = 0.0f;
|
||||
// Used to support early exit and prevent long computation.
|
||||
private double mBadFPS;
|
||||
private double mPerfectFPS;
|
||||
|
||||
ValidateThread(double badFPS, double perfectFPS) {
|
||||
mBadFPS = badFPS;
|
||||
mPerfectFPS = perfectFPS;
|
||||
}
|
||||
|
||||
public void run() {
|
||||
long startTime = System.currentTimeMillis();
|
||||
while (System.currentTimeMillis() - startTime < DURATION_TO_WARMUP_MS) {
|
||||
invalidateSurface(false);
|
||||
}
|
||||
|
||||
startTime = System.currentTimeMillis();
|
||||
long endTime;
|
||||
int frameCnt = 0;
|
||||
while (true) {
|
||||
invalidateSurface(false);
|
||||
endTime = System.currentTimeMillis();
|
||||
++frameCnt;
|
||||
mFPS = (double)frameCnt * 1000.0 / (endTime - startTime);
|
||||
if ((endTime - startTime) >= DURATION_TO_MEASURE_ROUGH_MS) {
|
||||
// Test if result looks too bad or perfect and stop early.
|
||||
if (mFPS <= mBadFPS || mFPS >= mPerfectFPS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ((endTime - startTime) >= DURATION_TO_MEASURE_PRECISE_MS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public double getFPS() {
|
||||
return mFPS;
|
||||
}
|
||||
}
|
||||
|
||||
public CustomSurfaceView(Context context, String name) {
|
||||
super(context);
|
||||
mName = name;
|
||||
getHolder().addCallback(this);
|
||||
}
|
||||
|
||||
public void setMode(int pixelFormat, boolean drawNameOnReady) {
|
||||
mDrawNameOnReady = drawNameOnReady;
|
||||
getHolder().setFormat(pixelFormat);
|
||||
}
|
||||
|
||||
public void acquireCanvas() {
|
||||
synchronized (mSurfaceLock) {
|
||||
if (mCanvas != null) {
|
||||
throw new RuntimeException("Surface canvas was already acquired.");
|
||||
}
|
||||
if (mSurface != null) {
|
||||
mCanvas = mSurface.lockCanvas(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void releaseCanvas() {
|
||||
synchronized (mSurfaceLock) {
|
||||
if (mCanvas != null) {
|
||||
if (mSurface == null) {
|
||||
throw new RuntimeException(
|
||||
"Surface was destroyed but canvas was not released.");
|
||||
}
|
||||
mSurface.unlockCanvasAndPost(mCanvas);
|
||||
mCanvas = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Invalidate surface.
|
||||
*/
|
||||
private void invalidateSurface(boolean drawSurfaceId) {
|
||||
synchronized (mSurfaceLock) {
|
||||
if (mSurface != null) {
|
||||
Canvas canvas = mSurface.lockCanvas(null);
|
||||
// Draw surface name for debug purpose only. This does not affect the test
|
||||
// because it is drawn only during allocation.
|
||||
if (drawSurfaceId) {
|
||||
int textSize = canvas.getHeight() / 24;
|
||||
Paint paint = new Paint();
|
||||
paint.setTextSize(textSize);
|
||||
int textWidth = (int)(paint.measureText(mName) + 0.5f);
|
||||
int x = mRandom.nextInt(canvas.getWidth() - textWidth);
|
||||
int y = textSize + mRandom.nextInt(canvas.getHeight() - textSize);
|
||||
// Create effect of fog to visually control correctness of composition.
|
||||
paint.setColor(0xFFFF8040);
|
||||
canvas.drawARGB(32, 255, 255, 255);
|
||||
canvas.drawText(mName, x, y, paint);
|
||||
}
|
||||
mSurface.unlockCanvasAndPost(canvas);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until surface is created and ready to use or return immediately if surface
|
||||
* already exists.
|
||||
*/
|
||||
public void waitForSurfaceReady() {
|
||||
synchronized (mSurfaceLock) {
|
||||
if (mSurface == null) {
|
||||
try {
|
||||
mSurfaceLock.wait(5000);
|
||||
} catch(InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
if (mSurface == null)
|
||||
throw new RuntimeException("Surface is not ready.");
|
||||
mSurfaceWasChanged = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until surface is destroyed or return immediately if surface does not exist.
|
||||
*/
|
||||
public void waitForSurfaceDestroyed() {
|
||||
synchronized (mSurfaceLock) {
|
||||
if (mSurface != null) {
|
||||
try {
|
||||
mSurfaceLock.wait(5000);
|
||||
} catch(InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
if (mSurface != null)
|
||||
throw new RuntimeException("Surface still exists.");
|
||||
mSurfaceWasChanged = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate that surface has not been changed since waitForSurfaceReady or
|
||||
* waitForSurfaceDestroyed.
|
||||
*/
|
||||
public void validateSurfaceNotChanged() {
|
||||
synchronized (mSurfaceLock) {
|
||||
if (mSurfaceWasChanged) {
|
||||
throw new RuntimeException("Surface was changed during the test execution.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public double measureFPS(double badFPS, double perfectFPS) {
|
||||
try {
|
||||
ValidateThread validateThread = new ValidateThread(badFPS, perfectFPS);
|
||||
validateThread.start();
|
||||
validateThread.join();
|
||||
return validateThread.getFPS();
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceCreated(SurfaceHolder holder) {
|
||||
synchronized (mSurfaceLock) {
|
||||
mSurfaceWasChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
|
||||
// This method is always called at least once, after surfaceCreated.
|
||||
synchronized (mSurfaceLock) {
|
||||
mSurface = holder.getSurface();
|
||||
// We only need to invalidate the surface for the compositor performance test so that
|
||||
// it gets included in the composition process. For allocation performance we
|
||||
// don't need to invalidate surface and this allows us to remove non-necessary
|
||||
// surface invalidation from the test.
|
||||
if (mDrawNameOnReady) {
|
||||
invalidateSurface(true);
|
||||
}
|
||||
mSurfaceWasChanged = true;
|
||||
mSurfaceLock.notify();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||
synchronized (mSurfaceLock) {
|
||||
mSurface = null;
|
||||
mSurfaceWasChanged = true;
|
||||
mSurfaceLock.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.surfacecomposition;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
/**
|
||||
* This task will simulate CPU activity by consuming memory bandwidth from the system.
|
||||
* Note: On most system the CPU and GPU will share the same memory.
|
||||
*/
|
||||
public class MemoryAccessTask {
|
||||
private final static String TAG = "MemoryAccessTask";
|
||||
private final static int BUFFER_SIZE = 32 * 1024 * 1024;
|
||||
private final static int BUFFER_STEP = 256;
|
||||
private boolean mStopRequested;
|
||||
private WorkThread mThread;
|
||||
private final Object mLock = new Object();
|
||||
|
||||
public class WorkThread extends Thread {
|
||||
public void run() {
|
||||
byte[] memory = new byte[BUFFER_SIZE];
|
||||
while (true) {
|
||||
synchronized (mLock) {
|
||||
if (mStopRequested) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
long result = 0;
|
||||
for (int index = 0; index < BUFFER_SIZE; index += BUFFER_STEP) {
|
||||
result += ++memory[index];
|
||||
}
|
||||
Log.v(TAG, "Processing...:" + result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void start() {
|
||||
if (mThread != null) {
|
||||
throw new RuntimeException("Work thread is already started");
|
||||
}
|
||||
mStopRequested = false;
|
||||
mThread = new WorkThread();
|
||||
mThread.start();
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
if (mThread != null) {
|
||||
synchronized (mLock) {
|
||||
mStopRequested = true;
|
||||
}
|
||||
try {
|
||||
mThread.join();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,602 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.surfacecomposition;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import android.app.ActionBar;
|
||||
import android.app.Activity;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.ActivityManager.MemoryInfo;
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.os.Bundle;
|
||||
import android.view.Display;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
|
||||
/**
|
||||
* This activity is designed to measure peformance scores of Android surfaces.
|
||||
* It can work in two modes. In first mode functionality of this activity is
|
||||
* invoked from Cts test (SurfaceCompositionTest). This activity can also be
|
||||
* used in manual mode as a normal app. Different pixel formats are supported.
|
||||
*
|
||||
* measureCompositionScore(pixelFormat)
|
||||
* This test measures surface compositor performance which shows how many
|
||||
* surfaces of specific format surface compositor can combine without dropping
|
||||
* frames. We allow one dropped frame per half second.
|
||||
*
|
||||
* measureAllocationScore(pixelFormat)
|
||||
* This test measures surface allocation/deallocation performance. It shows
|
||||
* how many surface lifecycles (creation, destruction) can be done per second.
|
||||
*
|
||||
* In manual mode, which activated by pressing button 'Compositor speed' or
|
||||
* 'Allocator speed', all possible pixel format are tested and combined result
|
||||
* is displayed in text view. Additional system information such as memory
|
||||
* status, display size and surface format is also displayed and regulary
|
||||
* updated.
|
||||
*/
|
||||
public class SurfaceCompositionMeasuringActivity extends Activity implements OnClickListener {
|
||||
private final static int MIN_NUMBER_OF_SURFACES = 15;
|
||||
private final static int MAX_NUMBER_OF_SURFACES = 40;
|
||||
private final static int WARM_UP_ALLOCATION_CYCLES = 2;
|
||||
private final static int MEASURE_ALLOCATION_CYCLES = 5;
|
||||
private final static int TEST_COMPOSITOR = 1;
|
||||
private final static int TEST_ALLOCATION = 2;
|
||||
private final static float MIN_REFRESH_RATE_SUPPORTED = 50.0f;
|
||||
|
||||
private final static DecimalFormat DOUBLE_FORMAT = new DecimalFormat("#.00");
|
||||
// Possible selection in pixel format selector.
|
||||
private final static int[] PIXEL_FORMATS = new int[] {
|
||||
PixelFormat.TRANSLUCENT,
|
||||
PixelFormat.TRANSPARENT,
|
||||
PixelFormat.OPAQUE,
|
||||
PixelFormat.RGBA_8888,
|
||||
PixelFormat.RGBX_8888,
|
||||
PixelFormat.RGB_888,
|
||||
PixelFormat.RGB_565,
|
||||
};
|
||||
|
||||
|
||||
private List<CustomSurfaceView> mViews = new ArrayList<CustomSurfaceView>();
|
||||
private Button mMeasureCompositionButton;
|
||||
private Button mMeasureAllocationButton;
|
||||
private Spinner mPixelFormatSelector;
|
||||
private TextView mResultView;
|
||||
private TextView mSystemInfoView;
|
||||
private final Object mLockResumed = new Object();
|
||||
private boolean mResumed;
|
||||
|
||||
// Drop one frame per half second.
|
||||
// TODO(khmel)
|
||||
// Add a feature flag and set the target FPS dependent on the target system as e.g.:
|
||||
// 59FPS for MULTI_WINDOW and 54 otherwise (to satisfy the default lax Android requirements).
|
||||
private double mRefreshRate;
|
||||
private double mTargetFPS;
|
||||
|
||||
private int mWidth;
|
||||
private int mHeight;
|
||||
|
||||
class CompositorScore {
|
||||
double mSurfaces;
|
||||
double mBitrate;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return DOUBLE_FORMAT.format(mSurfaces) + " surfaces. " +
|
||||
"Bitrate: " + getReadableMemory((long)mBitrate) + "/s";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Measure performance score.
|
||||
*
|
||||
* @return biggest possible number of visible surfaces which surface
|
||||
* compositor can handle.
|
||||
*/
|
||||
public CompositorScore measureCompositionScore(int pixelFormat) {
|
||||
waitForActivityResumed();
|
||||
//MemoryAccessTask memAccessTask = new MemoryAccessTask();
|
||||
//memAccessTask.start();
|
||||
// Destroy any active surface.
|
||||
configureSurfacesAndWait(0, pixelFormat, false);
|
||||
CompositorScore score = new CompositorScore();
|
||||
score.mSurfaces = measureCompositionScore(new Measurement(0, 60.0),
|
||||
new Measurement(mViews.size() + 1, 0.0f), pixelFormat);
|
||||
// Assume 32 bits per pixel.
|
||||
score.mBitrate = score.mSurfaces * mTargetFPS * mWidth * mHeight * 4.0;
|
||||
//memAccessTask.stop();
|
||||
return score;
|
||||
}
|
||||
|
||||
static class AllocationScore {
|
||||
double mMedian;
|
||||
double mMin;
|
||||
double mMax;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return DOUBLE_FORMAT.format(mMedian) + " (min:" + DOUBLE_FORMAT.format(mMin) +
|
||||
", max:" + DOUBLE_FORMAT.format(mMax) + ") surface allocations per second";
|
||||
}
|
||||
}
|
||||
|
||||
public AllocationScore measureAllocationScore(int pixelFormat) {
|
||||
waitForActivityResumed();
|
||||
AllocationScore score = new AllocationScore();
|
||||
for (int i = 0; i < MEASURE_ALLOCATION_CYCLES + WARM_UP_ALLOCATION_CYCLES; ++i) {
|
||||
long time1 = System.currentTimeMillis();
|
||||
configureSurfacesAndWait(MIN_NUMBER_OF_SURFACES, pixelFormat, false);
|
||||
acquireSurfacesCanvas();
|
||||
long time2 = System.currentTimeMillis();
|
||||
releaseSurfacesCanvas();
|
||||
configureSurfacesAndWait(0, pixelFormat, false);
|
||||
// Give SurfaceFlinger some time to rebuild the layer stack and release the buffers.
|
||||
try {
|
||||
Thread.sleep(500);
|
||||
} catch(InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
if (i < WARM_UP_ALLOCATION_CYCLES) {
|
||||
// This is warm-up cycles, ignore result so far.
|
||||
continue;
|
||||
}
|
||||
double speed = MIN_NUMBER_OF_SURFACES * 1000.0 / (time2 - time1);
|
||||
score.mMedian += speed / MEASURE_ALLOCATION_CYCLES;
|
||||
if (i == WARM_UP_ALLOCATION_CYCLES) {
|
||||
score.mMin = speed;
|
||||
score.mMax = speed;
|
||||
} else {
|
||||
score.mMin = Math.min(score.mMin, speed);
|
||||
score.mMax = Math.max(score.mMax, speed);
|
||||
}
|
||||
}
|
||||
|
||||
return score;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
if (view == mMeasureCompositionButton) {
|
||||
doTest(TEST_COMPOSITOR);
|
||||
} else if (view == mMeasureAllocationButton) {
|
||||
doTest(TEST_ALLOCATION);
|
||||
}
|
||||
}
|
||||
|
||||
private void doTest(final int test) {
|
||||
enableControls(false);
|
||||
final int pixelFormat = PIXEL_FORMATS[mPixelFormatSelector.getSelectedItemPosition()];
|
||||
new Thread() {
|
||||
public void run() {
|
||||
final StringBuffer sb = new StringBuffer();
|
||||
switch (test) {
|
||||
case TEST_COMPOSITOR: {
|
||||
sb.append("Compositor score:");
|
||||
CompositorScore score = measureCompositionScore(pixelFormat);
|
||||
sb.append("\n " + getPixelFormatInfo(pixelFormat) + ":" +
|
||||
score + ".");
|
||||
}
|
||||
break;
|
||||
case TEST_ALLOCATION: {
|
||||
sb.append("Allocation score:");
|
||||
AllocationScore score = measureAllocationScore(pixelFormat);
|
||||
sb.append("\n " + getPixelFormatInfo(pixelFormat) + ":" +
|
||||
score + ".");
|
||||
}
|
||||
break;
|
||||
}
|
||||
runOnUiThreadAndWait(new Runnable() {
|
||||
public void run() {
|
||||
mResultView.setText(sb.toString());
|
||||
enableControls(true);
|
||||
updateSystemInfo(pixelFormat);
|
||||
}
|
||||
});
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait until activity is resumed.
|
||||
*/
|
||||
public void waitForActivityResumed() {
|
||||
synchronized (mLockResumed) {
|
||||
if (!mResumed) {
|
||||
try {
|
||||
mLockResumed.wait(10000);
|
||||
} catch (InterruptedException e) {
|
||||
}
|
||||
}
|
||||
if (!mResumed) {
|
||||
throw new RuntimeException("Activity was not resumed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||
|
||||
detectRefreshRate();
|
||||
|
||||
// To layouts in parent. First contains list of Surfaces and second
|
||||
// controls. Controls stay on top.
|
||||
RelativeLayout rootLayout = new RelativeLayout(this);
|
||||
rootLayout.setLayoutParams(new ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
|
||||
CustomLayout layout = new CustomLayout(this);
|
||||
layout.setLayoutParams(new ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
|
||||
Rect rect = new Rect();
|
||||
getWindow().getDecorView().getWindowVisibleDisplayFrame(rect);
|
||||
mWidth = rect.right;
|
||||
mHeight = rect.bottom;
|
||||
long maxMemoryPerSurface = roundToNextPowerOf2(mWidth) * roundToNextPowerOf2(mHeight) * 4;
|
||||
// Use 75% of available memory.
|
||||
int surfaceCnt = (int)((getMemoryInfo().availMem * 3) / (4 * maxMemoryPerSurface));
|
||||
if (surfaceCnt < MIN_NUMBER_OF_SURFACES) {
|
||||
throw new RuntimeException("Not enough memory to allocate " +
|
||||
MIN_NUMBER_OF_SURFACES + " surfaces.");
|
||||
}
|
||||
if (surfaceCnt > MAX_NUMBER_OF_SURFACES) {
|
||||
surfaceCnt = MAX_NUMBER_OF_SURFACES;
|
||||
}
|
||||
|
||||
LinearLayout controlLayout = new LinearLayout(this);
|
||||
controlLayout.setOrientation(LinearLayout.VERTICAL);
|
||||
controlLayout.setLayoutParams(new ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
|
||||
mMeasureCompositionButton = createButton("Compositor speed.", controlLayout);
|
||||
mMeasureAllocationButton = createButton("Allocation speed", controlLayout);
|
||||
|
||||
String[] pixelFomats = new String[PIXEL_FORMATS.length];
|
||||
for (int i = 0; i < pixelFomats.length; ++i) {
|
||||
pixelFomats[i] = getPixelFormatInfo(PIXEL_FORMATS[i]);
|
||||
}
|
||||
mPixelFormatSelector = new Spinner(this);
|
||||
ArrayAdapter<String> pixelFormatSelectorAdapter =
|
||||
new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item, pixelFomats);
|
||||
pixelFormatSelectorAdapter.setDropDownViewResource(
|
||||
android.R.layout.simple_spinner_dropdown_item);
|
||||
mPixelFormatSelector.setAdapter(pixelFormatSelectorAdapter);
|
||||
mPixelFormatSelector.setLayoutParams(new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
controlLayout.addView(mPixelFormatSelector);
|
||||
|
||||
mResultView = new TextView(this);
|
||||
mResultView.setBackgroundColor(0);
|
||||
mResultView.setText("Press button to start test.");
|
||||
mResultView.setLayoutParams(new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
controlLayout.addView(mResultView);
|
||||
|
||||
mSystemInfoView = new TextView(this);
|
||||
mSystemInfoView.setBackgroundColor(0);
|
||||
mSystemInfoView.setLayoutParams(new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
controlLayout.addView(mSystemInfoView);
|
||||
|
||||
for (int i = 0; i < surfaceCnt; ++i) {
|
||||
CustomSurfaceView view = new CustomSurfaceView(this, "Surface:" + i);
|
||||
// Create all surfaces overlapped in order to prevent SurfaceFlinger
|
||||
// to filter out surfaces by optimization in case surface is opaque.
|
||||
// In case surface is transparent it will be drawn anyway. Note that first
|
||||
// surface covers whole screen and must stand below other surfaces. Z order of
|
||||
// layers is not predictable and there is only one way to force first
|
||||
// layer to be below others is to mark it as media and all other layers
|
||||
// to mark as media overlay.
|
||||
if (i == 0) {
|
||||
view.setLayoutParams(new CustomLayout.LayoutParams(0, 0, mWidth, mHeight));
|
||||
view.setZOrderMediaOverlay(false);
|
||||
} else {
|
||||
// Z order of other layers is not predefined so make offset on x and reverse
|
||||
// offset on y to make sure that surface is visible in any layout.
|
||||
int x = i;
|
||||
int y = (surfaceCnt - i);
|
||||
view.setLayoutParams(new CustomLayout.LayoutParams(x, y, x + mWidth, y + mHeight));
|
||||
view.setZOrderMediaOverlay(true);
|
||||
}
|
||||
view.setVisibility(View.INVISIBLE);
|
||||
layout.addView(view);
|
||||
mViews.add(view);
|
||||
}
|
||||
|
||||
rootLayout.addView(layout);
|
||||
rootLayout.addView(controlLayout);
|
||||
|
||||
setContentView(rootLayout);
|
||||
}
|
||||
|
||||
private Button createButton(String caption, LinearLayout layout) {
|
||||
Button button = new Button(this);
|
||||
button.setText(caption);
|
||||
button.setLayoutParams(new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
button.setOnClickListener(this);
|
||||
layout.addView(button);
|
||||
return button;
|
||||
}
|
||||
|
||||
private void enableControls(boolean enabled) {
|
||||
mMeasureCompositionButton.setEnabled(enabled);
|
||||
mMeasureAllocationButton.setEnabled(enabled);
|
||||
mPixelFormatSelector.setEnabled(enabled);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
updateSystemInfo(PixelFormat.UNKNOWN);
|
||||
|
||||
synchronized (mLockResumed) {
|
||||
mResumed = true;
|
||||
mLockResumed.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
|
||||
synchronized (mLockResumed) {
|
||||
mResumed = false;
|
||||
}
|
||||
}
|
||||
|
||||
class Measurement {
|
||||
Measurement(int surfaceCnt, double fps) {
|
||||
mSurfaceCnt = surfaceCnt;
|
||||
mFPS = fps;
|
||||
}
|
||||
|
||||
public final int mSurfaceCnt;
|
||||
public final double mFPS;
|
||||
}
|
||||
|
||||
private double measureCompositionScore(Measurement ok, Measurement fail, int pixelFormat) {
|
||||
if (ok.mSurfaceCnt + 1 == fail.mSurfaceCnt) {
|
||||
// Interpolate result.
|
||||
double fraction = (mTargetFPS - fail.mFPS) / (ok.mFPS - fail.mFPS);
|
||||
return ok.mSurfaceCnt + fraction;
|
||||
}
|
||||
|
||||
int medianSurfaceCnt = (ok.mSurfaceCnt + fail.mSurfaceCnt) / 2;
|
||||
Measurement median = new Measurement(medianSurfaceCnt,
|
||||
measureFPS(medianSurfaceCnt, pixelFormat));
|
||||
|
||||
if (median.mFPS >= mTargetFPS) {
|
||||
return measureCompositionScore(median, fail, pixelFormat);
|
||||
} else {
|
||||
return measureCompositionScore(ok, median, pixelFormat);
|
||||
}
|
||||
}
|
||||
|
||||
private double measureFPS(int surfaceCnt, int pixelFormat) {
|
||||
configureSurfacesAndWait(surfaceCnt, pixelFormat, true);
|
||||
// At least one view is visible and it is enough to update only
|
||||
// one overlapped surface in order to force SurfaceFlinger to send
|
||||
// all surfaces to compositor.
|
||||
double fps = mViews.get(0).measureFPS(mRefreshRate * 0.8, mRefreshRate * 0.999);
|
||||
|
||||
// Make sure that surface configuration was not changed.
|
||||
validateSurfacesNotChanged();
|
||||
|
||||
return fps;
|
||||
}
|
||||
|
||||
private void waitForSurfacesConfigured(final int pixelFormat) {
|
||||
for (int i = 0; i < mViews.size(); ++i) {
|
||||
CustomSurfaceView view = mViews.get(i);
|
||||
if (view.getVisibility() == View.VISIBLE) {
|
||||
view.waitForSurfaceReady();
|
||||
} else {
|
||||
view.waitForSurfaceDestroyed();
|
||||
}
|
||||
}
|
||||
runOnUiThreadAndWait(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
updateSystemInfo(pixelFormat);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void validateSurfacesNotChanged() {
|
||||
for (int i = 0; i < mViews.size(); ++i) {
|
||||
CustomSurfaceView view = mViews.get(i);
|
||||
view.validateSurfaceNotChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private void configureSurfaces(int surfaceCnt, int pixelFormat, boolean invalidate) {
|
||||
for (int i = 0; i < mViews.size(); ++i) {
|
||||
CustomSurfaceView view = mViews.get(i);
|
||||
if (i < surfaceCnt) {
|
||||
view.setMode(pixelFormat, invalidate);
|
||||
view.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
view.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void configureSurfacesAndWait(final int surfaceCnt, final int pixelFormat,
|
||||
final boolean invalidate) {
|
||||
runOnUiThreadAndWait(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
configureSurfaces(surfaceCnt, pixelFormat, invalidate);
|
||||
}
|
||||
});
|
||||
waitForSurfacesConfigured(pixelFormat);
|
||||
}
|
||||
|
||||
private void acquireSurfacesCanvas() {
|
||||
for (int i = 0; i < mViews.size(); ++i) {
|
||||
CustomSurfaceView view = mViews.get(i);
|
||||
view.acquireCanvas();
|
||||
}
|
||||
}
|
||||
|
||||
private void releaseSurfacesCanvas() {
|
||||
for (int i = 0; i < mViews.size(); ++i) {
|
||||
CustomSurfaceView view = mViews.get(i);
|
||||
view.releaseCanvas();
|
||||
}
|
||||
}
|
||||
|
||||
private static String getReadableMemory(long bytes) {
|
||||
long unit = 1024;
|
||||
if (bytes < unit) {
|
||||
return bytes + " B";
|
||||
}
|
||||
int exp = (int) (Math.log(bytes) / Math.log(unit));
|
||||
return String.format("%.1f %sB", bytes / Math.pow(unit, exp),
|
||||
"KMGTPE".charAt(exp-1));
|
||||
}
|
||||
|
||||
private MemoryInfo getMemoryInfo() {
|
||||
ActivityManager activityManager = (ActivityManager)
|
||||
getSystemService(ACTIVITY_SERVICE);
|
||||
MemoryInfo memInfo = new MemoryInfo();
|
||||
activityManager.getMemoryInfo(memInfo);
|
||||
return memInfo;
|
||||
}
|
||||
|
||||
private void updateSystemInfo(int pixelFormat) {
|
||||
int visibleCnt = 0;
|
||||
for (int i = 0; i < mViews.size(); ++i) {
|
||||
if (mViews.get(i).getVisibility() == View.VISIBLE) {
|
||||
++visibleCnt;
|
||||
}
|
||||
}
|
||||
|
||||
MemoryInfo memInfo = getMemoryInfo();
|
||||
String info = "Available " +
|
||||
getReadableMemory(memInfo.availMem) + " from " +
|
||||
getReadableMemory(memInfo.totalMem) + ".\nVisible " +
|
||||
visibleCnt + " from " + mViews.size() + " " +
|
||||
getPixelFormatInfo(pixelFormat) + " surfaces.\n" +
|
||||
"View size: " + mWidth + "x" + mHeight +
|
||||
". Refresh rate: " + DOUBLE_FORMAT.format(mRefreshRate) + ".";
|
||||
mSystemInfoView.setText(info);
|
||||
}
|
||||
|
||||
private void detectRefreshRate() {
|
||||
WindowManager wm = (WindowManager)getSystemService(Context.WINDOW_SERVICE);
|
||||
mRefreshRate = wm.getDefaultDisplay().getRefreshRate();
|
||||
if (mRefreshRate < MIN_REFRESH_RATE_SUPPORTED)
|
||||
throw new RuntimeException("Unsupported display refresh rate: " + mRefreshRate);
|
||||
mTargetFPS = mRefreshRate - 2.0f;
|
||||
}
|
||||
|
||||
private int roundToNextPowerOf2(int value) {
|
||||
--value;
|
||||
value |= value >> 1;
|
||||
value |= value >> 2;
|
||||
value |= value >> 4;
|
||||
value |= value >> 8;
|
||||
value |= value >> 16;
|
||||
return value + 1;
|
||||
}
|
||||
|
||||
public static String getPixelFormatInfo(int pixelFormat) {
|
||||
switch (pixelFormat) {
|
||||
case PixelFormat.TRANSLUCENT:
|
||||
return "TRANSLUCENT";
|
||||
case PixelFormat.TRANSPARENT:
|
||||
return "TRANSPARENT";
|
||||
case PixelFormat.OPAQUE:
|
||||
return "OPAQUE";
|
||||
case PixelFormat.RGBA_8888:
|
||||
return "RGBA_8888";
|
||||
case PixelFormat.RGBX_8888:
|
||||
return "RGBX_8888";
|
||||
case PixelFormat.RGB_888:
|
||||
return "RGB_888";
|
||||
case PixelFormat.RGB_565:
|
||||
return "RGB_565";
|
||||
default:
|
||||
return "PIX.FORMAT:" + pixelFormat;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper that executes a task in the UI thread and waits for its completion.
|
||||
*
|
||||
* @param task - task to execute.
|
||||
*/
|
||||
private void runOnUiThreadAndWait(Runnable task) {
|
||||
new UIExecutor(task);
|
||||
}
|
||||
|
||||
class UIExecutor implements Runnable {
|
||||
private final Object mLock = new Object();
|
||||
private Runnable mTask;
|
||||
private boolean mDone = false;
|
||||
|
||||
UIExecutor(Runnable task) {
|
||||
mTask = task;
|
||||
mDone = false;
|
||||
runOnUiThread(this);
|
||||
synchronized (mLock) {
|
||||
while (!mDone) {
|
||||
try {
|
||||
mLock.wait();
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void run() {
|
||||
mTask.run();
|
||||
synchronized (mLock) {
|
||||
mDone = true;
|
||||
mLock.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.surfacecomposition;
|
||||
|
||||
import android.graphics.PixelFormat;
|
||||
import android.surfacecomposition.SurfaceCompositionMeasuringActivity.AllocationScore;
|
||||
import android.surfacecomposition.SurfaceCompositionMeasuringActivity.CompositorScore;
|
||||
import android.test.ActivityInstrumentationTestCase2;
|
||||
import android.test.suitebuilder.annotation.SmallTest;
|
||||
import android.util.Log;
|
||||
|
||||
public class SurfaceCompositionTest extends
|
||||
ActivityInstrumentationTestCase2<SurfaceCompositionMeasuringActivity> {
|
||||
private final static String TAG = "SurfaceCompositionTest";
|
||||
|
||||
// Pass threshold for major pixel formats.
|
||||
private final static int[] TEST_PIXEL_FORMATS = new int[] {
|
||||
PixelFormat.TRANSLUCENT,
|
||||
PixelFormat.OPAQUE,
|
||||
};
|
||||
|
||||
// Based on Nexus 9 performance which is usually < 9.0.
|
||||
private final static double[] MIN_ACCEPTED_COMPOSITION_SCORE = new double[] {
|
||||
8.0,
|
||||
8.0,
|
||||
};
|
||||
|
||||
// Based on Nexus 6 performance which is usually < 28.0.
|
||||
private final static double[] MIN_ACCEPTED_ALLOCATION_SCORE = new double[] {
|
||||
20.0,
|
||||
20.0,
|
||||
};
|
||||
|
||||
public SurfaceCompositionTest() {
|
||||
super(SurfaceCompositionMeasuringActivity.class);
|
||||
}
|
||||
|
||||
private void testRestoreContexts() {
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
public void testSurfaceCompositionPerformance() {
|
||||
for (int i = 0; i < TEST_PIXEL_FORMATS.length; ++i) {
|
||||
int pixelFormat = TEST_PIXEL_FORMATS[i];
|
||||
String formatName = SurfaceCompositionMeasuringActivity.getPixelFormatInfo(pixelFormat);
|
||||
CompositorScore score = getActivity().measureCompositionScore(pixelFormat);
|
||||
Log.i(TAG, "testSurfaceCompositionPerformance(" + formatName + ") = " + score);
|
||||
assertTrue("Device does not support surface(" + formatName + ") composition " +
|
||||
"performance score. " + score.mSurfaces + " < " +
|
||||
MIN_ACCEPTED_COMPOSITION_SCORE[i] + ".",
|
||||
score.mSurfaces >= MIN_ACCEPTED_COMPOSITION_SCORE[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
public void testSurfaceAllocationPerformance() {
|
||||
for (int i = 0; i < TEST_PIXEL_FORMATS.length; ++i) {
|
||||
int pixelFormat = TEST_PIXEL_FORMATS[i];
|
||||
String formatName = SurfaceCompositionMeasuringActivity.getPixelFormatInfo(pixelFormat);
|
||||
AllocationScore score = getActivity().measureAllocationScore(pixelFormat);
|
||||
Log.i(TAG, "testSurfaceAllocationPerformance(" + formatName + ") = " + score);
|
||||
assertTrue("Device does not support surface(" + formatName + ") allocation " +
|
||||
"performance score. " + score.mMedian + " < " +
|
||||
MIN_ACCEPTED_ALLOCATION_SCORE[i] + ".",
|
||||
score.mMedian >= MIN_ACCEPTED_ALLOCATION_SCORE[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user