1 /* 2 * Copyright 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.hardware.camera2.cts.rs; 18 19 import static android.hardware.camera2.cts.helpers.Preconditions.*; 20 import static junit.framework.Assert.*; 21 22 import android.graphics.ImageFormat; 23 import android.graphics.PixelFormat; 24 import android.util.Size; 25 import android.hardware.camera2.cts.helpers.MaybeNull; 26 import android.hardware.camera2.cts.helpers.UncheckedCloseable; 27 import android.hardware.camera2.cts.rs.Script.ParameterMap; 28 import android.renderscript.Allocation; 29 import android.util.Log; 30 import android.view.Surface; 31 32 import java.lang.reflect.Constructor; 33 import java.lang.reflect.InvocationTargetException; 34 import java.util.ArrayList; 35 import java.util.List; 36 import com.android.ex.camera2.exceptions.TimeoutRuntimeException; 37 38 /** 39 * An abstraction to simplify chaining together the execution of multiple RenderScript 40 * {@link android.renderscript.Script scripts} and managing their {@link Allocation allocations}. 41 * 42 * <p>Create a new script graph by using {@link #create}, configure the input with 43 * {@link Builder#configureInput}, then configure one or more scripts with 44 * {@link Builder#configureScript} or {@link Builder#chainScript}. Finally, freeze the graph 45 * with {@link Builder#buildGraph}.</p> 46 * 47 * <p>Once a script graph has been built, all underlying scripts and allocations are instantiated. 48 * Each script may be executed with {@link #execute}. Scripts are executed in the order that they 49 * were configured, with each previous script's output used as the input for the next script. 50 * </p> 51 * 52 * <p>In case the input {@link Allocation} is actually backed by a {@link Surface}, convenience 53 * methods ({@link #advanceInputWaiting} and {@link #advanceInputAndDrop} are provided to 54 * automatically update the {@link Allocation allocation} with the latest buffer available.</p> 55 * 56 * <p>All resources are managed by the {@link ScriptGraph} and {@link #close closing} the graph 57 * will release all underlying resources. See {@link #close} for more details.</p> 58 */ 59 public class ScriptGraph implements UncheckedCloseable { 60 61 private static final String TAG = "ScriptGraph"; 62 private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE); 63 64 private static final int INPUT_SCRIPT_LOCATION = 0; 65 private final int OUTPUT_SCRIPT_LOCATION; // calculated in constructor 66 67 private final AllocationCache mCache = RenderScriptSingleton.getCache(); 68 69 private final Size mSize; 70 private final int mFormat; 71 private final int mUsage; 72 private final List<Script<?>> mScripts; 73 74 private final BlockingInputAllocation mInputBlocker; 75 private final Allocation mOutputAllocation; 76 private boolean mClosed = false; 77 78 /** 79 * Create a new {@link Builder} that will be used to configure the graph's inputs 80 * and scripts (and parameters). 81 * 82 * <p>Once a graph has been fully built, the configuration is immutable.</p> 83 * 84 * @return a {@link Builder} that will be used to configure the graph settings 85 */ create()86 public static Builder create() { 87 return new Builder(); 88 } 89 90 /** 91 * Wait until another buffer is produced into the input {@link Surface}, then 92 * update the backing input {@link Allocation} with the latest buffer with 93 * {@link Allocation#ioReceive ioReceive}. 94 * 95 * @throws IllegalArgumentException 96 * if the graph wasn't configured with 97 * {@link Builder#configureInputWithSurface configureInputWithSurface} 98 * @throws TimeoutRuntimeException 99 * if waiting for the buffer times out 100 */ advanceInputWaiting()101 public void advanceInputWaiting() { 102 checkNotClosed(); 103 if (!isInputFromSurface()) { 104 throw new IllegalArgumentException("Graph was not configured with USAGE_IO_INPUT"); 105 } 106 107 mInputBlocker.waitForBufferAndReceive(); 108 } 109 110 /** 111 * Update the backing input {@link Allocation} with the latest buffer with 112 * {@link Allocation#ioReceive ioReceive} repeatedly until no more buffers are pending. 113 * 114 * <p>Does not wait for new buffers to become available if none are currently available 115 * (i.e. {@code false} is returned immediately).</p> 116 * 117 * @return true if any buffers were pending 118 * 119 * @throws IllegalArgumentException 120 * if the graph wasn't configured with 121 * {@link Builder#configureInputWithSurface configureInputWithSurface} 122 * @throws TimeoutRuntimeException 123 * if waiting for the buffer times out 124 */ advanceInputAndDrop()125 public boolean advanceInputAndDrop() { 126 checkNotClosed(); 127 if (!isInputFromSurface()) { 128 throw new IllegalArgumentException("Graph was not configured with USAGE_IO_INPUT"); 129 } 130 131 return mInputBlocker.receiveLatestAvailableBuffers(); 132 } 133 134 /** 135 * Execute each script in the graph, with each next script's input using the 136 * previous script's output. 137 * 138 * <p>Scripts are executed in the same order that they were configured by the {@link Builder}. 139 * </p> 140 * 141 * @throws IllegalStateException if the graph was already {@link #close closed} 142 */ execute()143 public void execute() { 144 checkNotClosed(); 145 146 // TODO: Can we use android.renderscript.ScriptGroup here to make it faster? 147 148 int i = 0; 149 for (Script<?> script : mScripts) { 150 script.execute(); 151 i++; 152 } 153 154 if (VERBOSE) Log.v(TAG, "execute - invoked " + i + " scripts"); 155 } 156 157 /** 158 * Copies the data from the last script's output {@link Allocation} into a byte array. 159 * 160 * <p>The output allocation must be of an 8 bit integer 161 * {@link android.renderscript.Element Element} type.</p> 162 * 163 * @return A byte[] copy. 164 * 165 * @throws IllegalStateException if the graph was already {@link #close closed} 166 */ getOutputData()167 public byte[] getOutputData() { 168 checkNotClosed(); 169 170 Allocation outputAllocation = getOutputAllocation(); 171 172 byte[] destination = new byte[outputAllocation.getBytesSize()]; 173 outputAllocation.copyTo(destination); 174 175 return destination; 176 } 177 178 /** 179 * Copies the data from the first script's input {@link Allocation} into a byte array. 180 * 181 * <p>The input allocation must be of an 8 bit integer 182 * {@link android.renderscript.Element Element} type.</p> 183 * 184 * @return A byte[] copy. 185 * 186 * @throws IllegalStateException if the graph was already {@link #close closed} 187 */ getInputData()188 public byte[] getInputData() { 189 checkNotClosed(); 190 191 Allocation inputAllocation = getInputAllocation(); 192 193 byte[] destination = new byte[inputAllocation.getBytesSize()]; 194 inputAllocation.copyTo(destination); 195 196 return destination; 197 } 198 199 /** 200 * Builds a {@link ScriptGraph} by configuring input size/format/usage, 201 * the script classes in the graph, and the parameters passed to the scripts. 202 * 203 * @see ScriptGraph#create 204 */ 205 public static class Builder { 206 207 private Size mSize; 208 private int mFormat; 209 private int mUsage; 210 211 private final List<ScriptBuilder<? extends Script<?>>> mChainedScriptBuilders = 212 new ArrayList<ScriptBuilder<? extends Script<?>>>(); 213 214 /** 215 * Configure the {@link Allocation} that will be used as the input to the first 216 * script, using the default usage. 217 * 218 * <p>Short hand for calling {@link #configureInput(int, int, int, int)} with a 219 * {@code 0} usage.</p> 220 * 221 * @param width Width in pixels 222 * @param height Height in pixels 223 * @param format Format from {@link ImageFormat} or {@link PixelFormat} 224 * 225 * @return The current builder ({@code this}). Use for chaining method calls. 226 */ configureInput(int width, int height, int format)227 public Builder configureInput(int width, int height, int format) { 228 return configureInput(new Size(width, height), format, /*usage*/0); 229 } 230 231 /** 232 * Configure the {@link Allocation} that will be used as the input to the first 233 * script. 234 * 235 * <p>The {@code usage} is always ORd together with {@link Allocation#USAGE_SCRIPT}.</p> 236 * 237 * @param width Width in pixels 238 * @param height Height in pixels 239 * @param format Format from {@link ImageFormat} or {@link PixelFormat} 240 * @param usage Usage flags such as {@link Allocation#USAGE_IO_INPUT} 241 * 242 * @return The current builder ({@code this}). Use for chaining method calls. 243 */ configureInput(int width, int height, int format, int usage)244 public Builder configureInput(int width, int height, int format, int usage) { 245 return configureInput(new Size(width, height), format, usage); 246 } 247 248 /** 249 * Configure the {@link Allocation} that will be used as the input to the first 250 * script, using the default usage. 251 * 252 * <p>Short hand for calling {@link #configureInput(Size, int, int)} with a 253 * {@code 0} usage.</p> 254 * 255 * @param size Size (width, height) 256 * @param format Format from {@link ImageFormat} or {@link PixelFormat} 257 * 258 * @return The current builder ({@code this}). Use for chaining method calls. 259 * 260 * @throws NullPointerException if size was {@code null} 261 */ configureInput(Size size, int format)262 public Builder configureInput(Size size, int format) { 263 return configureInput(size, format, /*usage*/0); 264 } 265 266 /** 267 * Configure the {@link Allocation} that will use a {@link Surface} to produce input into 268 * the first script. 269 * 270 * <p>Short hand for calling {@link #configureInput(Size, int, int)} with the 271 * {@link Allocation#USAGE_IO_INPUT} usage.</p> 272 * 273 * <p>The {@code usage} is always ORd together with {@link Allocation#USAGE_SCRIPT}.</p> 274 * 275 * @param size Size (width, height) 276 * @param format Format from {@link ImageFormat} or {@link PixelFormat} 277 * 278 * @return The current builder ({@code this}). Use for chaining method calls. 279 * 280 * @throws NullPointerException if size was {@code null} 281 */ configureInputWithSurface(Size size, int format)282 public Builder configureInputWithSurface(Size size, int format) { 283 return configureInput(size, format, Allocation.USAGE_IO_INPUT); 284 } 285 286 /** 287 * Configure the {@link Allocation} that will be used as the input to the first 288 * script. 289 * 290 * <p>The {@code usage} is always ORd together with {@link Allocation#USAGE_SCRIPT}.</p> 291 * 292 * @param size Size (width, height) 293 * @param format Format from {@link ImageFormat} or {@link PixelFormat} 294 * @param usage Usage flags such as {@link Allocation#USAGE_IO_INPUT} 295 * 296 * @return The current builder ({@code this}). Use for chaining method calls. 297 * 298 * @throws NullPointerException if size was {@code null} 299 */ configureInput(Size size, int format, int usage)300 public Builder configureInput(Size size, int format, int usage) { 301 checkNotNull("size", size); 302 303 mSize = size; 304 mFormat = format; 305 mUsage = usage | Allocation.USAGE_SCRIPT; 306 307 return this; 308 } 309 310 /** 311 * Build a {@link Script} by setting parameters it might require for execution. 312 * 313 * <p>Refer to the documentation for {@code T} to see if there are any 314 * {@link Script.ScriptParameter parameters} in it. 315 * </p> 316 * 317 * @param <T> Concrete type subclassing the {@link Script} class. 318 */ 319 public class ScriptBuilder<T extends Script<?>> { 320 321 private final Class<T> mScriptClass; 322 ScriptBuilder(Class<T> scriptClass)323 private ScriptBuilder(Class<T> scriptClass) { 324 mScriptClass = scriptClass; 325 } 326 327 private final ParameterMap<T> mParameterMap = new ParameterMap<T>(); 328 329 /** 330 * Set a script parameter to the specified value. 331 * 332 * @param parameter The {@link Script.ScriptParameter parameter key} in {@code T} 333 * @param value A value of type {@code K} that the script expects. 334 * @param <K> The type of the parameter {@code value}. 335 * 336 * @return The current builder ({@code this}). Use to chain method calls. 337 * 338 * @throws NullPointerException if parameter was {@code null} 339 * @throws NullPointerException if value was {@code null} 340 * @throws IllegalStateException if the parameter was already {@link #set} 341 */ set(Script.ScriptParameter<T, K> parameter, K value)342 public <K> ScriptBuilder<T> set(Script.ScriptParameter<T, K> parameter, K value) { 343 checkNotNull("parameter", parameter); 344 checkNotNull("value", value); 345 checkState("Parameter has already been set", !mParameterMap.contains(parameter)); 346 347 mParameterMap.set(parameter, value); 348 349 return this; 350 } 351 getParameterMap()352 ParameterMap<T> getParameterMap() { 353 return mParameterMap; 354 } 355 getScriptClass()356 Class<T> getScriptClass() { 357 return mScriptClass; 358 } 359 360 /** 361 * Build the script and freeze the parameter list to what was {@link #set}. 362 * 363 * @return 364 * The {@link ScriptGraph#Builder} that was used to configure 365 * {@link this} script.</p> 366 */ buildScript()367 public Builder buildScript() { 368 mChainedScriptBuilders.add(this); 369 370 return Builder.this; 371 } 372 } 373 374 /** 375 * Configure the script with no parameters. 376 * 377 * <p>Short hand for invoking {@link #configureScript} immediately followed by 378 * {@link ScriptBuilder#buildScript()}. 379 * 380 * @param scriptClass A concrete class that subclasses {@link Script} 381 * @return The current builder ({@code this}). Use to chain method calls. 382 * 383 * @throws NullPointerException if {@code scriptClass} was {@code null} 384 */ chainScript(Class<T> scriptClass)385 public <T extends Script<?>> Builder chainScript(Class<T> scriptClass) { 386 checkNotNull("scriptClass", scriptClass); 387 388 return (new ScriptBuilder<T>(scriptClass)).buildScript(); 389 } 390 391 /** 392 * Configure the script with parameters. 393 * 394 * <p>Only useful when the {@code scriptClass} has one or more 395 * {@link Script.ScriptParameter script parameters} defined.</p> 396 * 397 * @param scriptClass A concrete class that subclasses {@link Script} 398 * @return A script configuration {@link ScriptBuilder builder}. Use to chain method calls. 399 * 400 * @throws NullPointerException if {@code scriptClass} was {@code null} 401 */ configureScript(Class<T> scriptClass)402 public <T extends Script<?>> ScriptBuilder<T> configureScript(Class<T> scriptClass) { 403 checkNotNull("scriptClass", scriptClass); 404 405 return new ScriptBuilder<T>(scriptClass); 406 } 407 408 /** 409 * Finish configuring the graph and freeze the settings, instantiating all 410 * the {@link Script scripts} and {@link Allocation allocations}. 411 * 412 * @return A constructed {@link ScriptGraph}. 413 */ buildGraph()414 public ScriptGraph buildGraph() { 415 return new ScriptGraph(this); 416 } 417 Builder()418 private Builder() {} 419 } 420 ScriptGraph(Builder builder)421 private ScriptGraph(Builder builder) { 422 mSize = builder.mSize; 423 mFormat = builder.mFormat; 424 mUsage = builder.mUsage; 425 List<Builder.ScriptBuilder<? extends Script<?>>> chainedScriptBuilders = 426 builder.mChainedScriptBuilders; 427 mScripts = new ArrayList<Script<?>>(/*capacity*/chainedScriptBuilders.size()); 428 OUTPUT_SCRIPT_LOCATION = chainedScriptBuilders.size() - 1; 429 430 if (mSize == null) { 431 throw new IllegalArgumentException("Inputs were not configured"); 432 } 433 434 if (chainedScriptBuilders.isEmpty()) { 435 throw new IllegalArgumentException("At least one script should be chained"); 436 } 437 438 /* 439 * The first input is special since it could be USAGE_IO_INPUT. 440 */ 441 AllocationInfo inputInfo = AllocationInfo.newInstance(mSize, mFormat, mUsage); 442 Allocation inputAllocation; 443 444 // Create an Allocation with a Surface if the input to the graph requires it 445 if (isInputFromSurface()) { 446 mInputBlocker = inputInfo.createBlockingInputAllocation(); 447 inputAllocation = mInputBlocker.getAllocation(); 448 } else { 449 mInputBlocker = null; 450 inputAllocation = inputInfo.createAllocation(); 451 } 452 453 if (VERBOSE) Log.v(TAG, "ScriptGraph() - Instantiating all script classes"); 454 455 // Create all scripts. 456 for (Builder.ScriptBuilder<? extends Script<?>> scriptBuilder: chainedScriptBuilders) { 457 458 @SuppressWarnings("unchecked") 459 Class<Script<?>> scriptClass = (Class<Script<?>>) scriptBuilder.getScriptClass(); 460 461 @SuppressWarnings("unchecked") 462 ParameterMap<Script<?>> parameters = (ParameterMap<Script<?>>) 463 scriptBuilder.getParameterMap(); 464 465 Script<?> script = instantiateScript(scriptClass, inputInfo, parameters); 466 mScripts.add(script); 467 468 // The next script's input info is the current script's output info 469 inputInfo = script.getOutputInfo(); 470 } 471 472 if (VERBOSE) Log.v(TAG, "ScriptGraph() - Creating all inputs"); 473 474 // Create and wire up all inputs. 475 int i = 0; 476 Script<?> inputScript = mScripts.get(INPUT_SCRIPT_LOCATION); 477 do { 478 if (VERBOSE) { 479 Log.v(TAG, "ScriptGraph() - Setting input for script " + inputScript.getName()); 480 } 481 482 inputScript.setInput(inputAllocation); 483 484 i++; 485 486 if (i >= mScripts.size()) { 487 break; 488 } 489 490 // Use the graph input for the first loop iteration 491 inputScript = mScripts.get(i); 492 inputInfo = inputScript.getInputInfo(); 493 inputAllocation = inputInfo.createAllocation(); 494 } while (true); 495 496 if (VERBOSE) Log.v(TAG, "ScriptGraph() - Creating all outputs"); 497 498 // Create and wire up all outputs. 499 Allocation lastOutput = null; 500 for (i = 0; i < mScripts.size(); ++i) { 501 Script<?> script = mScripts.get(i); 502 Script<?> nextScript = (i + 1 < mScripts.size()) ? mScripts.get(i + 1) : null; 503 504 // Each script's output uses the next script's input. 505 // -- Since the last script has no next script, we own its output allocation. 506 lastOutput = (nextScript != null) ? nextScript.getInput() 507 : script.getOutputInfo().createAllocation(); 508 509 if (VERBOSE) { 510 Log.v(TAG, "ScriptGraph() - Setting output for script " + script.getName()); 511 } 512 513 script.setOutput(lastOutput); 514 } 515 mOutputAllocation = checkNotNull("lastOutput", lastOutput); 516 517 // Done. Safe to execute the graph now. 518 519 if (VERBOSE) Log.v(TAG, "ScriptGraph() - Graph has been built"); 520 } 521 522 /** 523 * Construct the script by instantiating it via reflection. 524 * 525 * <p>The {@link Script scriptClass} should have a {@code Script(AllocationInfo inputInfo)} 526 * constructor if it expects an empty parameter map.</p> 527 * 528 * <p>If it expects a non-empty parameter map, it should have a 529 * {@code Script(AllocationInfo inputInfo, ParameterMap<T> parameterMap)} constructor.</p> 530 */ instantiateScript( Class<T> scriptClass, AllocationInfo inputInfo, ParameterMap<T> parameterMap)531 private static <T extends Script<?>> T instantiateScript( 532 Class<T> scriptClass, AllocationInfo inputInfo, ParameterMap<T> parameterMap) { 533 534 Constructor<T> ctor; 535 try { 536 // TODO: Would be better if we looked at the script class to see if it expects params 537 if (parameterMap.isEmpty()) { 538 // Script(AllocationInfo inputInfo) 539 ctor = scriptClass.getConstructor(AllocationInfo.class); 540 } else { 541 // Script(AllocationInfo inputInfo, ParameterMap<T> parameterMap) 542 ctor = scriptClass.getConstructor(AllocationInfo.class, ParameterMap.class); 543 } 544 } catch (NoSuchMethodException e) { 545 throw new UnsupportedOperationException( 546 "Script class " + scriptClass + " must have a matching constructor", e); 547 } 548 549 try { 550 if (parameterMap.isEmpty()) { 551 return ctor.newInstance(inputInfo); 552 } else { 553 return ctor.newInstance(inputInfo, parameterMap); 554 } 555 } catch (InstantiationException e) { 556 throw new UnsupportedOperationException(e); 557 } catch (IllegalAccessException e) { 558 throw new UnsupportedOperationException(e); 559 } catch (IllegalArgumentException e) { 560 throw new UnsupportedOperationException(e); 561 } catch (InvocationTargetException e) { 562 throw new UnsupportedOperationException(e); 563 } 564 } 565 isInputFromSurface()566 private boolean isInputFromSurface() { 567 return (mUsage & Allocation.USAGE_IO_INPUT) != 0; 568 } 569 570 /** 571 * Get the input {@link Allocation} that is used by the first script as the input. 572 * 573 * @return An {@link Allocation} (never {@code null}). 574 * 575 * @throws IllegalStateException if the graph was already {@link #close closed} 576 */ getInputAllocation()577 public Allocation getInputAllocation() { 578 checkNotClosed(); 579 580 return mScripts.get(INPUT_SCRIPT_LOCATION).getInput(); 581 } 582 583 /** 584 * Get the output {@link Allocation} that is used by the last script as the output. 585 * 586 * @return An {@link Allocation} (never {@code null}). 587 * 588 * @throws IllegalStateException if the graph was already {@link #close closed} 589 */ getOutputAllocation()590 public Allocation getOutputAllocation() { 591 checkNotClosed(); 592 Allocation output = mScripts.get(OUTPUT_SCRIPT_LOCATION).getOutput(); 593 594 assertEquals("Graph's output should match last script's output", mOutputAllocation, output); 595 596 return output; 597 } 598 599 /** 600 * Get the {@link Surface} that can be used produce buffers into the 601 * {@link #getInputAllocation input allocation}. 602 * 603 * @throws IllegalStateException 604 * if input wasn't configured with {@link Allocation#USAGE_IO_INPUT} {@code usage}. 605 * @throws IllegalStateException 606 * if the graph was already {@link #close closed} 607 * 608 * @return A {@link Surface} (never {@code null}). 609 */ getInputSurface()610 public Surface getInputSurface() { 611 checkNotClosed(); 612 checkState("This graph was not configured with IO_USAGE_INPUT", isInputFromSurface()); 613 614 return getInputAllocation().getSurface(); 615 } 616 checkNotClosed()617 private void checkNotClosed() { 618 checkState("ScriptGraph has been closed", !mClosed); 619 } 620 621 /** 622 * Releases all underlying resources associated with this {@link ScriptGraph}. 623 * 624 * <p>In particular, all underlying {@link Script scripts} and all 625 * {@link Allocation allocations} are also closed.</p> 626 * 627 * <p>All further calls to any other public methods (other than {@link #close}) will throw 628 * an {@link IllegalStateException}.</p> 629 * 630 * <p>This method is idempotent; calling it more than once will 631 * have no further effect.</p> 632 */ 633 @Override close()634 public synchronized void close() { 635 if (mClosed) return; 636 637 for (Script<?> script : mScripts) { 638 script.close(); 639 } 640 mScripts.clear(); 641 642 MaybeNull.close(mInputBlocker); 643 mCache.returnToCache(mOutputAllocation); 644 645 mClosed = true; 646 } 647 648 @Override finalize()649 protected void finalize() throws Throwable { 650 try { 651 close(); 652 } finally { 653 super.finalize(); 654 } 655 } 656 } 657