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