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