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 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      */
     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      */
     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      */
     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      */
    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      */
    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
    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
    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 
    211         public AllocationKey(Type type, int usage) {
    212             mType = type;
    213             mUsage = usage;
    214         }
    215 
    216         @Override
    217         public int hashCode() {
    218             return mType.hashCode() ^ mUsage;
    219         }
    220 
    221         @Override
    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 
    233     private void checkNotClosed() {
    234         if (mClosed == true) {
    235             throw new IllegalStateException("AllocationCache has already been closed");
    236         }
    237     }
    238 }
    239