/* * 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.image2; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.graphics.BitmapFactory; import android.graphics.Bitmap; import android.graphics.Canvas; import android.support.v8.renderscript.*; import android.os.Handler; import android.os.Message; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.ImageView; 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.util.Log; public class ImageProcessingActivity2 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 ImageView mDisplayView; private SizedTV mDisplayViewIO; private int mTestList[]; private float mTestResults[]; private boolean mToggleLong; private boolean mTogglePause; private boolean mToggleAnimate; private boolean mToggleDisplay; private boolean mToggleIO; private int mBitmapWidth; private int mBitmapHeight; private boolean mDemoMode; // Updates pending is a counter of how many kernels have been // sent to RS for processing and are not yet started. private int mUpdatesPending; // This is used to count updates in the pipeline. It's // incremented when work is submitted to RS and decremented when we // receive the callback the kernel has finished. private int mShowsPending; private boolean mTestNameChanged = true; private String mTestName; 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) { // 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(); if (mToggleDisplay) { mProcessor.mHandler.sendMessage(Message.obtain()); } } } } } ///////////////////////////////////////////////////////////////////////// // Processor is a helper thread for running the work without // blocking the UI thread. class Processor extends Thread { RenderScript mRS; ScriptC_util mScriptUtils; Allocation mInPixelsAllocation; Allocation mInPixelsAllocation2; Allocation mOutDisplayAllocation1; Allocation mOutDisplayAllocation2; Bitmap mBitmapOut1; Bitmap mBitmapOut2; int mActiveBitmap; private float mLastResult; private boolean mRun = true; private boolean mDoingBenchmark; private TestBase mTest; private boolean mBenchmarkMode; Allocation mOutDisplayAllocationIO; Allocation mOutPixelsAllocationIO; private TextureView mDisplayViewIO; private Surface mOutSurface; // 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. // // These are UI elements which can be used to control the operation of // a filter. For example, blur radius or white level. 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, boolean benchmarkMode) { mRS = rs; mRS.setMessageHandler(new MessageProcessor()); mScriptUtils = new ScriptC_util(mRS); switch(mBitmapWidth) { case 1920: mInPixelsAllocation = Allocation.createFromBitmapResource( mRS, getResources(), R.drawable.img1920x1080a); mInPixelsAllocation2 = Allocation.createFromBitmapResource( mRS, getResources(), R.drawable.img1920x1080b); mBitmapOut1 = Bitmap.createBitmap(1920, 1080, Bitmap.Config.ARGB_8888); mBitmapOut2 = Bitmap.createBitmap(1920, 1080, Bitmap.Config.ARGB_8888); break; case 1280: mInPixelsAllocation = Allocation.createFromBitmapResource( mRS, getResources(), R.drawable.img1280x720a); mInPixelsAllocation2 = Allocation.createFromBitmapResource( mRS, getResources(), R.drawable.img1280x720b); mBitmapOut1 = Bitmap.createBitmap(1280, 720, Bitmap.Config.ARGB_8888); mBitmapOut2 = Bitmap.createBitmap(1280, 720, Bitmap.Config.ARGB_8888); break; case 800: mInPixelsAllocation = Allocation.createFromBitmapResource( mRS, getResources(), R.drawable.img800x450a); mInPixelsAllocation2 = Allocation.createFromBitmapResource( mRS, getResources(), R.drawable.img800x450b); mBitmapOut1 = Bitmap.createBitmap(800, 450, Bitmap.Config.ARGB_8888); mBitmapOut2 = Bitmap.createBitmap(800, 450, Bitmap.Config.ARGB_8888); break; default: throw new RuntimeException("unhandled bitmap width."); } mBitmapOut1.setHasAlpha(false); mBitmapOut2.setHasAlpha(false); mOutDisplayAllocation1 = Allocation.createFromBitmap(mRS, mBitmapOut1); mOutDisplayAllocation2 = Allocation.createFromBitmap(mRS, mBitmapOut2); mBenchmarkMode = benchmarkMode; start(); } Processor(RenderScript rs, TextureView v, boolean benchmarkMode) { mRS = rs; mRS.setMessageHandler(new MessageProcessor()); mScriptUtils = new ScriptC_util(mRS); mDisplayViewIO = v; switch(mBitmapWidth) { 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; } mOutDisplayAllocationIO = Allocation.createTyped(mRS, mInPixelsAllocation.getType(), Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT | Allocation.USAGE_IO_OUTPUT); mOutPixelsAllocationIO = mOutDisplayAllocationIO; mBenchmarkMode = benchmarkMode; start(); } class Result { float totalTime; int iterations; } // 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) { mUpdatesPending = 0; Result r = new Result(); long t = java.lang.System.currentTimeMillis(); 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.totalTime); } // Run the kernel if (!mToggleIO) { if (mActiveBitmap == 0) { mTest.mOutPixelsAllocation = mOutDisplayAllocation1; } else { mTest.mOutPixelsAllocation = mOutDisplayAllocation2; } } else { mTest.mOutPixelsAllocation = mOutDisplayAllocationIO; } mTest.runTest(); r.iterations ++; if (!mToggleIO) { if (mToggleDisplay) { if (mActiveBitmap == 0) { mOutDisplayAllocation1.copyTo(mBitmapOut1); } else { mOutDisplayAllocation2.copyTo(mBitmapOut2); } } } // Send our RS message handler a message so we know when this work has completed mScriptUtils.invoke_utilSendMessage(mActiveBitmap); mActiveBitmap ^= 1; long t2 = java.lang.System.currentTimeMillis(); r.totalTime = (t2 - t) / 1000.f; } while (r.totalTime < minTime); mRS.finish(); long t2 = java.lang.System.currentTimeMillis(); r.totalTime = (t2 - t) / 1000.f; // Even if we are not displaying as we go, show the final output if (!mToggleIO) { mOutDisplayAllocation1.copyTo(mBitmapOut1); mOutDisplayAllocation2.copyTo(mBitmapOut2); } return r; } // Get a benchmark result for a specific test private float getBenchmark() { mDoingBenchmark = true; mUpdatesPending = 0; long result = 0; float runtime = 1.f; if (mToggleLong) { runtime = 10.f; } // 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); // Run the actual benchmark Result r = runBenchmarkLoop(runtime); Log.v("rs", "Test: time=" + r.totalTime +"s, frames=" + r.iterations + ", avg=" + r.totalTime / r.iterations * 1000.f); mDoingBenchmark = false; return r.totalTime / r.iterations * 1000.f; } private int mDisplayedBitmap; private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { if (mTestNameChanged) { getActionBar().setTitle("IP-Compat test: " + mTestName); } if (!mToggleIO) { if (mDisplayedBitmap == 0) { mDisplayView.setImageBitmap(mBitmapOut1); } else { mDisplayView.setImageBitmap(mBitmapOut2); } mDisplayedBitmap ^= 1; mDisplayView.invalidate(); } else { synchronized(this) { if (mRS == null || mOutPixelsAllocationIO == null) { return; } if (mOutDisplayAllocationIO != mOutPixelsAllocationIO) { mOutDisplayAllocationIO.copyFrom(mOutPixelsAllocationIO); } mOutDisplayAllocationIO.ioSend(); } } } }; 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) { } } if (mToggleIO) { if ((mOutSurface == null) || (mOutPixelsAllocationIO == null)) { continue; } if (lastSurface != mOutSurface) { mOutDisplayAllocationIO.setSurface(mOutSurface); lastSurface = mOutSurface; } } // We may have been asked to exit while waiting if (!mRun) return; } 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(); } onBenchmarkFinish(mRun); return; } 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 if (mActiveBitmap == 0) { mTest.mOutPixelsAllocation = mOutDisplayAllocation1; } else { mTest.mOutPixelsAllocation = mOutDisplayAllocation2; } if (mToggleIO) { mTest.mOutPixelsAllocation = mOutDisplayAllocationIO; } runTest(); if (mToggleDisplay && !mToggleIO) { if (mActiveBitmap == 0) { mOutDisplayAllocation1.copyTo(mBitmapOut1); } else { mOutDisplayAllocation2.copyTo(mBitmapOut2); } } // Send our RS message handler a message so we know when this work has completed mScriptUtils.invoke_utilSendMessage(mActiveBitmap); mActiveBitmap ^= 1; } } } } 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) { synchronized(this) { mOutSurface = s; notifyAll(); } //update(); } public void exit() { mRun = false; synchronized(this) { notifyAll(); } try { this.join(); } catch(InterruptedException e) { } mInPixelsAllocation.destroy(); mInPixelsAllocation2.destroy(); if (mTest != null) { mTest.destroy(); mTest = null; } if(!mToggleIO) { mOutDisplayAllocation1.destroy(); mOutDisplayAllocation2.destroy(); } else { if (mOutPixelsAllocationIO != mOutDisplayAllocationIO) { mOutPixelsAllocationIO.destroy(); } mOutDisplayAllocationIO.destroy(); } mRS.destroy(); mInPixelsAllocation = null; mInPixelsAllocation2 = null; mOutDisplayAllocation1 = null; mOutDisplayAllocation2 = null; mOutDisplayAllocationIO = null; mOutPixelsAllocationIO = null; mRS = null; } } /////////////////////////////////////////////////////////////////////////////////////// private boolean mDoingBenchmark; public Processor mProcessor; TestBase changeTest(IPTestList.TestName t, boolean setupUI) { TestBase tb = IPTestList.newTest(t); mTestNameChanged = true; mTestName = t.toString(); mProcessor.mHandler.sendMessage(Message.obtain()); tb.createBaseTest(this); if (setupUI) { setupBars(tb); } return tb; } TestBase changeTest(int id, boolean setupUI) { IPTestList.TestName t = IPTestList.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); mDisplayView = findViewById(R.id.display); mDisplayViewIO = findViewById(R.id.display2); //mDisplayView.setImageBitmap(mBitmapOut); 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(); mProcessor.exit(); } 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() { if (!mDemoMode) { hideBars(); } 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); if (mToggleIO) { mDisplayViewIO.mWidth = tw; mDisplayViewIO.mHeight = th; mProcessor = new Processor(RenderScript.create(this), mDisplayViewIO, !mDemoMode); mDisplayViewIO.setSurfaceTextureListener(this); } else { mProcessor = new Processor(RenderScript.create(this), !mDemoMode); } if (mDemoMode) { mProcessor.mTest = changeTest(mTestList[0], true); } mProcessor.update(); } @Override protected void onResume() { super.onResume(); Intent i = getIntent(); mTestList = i.getIntArrayExtra("tests"); mToggleIO = i.getBooleanExtra("enable io", 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); 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) { mProcessor.setSurface(null); return true; } @Override public void onSurfaceTextureUpdated(SurfaceTexture surface) { } }