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