1/*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.rs.imagejb;
18
19import android.app.Activity;
20
21import android.content.Intent;
22import android.os.Bundle;
23import android.os.Handler;
24import android.os.Message;
25import android.widget.AdapterView;
26import android.widget.ArrayAdapter;
27import android.widget.SeekBar;
28import android.widget.Spinner;
29import android.widget.TextView;
30import android.view.View;
31import android.view.TextureView;
32import android.view.Surface;
33import android.graphics.SurfaceTexture;
34import android.graphics.Point;
35
36import android.util.Log;
37import android.renderscript.ScriptC;
38import android.renderscript.RenderScript;
39import android.renderscript.Type;
40import android.renderscript.Allocation;
41import android.renderscript.Element;
42import android.renderscript.Script;
43
44
45public class ImageProcessingActivityJB extends Activity
46                                       implements SeekBar.OnSeekBarChangeListener,
47                                                  TextureView.SurfaceTextureListener {
48    private final String TAG = "Img";
49
50    private Spinner mSpinner;
51    private SeekBar mBar1;
52    private SeekBar mBar2;
53    private SeekBar mBar3;
54    private SeekBar mBar4;
55    private SeekBar mBar5;
56
57    private int mBars[] = new int[5];
58    private int mBarsOld[] = new int[5];
59
60    private TextView mText1;
61    private TextView mText2;
62    private TextView mText3;
63    private TextView mText4;
64    private TextView mText5;
65    private SizedTV mDisplayView;
66
67    private int mTestList[];
68    private float mTestResults[];
69
70    private boolean mToggleIO;
71    private boolean mToggleDVFS;
72    private boolean mToggleLong;
73    private boolean mTogglePause;
74    private boolean mToggleAnimate;
75    private boolean mToggleDisplay;
76    private int mBitmapWidth;
77    private int mBitmapHeight;
78    private boolean mDemoMode;
79
80    // Updates pending is a counter of how many kernels have been
81    // sent to RS for processing
82    //
83    // In benchmark this is incremented each time a kernel is launched and
84    // decremented each time a kernel completes
85    //
86    // In demo mode, each UI input increments the counter and it is zeroed
87    // when the latest settings are sent to RS for processing.
88    private int mUpdatesPending;
89
90    // In demo mode this is used to count updates in the pipeline.  It's
91    // incremented when work is submitted to RS and decremented when invalidate is
92    // called to display a result.
93    private int mShowsPending;
94
95
96    static public class SizedTV extends TextureView {
97        int mWidth;
98        int mHeight;
99
100        public SizedTV(android.content.Context c) {
101            super(c);
102            mWidth = 800;
103            mHeight = 450;
104        }
105
106        public SizedTV(android.content.Context c, android.util.AttributeSet attrs) {
107            super(c, attrs);
108            mWidth = 800;
109            mHeight = 450;
110        }
111
112        public SizedTV(android.content.Context c, android.util.AttributeSet attrs, int f) {
113            super(c, attrs, f);
114            mWidth = 800;
115            mHeight = 450;
116        }
117
118        protected void onMeasure(int w, int h) {
119            setMeasuredDimension(mWidth, mHeight);
120        }
121    }
122
123    /////////////////////////////////////////////////////////////////////////
124
125    // Message processor to handle notifications for when kernel completes
126    private class MessageProcessor extends RenderScript.RSMessageHandler {
127        MessageProcessor() {
128        }
129
130        public void run() {
131            synchronized(mProcessor) {
132                // In demo mode, decrement the pending displays and notify the
133                // UI processor it can now enqueue more work if additional updates
134                // are blocked by a full pipeline.
135                if (mShowsPending > 0) {
136                    mShowsPending --;
137                    mProcessor.notifyAll();
138                }
139            }
140        }
141    }
142
143
144    /////////////////////////////////////////////////////////////////////////
145    // Processor is a helper thread for running the work without
146    // blocking the UI thread.
147    class Processor extends Thread {
148        RenderScript mRS;
149        Allocation mInPixelsAllocation;
150        Allocation mInPixelsAllocation2;
151        Allocation mOutDisplayAllocation;
152        Allocation mOutPixelsAllocation;
153
154        private Surface mOutSurface;
155        private float mLastResult;
156        private boolean mRun = true;
157        private boolean mDoingBenchmark;
158        private TestBase mTest;
159        private TextureView mDisplayView;
160
161        private boolean mBenchmarkMode;
162
163        // We don't want to call the "changed" methods excessively as this
164        // can cause extra work for drivers.  Before running a test update
165        // any bars which have changed.
166        void runTest() {
167            if (mBars[0] != mBarsOld[0]) {
168                mTest.onBar1Changed(mBars[0]);
169                mBarsOld[0] = mBars[0];
170            }
171            if (mBars[1] != mBarsOld[1]) {
172                mTest.onBar2Changed(mBars[1]);
173                mBarsOld[1] = mBars[1];
174            }
175            if (mBars[2] != mBarsOld[2]) {
176                mTest.onBar3Changed(mBars[2]);
177                mBarsOld[2] = mBars[2];
178            }
179            if (mBars[3] != mBarsOld[3]) {
180                mTest.onBar4Changed(mBars[3]);
181                mBarsOld[3] = mBars[3];
182            }
183            if (mBars[4] != mBarsOld[4]) {
184                mTest.onBar5Changed(mBars[4]);
185                mBarsOld[4] = mBars[4];
186            }
187            mTest.runTest();
188        }
189
190        Processor(RenderScript rs, TextureView v, boolean benchmarkMode) {
191            mRS = rs;
192            mDisplayView = v;
193
194            mRS.setMessageHandler(new MessageProcessor());
195
196            switch(mBitmapWidth) {
197            case 3840:
198                mInPixelsAllocation = Allocation.createFromBitmapResource(
199                        mRS, getResources(), R.drawable.img3840x2160a);
200                mInPixelsAllocation2 = Allocation.createFromBitmapResource(
201                        mRS, getResources(), R.drawable.img3840x2160b);
202                break;
203            case 1920:
204                mInPixelsAllocation = Allocation.createFromBitmapResource(
205                        mRS, getResources(), R.drawable.img1920x1080a);
206                mInPixelsAllocation2 = Allocation.createFromBitmapResource(
207                        mRS, getResources(), R.drawable.img1920x1080b);
208                break;
209            case 1280:
210                mInPixelsAllocation = Allocation.createFromBitmapResource(
211                        mRS, getResources(), R.drawable.img1280x720a);
212                mInPixelsAllocation2 = Allocation.createFromBitmapResource(
213                        mRS, getResources(), R.drawable.img1280x720b);
214                break;
215            case 800:
216                mInPixelsAllocation = Allocation.createFromBitmapResource(
217                        mRS, getResources(), R.drawable.img800x450a);
218                mInPixelsAllocation2 = Allocation.createFromBitmapResource(
219                        mRS, getResources(), R.drawable.img800x450b);
220                break;
221            }
222
223            // We create the output allocation using USAGE_IO_OUTPUT so we can share the
224            // bits with a TextureView.  This is more efficient than using a bitmap.
225            mOutDisplayAllocation = Allocation.createTyped(mRS, mInPixelsAllocation.getType(),
226                                                               Allocation.MipmapControl.MIPMAP_NONE,
227                                                               Allocation.USAGE_SCRIPT |
228                                                               Allocation.USAGE_IO_OUTPUT);
229            mOutPixelsAllocation = mOutDisplayAllocation;
230
231            if (!mToggleIO) {
232                // Not using USAGE_IO for the script so create a non-io kernel to copy from
233                mOutPixelsAllocation = Allocation.createTyped(mRS, mInPixelsAllocation.getType(),
234                                                              Allocation.MipmapControl.MIPMAP_NONE,
235                                                              Allocation.USAGE_SCRIPT);
236            }
237
238            mBenchmarkMode = benchmarkMode;
239            start();
240        }
241
242        class Result {
243            float totalTime;
244            int itterations;
245        }
246
247        // Run one loop of kernels for at least the specified minimum time.
248        // The function returns the average time in ms for the test run
249        private Result runBenchmarkLoop(float minTime) {
250            mUpdatesPending = 0;
251            Result r = new Result();
252
253            long t = java.lang.System.currentTimeMillis();
254            do {
255                synchronized(this) {
256                    // Shows pending is used to track the number of kernels in the RS pipeline
257                    // We throttle it to 2.  This provide some buffering to allow a kernel to be started
258                    // before we are nofitied the previous finished.  However, larger numbers are uncommon
259                    // in interactive apps as they introduce 'lag' between user input and display.
260                    mShowsPending++;
261                    if (mShowsPending > 2) {
262                        try {
263                            this.wait();
264                        } catch(InterruptedException e) {
265                        }
266                    }
267                }
268
269                // If animations are enabled update the test state.
270                if (mToggleAnimate) {
271                    mTest.animateBars(r.totalTime);
272                }
273
274                // Run the kernel
275                mTest.runTest();
276                r.itterations ++;
277
278                if (mToggleDisplay) {
279                    // If we are not outputting directly to the TextureView we need to copy from
280                    // our temporary buffer.
281                    if (mOutDisplayAllocation != mOutPixelsAllocation) {
282                        mOutDisplayAllocation.copyFrom(mOutPixelsAllocation);
283                    }
284
285                    // queue the update of the TextureView with the allocation contents
286                    mOutDisplayAllocation.ioSend();
287                }
288
289                // Send our RS message handler a message so we know when this work has completed
290                mRS.sendMessage(0, null);
291
292                long t2 = java.lang.System.currentTimeMillis();
293                r.totalTime += (t2 - t) / 1000.f;
294                t = t2;
295            } while (r.totalTime < minTime);
296
297            // Wait for any stray operations to complete and update the final time
298            mRS.finish();
299            long t2 = java.lang.System.currentTimeMillis();
300            r.totalTime += (t2 - t) / 1000.f;
301            t = t2;
302            return r;
303        }
304
305
306        // Get a benchmark result for a specific test
307        private float getBenchmark() {
308            mDoingBenchmark = true;
309            mUpdatesPending = 0;
310
311            long result = 0;
312            float runtime = 1.f;
313            if (mToggleLong) {
314                runtime = 10.f;
315            }
316
317            if (mToggleDVFS) {
318                mDvfsWar.go();
319            }
320
321            // We run a short bit of work before starting the actual test
322            // this is to let any power management do its job and respond
323            runBenchmarkLoop(0.3f);
324
325            // Run the actual benchmark
326            Result r = runBenchmarkLoop(runtime);
327
328            Log.v("rs", "Test: time=" + r.totalTime +"s,  frames=" + r.itterations +
329                  ", avg=" + r.totalTime / r.itterations * 1000.f);
330
331            mDoingBenchmark = false;
332            return r.totalTime / r.itterations * 1000.f;
333        }
334
335        public void run() {
336            Surface lastSurface = null;
337            while (mRun) {
338                // Our loop for launching tests or benchmarks
339                synchronized(this) {
340                    // If we have no work to do, or we have displays pending, wait
341                    if ((mUpdatesPending == 0) || (mShowsPending != 0)) {
342                        try {
343                            this.wait();
344                        } catch(InterruptedException e) {
345                        }
346                    }
347
348                    // We may have been asked to exit while waiting
349                    if (!mRun) return;
350
351                    // During startup we may not have a surface yet to display, if
352                    // this is the case, wait.
353                    if ((mOutSurface == null) || (mOutPixelsAllocation == null)) {
354                        continue;
355                    }
356
357                    // Our display surface changed, set it.
358                    if (lastSurface != mOutSurface) {
359                        mOutDisplayAllocation.setSurface(mOutSurface);
360                        lastSurface = mOutSurface;
361                    }
362                }
363
364                if (mBenchmarkMode) {
365                    // Loop over the tests we want to benchmark
366                    for (int ct=0; (ct < mTestList.length) && mRun; ct++) {
367
368                        // For reproducibility we wait a short time for any sporadic work
369                        // created by the user touching the screen to launch the test to pass.
370                        // Also allows for things to settle after the test changes.
371                        mRS.finish();
372                        try {
373                            sleep(250);
374                        } catch(InterruptedException e) {
375                        }
376
377                        // If we just ran a test, we destroy it here to relieve some memory pressure
378                        if (mTest != null) {
379                            mTest.destroy();
380                        }
381
382                        // Select the next test
383                        mTest = changeTest(mTestList[ct], false);
384
385                        // If the user selected the "long pause" option, wait
386                        if (mTogglePause) {
387                            for (int i=0; (i < 100) && mRun; i++) {
388                                try {
389                                    sleep(100);
390                                } catch(InterruptedException e) {
391                                }
392                            }
393                        }
394
395                        // Run the test
396                        mTestResults[ct] = getBenchmark();
397                    }
398                    onBenchmarkFinish(mRun);
399                } else {
400                    boolean update = false;
401                    synchronized(this) {
402                        // If we have updates to process and are not blocked by pending shows,
403                        // start the next kernel
404                        if ((mUpdatesPending > 0) && (mShowsPending == 0)) {
405                            mUpdatesPending = 0;
406                            update = true;
407                            mShowsPending++;
408                        }
409                    }
410
411                    if (update) {
412                        // Run the kernel
413                        runTest();
414
415                        // If we are not outputting directly to the TextureView we need to copy from
416                        // our temporary buffer.
417                        if (mOutDisplayAllocation != mOutPixelsAllocation) {
418                            mOutDisplayAllocation.copyFrom(mOutPixelsAllocation);
419                        }
420
421                        // queue the update of the TextureView with the allocation contents
422                        mOutDisplayAllocation.ioSend();
423
424                        // Send our RS message handler a message so we know when this work has completed
425                        mRS.sendMessage(0, null);
426                    }
427                }
428            }
429
430        }
431
432        public void update() {
433            // something UI related has changed, enqueue an update if one is not
434            // already pending.  Wake the worker if needed
435            synchronized(this) {
436                if (mUpdatesPending < 2) {
437                    mUpdatesPending++;
438                    notifyAll();
439                }
440            }
441        }
442
443        public void setSurface(Surface s) {
444            mOutSurface = s;
445            update();
446        }
447
448        public void exit() {
449            mRun = false;
450
451            synchronized(this) {
452                notifyAll();
453            }
454
455            try {
456                this.join();
457            } catch(InterruptedException e) {
458            }
459
460            mInPixelsAllocation.destroy();
461            mInPixelsAllocation2.destroy();
462            if (mOutPixelsAllocation != mOutDisplayAllocation) {
463                mOutPixelsAllocation.destroy();
464            }
465
466            if (mTest != null) {
467                mTest.destroy();
468                mTest = null;
469            }
470            mOutDisplayAllocation.destroy();
471            mRS.destroy();
472
473            mInPixelsAllocation = null;
474            mInPixelsAllocation2 = null;
475            mOutPixelsAllocation = null;
476            mOutDisplayAllocation = null;
477            mRS = null;
478        }
479    }
480
481    ///////////////////////////////////////////////////////////////////////////////////////
482
483    static class DVFSWorkaround {
484        static class spinner extends Thread {
485            boolean mRun = true;
486            long mNextSleep;
487
488            spinner() {
489                setPriority(MIN_PRIORITY);
490                start();
491            }
492
493            public void run() {
494                while (mRun) {
495                    Thread.yield();
496                    synchronized(this) {
497                        long t = java.lang.System.currentTimeMillis();
498                        if (t > mNextSleep) {
499                            try {
500                                this.wait();
501                            } catch(InterruptedException e) {
502                            }
503                        }
504                    }
505                }
506            }
507
508            public void go(long t) {
509                synchronized(this) {
510                    mNextSleep = t;
511                    notifyAll();
512                }
513            }
514        }
515
516        spinner s1;
517        DVFSWorkaround() {
518            s1 = new spinner();
519        }
520
521        void go() {
522            long t = java.lang.System.currentTimeMillis() + 2000;
523            s1.go(t);
524        }
525
526        void destroy() {
527            synchronized(this) {
528                s1.mRun = false;
529                notifyAll();
530            }
531        }
532    }
533    DVFSWorkaround mDvfsWar = new DVFSWorkaround();
534
535    ///////////////////////////////////////////////////////////
536
537
538    private boolean mDoingBenchmark;
539    public Processor mProcessor;
540
541    TestBase changeTest(IPTestListJB.TestName t, boolean setupUI) {
542        TestBase tb = IPTestListJB.newTest(t);
543
544        tb.createBaseTest(this);
545        if (setupUI) {
546            setupBars(tb);
547        }
548        return tb;
549    }
550
551    TestBase changeTest(int id, boolean setupUI) {
552        IPTestListJB.TestName t = IPTestListJB.TestName.values()[id];
553        return changeTest(t, setupUI);
554    }
555
556    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
557        if (fromUser) {
558            if (seekBar == mBar1) {
559                mBars[0] = progress;
560            } else if (seekBar == mBar2) {
561                mBars[1] = progress;
562            } else if (seekBar == mBar3) {
563                mBars[2] = progress;
564            } else if (seekBar == mBar4) {
565                mBars[3] = progress;
566            } else if (seekBar == mBar5) {
567                mBars[4] = progress;
568            }
569            mProcessor.update();
570        }
571    }
572
573    public void onStartTrackingTouch(SeekBar seekBar) {
574    }
575
576    public void onStopTrackingTouch(SeekBar seekBar) {
577    }
578
579    void setupBars(TestBase t) {
580        mSpinner.setVisibility(View.VISIBLE);
581        t.onSpinner1Setup(mSpinner);
582
583        mBar1.setVisibility(View.VISIBLE);
584        mText1.setVisibility(View.VISIBLE);
585        t.onBar1Setup(mBar1, mText1);
586
587        mBar2.setVisibility(View.VISIBLE);
588        mText2.setVisibility(View.VISIBLE);
589        t.onBar2Setup(mBar2, mText2);
590
591        mBar3.setVisibility(View.VISIBLE);
592        mText3.setVisibility(View.VISIBLE);
593        t.onBar3Setup(mBar3, mText3);
594
595        mBar4.setVisibility(View.VISIBLE);
596        mText4.setVisibility(View.VISIBLE);
597        t.onBar4Setup(mBar4, mText4);
598
599        mBar5.setVisibility(View.VISIBLE);
600        mText5.setVisibility(View.VISIBLE);
601        t.onBar5Setup(mBar5, mText5);
602    }
603
604    void hideBars() {
605        mSpinner.setVisibility(View.INVISIBLE);
606
607        mBar1.setVisibility(View.INVISIBLE);
608        mText1.setVisibility(View.INVISIBLE);
609
610        mBar2.setVisibility(View.INVISIBLE);
611        mText2.setVisibility(View.INVISIBLE);
612
613        mBar3.setVisibility(View.INVISIBLE);
614        mText3.setVisibility(View.INVISIBLE);
615
616        mBar4.setVisibility(View.INVISIBLE);
617        mText4.setVisibility(View.INVISIBLE);
618
619        mBar5.setVisibility(View.INVISIBLE);
620        mText5.setVisibility(View.INVISIBLE);
621    }
622
623    @Override
624    protected void onCreate(Bundle savedInstanceState) {
625        super.onCreate(savedInstanceState);
626        setContentView(R.layout.main);
627
628        mDisplayView = (SizedTV) findViewById(R.id.display);
629
630        mSpinner = (Spinner) findViewById(R.id.spinner1);
631
632        mBar1 = (SeekBar) findViewById(R.id.slider1);
633        mBar2 = (SeekBar) findViewById(R.id.slider2);
634        mBar3 = (SeekBar) findViewById(R.id.slider3);
635        mBar4 = (SeekBar) findViewById(R.id.slider4);
636        mBar5 = (SeekBar) findViewById(R.id.slider5);
637
638        mBar1.setOnSeekBarChangeListener(this);
639        mBar2.setOnSeekBarChangeListener(this);
640        mBar3.setOnSeekBarChangeListener(this);
641        mBar4.setOnSeekBarChangeListener(this);
642        mBar5.setOnSeekBarChangeListener(this);
643
644        mText1 = (TextView) findViewById(R.id.slider1Text);
645        mText2 = (TextView) findViewById(R.id.slider2Text);
646        mText3 = (TextView) findViewById(R.id.slider3Text);
647        mText4 = (TextView) findViewById(R.id.slider4Text);
648        mText5 = (TextView) findViewById(R.id.slider5Text);
649    }
650
651    @Override
652    protected void onPause() {
653        super.onPause();
654        mProcessor.exit();
655    }
656
657    public void onBenchmarkFinish(boolean ok) {
658        if (ok) {
659            Intent intent = new Intent();
660            intent.putExtra("tests", mTestList);
661            intent.putExtra("results", mTestResults);
662            setResult(RESULT_OK, intent);
663        } else {
664            setResult(RESULT_CANCELED);
665        }
666        finish();
667    }
668
669
670    void startProcessor() {
671        if (!mDemoMode) {
672            hideBars();
673        }
674
675        Point size = new Point();
676        getWindowManager().getDefaultDisplay().getSize(size);
677
678        int mScreenWidth = size.x;
679        int mScreenHeight = size.y;
680
681        int tw = mBitmapWidth;
682        int th = mBitmapHeight;
683
684        if (tw > mScreenWidth || th > mScreenHeight) {
685            float s1 = (float)tw / (float)mScreenWidth;
686            float s2 = (float)th / (float)mScreenHeight;
687
688            if (s1 > s2) {
689                tw /= s1;
690                th /= s1;
691            } else {
692                tw /= s2;
693                th /= s2;
694            }
695        }
696
697        android.util.Log.v("rs", "TV sizes " + tw + ", " + th);
698
699        mDisplayView.mWidth = tw;
700        mDisplayView.mHeight = th;
701        //mDisplayView.setTransform(new android.graphics.Matrix());
702
703        mProcessor = new Processor(RenderScript.create(this), mDisplayView, !mDemoMode);
704        mDisplayView.setSurfaceTextureListener(this);
705
706        if (mDemoMode) {
707            mProcessor.mTest = changeTest(mTestList[0], true);
708        }
709    }
710
711    @Override
712    protected void onResume() {
713        super.onResume();
714        Intent i = getIntent();
715        mTestList = i.getIntArrayExtra("tests");
716
717        mToggleIO = i.getBooleanExtra("enable io", false);
718        mToggleDVFS = i.getBooleanExtra("enable dvfs", false);
719        mToggleLong = i.getBooleanExtra("enable long", false);
720        mTogglePause = i.getBooleanExtra("enable pause", false);
721        mToggleAnimate = i.getBooleanExtra("enable animate", false);
722        mToggleDisplay = i.getBooleanExtra("enable display", false);
723        mBitmapWidth = i.getIntExtra("resolution X", 0);
724        mBitmapHeight = i.getIntExtra("resolution Y", 0);
725        mDemoMode = i.getBooleanExtra("demo", false);
726
727        mTestResults = new float[mTestList.length];
728
729        startProcessor();
730    }
731
732    protected void onDestroy() {
733        super.onDestroy();
734    }
735
736
737    @Override
738    public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
739        mProcessor.setSurface(new Surface(surface));
740    }
741
742    @Override
743    public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
744        mProcessor.setSurface(new Surface(surface));
745    }
746
747    @Override
748    public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
749        mProcessor.setSurface(null);
750        return true;
751    }
752
753    @Override
754    public void onSurfaceTextureUpdated(SurfaceTexture surface) {
755    }
756}
757