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 android.hardware.camera2.cts.helpers.UncheckedCloseable;
20 import android.renderscript.Allocation;
21 import android.renderscript.RenderScript;
22 import android.renderscript.Type;
23 import android.util.Log;
24 
25 import java.util.ArrayList;
26 import java.util.HashMap;
27 import java.util.List;
28 import java.util.Map;
29 
30 import static android.hardware.camera2.cts.helpers.Preconditions.*;
31 
32 /**
33  * Cache {@link Allocation} objects based on their type and usage.
34  *
35  * <p>This avoids expensive re-allocation of objects when they are used over and over again
36  * by different scripts.</p>
37  */
38 public class AllocationCache implements UncheckedCloseable {
39 
40     private static final String TAG = "AllocationCache";
41     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
42     private static int sDebugHits = 0;
43     private static int sDebugMisses = 0;
44 
45     private final RenderScript mRS;
46     private final HashMap<AllocationKey, List<Allocation>> mAllocationMap =
47             new HashMap<AllocationKey, List<Allocation>>();
48     private boolean mClosed = false;
49 
50     /**
51      * Create a new cache with the specified RenderScript context.
52      *
53      * @param rs A non-{@code null} RenderScript context.
54      *
55      * @throws NullPointerException if rs was null
56      */
AllocationCache(RenderScript rs)57     public AllocationCache(RenderScript rs) {
58         mRS = checkNotNull("rs", rs);
59     }
60 
61     /**
62      * Returns the {@link RenderScript} context associated with this AllocationCache.
63      *
64      * @return A non-{@code null} RenderScript value.
65      */
getRenderScript()66     public RenderScript getRenderScript() {
67         return mRS;
68     }
69 
70     /**
71      * Try to lookup a compatible Allocation from the cache, create one if none exist.
72      *
73      * @param type A non-{@code null} RenderScript Type.
74      * @throws NullPointerException if type was null
75      * @throws IllegalStateException if the cache was closed with {@link #close}
76      */
getOrCreateTyped(Type type, int usage)77     public Allocation getOrCreateTyped(Type type, int usage) {
78         synchronized (this) {
79           checkNotNull("type", type);
80           checkNotClosed();
81 
82           AllocationKey key = new AllocationKey(type, usage);
83           List<Allocation> list = mAllocationMap.get(key);
84 
85           if (list != null && !list.isEmpty()) {
86               Allocation alloc = list.remove(list.size() - 1);
87               if (DEBUG) {
88                   sDebugHits++;
89                   Log.d(TAG, String.format(
90                       "Cache HIT (%d): type = '%s', usage = '%x'", sDebugHits, type, usage));
91               }
92               return alloc;
93           }
94           if (DEBUG) {
95               sDebugMisses++;
96               Log.d(TAG, String.format(
97                   "Cache MISS (%d): type = '%s', usage = '%x'", sDebugMisses, type, usage));
98           }
99         }
100         return Allocation.createTyped(mRS, type, usage);
101     }
102 
103     /**
104      * Return the Allocation to the cache.
105      *
106      * <p>Future calls to getOrCreateTyped with the same type and usage may
107      * return this allocation.</p>
108      *
109      * <p>Allocations that have usage {@link Allocation#USAGE_IO_INPUT} get their
110      * listeners reset. Those that have {@link Allocation#USAGE_IO_OUTPUT} get their
111      * surfaces reset.</p>
112      *
113      * @param allocation A non-{@code null} RenderScript {@link Allocation}
114      * @throws NullPointerException if allocation was null
115      * @throws IllegalArgumentException if the allocation was already returned previously
116      * @throws IllegalStateException if the cache was closed with {@link #close}
117      */
returnToCache(Allocation allocation)118     public synchronized void returnToCache(Allocation allocation) {
119         checkNotNull("allocation", allocation);
120         checkNotClosed();
121 
122         int usage = allocation.getUsage();
123         AllocationKey key = new AllocationKey(allocation.getType(), usage);
124         List<Allocation> value = mAllocationMap.get(key);
125 
126         if (value != null && value.contains(allocation)) {
127             throw new IllegalArgumentException("allocation was already returned to the cache");
128         }
129 
130         if ((usage & Allocation.USAGE_IO_INPUT) != 0) {
131             allocation.setOnBufferAvailableListener(null);
132         }
133         if ((usage & Allocation.USAGE_IO_OUTPUT) != 0) {
134             allocation.setSurface(null);
135         }
136 
137         if (value == null) {
138             value = new ArrayList<Allocation>(/*capacity*/1);
139             mAllocationMap.put(key, value);
140         }
141 
142         value.add(allocation);
143 
144         // TODO: Evict existing allocations from cache when we get too many items in it,
145         // to avoid running out of memory
146 
147         // TODO: move to using android.util.LruCache under the hood
148     }
149 
150     /**
151      * Return the allocation to the cache, if it wasn't {@code null}.
152      *
153      * <p>Future calls to getOrCreateTyped with the same type and usage may
154      * return this allocation.</p>
155      *
156      * <p>Allocations that have usage {@link Allocation#USAGE_IO_INPUT} get their
157      * listeners reset. Those that have {@link Allocation#USAGE_IO_OUTPUT} get their
158      * surfaces reset.</p>
159      *
160      * <p>{@code null} values are a no-op.</p>
161      *
162      * @param allocation A potentially {@code null} RenderScript {@link Allocation}
163      * @throws IllegalArgumentException if the allocation was already returned previously
164      * @throws IllegalStateException if the cache was closed with {@link #close}
165      */
returnToCacheIfNotNull(Allocation allocation)166     public synchronized void returnToCacheIfNotNull(Allocation allocation) {
167         if (allocation != null) {
168             returnToCache(allocation);
169         }
170     }
171 
172     /**
173      * Closes the object and destroys any Allocations still in the cache.
174      */
175     @Override
close()176     public synchronized void close() {
177         if (mClosed) return;
178 
179         for (Map.Entry<AllocationKey, List<Allocation>> entry : mAllocationMap.entrySet()) {
180             List<Allocation> value = entry.getValue();
181 
182             for (Allocation alloc : value) {
183                 alloc.destroy();
184             }
185 
186             value.clear();
187         }
188 
189         mAllocationMap.clear();
190         mClosed = true;
191     }
192 
193     @Override
finalize()194     protected void finalize() throws Throwable {
195         try {
196             close();
197         } finally {
198             super.finalize();
199         }
200     }
201 
202     /**
203      * Holder class to check if one allocation is compatible with another.
204      *
205      * <p>An Allocation is considered compatible if both it's Type and usage is equivalent.</p>
206      */
207     private static class AllocationKey {
208         private final Type mType;
209         private final int mUsage;
210 
AllocationKey(Type type, int usage)211         public AllocationKey(Type type, int usage) {
212             mType = type;
213             mUsage = usage;
214         }
215 
216         @Override
hashCode()217         public int hashCode() {
218             return mType.hashCode() ^ mUsage;
219         }
220 
221         @Override
equals(Object other)222         public boolean equals(Object other) {
223             if (other instanceof AllocationKey){
224                 AllocationKey otherKey = (AllocationKey) other;
225 
226                 return otherKey.mType.equals(mType) && otherKey.mUsage == mUsage;
227             }
228 
229             return false;
230         }
231     }
232 
checkNotClosed()233     private void checkNotClosed() {
234         if (mClosed == true) {
235             throw new IllegalStateException("AllocationCache has already been closed");
236         }
237     }
238 }
239