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