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