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
17package android.hardware.camera2.cts.rs;
18
19import static android.hardware.camera2.cts.helpers.Preconditions.*;
20
21import android.hardware.camera2.cts.helpers.UncheckedCloseable;
22import android.renderscript.Allocation;
23import android.renderscript.RenderScript;
24import android.util.Log;
25
26import java.util.HashMap;
27
28/**
29 * Base class for all renderscript script abstractions.
30 *
31 * <p>Each script has exactly one input and one output allocation, and is able to execute
32 * one {@link android.renderscript.Script} script file.</p>
33 *
34 * <p>Each script owns it's input allocation, but not the output allocation.</p>
35 *
36 * <p>Subclasses of this class must implement exactly one of two constructors:
37 * <ul>
38 * <li>{@code ScriptSubclass(AllocationInfo inputInfo)}
39 *  - if it expects 0 parameters
40 * <li>{@code ScriptSubclass(AllocationInfo inputInfo, ParameterMap<T> parameterMap))}
41 *  - if it expects 1 or more parameters
42 * </ul>
43 *
44 * @param <T> A concrete subclass of {@link android.renderscript.Script}
45 */
46public abstract class Script<T extends android.renderscript.Script> implements UncheckedCloseable {
47
48    /**
49     * A type-safe heterogenous parameter map for script parameters.
50     *
51     * @param  A concrete subclass of {@link Script}.
52     */
53    public static class ParameterMap<ScriptT extends Script<?>> {
54        private final HashMap<Script.ScriptParameter<ScriptT, ?>, Object> mParameterMap =
55                new HashMap<Script.ScriptParameter<ScriptT, ?>, Object>();
56
57        /**
58         * Create a new parameter map with 0 parameters.</p>
59         */
60        public ParameterMap() {}
61
62        /**
63         * Get the value associated with the given parameter key.
64         *
65         * @param parameter A type-safe key corresponding to a parameter.
66         *
67         * @return The value, or {@code null} if none was set.
68         *
69         * @param <T> The type of the value
70         *
71         * @throws NullPointerException if parameter was {@code null}
72         */
73        @SuppressWarnings("unchecked")
74        public <T> T get(Script.ScriptParameter<ScriptT, T> parameter) {
75            checkNotNull("parameter", parameter);
76
77            return (T) mParameterMap.get(parameter);
78        }
79
80        /**
81         * Sets the value associated with the given parameter key.
82         *
83         * @param parameter A type-safe key corresponding to a parameter.
84         * @param value The value
85         *
86         * @param <T> The type of the value
87         *
88         * @throws NullPointerException if parameter was {@code null}
89         * @throws NullPointerException if value was {@code null}
90         */
91        public <T> void set(Script.ScriptParameter<ScriptT, T> parameter, T value) {
92            checkNotNull("parameter", parameter);
93            checkNotNull("value", value);
94
95            if (!parameter.getValueClass().isInstance(value)) {
96                throw new IllegalArgumentException(
97                        "Runtime type mismatch between " + parameter + " and value " + value);
98            }
99
100            mParameterMap.put(parameter, value);
101        }
102
103        /**
104         * Whether or not at least one parameter has been {@link #set}.
105         *
106         * @return true if there is at least one element in the map
107         */
108        public boolean isEmpty() {
109            return mParameterMap.isEmpty();
110        }
111
112        /**
113         * Check if the parameter has been {@link #set} to a value.
114         *
115         * @param parameter A type-safe key corresponding to a parameter.
116         * @return true if there is a value corresponding to this parameter, false otherwise.
117         */
118        public boolean contains(Script.ScriptParameter<ScriptT, ?> parameter) {
119            checkNotNull("parameter", parameter);
120
121            return mParameterMap.containsKey(parameter);
122        }
123    }
124
125    /**
126     * A type-safe parameter key to be used with {@link ParameterMap}.
127     *
128     * @param <J> A concrete subclass of {@link Script}.
129     * @param <K> The type of the value that the parameter holds.
130     */
131    public static class ScriptParameter<J extends Script<?>, K> {
132        private final Class<J> mScriptClass;
133        private final Class<K> mValueClass;
134
135        ScriptParameter(Class<J> jClass, Class<K> kClass) {
136            checkNotNull("jClass", jClass);
137            checkNotNull("kClass", kClass);
138
139            mScriptClass = jClass;
140            mValueClass = kClass;
141        }
142
143        /**
144         * Get the runtime class associated with the value.
145         */
146        public Class<K> getValueClass() {
147            return mValueClass;
148        }
149
150        /**
151         * Compare with another object.
152         *
153         * <p>Two script parameters are considered equal only if their script class and value
154         * class are both equal.</p>
155         */
156        @SuppressWarnings("unchecked")
157        @Override
158        public boolean equals(Object other) {
159            if (other instanceof ScriptParameter) {
160                ScriptParameter<J, K> otherParam = (ScriptParameter<J,K>) other;
161
162                return mScriptClass.equals(otherParam.mScriptClass) &&
163                        mValueClass.equals(otherParam.mValueClass);
164            }
165
166            return false;
167        }
168
169        /**
170         * Gets the hash code for this object.
171         */
172        @Override
173        public int hashCode() {
174            return mScriptClass.hashCode() ^ mValueClass.hashCode();
175        }
176    }
177
178    private static final String TAG = "Script";
179    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
180
181    protected final AllocationCache mCache = RenderScriptSingleton.getCache();
182    protected final RenderScript mRS = RenderScriptSingleton.getRS();
183
184    protected final AllocationInfo mInputInfo;
185    protected final AllocationInfo mOutputInfo;
186
187    protected Allocation mOutputAllocation;
188    protected Allocation mInputAllocation;
189
190    protected final T mScript;
191    private boolean mClosed = false;
192
193    /**
194     * Gets the {@link AllocationInfo info} associated with this script's input.
195     *
196     * @return A non-{@code null} {@link AllocationInfo} object.
197     *
198     * @throws IllegalStateException If the script has already been {@link #close closed}.
199     */
200    public AllocationInfo getInputInfo() {
201        checkNotClosed();
202
203        return mInputInfo;
204    }
205    /**
206     * Gets the {@link AllocationInfo info} associated with this script's output.
207     *
208     * @return A non-{@code null} {@link AllocationInfo} object.
209     *
210     * @throws IllegalStateException If the script has already been {@link #close closed}.
211     */
212    public AllocationInfo getOutputInfo() {
213        checkNotClosed();
214
215        return mOutputInfo;
216    }
217
218    /**
219     * Set the input.
220     *
221     * <p>Must be called before executing any scripts.</p>
222     *
223     * @throws IllegalStateException If the script has already been {@link #close closed}.
224     */
225    void setInput(Allocation allocation) {
226        checkNotClosed();
227        checkNotNull("allocation", allocation);
228        checkEquals("allocation info", AllocationInfo.newInstance(allocation),
229                "input info", mInputInfo);
230
231        // Scripts own the input, so return old input to cache if the input changes
232        if (mInputAllocation != allocation) {
233            mCache.returnToCacheIfNotNull(mInputAllocation);
234        }
235
236        mInputAllocation = allocation;
237        updateScriptInput();
238    }
239
240    protected abstract void updateScriptInput();
241
242    /**
243     * Set the output.
244     *
245     * <p>Must be called before executing any scripts.</p>
246     *
247     * @throws IllegalStateException If the script has already been {@link #close closed}.
248     */
249    void setOutput(Allocation allocation) {
250        checkNotClosed();
251        checkNotNull("allocation", allocation);
252        checkEquals("allocation info", AllocationInfo.newInstance(allocation),
253                "output info", mOutputInfo);
254
255        // Scripts do not own the output, simply set a reference to the new one.
256        mOutputAllocation = allocation;
257    }
258
259    protected Script(AllocationInfo inputInfo, AllocationInfo outputInfo, T rsScript) {
260        checkNotNull("inputInfo", inputInfo);
261        checkNotNull("outputInfo", outputInfo);
262        checkNotNull("rsScript", rsScript);
263
264        mInputInfo = inputInfo;
265        mOutputInfo = outputInfo;
266        mScript = rsScript;
267
268        if (VERBOSE) {
269            Log.v(TAG, String.format("%s - inputInfo = %s, outputInfo = %s, rsScript = %s",
270                    getName(), inputInfo, outputInfo, rsScript));
271        }
272    }
273
274    /**
275     * Get the {@link Allocation} associated with this script's input.</p>
276     *
277     * @return The input {@link Allocation}, which is never {@code null}.
278     *
279     * @throws IllegalStateException If the script has already been {@link #close closed}.
280     */
281    public Allocation getInput() {
282        checkNotClosed();
283
284        return mInputAllocation;
285    }
286    /**
287     * Get the {@link Allocation} associated with this script's output.</p>
288     *
289     * @return The output {@link Allocation}, which is never {@code null}.
290     *
291     * @throws IllegalStateException If the script has already been {@link #close closed}.
292     */
293    public Allocation getOutput() {
294        checkNotClosed();
295
296        return mOutputAllocation;
297    }
298
299    /**
300     * Execute the script's kernel against the input/output {@link Allocation allocations}.
301     *
302     * <p>Once this is complete, the output will have the new data available (for either
303     * the next script, or to read out with a copy).</p>
304     *
305     * @throws IllegalStateException If the script has already been {@link #close closed}.
306     */
307    public void execute() {
308        checkNotClosed();
309
310        if (mInputAllocation == null || mOutputAllocation == null) {
311            throw new IllegalStateException("Both inputs and outputs must have been set");
312        }
313
314        executeUnchecked();
315    }
316
317    /**
318     * Get the name of this script.
319     *
320     * <p>The name is the short hand name of the concrete class backing this script.</p>
321     *
322     * <p>This method works even if the script has already been {@link #close closed}.</p>
323     *
324     * @return A string representing the script name.
325     */
326    public String getName() {
327        return getClass().getSimpleName();
328    }
329
330    protected abstract void executeUnchecked();
331
332    protected void checkNotClosed() {
333        if (mClosed) {
334            throw new IllegalStateException("Script has been closed");
335        }
336    }
337
338    /**
339     * Destroy the underlying script object and return the input allocation back to the
340     * {@link AllocationCache cache}.
341     *
342     * <p>This method has no effect if called more than once.</p>
343     */
344    @Override
345    public void close() {
346        if (mClosed) return;
347
348        // Scripts own the input allocation. They do NOT own outputs.
349        mCache.returnToCacheIfNotNull(mInputAllocation);
350
351        mScript.destroy();
352
353        mClosed = true;
354    }
355
356    @Override
357    protected void finalize() throws Throwable {
358        try {
359            close();
360        } finally {
361            super.finalize();
362        }
363    }
364
365    protected static RenderScript getRS() {
366        return RenderScriptSingleton.getRS();
367    }
368}
369