/* * 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) { } }