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