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