blob: 24c0f46df5fd884e259e1ef4b29d054837b78a12 [file] [log] [blame]
/*
* Copyright (C) 2012 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.rs.imagejb;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.widget.SeekBar;
import android.widget.Spinner;
import android.widget.TextView;
import android.view.View;
import android.view.TextureView;
import android.view.Surface;
import android.graphics.SurfaceTexture;
import android.graphics.Point;
import android.view.WindowManager;
import android.util.Log;
import android.renderscript.Allocation;
import android.renderscript.RenderScript;
import android.support.test.InstrumentationRegistry;
public class ImageProcessingActivityJB extends Activity
implements SeekBar.OnSeekBarChangeListener,
TextureView.SurfaceTextureListener {
private final String TAG = "Img";
private Spinner mSpinner;
private SeekBar mBar1;
private SeekBar mBar2;
private SeekBar mBar3;
private SeekBar mBar4;
private SeekBar mBar5;
private int mBars[] = new int[5];
private int mBarsOld[] = new int[5];
private TextView mText1;
private TextView mText2;
private TextView mText3;
private TextView mText4;
private TextView mText5;
private SizedTV mDisplayView;
private int mTestList[];
private float mTestResults[];
private boolean mToggleIO;
private boolean mToggleDVFS;
private boolean mToggleLong;
private boolean mTogglePause;
private boolean mToggleAnimate;
private boolean mToggleDisplay;
private int mBitmapWidth;
private int mBitmapHeight;
private boolean mDemoMode;
private float mMinTestRuntime;
private int mMinTestIterations;
// Updates pending is a counter of how many kernels have been
// sent to RS for processing
//
// In benchmark this is incremented each time a kernel is launched and
// decremented each time a kernel completes
//
// In demo mode, each UI input increments the counter and it is zeroed
// when the latest settings are sent to RS for processing.
private int mUpdatesPending;
// In demo mode this is used to count updates in the pipeline. It's
// incremented when work is submitted to RS and decremented when invalidate is
// called to display a result.
private int mShowsPending;
// Initialize the parameters for Instrumentation tests.
protected void prepareInstrumentationTest() {
mTestList = new int[1];
mBitmapWidth = 1920;
mBitmapHeight = 1080;
mTestResults = new float[1];
startProcessor();
}
static public class SizedTV extends TextureView {
int mWidth;
int mHeight;
public SizedTV(android.content.Context c) {
super(c);
mWidth = 800;
mHeight = 450;
}
public SizedTV(android.content.Context c, android.util.AttributeSet attrs) {
super(c, attrs);
mWidth = 800;
mHeight = 450;
}
public SizedTV(android.content.Context c, android.util.AttributeSet attrs, int f) {
super(c, attrs, f);
mWidth = 800;
mHeight = 450;
}
protected void onMeasure(int w, int h) {
setMeasuredDimension(mWidth, mHeight);
}
}
/////////////////////////////////////////////////////////////////////////
// Message processor to handle notifications for when kernel completes
private class MessageProcessor extends RenderScript.RSMessageHandler {
MessageProcessor() {
}
public void run() {
synchronized(mProcessor) {
// In demo mode, decrement the pending displays and notify the
// UI processor it can now enqueue more work if additional updates
// are blocked by a full pipeline.
if (mShowsPending > 0) {
mShowsPending --;
mProcessor.notifyAll();
}
}
}
}
/////////////////////////////////////////////////////////////////////////
// Processor is a helper thread for running the work without
// blocking the UI thread.
class Processor extends Thread {
RenderScript mRS;
Allocation mInPixelsAllocation;
Allocation mInPixelsAllocation2;
Allocation mOutDisplayAllocation;
Allocation mOutPixelsAllocation;
private Surface mOutSurface;
private float mLastResult;
private boolean mRun = true;
private boolean mDoingBenchmark;
private TestBase mTest;
private TextureView mDisplayView;
private boolean mBenchmarkMode;
// We don't want to call the "changed" methods excessively as this
// can cause extra work for drivers. Before running a test update
// any bars which have changed.
void runTest() {
if (mBars[0] != mBarsOld[0]) {
mTest.onBar1Changed(mBars[0]);
mBarsOld[0] = mBars[0];
}
if (mBars[1] != mBarsOld[1]) {
mTest.onBar2Changed(mBars[1]);
mBarsOld[1] = mBars[1];
}
if (mBars[2] != mBarsOld[2]) {
mTest.onBar3Changed(mBars[2]);
mBarsOld[2] = mBars[2];
}
if (mBars[3] != mBarsOld[3]) {
mTest.onBar4Changed(mBars[3]);
mBarsOld[3] = mBars[3];
}
if (mBars[4] != mBarsOld[4]) {
mTest.onBar5Changed(mBars[4]);
mBarsOld[4] = mBars[4];
}
mTest.runTest();
}
Processor(RenderScript rs, TextureView v, boolean benchmarkMode) {
mRS = rs;
mDisplayView = v;
mRS.setMessageHandler(new MessageProcessor());
switch(mBitmapWidth) {
case 3840:
mInPixelsAllocation = Allocation.createFromBitmapResource(
mRS, getResources(), R.drawable.img3840x2160a);
mInPixelsAllocation2 = Allocation.createFromBitmapResource(
mRS, getResources(), R.drawable.img3840x2160b);
break;
case 1920:
mInPixelsAllocation = Allocation.createFromBitmapResource(
mRS, getResources(), R.drawable.img1920x1080a);
mInPixelsAllocation2 = Allocation.createFromBitmapResource(
mRS, getResources(), R.drawable.img1920x1080b);
break;
case 1280:
mInPixelsAllocation = Allocation.createFromBitmapResource(
mRS, getResources(), R.drawable.img1280x720a);
mInPixelsAllocation2 = Allocation.createFromBitmapResource(
mRS, getResources(), R.drawable.img1280x720b);
break;
case 800:
mInPixelsAllocation = Allocation.createFromBitmapResource(
mRS, getResources(), R.drawable.img800x450a);
mInPixelsAllocation2 = Allocation.createFromBitmapResource(
mRS, getResources(), R.drawable.img800x450b);
break;
}
// We create the output allocation using USAGE_IO_OUTPUT so we can share the
// bits with a TextureView. This is more efficient than using a bitmap.
mOutDisplayAllocation = Allocation.createTyped(mRS, mInPixelsAllocation.getType(),
Allocation.MipmapControl.MIPMAP_NONE,
Allocation.USAGE_SCRIPT |
Allocation.USAGE_IO_OUTPUT);
mOutPixelsAllocation = mOutDisplayAllocation;
if (!mToggleIO) {
// Not using USAGE_IO for the script so create a non-io kernel to copy from
mOutPixelsAllocation = Allocation.createTyped(mRS, mInPixelsAllocation.getType(),
Allocation.MipmapControl.MIPMAP_NONE,
Allocation.USAGE_SCRIPT);
}
mBenchmarkMode = benchmarkMode;
start();
}
// Run one loop of kernels for at least the specified minimum time.
// The function returns the average time in ms for the test run
private Result runBenchmarkLoop(float minTime, int minIter) {
mUpdatesPending = 0;
Result r = new Result();
long t = java.lang.System.nanoTime();
do {
synchronized(this) {
// Shows pending is used to track the number of kernels in the RS pipeline
// We throttle it to 2. This provide some buffering to allow a kernel to be started
// before we are nofitied the previous finished. However, larger numbers are uncommon
// in interactive apps as they introduce 'lag' between user input and display.
mShowsPending++;
if (mShowsPending > 2) {
try {
this.wait();
} catch(InterruptedException e) {
}
}
}
// If animations are enabled update the test state.
if (mToggleAnimate) {
mTest.animateBars(r.getTotalTime());
}
// Run the kernel
mTest.runTest();
if (mToggleDisplay) {
// If we are not outputting directly to the TextureView we need to copy from
// our temporary buffer.
if (mOutDisplayAllocation != mOutPixelsAllocation) {
mOutDisplayAllocation.copyFrom(mOutPixelsAllocation);
}
// queue the update of the TextureView with the allocation contents
mOutDisplayAllocation.ioSend();
}
// Send our RS message handler a message so we know when this work has completed
mRS.sendMessage(0, null);
// Finish previous iteration before recording the time. Without this, the first
// few iterations finish very quickly and the later iterations take much longer
mRS.finish();
long t2 = java.lang.System.nanoTime();
r.add((t2 - t) / 1000000000.f);
t = t2;
} while (r.getTotalTime() < minTime || r.getIterations() < minIter);
// Wait for any stray operations to complete and update the final time
mRS.finish();
return r;
}
// Method to retreive benchmark results for instrumentation tests.
Result getInstrumentationResult(IPTestListJB.TestName t) {
mTest = changeTest(t, false);
return getBenchmark();
}
// Get a benchmark result for a specific test
private Result getBenchmark() {
mDoingBenchmark = true;
mUpdatesPending = 0;
if (mToggleDVFS) {
mDvfsWar.go();
}
// We run a short bit of work before starting the actual test
// this is to let any power management do its job and respond
runBenchmarkLoop(0.3f, 0);
// Run the actual benchmark
Result r = runBenchmarkLoop(mMinTestRuntime, mMinTestIterations);
Log.v("rs", "Test: time=" + r.getTotalTime() + "s, frames=" + r.getIterations() +
", avg=" + r.getAvg() * 1000.f + ", stdcoef=" + r.getStdCoef() * 100.0f + "%");
mDoingBenchmark = false;
return r;
}
public void run() {
Surface lastSurface = null;
while (mRun) {
// Our loop for launching tests or benchmarks
synchronized(this) {
// If we have no work to do, or we have displays pending, wait
if ((mUpdatesPending == 0) || (mShowsPending != 0)) {
try {
this.wait();
} catch(InterruptedException e) {
}
}
// We may have been asked to exit while waiting
if (!mRun) return;
// During startup we may not have a surface yet to display, if
// this is the case, wait.
if ((mOutSurface == null) || (mOutPixelsAllocation == null)) {
continue;
}
// Our display surface changed, set it.
if (lastSurface != mOutSurface) {
mOutDisplayAllocation.setSurface(mOutSurface);
lastSurface = mOutSurface;
}
}
if (mBenchmarkMode) {
// Loop over the tests we want to benchmark
for (int ct=0; (ct < mTestList.length) && mRun; ct++) {
// For reproducibility we wait a short time for any sporadic work
// created by the user touching the screen to launch the test to pass.
// Also allows for things to settle after the test changes.
mRS.finish();
try {
sleep(250);
} catch(InterruptedException e) {
}
// If we just ran a test, we destroy it here to relieve some memory pressure
if (mTest != null) {
mTest.destroy();
}
// Select the next test
mTest = changeTest(mTestList[ct], false);
// If the user selected the "long pause" option, wait
if (mTogglePause) {
for (int i=0; (i < 100) && mRun; i++) {
try {
sleep(100);
} catch(InterruptedException e) {
}
}
}
// Run the test
mTestResults[ct] = getBenchmark().getAvg() * 1000.0f;
}
onBenchmarkFinish(mRun);
} else {
boolean update = false;
synchronized(this) {
// If we have updates to process and are not blocked by pending shows,
// start the next kernel
if ((mUpdatesPending > 0) && (mShowsPending == 0)) {
mUpdatesPending = 0;
update = true;
mShowsPending++;
}
}
if (update) {
// Run the kernel
runTest();
// If we are not outputting directly to the TextureView we need to copy from
// our temporary buffer.
if (mOutDisplayAllocation != mOutPixelsAllocation) {
mOutDisplayAllocation.copyFrom(mOutPixelsAllocation);
}
// queue the update of the TextureView with the allocation contents
mOutDisplayAllocation.ioSend();
// Send our RS message handler a message so we know when this work has completed
mRS.sendMessage(0, null);
}
}
}
}
public void update() {
// something UI related has changed, enqueue an update if one is not
// already pending. Wake the worker if needed
synchronized(this) {
if (mUpdatesPending < 2) {
mUpdatesPending++;
notifyAll();
}
}
}
public void setSurface(Surface s) {
mOutSurface = s;
update();
}
public void exit() {
mRun = false;
synchronized(this) {
notifyAll();
}
try {
this.join();
} catch(InterruptedException e) {
}
mInPixelsAllocation.destroy();
mInPixelsAllocation2.destroy();
if (mOutPixelsAllocation != mOutDisplayAllocation) {
mOutPixelsAllocation.destroy();
}
if (mTest != null) {
mTest.destroy();
mTest = null;
}
mOutDisplayAllocation.destroy();
mRS.destroy();
mInPixelsAllocation = null;
mInPixelsAllocation2 = null;
mOutPixelsAllocation = null;
mOutDisplayAllocation = null;
mRS = null;
}
}
///////////////////////////////////////////////////////////////////////////////////////
static class DVFSWorkaround {
static class spinner extends Thread {
boolean mRun = true;
long mNextSleep;
spinner() {
setPriority(MIN_PRIORITY);
start();
}
public void run() {
while (mRun) {
Thread.yield();
synchronized(this) {
long t = java.lang.System.currentTimeMillis();
if (t > mNextSleep) {
try {
this.wait();
} catch(InterruptedException e) {
}
}
}
}
}
public void go(long t) {
synchronized(this) {
mNextSleep = t;
notifyAll();
}
}
}
spinner s1;
DVFSWorkaround() {
s1 = new spinner();
}
void go() {
long t = java.lang.System.currentTimeMillis() + 2000;
s1.go(t);
}
void destroy() {
synchronized(this) {
s1.mRun = false;
notifyAll();
}
}
}
DVFSWorkaround mDvfsWar = new DVFSWorkaround();
///////////////////////////////////////////////////////////
private boolean mDoingBenchmark;
public Processor mProcessor;
TestBase changeTest(IPTestListJB.TestName t, boolean setupUI) {
TestBase tb = IPTestListJB.newTest(t);
tb.createBaseTest(this);
if (setupUI) {
setupBars(tb);
}
return tb;
}
TestBase changeTest(int id, boolean setupUI) {
IPTestListJB.TestName t = IPTestListJB.TestName.values()[id];
return changeTest(t, setupUI);
}
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (fromUser) {
if (seekBar == mBar1) {
mBars[0] = progress;
} else if (seekBar == mBar2) {
mBars[1] = progress;
} else if (seekBar == mBar3) {
mBars[2] = progress;
} else if (seekBar == mBar4) {
mBars[3] = progress;
} else if (seekBar == mBar5) {
mBars[4] = progress;
}
mProcessor.update();
}
}
public void onStartTrackingTouch(SeekBar seekBar) {
}
public void onStopTrackingTouch(SeekBar seekBar) {
}
void setupBars(TestBase t) {
mSpinner.setVisibility(View.VISIBLE);
t.onSpinner1Setup(mSpinner);
mBar1.setVisibility(View.VISIBLE);
mText1.setVisibility(View.VISIBLE);
t.onBar1Setup(mBar1, mText1);
mBar2.setVisibility(View.VISIBLE);
mText2.setVisibility(View.VISIBLE);
t.onBar2Setup(mBar2, mText2);
mBar3.setVisibility(View.VISIBLE);
mText3.setVisibility(View.VISIBLE);
t.onBar3Setup(mBar3, mText3);
mBar4.setVisibility(View.VISIBLE);
mText4.setVisibility(View.VISIBLE);
t.onBar4Setup(mBar4, mText4);
mBar5.setVisibility(View.VISIBLE);
mText5.setVisibility(View.VISIBLE);
t.onBar5Setup(mBar5, mText5);
}
void hideBars() {
mSpinner.setVisibility(View.INVISIBLE);
mBar1.setVisibility(View.INVISIBLE);
mText1.setVisibility(View.INVISIBLE);
mBar2.setVisibility(View.INVISIBLE);
mText2.setVisibility(View.INVISIBLE);
mBar3.setVisibility(View.INVISIBLE);
mText3.setVisibility(View.INVISIBLE);
mBar4.setVisibility(View.INVISIBLE);
mText4.setVisibility(View.INVISIBLE);
mBar5.setVisibility(View.INVISIBLE);
mText5.setVisibility(View.INVISIBLE);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
mDisplayView = findViewById(R.id.display);
mSpinner = findViewById(R.id.spinner1);
mBar1 = findViewById(R.id.slider1);
mBar2 = findViewById(R.id.slider2);
mBar3 = findViewById(R.id.slider3);
mBar4 = findViewById(R.id.slider4);
mBar5 = findViewById(R.id.slider5);
mBar1.setOnSeekBarChangeListener(this);
mBar2.setOnSeekBarChangeListener(this);
mBar3.setOnSeekBarChangeListener(this);
mBar4.setOnSeekBarChangeListener(this);
mBar5.setOnSeekBarChangeListener(this);
mText1 = findViewById(R.id.slider1Text);
mText2 = findViewById(R.id.slider2Text);
mText3 = findViewById(R.id.slider3Text);
mText4 = findViewById(R.id.slider4Text);
mText5 = findViewById(R.id.slider5Text);
}
@Override
protected void onPause() {
super.onPause();
if (mProcessor != null) {
mProcessor.exit();
mProcessor = null;
}
}
public void onBenchmarkFinish(boolean ok) {
if (ok) {
Intent intent = new Intent();
intent.putExtra("tests", mTestList);
intent.putExtra("results", mTestResults);
setResult(RESULT_OK, intent);
} else {
setResult(RESULT_CANCELED);
}
finish();
}
void startProcessor() {
Point size = new Point();
getWindowManager().getDefaultDisplay().getSize(size);
int mScreenWidth = size.x;
int mScreenHeight = size.y;
int tw = mBitmapWidth;
int th = mBitmapHeight;
if (tw > mScreenWidth || th > mScreenHeight) {
float s1 = (float)tw / (float)mScreenWidth;
float s2 = (float)th / (float)mScreenHeight;
if (s1 > s2) {
tw /= s1;
th /= s1;
} else {
tw /= s2;
th /= s2;
}
}
android.util.Log.v("rs", "TV sizes " + tw + ", " + th);
mDisplayView.mWidth = tw;
mDisplayView.mHeight = th;
//mDisplayView.setTransform(new android.graphics.Matrix());
mProcessor = new Processor(RenderScript.create(this), mDisplayView, !mDemoMode);
mDisplayView.setSurfaceTextureListener(this);
if (mDemoMode) {
mProcessor.mTest = changeTest(mTestList[0], true);
}
}
@Override
protected void onResume() {
super.onResume();
Intent i = getIntent();
mTestList = i.getIntArrayExtra("tests");
mToggleIO = i.getBooleanExtra("enable io", false);
mToggleDVFS = i.getBooleanExtra("enable dvfs", false);
mToggleLong = i.getBooleanExtra("enable long", false);
mTogglePause = i.getBooleanExtra("enable pause", false);
mToggleAnimate = i.getBooleanExtra("enable animate", false);
mToggleDisplay = i.getBooleanExtra("enable display", false);
mBitmapWidth = i.getIntExtra("resolution X", 0);
mBitmapHeight = i.getIntExtra("resolution Y", 0);
mDemoMode = i.getBooleanExtra("demo", false);
// Default values
mMinTestRuntime = 1.0f;
mMinTestIterations = 2;
// Pass in arguments from "am instrumentation ..." if present
// This is wrapped in a try..catch because there was an exception thrown whenever
// instrumentation was not used (ie. when the graphical interface was used)
try {
Bundle extras = InstrumentationRegistry.getArguments();
String passedInString = null;
if ( extras != null ) {
if ( extras.containsKey ("minimum_test_runtime") ) {
mMinTestRuntime = Float.parseFloat(extras.getString("minimum_test_runtime"));
}
if ( extras.containsKey ("minimum_test_iterations") ) {
mMinTestIterations = Integer.parseInt(
extras.getString("minimum_test_iterations"));
}
}
} catch(Exception e) {
}
// User chose the longer pre-set runtime
if (mToggleLong) {
mMinTestRuntime = 10.f;
}
// Hide the bars in demo mode.
// Calling from onResume() to make sure the operation is on the UI thread.
if (!mDemoMode) {
hideBars();
}
// Start the processor only when a non-empty list received from the intent.
if (mTestList != null) {
mTestResults = new float[mTestList.length];
startProcessor();
}
}
protected void onDestroy() {
super.onDestroy();
}
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
mProcessor.setSurface(new Surface(surface));
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
mProcessor.setSurface(new Surface(surface));
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
if (mProcessor != null) {
mProcessor.setSurface(null);
}
return true;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
}
}