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