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 
17 package androidx.renderscript;
18 
19 import android.util.Log;
20 import android.util.Pair;
21 import java.lang.reflect.Method;
22 import java.util.ArrayList;
23 import java.util.Collections;
24 import java.util.Comparator;
25 import java.util.HashMap;
26 import java.util.List;
27 import java.util.Map;
28 
29 /**
30  * A group of kernels that are executed
31  * together with one execution call as if they were a single kernel
32  * <p>
33  * In addition to kernels, a script group may contain invocable functions as well.
34  * A script group may take inputs and generate outputs, which are consumed and
35  * produced by its member kernels.
36  * Inside a script group, outputs from one kernel can be passed to another kernel as inputs.
37  * The API disallows cyclic dependencies among kernels in a script group,
38  * effectively making it a directed acyclic graph (DAG) of kernels.
39  * <p>
40  * Grouping kernels together allows for more efficient execution. For example,
41  * runtime and compiler optimization can be applied to reduce computation and
42  * communication overhead, and to make better use of the CPU and the GPU.
43  **/
44 public final class ScriptGroup extends BaseObj {
45     //FIXME: Change 23 to the codename when that is decided.
46     private static final int MIN_API_VERSION = 23;
47     private static final String TAG = "ScriptGroup";
48     IO mOutputs[];
49     IO mInputs[];
50     private boolean mUseIncSupp = false;
51     private ArrayList<Node> mNodes = new ArrayList<Node>();
52 
53     static class IO {
54         Script.KernelID mKID;
55         Allocation mAllocation;
56 
IO(Script.KernelID s)57         IO(Script.KernelID s) {
58             mKID = s;
59         }
60     }
61 
62     static class ConnectLine {
ConnectLine(Type t, Script.KernelID from, Script.KernelID to)63         ConnectLine(Type t, Script.KernelID from, Script.KernelID to) {
64             mFrom = from;
65             mToK = to;
66             mAllocationType = t;
67         }
68 
ConnectLine(Type t, Script.KernelID from, Script.FieldID to)69         ConnectLine(Type t, Script.KernelID from, Script.FieldID to) {
70             mFrom = from;
71             mToF = to;
72             mAllocationType = t;
73         }
74 
75         Script.FieldID mToF;
76         Script.KernelID mToK;
77         Script.KernelID mFrom;
78         Type mAllocationType;
79         Allocation mAllocation;
80     }
81 
82     static class Node {
83         Script mScript;
84         ArrayList<Script.KernelID> mKernels = new ArrayList<Script.KernelID>();
85         ArrayList<ConnectLine> mInputs = new ArrayList<ConnectLine>();
86         ArrayList<ConnectLine> mOutputs = new ArrayList<ConnectLine>();
87         int dagNumber;
88         boolean mSeen;
89         int mOrder;
90 
91         Node mNext;
92 
Node(Script s)93         Node(Script s) {
94             mScript = s;
95         }
96     }
97 
98     /**
99      * An opaque class for closures
100      * <p>
101      * A closure represents a function call to a kernel or invocable function,
102      * combined with arguments and values for global variables. A closure is
103      * created using the {@link Builder2#addKernel} or
104      * {@link Builder2#addInvoke}
105      * method.
106      */
107 
108     public static final class Closure extends BaseObj {
109         private Object[] mArgs;
110         private Allocation mReturnValue;
111         private Map<Script.FieldID, Object> mBindings;
112 
113         private Future mReturnFuture;
114         private Map<Script.FieldID, Future> mGlobalFuture;
115 
116         private FieldPacker mFP;
117 
118         private static final String TAG = "Closure";
119 
Closure(long id, RenderScript rs)120         Closure(long id, RenderScript rs) {
121             super(id, rs);
122         }
123 
Closure(RenderScript rs, Script.KernelID kernelID, Type returnType, Object[] args, Map<Script.FieldID, Object> globals)124         Closure(RenderScript rs, Script.KernelID kernelID, Type returnType,
125                        Object[] args, Map<Script.FieldID, Object> globals) {
126             super(0, rs);
127 
128             if (android.os.Build.VERSION.SDK_INT < MIN_API_VERSION && rs.isUseNative()) {
129                 throw new RSRuntimeException("ScriptGroup2 not supported in this API level");
130             }
131 
132             mArgs = args;
133             mReturnValue = Allocation.createTyped(rs, returnType);
134             mBindings = globals;
135             mGlobalFuture = new HashMap<Script.FieldID, Future>();
136 
137             int numValues = args.length + globals.size();
138 
139             long[] fieldIDs = new long[numValues];
140             long[] values = new long[numValues];
141             int[] sizes = new int[numValues];
142             long[] depClosures = new long[numValues];
143             long[] depFieldIDs = new long[numValues];
144 
145             int i;
146             for (i = 0; i < args.length; i++) {
147                 fieldIDs[i] = 0;
148                 retrieveValueAndDependenceInfo(rs, i, null, args[i],
149                                                values, sizes, depClosures, depFieldIDs);
150             }
151             for (Map.Entry<Script.FieldID, Object> entry : globals.entrySet()) {
152                 Object obj = entry.getValue();
153                 Script.FieldID fieldID = entry.getKey();
154                 fieldIDs[i] = fieldID.getID(rs);
155                 retrieveValueAndDependenceInfo(rs, i, fieldID, obj,
156                                                values, sizes, depClosures, depFieldIDs);
157                 i++;
158             }
159 
160             long id = rs.nClosureCreate(kernelID.getID(rs), mReturnValue.getID(rs),
161                                         fieldIDs, values, sizes, depClosures, depFieldIDs);
162 
163             setID(id);
164         }
165 
Closure(RenderScript rs, Script.InvokeID invokeID, Object[] args, Map<Script.FieldID, Object> globals)166         Closure(RenderScript rs, Script.InvokeID invokeID,
167                 Object[] args, Map<Script.FieldID, Object> globals) {
168             super(0, rs);
169 
170             if (android.os.Build.VERSION.SDK_INT < MIN_API_VERSION && rs.isUseNative()) {
171                 throw new RSRuntimeException("ScriptGroup2 not supported in this API level");
172             }
173 
174             mFP = FieldPacker.createFromArray(args);
175 
176             mArgs = args;
177             mBindings = globals;
178             mGlobalFuture = new HashMap<Script.FieldID, Future>();
179 
180             int numValues = globals.size();
181 
182             long[] fieldIDs = new long[numValues];
183             long[] values = new long[numValues];
184             int[] sizes = new int[numValues];
185             long[] depClosures = new long[numValues];
186             long[] depFieldIDs = new long[numValues];
187 
188             int i = 0;
189             for (Map.Entry<Script.FieldID, Object> entry : globals.entrySet()) {
190                 Object obj = entry.getValue();
191                 Script.FieldID fieldID = entry.getKey();
192                 fieldIDs[i] = fieldID.getID(rs);
193                 retrieveValueAndDependenceInfo(rs, i, fieldID, obj, values,
194                                                sizes, depClosures, depFieldIDs);
195                 i++;
196             }
197 
198             long id = rs.nInvokeClosureCreate(invokeID.getID(rs), mFP.getData(), fieldIDs,
199                                               values, sizes);
200 
201             setID(id);
202         }
203 
retrieveValueAndDependenceInfo(RenderScript rs, int index, Script.FieldID fid, Object obj, long[] values, int[] sizes, long[] depClosures, long[] depFieldIDs)204         private void retrieveValueAndDependenceInfo(RenderScript rs,
205                                                     int index, Script.FieldID fid, Object obj,
206                                                     long[] values, int[] sizes,
207                                                     long[] depClosures,
208                                                     long[] depFieldIDs) {
209 
210             if (obj instanceof Future) {
211                 Future f = (Future)obj;
212                 obj = f.getValue();
213                 depClosures[index] = f.getClosure().getID(rs);
214                 Script.FieldID fieldID = f.getFieldID();
215                 depFieldIDs[index] = fieldID != null ? fieldID.getID(rs) : 0;
216             } else {
217                 depClosures[index] = 0;
218                 depFieldIDs[index] = 0;
219             }
220 
221             if (obj instanceof Input) {
222                 Input unbound = (Input)obj;
223                 if (index < mArgs.length) {
224                     unbound.addReference(this, index);
225                 } else {
226                     unbound.addReference(this, fid);
227                 }
228                 values[index] = 0;
229                 sizes[index] = 0;
230             } else {
231                 ValueAndSize vs = new ValueAndSize(rs, obj);
232                 values[index] = vs.value;
233                 sizes[index] = vs.size;
234             }
235         }
236 
237         /**
238          * Returns the future for the return value
239          *
240          * @return a future
241          */
242 
getReturn()243         public Future getReturn() {
244             if (mReturnFuture == null) {
245                 mReturnFuture = new Future(this, null, mReturnValue);
246             }
247 
248             return mReturnFuture;
249         }
250 
251         /**
252          * Returns the future for a global variable
253          *
254          * @param field the field ID for the global variable
255          * @return a future
256          */
257 
getGlobal(Script.FieldID field)258         public Future getGlobal(Script.FieldID field) {
259             Future f = mGlobalFuture.get(field);
260 
261             if (f == null) {
262                 // If the field is not bound to this closure, this will return a future
263                 // without an associated value (reference). So this is not working for
264                 // cross-module (cross-script) linking in this case where a field not
265                 // explicitly bound.
266                 Object obj = mBindings.get(field);
267                 if (obj instanceof Future) {
268                     obj = ((Future)obj).getValue();
269                 }
270                 f = new Future(this, field, obj);
271                 mGlobalFuture.put(field, f);
272             }
273 
274             return f;
275         }
276 
setArg(int index, Object obj)277         void setArg(int index, Object obj) {
278             if (obj instanceof Future) {
279                 obj = ((Future)obj).getValue();
280             }
281             mArgs[index] = obj;
282             ValueAndSize vs = new ValueAndSize(mRS, obj);
283             mRS.nClosureSetArg(getID(mRS), index, vs.value, vs.size);
284         }
285 
setGlobal(Script.FieldID fieldID, Object obj)286         void setGlobal(Script.FieldID fieldID, Object obj) {
287             if (obj instanceof Future) {
288                 obj = ((Future)obj).getValue();
289             }
290             mBindings.put(fieldID, obj);
291             ValueAndSize vs = new ValueAndSize(mRS, obj);
292             mRS.nClosureSetGlobal(getID(mRS), fieldID.getID(mRS), vs.value, vs.size);
293         }
294 
295         private static final class ValueAndSize {
ValueAndSize(RenderScript rs, Object obj)296             public ValueAndSize(RenderScript rs, Object obj) {
297                 if (obj instanceof Allocation) {
298                     value = ((Allocation)obj).getID(rs);
299                     size = -1;
300                 } else if (obj instanceof Boolean) {
301                     value = ((Boolean)obj).booleanValue() ? 1 : 0;
302                     size = 4;
303                 } else if (obj instanceof Integer) {
304                     value = ((Integer)obj).longValue();
305                     size = 4;
306                 } else if (obj instanceof Long) {
307                     value = ((Long)obj).longValue();
308                     size = 8;
309                 } else if (obj instanceof Float) {
310                     value = Float.floatToRawIntBits(((Float)obj).floatValue());
311                     size = 4;
312                 } else if (obj instanceof Double) {
313                     value = Double.doubleToRawLongBits(((Double)obj).doubleValue());
314                     size = 8;
315                 }
316             }
317             public long value;
318             public int size;
319         }
320     }
321 
322     /**
323      * An opaque class for futures
324      * <p>
325      * A future represents an output of a closure, either the return value of
326      * the function, or the value of a global variable written by the function.
327      * A future is created by calling the {@link Closure#getReturn}  or
328      * {@link Closure#getGlobal} method.
329      */
330 
331     public static final class Future {
332         Closure mClosure;
333         Script.FieldID mFieldID;
334         Object mValue;
335 
Future(Closure closure, Script.FieldID fieldID, Object value)336         Future(Closure closure, Script.FieldID fieldID, Object value) {
337             mClosure = closure;
338             mFieldID = fieldID;
339             mValue = value;
340         }
341 
getClosure()342         Closure getClosure() { return mClosure; }
getFieldID()343         Script.FieldID getFieldID() { return mFieldID; }
getValue()344         Object getValue() { return mValue; }
345     }
346 
347     /**
348      * An opaque class for unbound values (used for script group inputs)
349      * <p>
350      * Created by calling the {@link Builder2#addInput} method. The value
351      * is assigned in {@link ScriptGroup#execute(Object...)} method as
352      * one of its arguments. Arguments to the execute method should be in
353      * the same order as intputs are added using the addInput method.
354      */
355 
356     public static final class Input {
357         // Either mFieldID or mArgIndex should be set but not both.
358         List<Pair<Closure, Script.FieldID>> mFieldID;
359         // -1 means unset. Legal values are 0 .. n-1, where n is the number of
360         // arguments for the referencing closure.
361         List<Pair<Closure, Integer>> mArgIndex;
362         Object mValue;
363 
Input()364         Input() {
365             mFieldID = new ArrayList<Pair<Closure, Script.FieldID>>();
366             mArgIndex = new ArrayList<Pair<Closure, Integer>>();
367         }
368 
addReference(Closure closure, int index)369         void addReference(Closure closure, int index) {
370             mArgIndex.add(Pair.create(closure, Integer.valueOf(index)));
371         }
372 
addReference(Closure closure, Script.FieldID fieldID)373         void addReference(Closure closure, Script.FieldID fieldID) {
374             mFieldID.add(Pair.create(closure, fieldID));
375         }
376 
set(Object value)377         void set(Object value) {
378             mValue = value;
379             for (Pair<Closure, Integer> p : mArgIndex) {
380                 Closure closure = p.first;
381                 int index = p.second.intValue();
382                 closure.setArg(index, value);
383             }
384             for (Pair<Closure, Script.FieldID> p : mFieldID) {
385                 Closure closure = p.first;
386                 Script.FieldID fieldID = p.second;
387                 closure.setGlobal(fieldID, value);
388             }
389         }
390 
get()391         Object get() { return mValue; }
392     }
393 
394     private String mName;
395     private List<Closure> mClosures;
396     private List<Input> mInputs2;
397     private Future[] mOutputs2;
398 
ScriptGroup(long id, RenderScript rs)399     ScriptGroup(long id, RenderScript rs) {
400         super(id, rs);
401     }
402 
ScriptGroup(RenderScript rs, String name, List<Closure> closures, List<Input> inputs, Future[] outputs)403     ScriptGroup(RenderScript rs, String name, List<Closure> closures,
404                 List<Input> inputs, Future[] outputs) {
405         super(0, rs);
406 
407         if (android.os.Build.VERSION.SDK_INT < MIN_API_VERSION && rs.isUseNative()) {
408             throw new RSRuntimeException("ScriptGroup2 not supported in this API level");
409         }
410         mName = name;
411         mClosures = closures;
412         mInputs2 = inputs;
413         mOutputs2 = outputs;
414 
415         long[] closureIDs = new long[closures.size()];
416         for (int i = 0; i < closureIDs.length; i++) {
417             closureIDs[i] = closures.get(i).getID(rs);
418         }
419         String cachePath = rs.getApplicationContext().getCacheDir().toString();
420         long id = rs.nScriptGroup2Create(name, cachePath, closureIDs);
421         setID(id);
422     }
423 
424     /**
425      * Executes a script group
426      *
427      * @param inputs inputs to the script group
428      * @return outputs of the script group as an array of objects
429      */
430 
execute(Object... inputs)431     public Object[] execute(Object... inputs) {
432         if (inputs.length < mInputs2.size()) {
433             Log.e(TAG, this.toString() + " receives " + inputs.length + " inputs, " +
434                   "less than expected " + mInputs2.size());
435             return null;
436         }
437 
438         if (inputs.length > mInputs2.size()) {
439             Log.i(TAG, this.toString() + " receives " + inputs.length + " inputs, " +
440                   "more than expected " + mInputs2.size());
441         }
442 
443         for (int i = 0; i < mInputs2.size(); i++) {
444             Object obj = inputs[i];
445             if (obj instanceof Future || obj instanceof Input) {
446                 Log.e(TAG, this.toString() + ": input " + i +
447                       " is a future or unbound value");
448                 return null;
449             }
450             Input unbound = mInputs2.get(i);
451             unbound.set(obj);
452         }
453 
454         mRS.nScriptGroup2Execute(getID(mRS));
455 
456         Object[] outputObjs = new Object[mOutputs2.length];
457         int i = 0;
458         for (Future f : mOutputs2) {
459             Object output = f.getValue();
460             if (output instanceof Input) {
461                 output = ((Input)output).get();
462             }
463             outputObjs[i++] = output;
464         }
465         return outputObjs;
466     }
467 
468     /**
469      * Sets an input of the ScriptGroup. This specifies an
470      * Allocation to be used for kernels that require an input
471      * Allocation provided from outside of the ScriptGroup.
472      *
473      * @deprecated Set arguments to {@link #execute(Object...)} instead.
474      *
475      * @param s The ID of the kernel where the allocation should be
476      *          connected.
477      * @param a The allocation to connect.
478      */
479     @Deprecated
setInput(Script.KernelID s, Allocation a)480     public void setInput(Script.KernelID s, Allocation a) {
481         for (int ct=0; ct < mInputs.length; ct++) {
482             if (mInputs[ct].mKID == s) {
483                 mInputs[ct].mAllocation = a;
484                 if (!mUseIncSupp) {
485                     mRS.nScriptGroupSetInput(getID(mRS), s.getID(mRS), mRS.safeID(a));
486                 }
487                 return;
488             }
489         }
490         throw new RSIllegalArgumentException("Script not found");
491     }
492 
493     /**
494      * Sets an output of the ScriptGroup. This specifies an
495      * Allocation to be used for the kernels that require an output
496      * Allocation visible after the ScriptGroup is executed.
497      *
498      * @deprecated Use return value of {@link #execute(Object...)} instead.
499      *
500      * @param s The ID of the kernel where the allocation should be
501      *          connected.
502      * @param a The allocation to connect.
503      */
504     @Deprecated
setOutput(Script.KernelID s, Allocation a)505     public void setOutput(Script.KernelID s, Allocation a) {
506         for (int ct=0; ct < mOutputs.length; ct++) {
507             if (mOutputs[ct].mKID == s) {
508                 mOutputs[ct].mAllocation = a;
509                 if (!mUseIncSupp) {
510                     mRS.nScriptGroupSetOutput(getID(mRS), s.getID(mRS), mRS.safeID(a));
511                 }
512                 return;
513             }
514         }
515         throw new RSIllegalArgumentException("Script not found");
516     }
517 
518     /**
519      * Execute the ScriptGroup.  This will run all the kernels in
520      * the ScriptGroup.  No internal connection results will be visible
521      * after execution of the ScriptGroup.
522      *
523      * If Incremental Support for intrinsics is needed, the execution
524      * will take the naive path: execute kernels one by one in the
525      * correct order.
526      *
527      * @deprecated Use {@link #execute} instead.
528      */
529     @Deprecated
execute()530     public void execute() {
531         if (!mUseIncSupp) {
532             mRS.nScriptGroupExecute(getID(mRS));
533         } else {
534             // setup the allocations.
535             for (int ct=0; ct < mNodes.size(); ct++) {
536                 Node n = mNodes.get(ct);
537                 for (int ct2=0; ct2 < n.mOutputs.size(); ct2++) {
538                     ConnectLine l = n.mOutputs.get(ct2);
539                     if (l.mAllocation !=null) {
540                         continue;
541                     }
542 
543                     //create allocation here
544                     Allocation alloc = Allocation.createTyped(mRS, l.mAllocationType,
545                                                               Allocation.MipmapControl.MIPMAP_NONE,
546                                                               Allocation.USAGE_SCRIPT);
547 
548                     l.mAllocation = alloc;
549                     for (int ct3=ct2+1; ct3 < n.mOutputs.size(); ct3++) {
550                         if (n.mOutputs.get(ct3).mFrom == l.mFrom) {
551                             n.mOutputs.get(ct3).mAllocation = alloc;
552                         }
553                     }
554                 }
555             }
556             for (Node node : mNodes) {
557                 for (Script.KernelID kernel : node.mKernels) {
558                     Allocation ain  = null;
559                     Allocation aout = null;
560 
561                     for (ConnectLine nodeInput : node.mInputs) {
562                         if (nodeInput.mToK == kernel) {
563                             ain = nodeInput.mAllocation;
564                         }
565                     }
566 
567                     for (IO sgInput : mInputs) {
568                         if (sgInput.mKID == kernel) {
569                             ain = sgInput.mAllocation;
570                         }
571                     }
572 
573                     for (ConnectLine nodeOutput : node.mOutputs) {
574                         if (nodeOutput.mFrom == kernel) {
575                             aout = nodeOutput.mAllocation;
576                         }
577                     }
578 
579                     for (IO sgOutput : mOutputs) {
580                         if (sgOutput.mKID == kernel) {
581                             aout = sgOutput.mAllocation;
582                         }
583                     }
584 
585                     kernel.mScript.forEach(kernel.mSlot, ain, aout, null);
586                 }
587             }
588         }
589     }
590 
591 
592     /**
593      * Helper class to build a ScriptGroup. A ScriptGroup is
594      * created in two steps.
595      * <p>
596      * First, all kernels to be used by the ScriptGroup should be added.
597      * <p>
598      * Second, add connections between kernels. There are two types
599      * of connections: kernel to kernel and kernel to field.
600      * Kernel to kernel allows a kernel's output to be passed to
601      * another kernel as input. Kernel to field allows the output of
602      * one kernel to be bound as a script global. Kernel to kernel is
603      * higher performance and should be used where possible.
604      * <p>
605      * A ScriptGroup must contain a single directed acyclic graph (DAG); it
606      * cannot contain cycles. Currently, all kernels used in a ScriptGroup
607      * must come from different Script objects. Additionally, all kernels
608      * in a ScriptGroup must have at least one input, output, or internal
609      * connection.
610      * <p>
611      * Once all connections are made, a call to {@link #create} will
612      * return the ScriptGroup object.
613      *
614      * @deprecated Use {@link Builder2} instead.
615      *
616      */
617     @Deprecated
618     public static final class Builder {
619         private RenderScript mRS;
620         private ArrayList<Node> mNodes = new ArrayList<Node>();
621         private ArrayList<ConnectLine> mLines = new ArrayList<ConnectLine>();
622         private int mKernelCount;
623         private boolean mUseIncSupp = false;
624 
625         /**
626          * Create a Builder for generating a ScriptGroup.
627          *
628          *
629          * @param rs The RenderScript context.
630          */
Builder(RenderScript rs)631         public Builder(RenderScript rs) {
632             mRS = rs;
633         }
634 
635         // do a DFS from original node, looking for original node
636         // any cycle that could be created must contain original node
validateCycle(Node target, Node original)637         private void validateCycle(Node target, Node original) {
638             for (int ct = 0; ct < target.mOutputs.size(); ct++) {
639                 final ConnectLine cl = target.mOutputs.get(ct);
640                 if (cl.mToK != null) {
641                     Node tn = findNode(cl.mToK.mScript);
642                     if (tn.equals(original)) {
643                         throw new RSInvalidStateException("Loops in group not allowed.");
644                     }
645                     validateCycle(tn, original);
646                 }
647                 if (cl.mToF != null) {
648                     Node tn = findNode(cl.mToF.mScript);
649                     if (tn.equals(original)) {
650                         throw new RSInvalidStateException("Loops in group not allowed.");
651                     }
652                     validateCycle(tn, original);
653                 }
654             }
655         }
656 
mergeDAGs(int valueUsed, int valueKilled)657         private void mergeDAGs(int valueUsed, int valueKilled) {
658             for (int ct=0; ct < mNodes.size(); ct++) {
659                 if (mNodes.get(ct).dagNumber == valueKilled)
660                     mNodes.get(ct).dagNumber = valueUsed;
661             }
662         }
663 
validateDAGRecurse(Node n, int dagNumber)664         private void validateDAGRecurse(Node n, int dagNumber) {
665             // combine DAGs if this node has been seen already
666             if (n.dagNumber != 0 && n.dagNumber != dagNumber) {
667                 mergeDAGs(n.dagNumber, dagNumber);
668                 return;
669             }
670 
671             n.dagNumber = dagNumber;
672             for (int ct=0; ct < n.mOutputs.size(); ct++) {
673                 final ConnectLine cl = n.mOutputs.get(ct);
674                 if (cl.mToK != null) {
675                     Node tn = findNode(cl.mToK.mScript);
676                     validateDAGRecurse(tn, dagNumber);
677                 }
678                 if (cl.mToF != null) {
679                     Node tn = findNode(cl.mToF.mScript);
680                     validateDAGRecurse(tn, dagNumber);
681                 }
682             }
683         }
684 
validateDAG()685         private void validateDAG() {
686             for (int ct=0; ct < mNodes.size(); ct++) {
687                 Node n = mNodes.get(ct);
688                 if (n.mInputs.size() == 0) {
689                     if (n.mOutputs.size() == 0 && mNodes.size() > 1) {
690                         String msg = "Groups cannot contain unconnected scripts";
691                         throw new RSInvalidStateException(msg);
692                     }
693                     validateDAGRecurse(n, ct+1);
694                 }
695             }
696             int dagNumber = mNodes.get(0).dagNumber;
697             for (int ct=0; ct < mNodes.size(); ct++) {
698                 if (mNodes.get(ct).dagNumber != dagNumber) {
699                     throw new RSInvalidStateException("Multiple DAGs in group not allowed.");
700                 }
701             }
702         }
703 
findNode(Script s)704         private Node findNode(Script s) {
705             for (int ct=0; ct < mNodes.size(); ct++) {
706                 if (s == mNodes.get(ct).mScript) {
707                     return mNodes.get(ct);
708                 }
709             }
710             return null;
711         }
712 
findNode(Script.KernelID k)713         private Node findNode(Script.KernelID k) {
714             for (int ct=0; ct < mNodes.size(); ct++) {
715                 Node n = mNodes.get(ct);
716                 for (int ct2=0; ct2 < n.mKernels.size(); ct2++) {
717                     if (k == n.mKernels.get(ct2)) {
718                         return n;
719                     }
720                 }
721             }
722             return null;
723         }
724 
725         /**
726          * Adds a Kernel to the group.
727          *
728          *
729          * @param k The kernel to add.
730          *
731          * @return Builder Returns this.
732          */
addKernel(Script.KernelID k)733         public Builder addKernel(Script.KernelID k) {
734             if (mLines.size() != 0) {
735                 throw new RSInvalidStateException(
736                     "Kernels may not be added once connections exist.");
737             }
738             if (k.mScript.isIncSupp()) {
739                 mUseIncSupp = true;
740             }
741             //android.util.Log.v("RSR", "addKernel 1 k=" + k);
742             if (findNode(k) != null) {
743                 return this;
744             }
745             //android.util.Log.v("RSR", "addKernel 2 ");
746             mKernelCount++;
747             Node n = findNode(k.mScript);
748             if (n == null) {
749                 //android.util.Log.v("RSR", "addKernel 3 ");
750                 n = new Node(k.mScript);
751                 mNodes.add(n);
752             }
753             n.mKernels.add(k);
754             return this;
755         }
756 
757         /**
758          * Adds a connection to the group.
759          *
760          *
761          * @param t The type of the connection. This is used to
762          *          determine the kernel launch sizes on the source side
763          *          of this connection.
764          * @param from The source for the connection.
765          * @param to The destination of the connection.
766          *
767          * @return Builder Returns this
768          */
addConnection(Type t, Script.KernelID from, Script.FieldID to)769         public Builder addConnection(Type t, Script.KernelID from, Script.FieldID to) {
770             //android.util.Log.v("RSR", "addConnection " + t +", " + from + ", " + to);
771             Node nf = findNode(from);
772             if (nf == null) {
773                 throw new RSInvalidStateException("From script not found.");
774             }
775 
776             Node nt = findNode(to.mScript);
777             if (nt == null) {
778                 throw new RSInvalidStateException("To script not found.");
779             }
780 
781             ConnectLine cl = new ConnectLine(t, from, to);
782             mLines.add(new ConnectLine(t, from, to));
783 
784             nf.mOutputs.add(cl);
785             nt.mInputs.add(cl);
786 
787             validateCycle(nf, nf);
788             return this;
789         }
790 
791         /**
792          * Adds a connection to the group.
793          *
794          *
795          * @param t The type of the connection. This is used to
796          *          determine the kernel launch sizes for both sides of
797          *          this connection.
798          * @param from The source for the connection.
799          * @param to The destination of the connection.
800          *
801          * @return Builder Returns this
802          */
addConnection(Type t, Script.KernelID from, Script.KernelID to)803         public Builder addConnection(Type t, Script.KernelID from, Script.KernelID to) {
804             //android.util.Log.v("RSR", "addConnection " + t +", " + from + ", " + to);
805             Node nf = findNode(from);
806             if (nf == null) {
807                 throw new RSInvalidStateException("From script not found.");
808             }
809 
810             Node nt = findNode(to);
811             if (nt == null) {
812                 throw new RSInvalidStateException("To script not found.");
813             }
814 
815             ConnectLine cl = new ConnectLine(t, from, to);
816             mLines.add(new ConnectLine(t, from, to));
817 
818             nf.mOutputs.add(cl);
819             nt.mInputs.add(cl);
820 
821             validateCycle(nf, nf);
822             return this;
823         }
824 
825         /**
826          * Calculate the order of each node.
827          *
828          *
829          * @return Success or Fail
830          */
calcOrderRecurse(Node node0, int depth)831         private boolean calcOrderRecurse(Node node0, int depth) {
832             node0.mSeen = true;
833             if (node0.mOrder < depth) {
834                 node0.mOrder = depth;
835             }
836             boolean ret = true;
837 
838             for (ConnectLine link : node0.mOutputs) {
839                 Node node1 = null;
840                 if (link.mToF != null) {
841                     node1 = findNode(link.mToF.mScript);
842                 } else {
843                     node1 = findNode(link.mToK.mScript);
844                 }
845                 if (node1.mSeen) {
846                     return false;
847                 }
848                 ret &= calcOrderRecurse(node1, node0.mOrder + 1);
849             }
850 
851             return ret;
852         }
853 
calcOrder()854         private boolean calcOrder() {
855             boolean ret = true;
856             for (Node n0 : mNodes) {
857                 if (n0.mInputs.size() == 0) {
858                     for (Node n1 : mNodes) {
859                         n1.mSeen = false;
860                     }
861                     ret &= calcOrderRecurse(n0, 1);
862                 }
863             }
864 
865             Collections.sort(mNodes, new Comparator<Node>() {
866                 public int compare(Node n1, Node n2) {
867                     return n1.mOrder - n2.mOrder;
868                 }
869             });
870 
871             return ret;
872         }
873 
874         /**
875          * Creates the Script group.
876          *
877          *
878          * @return ScriptGroup The new ScriptGroup
879          */
create()880         public ScriptGroup create() {
881 
882             if (mNodes.size() == 0) {
883                 throw new RSInvalidStateException("Empty script groups are not allowed");
884             }
885 
886             // reset DAG numbers in case we're building a second group
887             for (int ct=0; ct < mNodes.size(); ct++) {
888                 mNodes.get(ct).dagNumber = 0;
889             }
890             validateDAG();
891 
892             ArrayList<IO> inputs = new ArrayList<IO>();
893             ArrayList<IO> outputs = new ArrayList<IO>();
894 
895             long[] kernels = new long[mKernelCount];
896             int idx = 0;
897             for (int ct=0; ct < mNodes.size(); ct++) {
898                 Node n = mNodes.get(ct);
899                 for (int ct2=0; ct2 < n.mKernels.size(); ct2++) {
900                     final Script.KernelID kid = n.mKernels.get(ct2);
901                     kernels[idx++] = kid.getID(mRS);
902 
903                     boolean hasInput = false;
904                     boolean hasOutput = false;
905                     for (int ct3=0; ct3 < n.mInputs.size(); ct3++) {
906                         if (n.mInputs.get(ct3).mToK == kid) {
907                             hasInput = true;
908                         }
909                     }
910                     for (int ct3=0; ct3 < n.mOutputs.size(); ct3++) {
911                         if (n.mOutputs.get(ct3).mFrom == kid) {
912                             hasOutput = true;
913                         }
914                     }
915                     if (!hasInput) {
916                         inputs.add(new IO(kid));
917                     }
918                     if (!hasOutput) {
919                         outputs.add(new IO(kid));
920                     }
921                 }
922             }
923             if (idx != mKernelCount) {
924                 throw new RSRuntimeException("Count mismatch, should not happen.");
925             }
926 
927             long id = 0;
928             if (!mUseIncSupp) {
929                 long[] src = new long[mLines.size()];
930                 long[] dstk = new long[mLines.size()];
931                 long[] dstf = new long[mLines.size()];
932                 long[] types = new long[mLines.size()];
933 
934                 for (int ct=0; ct < mLines.size(); ct++) {
935                     ConnectLine cl = mLines.get(ct);
936                     src[ct] = cl.mFrom.getID(mRS);
937                     if (cl.mToK != null) {
938                         dstk[ct] = cl.mToK.getID(mRS);
939                     }
940                     if (cl.mToF != null) {
941                         dstf[ct] = cl.mToF.getID(mRS);
942                     }
943                     types[ct] = cl.mAllocationType.getID(mRS);
944                 }
945                 id = mRS.nScriptGroupCreate(kernels, src, dstk, dstf, types);
946                 if (id == 0) {
947                     throw new RSRuntimeException("Object creation error, should not happen.");
948                 }
949             } else {
950                 //Calculate the order of the DAG so that script can run one after another.
951                 calcOrder();
952             }
953 
954             ScriptGroup sg = new ScriptGroup(id, mRS);
955             sg.mOutputs = new IO[outputs.size()];
956             for (int ct=0; ct < outputs.size(); ct++) {
957                 sg.mOutputs[ct] = outputs.get(ct);
958             }
959 
960             sg.mInputs = new IO[inputs.size()];
961             for (int ct=0; ct < inputs.size(); ct++) {
962                 sg.mInputs[ct] = inputs.get(ct);
963             }
964             sg.mNodes = mNodes;
965             sg.mUseIncSupp = mUseIncSupp;
966             return sg;
967         }
968 
969     }
970 
971     /**
972      * Represents a binding of a value to a global variable in a
973      * kernel or invocable function. Used in closure creation.
974      */
975 
976     public static final class Binding {
977         private final Script.FieldID mField;
978         private final Object mValue;
979 
980         /**
981          * Returns a Binding object that binds value to field
982          *
983          * @param field the Script.FieldID of the global variable
984          * @param value the value
985          */
986 
Binding(Script.FieldID field, Object value)987         public Binding(Script.FieldID field, Object value) {
988             mField = field;
989             mValue = value;
990         }
991 
992         /**
993          * Returns the field ID
994          */
995 
getField()996         public Script.FieldID getField() { return mField; }
997 
998         /**
999          * Returns the value
1000          */
1001 
getValue()1002         public Object getValue() { return mValue; }
1003     }
1004 
1005     /**
1006      * The builder class for creating script groups
1007      * <p>
1008      * A script group is created using closures (see class {@link Closure}).
1009      * A closure is a function call to a kernel or
1010      * invocable function. Each function argument or global variable accessed inside
1011      * the function is bound to 1) a known value, 2) a script group input
1012      * (see class {@link Input}), or 3) a
1013      * future (see class {@link Future}).
1014      * A future is the output of a closure, either the return value of the
1015      * function or a global variable written by that function.
1016      * <p>
1017      * Closures are created using the {@link #addKernel} or {@link #addInvoke}
1018      * methods.
1019      * When a closure is created, futures from previously created closures
1020      * can be used as its inputs.
1021      * External script group inputs can be used as inputs to individual closures as well.
1022      * An external script group input is created using the {@link #addInput} method.
1023      * A script group is created by a call to the {@link #create} method, which
1024      * accepts an array of futures as the outputs for the script group.
1025      * <p>
1026      * Closures in a script group can be evaluated in any order as long as the
1027      * following conditions are met:
1028      * 1) a closure must be evaluated before any other closures that take its
1029      * futures as inputs;
1030      * 2) all closures added before an invoke closure must be evaluated
1031      * before it;
1032      * and 3) all closures added after an invoke closure must be evaluated after
1033      * it.
1034      * As a special case, the order that the closures are added is a legal
1035      * evaluation order. However, other evaluation orders are possible, including
1036      * concurrently evaluating independent closures.
1037      */
1038 
1039     public static final class Builder2 {
1040         RenderScript mRS;
1041         List<Closure> mClosures;
1042         List<Input> mInputs;
1043         private static final String TAG = "ScriptGroup.Builder2";
1044 
1045         /**
1046          * Returns a Builder object
1047          *
1048          * @param rs the RenderScript context
1049          */
Builder2(RenderScript rs)1050         public Builder2(RenderScript rs) {
1051             mRS = rs;
1052             mClosures = new ArrayList<Closure>();
1053             mInputs = new ArrayList<Input>();
1054         }
1055 
1056         /**
1057          * Adds a closure for a kernel
1058          *
1059          * @param k Kernel ID for the kernel function
1060          * @param returnType Allocation type for the return value
1061          * @param args arguments to the kernel function
1062          * @param globalBindings bindings for global variables
1063          * @return a closure
1064          */
1065 
addKernelInternal(Script.KernelID k, Type returnType, Object[] args, Map<Script.FieldID, Object> globalBindings)1066         private Closure addKernelInternal(Script.KernelID k, Type returnType, Object[] args,
1067                                           Map<Script.FieldID, Object> globalBindings) {
1068             Closure c = new Closure(mRS, k, returnType, args, globalBindings);
1069             mClosures.add(c);
1070             return c;
1071         }
1072 
1073         /**
1074          * Adds a closure for an invocable function
1075          *
1076          * @param invoke Invoke ID for the invocable function
1077          * @param args arguments to the invocable function
1078          * @param globalBindings bindings for global variables
1079          * @return a closure
1080          */
1081 
addInvokeInternal(Script.InvokeID invoke, Object[] args, Map<Script.FieldID, Object> globalBindings)1082         private Closure addInvokeInternal(Script.InvokeID invoke, Object[] args,
1083                                           Map<Script.FieldID, Object> globalBindings) {
1084             Closure c = new Closure(mRS, invoke, args, globalBindings);
1085             mClosures.add(c);
1086             return c;
1087         }
1088 
1089         /**
1090          * Adds a script group input
1091          *
1092          * @return a script group input, which can be used as an argument or a value to
1093          *     a global variable for creating closures
1094          */
1095 
addInput()1096         public Input addInput() {
1097             Input unbound = new Input();
1098             mInputs.add(unbound);
1099             return unbound;
1100         }
1101 
1102         /**
1103          * Adds a closure for a kernel
1104          *
1105          * @param k Kernel ID for the kernel function
1106          * @param argsAndBindings arguments followed by bindings for global variables
1107          * @return a closure
1108          */
1109 
addKernel(Script.KernelID k, Type returnType, Object... argsAndBindings)1110         public Closure addKernel(Script.KernelID k, Type returnType, Object... argsAndBindings) {
1111             ArrayList<Object> args = new ArrayList<Object>();
1112             Map<Script.FieldID, Object> bindingMap = new HashMap<Script.FieldID, Object>();
1113             if (!seperateArgsAndBindings(argsAndBindings, args, bindingMap)) {
1114                 return null;
1115             }
1116             return addKernelInternal(k, returnType, args.toArray(), bindingMap);
1117         }
1118 
1119         /**
1120          * Adds a closure for an invocable function
1121          *
1122          * @param invoke Invoke ID for the invocable function
1123          * @param argsAndBindings arguments followed by bindings for global variables
1124          * @return a closure
1125          */
1126 
addInvoke(Script.InvokeID invoke, Object... argsAndBindings)1127         public Closure addInvoke(Script.InvokeID invoke, Object... argsAndBindings) {
1128             ArrayList<Object> args = new ArrayList<Object>();
1129             Map<Script.FieldID, Object> bindingMap = new HashMap<Script.FieldID, Object>();
1130             if (!seperateArgsAndBindings(argsAndBindings, args, bindingMap)) {
1131                 return null;
1132             }
1133             return addInvokeInternal(invoke, args.toArray(), bindingMap);
1134         }
1135 
1136         /**
1137          * Creates a script group
1138          *
1139          * @param name name for the script group. Legal names can only contain letters, digits,
1140          *        '-', or '_'. The name can be no longer than 100 characters.
1141          * @param outputs futures intended as outputs of the script group
1142          * @return a script group
1143          */
1144 
create(String name, Future... outputs)1145         public ScriptGroup create(String name, Future... outputs) {
1146             if (name == null || name.isEmpty() || name.length() > 100 ||
1147                 !name.equals(name.replaceAll("[^a-zA-Z0-9-]", "_"))) {
1148                 throw new RSIllegalArgumentException("invalid script group name");
1149             }
1150             ScriptGroup ret = new ScriptGroup(mRS, name, mClosures, mInputs, outputs);
1151             return ret;
1152         }
1153 
seperateArgsAndBindings(Object[] argsAndBindings, ArrayList<Object> args, Map<Script.FieldID, Object> bindingMap)1154         private boolean seperateArgsAndBindings(Object[] argsAndBindings,
1155                                                 ArrayList<Object> args,
1156                                                 Map<Script.FieldID, Object> bindingMap) {
1157             int i;
1158             for (i = 0; i < argsAndBindings.length; i++) {
1159                 if (argsAndBindings[i] instanceof Binding) {
1160                     break;
1161                 }
1162                 args.add(argsAndBindings[i]);
1163             }
1164 
1165             for (; i < argsAndBindings.length; i++) {
1166                 if (!(argsAndBindings[i] instanceof Binding)) {
1167                     return false;
1168                 }
1169                 Binding b = (Binding)argsAndBindings[i];
1170                 bindingMap.put(b.getField(), b.getValue());
1171             }
1172 
1173             return true;
1174         }
1175 
1176     }
1177 
1178 }
1179 
1180 
1181