• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2014 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 
17 package android.hardware.camera2.cts.rs;
18 
19 import static android.hardware.camera2.cts.helpers.Preconditions.*;
20 import static junit.framework.Assert.*;
21 
22 import android.graphics.ImageFormat;
23 import android.graphics.PixelFormat;
24 import android.util.Size;
25 import android.hardware.camera2.cts.helpers.MaybeNull;
26 import android.hardware.camera2.cts.helpers.UncheckedCloseable;
27 import android.hardware.camera2.cts.rs.Script.ParameterMap;
28 import android.renderscript.Allocation;
29 import android.util.Log;
30 import android.view.Surface;
31 
32 import java.lang.reflect.Constructor;
33 import java.lang.reflect.InvocationTargetException;
34 import java.util.ArrayList;
35 import java.util.List;
36 import com.android.ex.camera2.exceptions.TimeoutRuntimeException;
37 
38 /**
39  * An abstraction to simplify chaining together the execution of multiple RenderScript
40  * {@link android.renderscript.Script scripts} and managing their {@link Allocation allocations}.
41  *
42  * <p>Create a new script graph by using {@link #create}, configure the input with
43  * {@link Builder#configureInput}, then configure one or more scripts with
44  * {@link Builder#configureScript} or {@link Builder#chainScript}. Finally, freeze the graph
45  * with {@link Builder#buildGraph}.</p>
46  *
47  * <p>Once a script graph has been built, all underlying scripts and allocations are instantiated.
48  * Each script may be executed with {@link #execute}. Scripts are executed in the order that they
49  * were configured, with each previous script's output used as the input for the next script.
50  * </p>
51  *
52  * <p>In case the input {@link Allocation} is actually backed by a {@link Surface}, convenience
53  * methods ({@link #advanceInputWaiting} and {@link #advanceInputAndDrop} are provided to
54  * automatically update the {@link Allocation allocation} with the latest buffer available.</p>
55  *
56  * <p>All resources are managed by the {@link ScriptGraph} and {@link #close closing} the graph
57  * will release all underlying resources. See {@link #close} for more details.</p>
58  */
59 public class ScriptGraph implements UncheckedCloseable {
60 
61     private static final String TAG = "ScriptGraph";
62     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
63 
64     private static final int INPUT_SCRIPT_LOCATION = 0;
65     private final int OUTPUT_SCRIPT_LOCATION; // calculated in constructor
66 
67     private final AllocationCache mCache = RenderScriptSingleton.getCache();
68 
69     private final Size mSize;
70     private final int mFormat;
71     private final int mUsage;
72     private final List<Script<?>> mScripts;
73 
74     private final BlockingInputAllocation mInputBlocker;
75     private final Allocation mOutputAllocation;
76     private boolean mClosed = false;
77 
78     /**
79      * Create a new {@link Builder} that will be used to configure the graph's inputs
80      * and scripts (and parameters).
81      *
82      * <p>Once a graph has been fully built, the configuration is immutable.</p>
83      *
84      * @return a {@link Builder} that will be used to configure the graph settings
85      */
create()86     public static Builder create() {
87         return new Builder();
88     }
89 
90     /**
91      * Wait until another buffer is produced into the input {@link Surface}, then
92      * update the backing input {@link Allocation} with the latest buffer with
93      * {@link Allocation#ioReceive ioReceive}.
94      *
95      * @throws IllegalArgumentException
96      *            if the graph wasn't configured with
97      *            {@link Builder#configureInputWithSurface configureInputWithSurface}
98      * @throws TimeoutRuntimeException
99      *            if waiting for the buffer times out
100      */
advanceInputWaiting()101     public void advanceInputWaiting() {
102         checkNotClosed();
103         if (!isInputFromSurface()) {
104             throw new IllegalArgumentException("Graph was not configured with USAGE_IO_INPUT");
105         }
106 
107         mInputBlocker.waitForBufferAndReceive();
108     }
109 
110     /**
111      * Update the backing input {@link Allocation} with the latest buffer with
112      * {@link Allocation#ioReceive ioReceive} repeatedly until no more buffers are pending.
113      *
114      * <p>Does not wait for new buffers to become available if none are currently available
115      * (i.e. {@code false} is returned immediately).</p>
116      *
117      * @return true if any buffers were pending
118      *
119      * @throws IllegalArgumentException
120      *            if the graph wasn't configured with
121      *            {@link Builder#configureInputWithSurface configureInputWithSurface}
122      * @throws TimeoutRuntimeException
123      *            if waiting for the buffer times out
124      */
advanceInputAndDrop()125     public boolean advanceInputAndDrop() {
126         checkNotClosed();
127         if (!isInputFromSurface()) {
128             throw new IllegalArgumentException("Graph was not configured with USAGE_IO_INPUT");
129         }
130 
131         return mInputBlocker.receiveLatestAvailableBuffers();
132     }
133 
134     /**
135      * Execute each script in the graph, with each next script's input using the
136      * previous script's output.
137      *
138      * <p>Scripts are executed in the same order that they were configured by the {@link Builder}.
139      * </p>
140      *
141      * @throws IllegalStateException if the graph was already {@link #close closed}
142      */
execute()143     public void execute() {
144         checkNotClosed();
145 
146         // TODO: Can we use android.renderscript.ScriptGroup here to make it faster?
147 
148         int i = 0;
149         for (Script<?> script : mScripts) {
150             script.execute();
151             i++;
152         }
153 
154         if (VERBOSE) Log.v(TAG, "execute - invoked " + i + " scripts");
155     }
156 
157     /**
158      * Copies the data from the last script's output {@link Allocation} into a byte array.
159      *
160      * <p>The output allocation must be of an 8 bit integer
161      * {@link android.renderscript.Element Element} type.</p>
162      *
163      * @return A byte[] copy.
164      *
165      * @throws IllegalStateException if the graph was already {@link #close closed}
166      */
getOutputData()167     public byte[] getOutputData() {
168         checkNotClosed();
169 
170         Allocation outputAllocation = getOutputAllocation();
171 
172         byte[] destination = new byte[outputAllocation.getBytesSize()];
173         outputAllocation.copyTo(destination);
174 
175         return destination;
176     }
177 
178     /**
179      * Copies the data from the first script's input {@link Allocation} into a byte array.
180      *
181      * <p>The input allocation must be of an 8 bit integer
182      * {@link android.renderscript.Element Element} type.</p>
183      *
184      * @return A byte[] copy.
185      *
186      * @throws IllegalStateException if the graph was already {@link #close closed}
187      */
getInputData()188     public byte[] getInputData() {
189         checkNotClosed();
190 
191         Allocation inputAllocation = getInputAllocation();
192 
193         byte[] destination = new byte[inputAllocation.getBytesSize()];
194         inputAllocation.copyTo(destination);
195 
196         return destination;
197     }
198 
199     /**
200      * Builds a {@link ScriptGraph} by configuring input size/format/usage,
201      * the script classes in the graph, and the parameters passed to the scripts.
202      *
203      * @see ScriptGraph#create
204      */
205     public static class Builder {
206 
207         private Size mSize;
208         private int mFormat;
209         private int mUsage;
210 
211         private final List<ScriptBuilder<? extends Script<?>>> mChainedScriptBuilders =
212                 new ArrayList<ScriptBuilder<? extends Script<?>>>();
213 
214         /**
215          * Configure the {@link Allocation} that will be used as the input to the first
216          * script, using the default usage.
217          *
218          * <p>Short hand for calling {@link #configureInput(int, int, int, int)} with a
219          * {@code 0} usage.</p>
220          *
221          * @param width Width in pixels
222          * @param height Height in pixels
223          * @param format Format from {@link ImageFormat} or {@link PixelFormat}
224          *
225          * @return The current builder ({@code this}). Use for chaining method calls.
226          */
configureInput(int width, int height, int format)227         public Builder configureInput(int width, int height, int format) {
228             return configureInput(new Size(width, height), format, /*usage*/0);
229         }
230 
231         /**
232          * Configure the {@link Allocation} that will be used as the input to the first
233          * script.
234          *
235          * <p>The {@code usage} is always ORd together with {@link Allocation#USAGE_SCRIPT}.</p>
236          *
237          * @param width Width in pixels
238          * @param height Height in pixels
239          * @param format Format from {@link ImageFormat} or {@link PixelFormat}
240          * @param usage Usage flags such as {@link Allocation#USAGE_IO_INPUT}
241          *
242          * @return The current builder ({@code this}). Use for chaining method calls.
243          */
configureInput(int width, int height, int format, int usage)244         public Builder configureInput(int width, int height, int format, int usage) {
245             return configureInput(new Size(width, height), format, usage);
246         }
247 
248         /**
249          * Configure the {@link Allocation} that will be used as the input to the first
250          * script, using the default usage.
251          *
252          * <p>Short hand for calling {@link #configureInput(Size, int, int)} with a
253          * {@code 0} usage.</p>
254          *
255          * @param size Size (width, height)
256          * @param format Format from {@link ImageFormat} or {@link PixelFormat}
257          *
258          * @return The current builder ({@code this}). Use for chaining method calls.
259          *
260          * @throws NullPointerException if size was {@code null}
261          */
configureInput(Size size, int format)262         public Builder configureInput(Size size, int format) {
263             return configureInput(size, format, /*usage*/0);
264         }
265 
266         /**
267          * Configure the {@link Allocation} that will use a {@link Surface} to produce input into
268          * the first script.
269          *
270          * <p>Short hand for calling {@link #configureInput(Size, int, int)} with the
271          * {@link Allocation#USAGE_IO_INPUT} usage.</p>
272          *
273          * <p>The {@code usage} is always ORd together with {@link Allocation#USAGE_SCRIPT}.</p>
274          *
275          * @param size Size (width, height)
276          * @param format Format from {@link ImageFormat} or {@link PixelFormat}
277          *
278          * @return The current builder ({@code this}). Use for chaining method calls.
279          *
280          * @throws NullPointerException if size was {@code null}
281          */
configureInputWithSurface(Size size, int format)282         public Builder configureInputWithSurface(Size size, int format) {
283             return configureInput(size, format, Allocation.USAGE_IO_INPUT);
284         }
285 
286         /**
287          * Configure the {@link Allocation} that will be used as the input to the first
288          * script.
289          *
290          * <p>The {@code usage} is always ORd together with {@link Allocation#USAGE_SCRIPT}.</p>
291          *
292          * @param size Size (width, height)
293          * @param format Format from {@link ImageFormat} or {@link PixelFormat}
294          * @param usage Usage flags such as {@link Allocation#USAGE_IO_INPUT}
295          *
296          * @return The current builder ({@code this}). Use for chaining method calls.
297          *
298          * @throws NullPointerException if size was {@code null}
299          */
configureInput(Size size, int format, int usage)300         public Builder configureInput(Size size, int format, int usage) {
301             checkNotNull("size", size);
302 
303             mSize = size;
304             mFormat = format;
305             mUsage = usage | Allocation.USAGE_SCRIPT;
306 
307             return this;
308         }
309 
310         /**
311          * Build a {@link Script} by setting parameters it might require for execution.
312          *
313          * <p>Refer to the documentation for {@code T} to see if there are any
314          * {@link Script.ScriptParameter parameters} in it.
315          * </p>
316          *
317          * @param <T> Concrete type subclassing the {@link Script} class.
318          */
319         public class ScriptBuilder<T extends Script<?>> {
320 
321             private final Class<T> mScriptClass;
322 
ScriptBuilder(Class<T> scriptClass)323             private ScriptBuilder(Class<T> scriptClass) {
324                 mScriptClass = scriptClass;
325             }
326 
327             private final ParameterMap<T> mParameterMap = new ParameterMap<T>();
328 
329             /**
330              * Set a script parameter to the specified value.
331              *
332              * @param parameter The {@link Script.ScriptParameter parameter key} in {@code T}
333              * @param value A value of type {@code K} that the script expects.
334              * @param <K> The type of the parameter {@code value}.
335              *
336              * @return The current builder ({@code this}). Use to chain method calls.
337              *
338              * @throws NullPointerException if parameter was {@code null}
339              * @throws NullPointerException if value was {@code null}
340              * @throws IllegalStateException if the parameter was already {@link #set}
341              */
set(Script.ScriptParameter<T, K> parameter, K value)342             public <K> ScriptBuilder<T> set(Script.ScriptParameter<T, K> parameter, K value) {
343                 checkNotNull("parameter", parameter);
344                 checkNotNull("value", value);
345                 checkState("Parameter has already been set", !mParameterMap.contains(parameter));
346 
347                 mParameterMap.set(parameter, value);
348 
349                 return this;
350             }
351 
getParameterMap()352             ParameterMap<T> getParameterMap() {
353                 return mParameterMap;
354             }
355 
getScriptClass()356             Class<T> getScriptClass() {
357                 return mScriptClass;
358             }
359 
360             /**
361              * Build the script and freeze the parameter list to what was {@link #set}.
362              *
363              * @return
364              *            The {@link ScriptGraph#Builder} that was used to configure
365              *            {@link this} script.</p>
366              */
buildScript()367             public Builder buildScript() {
368                 mChainedScriptBuilders.add(this);
369 
370                 return Builder.this;
371             }
372         }
373 
374         /**
375          * Configure the script with no parameters.
376          *
377          * <p>Short hand for invoking {@link #configureScript} immediately followed by
378          * {@link ScriptBuilder#buildScript()}.
379          *
380          * @param scriptClass A concrete class that subclasses {@link Script}
381          * @return The current builder ({@code this}). Use to chain method calls.
382          *
383          * @throws NullPointerException if {@code scriptClass} was {@code null}
384          */
chainScript(Class<T> scriptClass)385         public <T extends Script<?>> Builder chainScript(Class<T> scriptClass) {
386             checkNotNull("scriptClass", scriptClass);
387 
388             return (new ScriptBuilder<T>(scriptClass)).buildScript();
389         }
390 
391         /**
392          * Configure the script with parameters.
393          *
394          * <p>Only useful when the {@code scriptClass} has one or more
395          * {@link Script.ScriptParameter script parameters} defined.</p>
396          *
397          * @param scriptClass A concrete class that subclasses {@link Script}
398          * @return A script configuration {@link ScriptBuilder builder}. Use to chain method calls.
399          *
400          * @throws NullPointerException if {@code scriptClass} was {@code null}
401          */
configureScript(Class<T> scriptClass)402         public <T extends Script<?>> ScriptBuilder<T> configureScript(Class<T> scriptClass) {
403             checkNotNull("scriptClass", scriptClass);
404 
405             return new ScriptBuilder<T>(scriptClass);
406         }
407 
408         /**
409          * Finish configuring the graph and freeze the settings, instantiating all
410          * the {@link Script scripts} and {@link Allocation allocations}.
411          *
412          * @return A constructed {@link ScriptGraph}.
413          */
buildGraph()414         public ScriptGraph buildGraph() {
415             return new ScriptGraph(this);
416         }
417 
Builder()418         private Builder() {}
419     }
420 
ScriptGraph(Builder builder)421     private ScriptGraph(Builder builder) {
422         mSize = builder.mSize;
423         mFormat = builder.mFormat;
424         mUsage = builder.mUsage;
425         List<Builder.ScriptBuilder<? extends Script<?>>> chainedScriptBuilders =
426                 builder.mChainedScriptBuilders;
427         mScripts = new ArrayList<Script<?>>(/*capacity*/chainedScriptBuilders.size());
428         OUTPUT_SCRIPT_LOCATION = chainedScriptBuilders.size() - 1;
429 
430         if (mSize == null) {
431             throw new IllegalArgumentException("Inputs were not configured");
432         }
433 
434         if (chainedScriptBuilders.isEmpty()) {
435             throw new IllegalArgumentException("At least one script should be chained");
436         }
437 
438         /*
439          * The first input is special since it could be USAGE_IO_INPUT.
440          */
441         AllocationInfo inputInfo = AllocationInfo.newInstance(mSize, mFormat, mUsage);
442         Allocation inputAllocation;
443 
444         // Create an Allocation with a Surface if the input to the graph requires it
445         if (isInputFromSurface()) {
446             mInputBlocker = inputInfo.createBlockingInputAllocation();
447             inputAllocation = mInputBlocker.getAllocation();
448         } else {
449             mInputBlocker = null;
450             inputAllocation = inputInfo.createAllocation();
451         }
452 
453         if (VERBOSE) Log.v(TAG, "ScriptGraph() - Instantiating all script classes");
454 
455         // Create all scripts.
456         for (Builder.ScriptBuilder<? extends Script<?>> scriptBuilder: chainedScriptBuilders) {
457 
458             @SuppressWarnings("unchecked")
459             Class<Script<?>> scriptClass = (Class<Script<?>>) scriptBuilder.getScriptClass();
460 
461             @SuppressWarnings("unchecked")
462             ParameterMap<Script<?>> parameters = (ParameterMap<Script<?>>)
463                     scriptBuilder.getParameterMap();
464 
465             Script<?> script = instantiateScript(scriptClass, inputInfo, parameters);
466             mScripts.add(script);
467 
468             // The next script's input info is the current script's output info
469             inputInfo = script.getOutputInfo();
470         }
471 
472         if (VERBOSE) Log.v(TAG, "ScriptGraph() - Creating all inputs");
473 
474         // Create and wire up all inputs.
475         int i = 0;
476         Script<?> inputScript = mScripts.get(INPUT_SCRIPT_LOCATION);
477         do {
478             if (VERBOSE) {
479                 Log.v(TAG, "ScriptGraph() - Setting input for script " + inputScript.getName());
480             }
481 
482             inputScript.setInput(inputAllocation);
483 
484             i++;
485 
486             if (i >= mScripts.size()) {
487                 break;
488             }
489 
490             // Use the graph input for the first loop iteration
491             inputScript = mScripts.get(i);
492             inputInfo = inputScript.getInputInfo();
493             inputAllocation = inputInfo.createAllocation();
494         } while (true);
495 
496         if (VERBOSE) Log.v(TAG, "ScriptGraph() - Creating all outputs");
497 
498         // Create and wire up all outputs.
499         Allocation lastOutput = null;
500         for (i = 0; i < mScripts.size(); ++i) {
501             Script<?> script = mScripts.get(i);
502             Script<?> nextScript = (i + 1 < mScripts.size()) ? mScripts.get(i + 1) : null;
503 
504             // Each script's output uses the next script's input.
505             // -- Since the last script has no next script, we own its output allocation.
506             lastOutput = (nextScript != null) ? nextScript.getInput()
507                                               : script.getOutputInfo().createAllocation();
508 
509             if (VERBOSE) {
510                 Log.v(TAG, "ScriptGraph() - Setting output for script " + script.getName());
511             }
512 
513             script.setOutput(lastOutput);
514         }
515         mOutputAllocation = checkNotNull("lastOutput", lastOutput);
516 
517         // Done. Safe to execute the graph now.
518 
519         if (VERBOSE) Log.v(TAG, "ScriptGraph() - Graph has been built");
520     }
521 
522     /**
523      * Construct the script by instantiating it via reflection.
524      *
525      * <p>The {@link Script scriptClass} should have a {@code Script(AllocationInfo inputInfo)}
526      * constructor if it expects an empty parameter map.</p>
527      *
528      * <p>If it expects a non-empty parameter map, it should have a
529      * {@code Script(AllocationInfo inputInfo, ParameterMap<T> parameterMap)} constructor.</p>
530      */
instantiateScript( Class<T> scriptClass, AllocationInfo inputInfo, ParameterMap<T> parameterMap)531     private static <T extends Script<?>> T instantiateScript(
532             Class<T> scriptClass, AllocationInfo inputInfo, ParameterMap<T> parameterMap) {
533 
534         Constructor<T> ctor;
535         try {
536             // TODO: Would be better if we looked at the script class to see if it expects params
537             if (parameterMap.isEmpty()) {
538                 // Script(AllocationInfo inputInfo)
539                 ctor = scriptClass.getConstructor(AllocationInfo.class);
540             } else {
541                 // Script(AllocationInfo inputInfo, ParameterMap<T> parameterMap)
542                 ctor = scriptClass.getConstructor(AllocationInfo.class, ParameterMap.class);
543             }
544         } catch (NoSuchMethodException e) {
545             throw new UnsupportedOperationException(
546                     "Script class " + scriptClass + " must have a matching constructor", e);
547         }
548 
549         try {
550             if (parameterMap.isEmpty()) {
551                 return ctor.newInstance(inputInfo);
552             } else {
553                 return ctor.newInstance(inputInfo, parameterMap);
554             }
555         } catch (InstantiationException e) {
556             throw new UnsupportedOperationException(e);
557         } catch (IllegalAccessException e) {
558             throw new UnsupportedOperationException(e);
559         } catch (IllegalArgumentException e) {
560             throw new UnsupportedOperationException(e);
561         } catch (InvocationTargetException e) {
562             throw new UnsupportedOperationException(e);
563         }
564     }
565 
isInputFromSurface()566     private boolean isInputFromSurface() {
567         return (mUsage & Allocation.USAGE_IO_INPUT) != 0;
568     }
569 
570     /**
571      * Get the input {@link Allocation} that is used by the first script as the input.
572      *
573      * @return An {@link Allocation} (never {@code null}).
574      *
575      * @throws IllegalStateException if the graph was already {@link #close closed}
576      */
getInputAllocation()577     public Allocation getInputAllocation() {
578         checkNotClosed();
579 
580         return mScripts.get(INPUT_SCRIPT_LOCATION).getInput();
581     }
582 
583     /**
584      * Get the output {@link Allocation} that is used by the last script as the output.
585      *
586      * @return An {@link Allocation} (never {@code null}).
587      *
588      * @throws IllegalStateException if the graph was already {@link #close closed}
589      */
getOutputAllocation()590     public Allocation getOutputAllocation() {
591         checkNotClosed();
592         Allocation output = mScripts.get(OUTPUT_SCRIPT_LOCATION).getOutput();
593 
594         assertEquals("Graph's output should match last script's output", mOutputAllocation, output);
595 
596         return output;
597     }
598 
599     /**
600      * Get the {@link Surface} that can be used produce buffers into the
601      * {@link #getInputAllocation input allocation}.
602      *
603      * @throws IllegalStateException
604      *            if input wasn't configured with {@link Allocation#USAGE_IO_INPUT} {@code usage}.
605      * @throws IllegalStateException
606      *            if the graph was already {@link #close closed}
607      *
608      * @return A {@link Surface} (never {@code null}).
609      */
getInputSurface()610     public Surface getInputSurface() {
611         checkNotClosed();
612         checkState("This graph was not configured with IO_USAGE_INPUT", isInputFromSurface());
613 
614         return getInputAllocation().getSurface();
615     }
616 
checkNotClosed()617     private void checkNotClosed() {
618         checkState("ScriptGraph has been closed", !mClosed);
619     }
620 
621     /**
622      * Releases all underlying resources associated with this {@link ScriptGraph}.
623      *
624      * <p>In particular, all underlying {@link Script scripts} and all
625      * {@link Allocation allocations} are also closed.</p>
626      *
627      * <p>All further calls to any other public methods (other than {@link #close}) will throw
628      * an {@link IllegalStateException}.</p>
629      *
630      * <p>This method is idempotent; calling it more than once will
631      * have no further effect.</p>
632      */
633     @Override
close()634     public synchronized void close() {
635         if (mClosed) return;
636 
637         for (Script<?> script : mScripts) {
638             script.close();
639         }
640         mScripts.clear();
641 
642         MaybeNull.close(mInputBlocker);
643         mCache.returnToCache(mOutputAllocation);
644 
645         mClosed = true;
646     }
647 
648     @Override
finalize()649     protected void finalize() throws Throwable {
650         try {
651             close();
652         } finally {
653             super.finalize();
654         }
655     }
656 }
657