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 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 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 Closure(long id, RenderScript rs) { 121 super(id, rs); 122 } 123 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 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 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 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 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 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 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 { 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)obj).longValue(); 311 size = 4; 312 } else if (obj instanceof Double) { 313 value = ((Double)obj).longValue(); 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 336 Future(Closure closure, Script.FieldID fieldID, Object value) { 337 mClosure = closure; 338 mFieldID = fieldID; 339 mValue = value; 340 } 341 342 Closure getClosure() { return mClosure; } 343 Script.FieldID getFieldID() { return mFieldID; } 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 364 Input() { 365 mFieldID = new ArrayList<Pair<Closure, Script.FieldID>>(); 366 mArgIndex = new ArrayList<Pair<Closure, Integer>>(); 367 } 368 369 void addReference(Closure closure, int index) { 370 mArgIndex.add(Pair.create(closure, Integer.valueOf(index))); 371 } 372 373 void addReference(Closure closure, Script.FieldID fieldID) { 374 mFieldID.add(Pair.create(closure, fieldID)); 375 } 376 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 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 399 ScriptGroup(long id, RenderScript rs) { 400 super(id, rs); 401 } 402 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 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 public void setInput(Script.KernelID s, Allocation a) { 480 for (int ct=0; ct < mInputs.length; ct++) { 481 if (mInputs[ct].mKID == s) { 482 mInputs[ct].mAllocation = a; 483 if (!mUseIncSupp) { 484 mRS.nScriptGroupSetInput(getID(mRS), s.getID(mRS), mRS.safeID(a)); 485 } 486 return; 487 } 488 } 489 throw new RSIllegalArgumentException("Script not found"); 490 } 491 492 /** 493 * Sets an output of the ScriptGroup. This specifies an 494 * Allocation to be used for the kernels that require an output 495 * Allocation visible after the ScriptGroup is executed. 496 * 497 * @deprecated Use return value of {@link #execute(Object...)} instead. 498 * 499 * @param s The ID of the kernel where the allocation should be 500 * connected. 501 * @param a The allocation to connect. 502 */ 503 public void setOutput(Script.KernelID s, Allocation a) { 504 for (int ct=0; ct < mOutputs.length; ct++) { 505 if (mOutputs[ct].mKID == s) { 506 mOutputs[ct].mAllocation = a; 507 if (!mUseIncSupp) { 508 mRS.nScriptGroupSetOutput(getID(mRS), s.getID(mRS), mRS.safeID(a)); 509 } 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 * If Incremental Support for intrinsics is needed, the execution 522 * will take the naive path: execute kernels one by one in the 523 * correct order. 524 * 525 * @deprecated Use {@link #execute} instead. 526 */ 527 public void execute() { 528 if (!mUseIncSupp) { 529 mRS.nScriptGroupExecute(getID(mRS)); 530 } else { 531 // setup the allocations. 532 for (int ct=0; ct < mNodes.size(); ct++) { 533 Node n = mNodes.get(ct); 534 for (int ct2=0; ct2 < n.mOutputs.size(); ct2++) { 535 ConnectLine l = n.mOutputs.get(ct2); 536 if (l.mAllocation !=null) { 537 continue; 538 } 539 540 //create allocation here 541 Allocation alloc = Allocation.createTyped(mRS, l.mAllocationType, 542 Allocation.MipmapControl.MIPMAP_NONE, 543 Allocation.USAGE_SCRIPT); 544 545 l.mAllocation = alloc; 546 for (int ct3=ct2+1; ct3 < n.mOutputs.size(); ct3++) { 547 if (n.mOutputs.get(ct3).mFrom == l.mFrom) { 548 n.mOutputs.get(ct3).mAllocation = alloc; 549 } 550 } 551 } 552 } 553 for (Node node : mNodes) { 554 for (Script.KernelID kernel : node.mKernels) { 555 Allocation ain = null; 556 Allocation aout = null; 557 558 for (ConnectLine nodeInput : node.mInputs) { 559 if (nodeInput.mToK == kernel) { 560 ain = nodeInput.mAllocation; 561 } 562 } 563 564 for (IO sgInput : mInputs) { 565 if (sgInput.mKID == kernel) { 566 ain = sgInput.mAllocation; 567 } 568 } 569 570 for (ConnectLine nodeOutput : node.mOutputs) { 571 if (nodeOutput.mFrom == kernel) { 572 aout = nodeOutput.mAllocation; 573 } 574 } 575 576 for (IO sgOutput : mOutputs) { 577 if (sgOutput.mKID == kernel) { 578 aout = sgOutput.mAllocation; 579 } 580 } 581 582 kernel.mScript.forEach(kernel.mSlot, ain, aout, null); 583 } 584 } 585 } 586 } 587 588 589 /** 590 * Helper class to build a ScriptGroup. A ScriptGroup is 591 * created in two steps. 592 * <p> 593 * First, all kernels to be used by the ScriptGroup should be added. 594 * <p> 595 * Second, add connections between kernels. There are two types 596 * of connections: kernel to kernel and kernel to field. 597 * Kernel to kernel allows a kernel's output to be passed to 598 * another kernel as input. Kernel to field allows the output of 599 * one kernel to be bound as a script global. Kernel to kernel is 600 * higher performance and should be used where possible. 601 * <p> 602 * A ScriptGroup must contain a single directed acyclic graph (DAG); it 603 * cannot contain cycles. Currently, all kernels used in a ScriptGroup 604 * must come from different Script objects. Additionally, all kernels 605 * in a ScriptGroup must have at least one input, output, or internal 606 * connection. 607 * <p> 608 * Once all connections are made, a call to {@link #create} will 609 * return the ScriptGroup object. 610 * 611 * @deprecated Use {@link Builder2} instead. 612 * 613 */ 614 public static final class Builder { 615 private RenderScript mRS; 616 private ArrayList<Node> mNodes = new ArrayList<Node>(); 617 private ArrayList<ConnectLine> mLines = new ArrayList<ConnectLine>(); 618 private int mKernelCount; 619 private boolean mUseIncSupp = false; 620 621 /** 622 * Create a Builder for generating a ScriptGroup. 623 * 624 * 625 * @param rs The RenderScript context. 626 */ 627 public Builder(RenderScript rs) { 628 mRS = rs; 629 } 630 631 // do a DFS from original node, looking for original node 632 // any cycle that could be created must contain original node 633 private void validateCycle(Node target, Node original) { 634 for (int ct = 0; ct < target.mOutputs.size(); ct++) { 635 final ConnectLine cl = target.mOutputs.get(ct); 636 if (cl.mToK != null) { 637 Node tn = findNode(cl.mToK.mScript); 638 if (tn.equals(original)) { 639 throw new RSInvalidStateException("Loops in group not allowed."); 640 } 641 validateCycle(tn, original); 642 } 643 if (cl.mToF != null) { 644 Node tn = findNode(cl.mToF.mScript); 645 if (tn.equals(original)) { 646 throw new RSInvalidStateException("Loops in group not allowed."); 647 } 648 validateCycle(tn, original); 649 } 650 } 651 } 652 653 private void mergeDAGs(int valueUsed, int valueKilled) { 654 for (int ct=0; ct < mNodes.size(); ct++) { 655 if (mNodes.get(ct).dagNumber == valueKilled) 656 mNodes.get(ct).dagNumber = valueUsed; 657 } 658 } 659 660 private void validateDAGRecurse(Node n, int dagNumber) { 661 // combine DAGs if this node has been seen already 662 if (n.dagNumber != 0 && n.dagNumber != dagNumber) { 663 mergeDAGs(n.dagNumber, dagNumber); 664 return; 665 } 666 667 n.dagNumber = dagNumber; 668 for (int ct=0; ct < n.mOutputs.size(); ct++) { 669 final ConnectLine cl = n.mOutputs.get(ct); 670 if (cl.mToK != null) { 671 Node tn = findNode(cl.mToK.mScript); 672 validateDAGRecurse(tn, dagNumber); 673 } 674 if (cl.mToF != null) { 675 Node tn = findNode(cl.mToF.mScript); 676 validateDAGRecurse(tn, dagNumber); 677 } 678 } 679 } 680 681 private void validateDAG() { 682 for (int ct=0; ct < mNodes.size(); ct++) { 683 Node n = mNodes.get(ct); 684 if (n.mInputs.size() == 0) { 685 if (n.mOutputs.size() == 0 && mNodes.size() > 1) { 686 String msg = "Groups cannot contain unconnected scripts"; 687 throw new RSInvalidStateException(msg); 688 } 689 validateDAGRecurse(n, ct+1); 690 } 691 } 692 int dagNumber = mNodes.get(0).dagNumber; 693 for (int ct=0; ct < mNodes.size(); ct++) { 694 if (mNodes.get(ct).dagNumber != dagNumber) { 695 throw new RSInvalidStateException("Multiple DAGs in group not allowed."); 696 } 697 } 698 } 699 700 private Node findNode(Script s) { 701 for (int ct=0; ct < mNodes.size(); ct++) { 702 if (s == mNodes.get(ct).mScript) { 703 return mNodes.get(ct); 704 } 705 } 706 return null; 707 } 708 709 private Node findNode(Script.KernelID k) { 710 for (int ct=0; ct < mNodes.size(); ct++) { 711 Node n = mNodes.get(ct); 712 for (int ct2=0; ct2 < n.mKernels.size(); ct2++) { 713 if (k == n.mKernels.get(ct2)) { 714 return n; 715 } 716 } 717 } 718 return null; 719 } 720 721 /** 722 * Adds a Kernel to the group. 723 * 724 * 725 * @param k The kernel to add. 726 * 727 * @return Builder Returns this. 728 */ 729 public Builder addKernel(Script.KernelID k) { 730 if (mLines.size() != 0) { 731 throw new RSInvalidStateException( 732 "Kernels may not be added once connections exist."); 733 } 734 if (k.mScript.isIncSupp()) { 735 mUseIncSupp = true; 736 } 737 //android.util.Log.v("RSR", "addKernel 1 k=" + k); 738 if (findNode(k) != null) { 739 return this; 740 } 741 //android.util.Log.v("RSR", "addKernel 2 "); 742 mKernelCount++; 743 Node n = findNode(k.mScript); 744 if (n == null) { 745 //android.util.Log.v("RSR", "addKernel 3 "); 746 n = new Node(k.mScript); 747 mNodes.add(n); 748 } 749 n.mKernels.add(k); 750 return this; 751 } 752 753 /** 754 * Adds a connection to the group. 755 * 756 * 757 * @param t The type of the connection. This is used to 758 * determine the kernel launch sizes on the source side 759 * of this connection. 760 * @param from The source for the connection. 761 * @param to The destination of the connection. 762 * 763 * @return Builder Returns this 764 */ 765 public Builder addConnection(Type t, Script.KernelID from, Script.FieldID to) { 766 //android.util.Log.v("RSR", "addConnection " + t +", " + from + ", " + to); 767 Node nf = findNode(from); 768 if (nf == null) { 769 throw new RSInvalidStateException("From script not found."); 770 } 771 772 Node nt = findNode(to.mScript); 773 if (nt == null) { 774 throw new RSInvalidStateException("To script not found."); 775 } 776 777 ConnectLine cl = new ConnectLine(t, from, to); 778 mLines.add(new ConnectLine(t, from, to)); 779 780 nf.mOutputs.add(cl); 781 nt.mInputs.add(cl); 782 783 validateCycle(nf, nf); 784 return this; 785 } 786 787 /** 788 * Adds a connection to the group. 789 * 790 * 791 * @param t The type of the connection. This is used to 792 * determine the kernel launch sizes for both sides of 793 * this connection. 794 * @param from The source for the connection. 795 * @param to The destination of the connection. 796 * 797 * @return Builder Returns this 798 */ 799 public Builder addConnection(Type t, Script.KernelID from, Script.KernelID to) { 800 //android.util.Log.v("RSR", "addConnection " + t +", " + from + ", " + to); 801 Node nf = findNode(from); 802 if (nf == null) { 803 throw new RSInvalidStateException("From script not found."); 804 } 805 806 Node nt = findNode(to); 807 if (nt == null) { 808 throw new RSInvalidStateException("To script not found."); 809 } 810 811 ConnectLine cl = new ConnectLine(t, from, to); 812 mLines.add(new ConnectLine(t, from, to)); 813 814 nf.mOutputs.add(cl); 815 nt.mInputs.add(cl); 816 817 validateCycle(nf, nf); 818 return this; 819 } 820 821 /** 822 * Calculate the order of each node. 823 * 824 * 825 * @return Success or Fail 826 */ 827 private boolean calcOrderRecurse(Node node0, int depth) { 828 node0.mSeen = true; 829 if (node0.mOrder < depth) { 830 node0.mOrder = depth; 831 } 832 boolean ret = true; 833 834 for (ConnectLine link : node0.mOutputs) { 835 Node node1 = null; 836 if (link.mToF != null) { 837 node1 = findNode(link.mToF.mScript); 838 } else { 839 node1 = findNode(link.mToK.mScript); 840 } 841 if (node1.mSeen) { 842 return false; 843 } 844 ret &= calcOrderRecurse(node1, node0.mOrder + 1); 845 } 846 847 return ret; 848 } 849 850 private boolean calcOrder() { 851 boolean ret = true; 852 for (Node n0 : mNodes) { 853 if (n0.mInputs.size() == 0) { 854 for (Node n1 : mNodes) { 855 n1.mSeen = false; 856 } 857 ret &= calcOrderRecurse(n0, 1); 858 } 859 } 860 861 Collections.sort(mNodes, new Comparator<Node>() { 862 public int compare(Node n1, Node n2) { 863 return n1.mOrder - n2.mOrder; 864 } 865 }); 866 867 return ret; 868 } 869 870 /** 871 * Creates the Script group. 872 * 873 * 874 * @return ScriptGroup The new ScriptGroup 875 */ 876 public ScriptGroup create() { 877 878 if (mNodes.size() == 0) { 879 throw new RSInvalidStateException("Empty script groups are not allowed"); 880 } 881 882 // reset DAG numbers in case we're building a second group 883 for (int ct=0; ct < mNodes.size(); ct++) { 884 mNodes.get(ct).dagNumber = 0; 885 } 886 validateDAG(); 887 888 ArrayList<IO> inputs = new ArrayList<IO>(); 889 ArrayList<IO> outputs = new ArrayList<IO>(); 890 891 long[] kernels = new long[mKernelCount]; 892 int idx = 0; 893 for (int ct=0; ct < mNodes.size(); ct++) { 894 Node n = mNodes.get(ct); 895 for (int ct2=0; ct2 < n.mKernels.size(); ct2++) { 896 final Script.KernelID kid = n.mKernels.get(ct2); 897 kernels[idx++] = kid.getID(mRS); 898 899 boolean hasInput = false; 900 boolean hasOutput = false; 901 for (int ct3=0; ct3 < n.mInputs.size(); ct3++) { 902 if (n.mInputs.get(ct3).mToK == kid) { 903 hasInput = true; 904 } 905 } 906 for (int ct3=0; ct3 < n.mOutputs.size(); ct3++) { 907 if (n.mOutputs.get(ct3).mFrom == kid) { 908 hasOutput = true; 909 } 910 } 911 if (!hasInput) { 912 inputs.add(new IO(kid)); 913 } 914 if (!hasOutput) { 915 outputs.add(new IO(kid)); 916 } 917 } 918 } 919 if (idx != mKernelCount) { 920 throw new RSRuntimeException("Count mismatch, should not happen."); 921 } 922 923 long id = 0; 924 if (!mUseIncSupp) { 925 long[] src = new long[mLines.size()]; 926 long[] dstk = new long[mLines.size()]; 927 long[] dstf = new long[mLines.size()]; 928 long[] types = new long[mLines.size()]; 929 930 for (int ct=0; ct < mLines.size(); ct++) { 931 ConnectLine cl = mLines.get(ct); 932 src[ct] = cl.mFrom.getID(mRS); 933 if (cl.mToK != null) { 934 dstk[ct] = cl.mToK.getID(mRS); 935 } 936 if (cl.mToF != null) { 937 dstf[ct] = cl.mToF.getID(mRS); 938 } 939 types[ct] = cl.mAllocationType.getID(mRS); 940 } 941 id = mRS.nScriptGroupCreate(kernels, src, dstk, dstf, types); 942 if (id == 0) { 943 throw new RSRuntimeException("Object creation error, should not happen."); 944 } 945 } else { 946 //Calculate the order of the DAG so that script can run one after another. 947 calcOrder(); 948 } 949 950 ScriptGroup sg = new ScriptGroup(id, mRS); 951 sg.mOutputs = new IO[outputs.size()]; 952 for (int ct=0; ct < outputs.size(); ct++) { 953 sg.mOutputs[ct] = outputs.get(ct); 954 } 955 956 sg.mInputs = new IO[inputs.size()]; 957 for (int ct=0; ct < inputs.size(); ct++) { 958 sg.mInputs[ct] = inputs.get(ct); 959 } 960 sg.mNodes = mNodes; 961 sg.mUseIncSupp = mUseIncSupp; 962 return sg; 963 } 964 965 } 966 967 /** 968 * Represents a binding of a value to a global variable in a 969 * kernel or invocable function. Used in closure creation. 970 */ 971 972 public static final class Binding { 973 private final Script.FieldID mField; 974 private final Object mValue; 975 976 /** 977 * Returns a Binding object that binds value to field 978 * 979 * @param field the Script.FieldID of the global variable 980 * @param value the value 981 */ 982 983 public Binding(Script.FieldID field, Object value) { 984 mField = field; 985 mValue = value; 986 } 987 988 /** 989 * Returns the field ID 990 */ 991 992 public Script.FieldID getField() { return mField; } 993 994 /** 995 * Returns the value 996 */ 997 998 public Object getValue() { return mValue; } 999 } 1000 1001 /** 1002 * The builder class for creating script groups 1003 * <p> 1004 * A script group is created using closures (see class {@link Closure}). 1005 * A closure is a function call to a kernel or 1006 * invocable function. Each function argument or global variable accessed inside 1007 * the function is bound to 1) a known value, 2) a script group input 1008 * (see class {@link Input}), or 3) a 1009 * future (see class {@link Future}). 1010 * A future is the output of a closure, either the return value of the 1011 * function or a global variable written by that function. 1012 * <p> 1013 * Closures are created using the {@link #addKernel} or {@link #addInvoke} 1014 * methods. 1015 * When a closure is created, futures from previously created closures 1016 * can be used as its inputs. 1017 * External script group inputs can be used as inputs to individual closures as well. 1018 * An external script group input is created using the {@link #addInput} method. 1019 * A script group is created by a call to the {@link #create} method, which 1020 * accepts an array of futures as the outputs for the script group. 1021 * <p> 1022 * Closures in a script group can be evaluated in any order as long as the 1023 * following conditions are met: 1024 * 1) a closure must be evaluated before any other closures that take its 1025 * futures as inputs; 1026 * 2) all closures added before an invoke closure must be evaluated 1027 * before it; 1028 * and 3) all closures added after an invoke closure must be evaluated after 1029 * it. 1030 * As a special case, the order that the closures are added is a legal 1031 * evaluation order. However, other evaluation orders are possible, including 1032 * concurrently evaluating independent closures. 1033 */ 1034 1035 public static final class Builder2 { 1036 RenderScript mRS; 1037 List<Closure> mClosures; 1038 List<Input> mInputs; 1039 private static final String TAG = "ScriptGroup.Builder2"; 1040 1041 /** 1042 * Returns a Builder object 1043 * 1044 * @param rs the RenderScript context 1045 */ 1046 public Builder2(RenderScript rs) { 1047 mRS = rs; 1048 mClosures = new ArrayList<Closure>(); 1049 mInputs = new ArrayList<Input>(); 1050 } 1051 1052 /** 1053 * Adds a closure for a kernel 1054 * 1055 * @param k Kernel ID for the kernel function 1056 * @param returnType Allocation type for the return value 1057 * @param args arguments to the kernel function 1058 * @param globalBindings bindings for global variables 1059 * @return a closure 1060 */ 1061 1062 private Closure addKernelInternal(Script.KernelID k, Type returnType, Object[] args, 1063 Map<Script.FieldID, Object> globalBindings) { 1064 Closure c = new Closure(mRS, k, returnType, args, globalBindings); 1065 mClosures.add(c); 1066 return c; 1067 } 1068 1069 /** 1070 * Adds a closure for an invocable function 1071 * 1072 * @param invoke Invoke ID for the invocable function 1073 * @param args arguments to the invocable function 1074 * @param globalBindings bindings for global variables 1075 * @return a closure 1076 */ 1077 1078 private Closure addInvokeInternal(Script.InvokeID invoke, Object[] args, 1079 Map<Script.FieldID, Object> globalBindings) { 1080 Closure c = new Closure(mRS, invoke, args, globalBindings); 1081 mClosures.add(c); 1082 return c; 1083 } 1084 1085 /** 1086 * Adds a script group input 1087 * 1088 * @return a script group input, which can be used as an argument or a value to 1089 * a global variable for creating closures 1090 */ 1091 1092 public Input addInput() { 1093 Input unbound = new Input(); 1094 mInputs.add(unbound); 1095 return unbound; 1096 } 1097 1098 /** 1099 * Adds a closure for a kernel 1100 * 1101 * @param k Kernel ID for the kernel function 1102 * @param argsAndBindings arguments followed by bindings for global variables 1103 * @return a closure 1104 */ 1105 1106 public Closure addKernel(Script.KernelID k, Type returnType, Object... argsAndBindings) { 1107 ArrayList<Object> args = new ArrayList<Object>(); 1108 Map<Script.FieldID, Object> bindingMap = new HashMap<Script.FieldID, Object>(); 1109 if (!seperateArgsAndBindings(argsAndBindings, args, bindingMap)) { 1110 return null; 1111 } 1112 return addKernelInternal(k, returnType, args.toArray(), bindingMap); 1113 } 1114 1115 /** 1116 * Adds a closure for an invocable function 1117 * 1118 * @param invoke Invoke ID for the invocable function 1119 * @param argsAndBindings arguments followed by bindings for global variables 1120 * @return a closure 1121 */ 1122 1123 public Closure addInvoke(Script.InvokeID invoke, Object... argsAndBindings) { 1124 ArrayList<Object> args = new ArrayList<Object>(); 1125 Map<Script.FieldID, Object> bindingMap = new HashMap<Script.FieldID, Object>(); 1126 if (!seperateArgsAndBindings(argsAndBindings, args, bindingMap)) { 1127 return null; 1128 } 1129 return addInvokeInternal(invoke, args.toArray(), bindingMap); 1130 } 1131 1132 /** 1133 * Creates a script group 1134 * 1135 * @param name name for the script group. Legal names can only contain letters, digits, 1136 * '-', or '_'. The name can be no longer than 100 characters. 1137 * @param outputs futures intended as outputs of the script group 1138 * @return a script group 1139 */ 1140 1141 public ScriptGroup create(String name, Future... outputs) { 1142 if (name == null || name.isEmpty() || name.length() > 100 || 1143 !name.equals(name.replaceAll("[^a-zA-Z0-9-]", "_"))) { 1144 throw new RSIllegalArgumentException("invalid script group name"); 1145 } 1146 ScriptGroup ret = new ScriptGroup(mRS, name, mClosures, mInputs, outputs); 1147 return ret; 1148 } 1149 1150 private boolean seperateArgsAndBindings(Object[] argsAndBindings, 1151 ArrayList<Object> args, 1152 Map<Script.FieldID, Object> bindingMap) { 1153 int i; 1154 for (i = 0; i < argsAndBindings.length; i++) { 1155 if (argsAndBindings[i] instanceof Binding) { 1156 break; 1157 } 1158 args.add(argsAndBindings[i]); 1159 } 1160 1161 for (; i < argsAndBindings.length; i++) { 1162 if (!(argsAndBindings[i] instanceof Binding)) { 1163 return false; 1164 } 1165 Binding b = (Binding)argsAndBindings[i]; 1166 bindingMap.put(b.getField(), b.getValue()); 1167 } 1168 1169 return true; 1170 } 1171 1172 } 1173 1174} 1175 1176 1177