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