1/*
2 * Copyright (C) 2011 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 androidx.media.filterfw;
18
19import androidx.media.filterfw.BackingStore.Backing;
20
21import java.util.Arrays;
22import java.util.Comparator;
23import java.util.HashMap;
24import java.util.HashSet;
25import java.util.Map;
26import java.util.PriorityQueue;
27import java.util.Set;
28
29/**
30 * The FrameManager tracks, caches, allocates and deallocates frame data.
31 * All Frame instances are managed by a FrameManager, and belong to exactly one of these. Frames
32 * cannot be shared across FrameManager instances, however multiple MffContexts may use the same
33 * FrameManager.
34 *
35 * Additionally, frame managers allow attaching Frames under a specified key. This allows decoupling
36 * filter-graphs by instructing one node to attach a frame under a specific key, and another to
37 * fetch the frame under the same key.
38 */
39public class FrameManager {
40
41    /** The default max cache size is set to 12 MB */
42    public final static int DEFAULT_MAX_CACHE_SIZE = 12 * 1024 * 1024;
43
44    /** Frame caching policy: No caching */
45    public final static int FRAME_CACHE_NONE = 0;
46    /** Frame caching policy: Drop least recently used frame buffers */
47    public final static int FRAME_CACHE_LRU = 1;
48    /** Frame caching policy: Drop least frequently used frame buffers */
49    public final static int FRAME_CACHE_LFU = 2;
50
51    /** Slot Flag: No flags set */
52    public final static int SLOT_FLAGS_NONE = 0x00;
53    /** Slot Flag: Sticky flag set: Frame will remain in slot after fetch. */
54    public final static int SLOT_FLAG_STICKY = 0x01;
55
56    private GraphRunner mRunner;
57    private Set<Backing> mBackings = new HashSet<Backing>();
58    private BackingCache mCache;
59
60    private Map<String, FrameSlot> mFrameSlots = new HashMap<String, FrameSlot>();
61
62    static class FrameSlot {
63        private FrameType mType;
64        private int mFlags;
65        private Frame mFrame = null;
66
67        public FrameSlot(FrameType type, int flags) {
68            mType = type;
69            mFlags = flags;
70        }
71
72        public FrameType getType() {
73            return mType;
74        }
75
76        public boolean hasFrame() {
77            return mFrame != null;
78        }
79
80        public void releaseFrame() {
81            if (mFrame != null) {
82                mFrame.release();
83                mFrame = null;
84            }
85        }
86
87        // TODO: Type check
88        public void assignFrame(Frame frame) {
89            Frame oldFrame = mFrame;
90            mFrame = frame.retain();
91            if (oldFrame != null) {
92                oldFrame.release();
93            }
94        }
95
96        public Frame getFrame() {
97            Frame result = mFrame.retain();
98            if ((mFlags & SLOT_FLAG_STICKY) == 0) {
99                releaseFrame();
100            }
101            return result;
102        }
103
104        public void markWritable() {
105            if (mFrame != null) {
106                mFrame.setReadOnly(false);
107            }
108        }
109    }
110
111    private static abstract class BackingCache {
112
113        protected int mCacheMaxSize = DEFAULT_MAX_CACHE_SIZE;
114
115        public abstract Backing fetchBacking(int mode, int access, int[] dimensions, int elemSize);
116
117        public abstract boolean cacheBacking(Backing backing);
118
119        public abstract void clear();
120
121        public abstract int getSizeLeft();
122
123        public void setSize(int size) {
124            mCacheMaxSize = size;
125        }
126
127        public int getSize() {
128            return mCacheMaxSize;
129        }
130    }
131
132    private static class BackingCacheNone extends BackingCache {
133
134        @Override
135        public Backing fetchBacking(int mode, int access, int[] dimensions, int elemSize) {
136            return null;
137        }
138
139        @Override
140        public boolean cacheBacking(Backing backing) {
141            return false;
142        }
143
144        @Override
145        public void clear() {
146        }
147
148        @Override
149        public int getSize() {
150            return 0;
151        }
152
153        @Override
154        public int getSizeLeft() {
155            return 0;
156        }
157    }
158
159    private static abstract class PriorityBackingCache extends BackingCache {
160        private int mSize = 0;
161        private PriorityQueue<Backing> mQueue;
162
163        public PriorityBackingCache() {
164            mQueue = new PriorityQueue<Backing>(4, new Comparator<Backing>() {
165                @Override
166                public int compare(Backing left, Backing right) {
167                    return left.cachePriority - right.cachePriority;
168                }
169            });
170        }
171
172        @Override
173        public Backing fetchBacking(int mode, int access, int[] dimensions, int elemSize) {
174            for (Backing backing : mQueue) {
175                int backingAccess = (mode == Frame.MODE_WRITE)
176                    ? backing.writeAccess()
177                    : backing.readAccess();
178                if ((backingAccess & access) == access
179                    && dimensionsCompatible(backing.getDimensions(), dimensions)
180                    && (elemSize == backing.getElementSize())) {
181                    mQueue.remove(backing);
182                    mSize -= backing.getSize();
183                    onFetchBacking(backing);
184                    return backing;
185                }
186            }
187            //Log.w("FrameManager", "Could not find backing for dimensions " + Arrays.toString(dimensions));
188            return null;
189        }
190
191        @Override
192        public boolean cacheBacking(Backing backing) {
193            if (reserve(backing.getSize())) {
194                onCacheBacking(backing);
195                mQueue.add(backing);
196                return true;
197            }
198            return false;
199        }
200
201        @Override
202        public void clear() {
203            mQueue.clear();
204            mSize = 0;
205        }
206
207        @Override
208        public int getSizeLeft() {
209            return mCacheMaxSize - mSize;
210        }
211
212        protected abstract void onCacheBacking(Backing backing);
213
214        protected abstract void onFetchBacking(Backing backing);
215
216        private boolean reserve(int size) {
217            //Log.i("FM", "Reserving " + size + " bytes (max: " + mCacheMaxSize + " bytes).");
218            //Log.i("FM", "Current size " + mSize);
219            if (size > mCacheMaxSize) {
220                return false;
221            }
222            mSize += size;
223            while (mSize > mCacheMaxSize) {
224                Backing dropped = mQueue.poll();
225                mSize -= dropped.getSize();
226                //Log.i("FM", "Dropping  " + dropped + " with priority "
227                //    + dropped.cachePriority + ". New size: " + mSize + "!");
228                dropped.destroy();
229            }
230            return true;
231        }
232
233
234    }
235
236    private static class BackingCacheLru extends PriorityBackingCache {
237        private int mTimestamp = 0;
238
239        @Override
240        protected void onCacheBacking(Backing backing) {
241            backing.cachePriority = 0;
242        }
243
244        @Override
245        protected void onFetchBacking(Backing backing) {
246            ++mTimestamp;
247            backing.cachePriority = mTimestamp;
248        }
249    }
250
251    private static class BackingCacheLfu extends PriorityBackingCache {
252        @Override
253        protected void onCacheBacking(Backing backing) {
254            backing.cachePriority = 0;
255        }
256
257        @Override
258        protected void onFetchBacking(Backing backing) {
259            ++backing.cachePriority;
260        }
261    }
262
263    public static FrameManager current() {
264        GraphRunner runner = GraphRunner.current();
265        return runner != null ? runner.getFrameManager() : null;
266    }
267
268    /**
269     * Returns the context that the FrameManager is bound to.
270     *
271     * @return the MffContext instance that the FrameManager is bound to.
272     */
273    public MffContext getContext() {
274        return mRunner.getContext();
275    }
276
277    /**
278     * Returns the GraphRunner that the FrameManager is bound to.
279     *
280     * @return the GraphRunner instance that the FrameManager is bound to.
281     */
282    public GraphRunner getRunner() {
283        return mRunner;
284    }
285
286    /**
287     * Sets the size of the cache.
288     *
289     * Resizes the cache to the specified size in bytes.
290     *
291     * @param bytes the new size in bytes.
292     */
293    public void setCacheSize(int bytes) {
294        mCache.setSize(bytes);
295    }
296
297    /**
298     * Returns the size of the cache.
299     *
300     * @return the size of the cache in bytes.
301     */
302    public int getCacheSize() {
303        return mCache.getSize();
304    }
305
306    /**
307     * Imports a frame from another FrameManager.
308     *
309     * This will return a frame with the contents of the given frame for use in this FrameManager.
310     * Note, that there is a substantial cost involved in moving a Frame from one FrameManager to
311     * another. This may be called from any thread. After the frame has been imported, it may be
312     * used in the runner that uses this FrameManager. As the new frame may share data with the
313     * provided frame, that frame must be read-only.
314     *
315     * @param frame The frame to import
316     */
317    public Frame importFrame(Frame frame) {
318        if (!frame.isReadOnly()) {
319            throw new IllegalArgumentException("Frame " + frame + " must be read-only to import "
320                    + "into another FrameManager!");
321        }
322        return frame.makeCpuCopy(this);
323    }
324
325    /**
326     * Adds a new frame slot to the frame manager.
327     * Filters can reference frame slots to pass frames between graphs or runs. If the name
328     * specified here is already taken the frame slot is overwritten. You can only
329     * modify frame-slots while no graph of the frame manager is running.
330     *
331     * @param name The name of the slot.
332     * @param type The type of Frame that will be assigned to this slot.
333     * @param flags A mask of {@code SLOT} flags.
334     */
335    public void addFrameSlot(String name, FrameType type, int flags) {
336        assertNotRunning();
337        FrameSlot oldSlot = mFrameSlots.get(name);
338        if (oldSlot != null) {
339            removeFrameSlot(name);
340        }
341        FrameSlot slot = new FrameSlot(type, flags);
342        mFrameSlots.put(name, slot);
343    }
344
345    /**
346     * Removes a frame slot from the frame manager.
347     * Any frame within the slot is released. You can only modify frame-slots while no graph
348     * of the frame manager is running.
349     *
350     * @param name The name of the slot
351     * @throws IllegalArgumentException if no such slot exists.
352     */
353    public void removeFrameSlot(String name) {
354        assertNotRunning();
355        FrameSlot slot = getSlot(name);
356        slot.releaseFrame();
357        mFrameSlots.remove(slot);
358    }
359
360    /**
361     * TODO: Document!
362     */
363    public void storeFrame(Frame frame, String slotName) {
364        assertInGraphRun();
365        getSlot(slotName).assignFrame(frame);
366    }
367
368    /**
369     * TODO: Document!
370     */
371    public Frame fetchFrame(String slotName) {
372        assertInGraphRun();
373        return getSlot(slotName).getFrame();
374    }
375
376    /**
377     * Clears the Frame cache.
378     */
379    public void clearCache() {
380        mCache.clear();
381    }
382
383    /**
384     * Create a new FrameManager instance.
385     *
386     * Creates a new FrameManager instance in the specified context and employing a cache with the
387     * specified cache type (see the cache type constants defined by the FrameManager class).
388     *
389     * @param runner the GraphRunner to bind the FrameManager to.
390     * @param cacheType the type of cache to use.
391     */
392    FrameManager(GraphRunner runner, int cacheType) {
393        mRunner = runner;
394        switch (cacheType) {
395            case FRAME_CACHE_NONE:
396                mCache = new BackingCacheNone();
397                break;
398            case FRAME_CACHE_LRU:
399                mCache = new BackingCacheLru();
400                break;
401            case FRAME_CACHE_LFU:
402                mCache = new BackingCacheLfu();
403                break;
404            default:
405                throw new IllegalArgumentException("Unknown cache-type " + cacheType + "!");
406        }
407    }
408
409    Backing fetchBacking(int mode, int access, int[] dimensions, int elemSize) {
410        return mCache.fetchBacking(mode, access, dimensions, elemSize);
411    }
412
413    void onBackingCreated(Backing backing) {
414        if (backing != null) {
415            mBackings.add(backing);
416            // Log.i("FrameManager", "RM: Now have " + mBackings.size() + " backings");
417        }
418    }
419
420    void onBackingAvailable(Backing backing) {
421        if (!backing.shouldCache() || !mCache.cacheBacking(backing)) {
422            backing.destroy();
423            mBackings.remove(backing);
424            //Log.i("FrameManager", "RM: Now have " + mBackings.size() + " backings (" + mCache.getSizeLeft() + ")");
425        }
426    }
427
428    /**
429     * Destroying all references makes any Frames that contain them invalid.
430     */
431    void destroyBackings() {
432        for (Backing backing : mBackings) {
433            backing.destroy();
434        }
435        mBackings.clear();
436        mCache.clear();
437    }
438
439    FrameSlot getSlot(String name) {
440        FrameSlot slot = mFrameSlots.get(name);
441        if (slot == null) {
442            throw new IllegalArgumentException("Unknown frame slot '" + name + "'!");
443        }
444        return slot;
445    }
446
447    void onBeginRun() {
448        for (FrameSlot slot : mFrameSlots.values()) {
449            slot.markWritable();
450        }
451    }
452
453    // Internals ///////////////////////////////////////////////////////////////////////////////////
454    private static boolean dimensionsCompatible(int[] dimA, int[] dimB) {
455        return dimA == null || dimB == null || Arrays.equals(dimA, dimB);
456    }
457
458    private void assertNotRunning() {
459        if (mRunner.isRunning()) {
460            throw new IllegalStateException("Attempting to modify FrameManager while graph is "
461                + "running!");
462        }
463    }
464
465    private void assertInGraphRun() {
466        if (!mRunner.isRunning() || GraphRunner.current() != mRunner) {
467            throw new IllegalStateException("Attempting to access FrameManager Frame data "
468                + "outside of graph run-loop!");
469        }
470    }
471
472}
473
474