1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5  * in compliance with the License. You may obtain a copy of the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the License
10  * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11  * or implied. See the License for the specific language governing permissions and limitations under
12  * the License.
13  */
14 
15 package androidx.media.filterfw;
16 
17 import android.graphics.Bitmap;
18 import android.util.Log;
19 
20 import java.nio.ByteBuffer;
21 import java.util.Arrays;
22 import java.util.Vector;
23 
24 final class BackingStore {
25 
26     /** Access mode None: Frame data will not be accessed at all. */
27     static final int ACCESS_NONE = 0x00;
28     /** Access mode Bytes: Frame data will be accessed as a ByteBuffer. */
29     static final int ACCESS_BYTES = 0x01;
30     /** Access mode Texture: Frame data will be accessed as a TextureSource. */
31     static final int ACCESS_TEXTURE = 0x02;
32     /** Access mode RenderTarget: Frame data will be accessed as a RenderTarget. */
33     static final int ACCESS_RENDERTARGET = 0x04;
34     /** Access mode Object: Frame data will be accessed as a generic Object. */
35     static final int ACCESS_OBJECT = 0x08;
36     /** Access mode Bitmap: Frame data will be accessed as a Bitmap. */
37     static final int ACCESS_BITMAP = 0x10;
38 
39     private static final int BACKING_BYTEBUFFER = 1;
40     private static final int BACKING_TEXTURE = 2;
41     private static final int BACKING_OBJECT = 3;
42     private static final int BACKING_BITMAP = 4;
43 
44     private final FrameType mType;
45     private int[] mDimensions;
46     private long mTimestamp = Frame.TIMESTAMP_NOT_SET;
47 
48     private final FrameManager mFrameManager;
49 
50     private Vector<Backing> mBackings = new Vector<Backing>();
51 
52     private boolean mWriteLocked = false;
53     private int mReadLocks = 0;
54 
55     private int mRefCount = 1;
56 
57     /** The most up-to-date data backing */
58     private Backing mCurrentBacking = null;
59 
60     /** The currently locked backing */
61     private Backing mLockedBacking = null;
62 
63     // Public Methods //////////////////////////////////////////////////////////////////////////////
BackingStore(FrameType type, int[] dimensions, FrameManager frameManager)64     public BackingStore(FrameType type, int[] dimensions, FrameManager frameManager) {
65         mType = type;
66         mDimensions = dimensions != null ? Arrays.copyOf(dimensions, dimensions.length) : null;
67         mFrameManager = frameManager;
68     }
69 
getFrameType()70     public FrameType getFrameType() {
71         return mType;
72     }
73 
lockData(int mode, int accessFormat)74     public Object lockData(int mode, int accessFormat) {
75         return lockBacking(mode, accessFormat).lock(accessFormat);
76     }
77 
lockBacking(int mode, int access)78     public Backing lockBacking(int mode, int access) {
79         Backing backing = fetchBacking(mode, access);
80         if (backing == null) {
81             throw new RuntimeException("Could not fetch frame data!");
82         }
83         lock(backing, mode);
84         return backing;
85     }
86 
unlock()87     public boolean unlock() {
88         if (mWriteLocked) {
89             mWriteLocked = false;
90         } else if (mReadLocks > 0) {
91             --mReadLocks;
92         } else {
93             return false;
94         }
95         mLockedBacking.unlock();
96         mLockedBacking = null;
97         return true;
98     }
99 
retain()100     public BackingStore retain() {
101         if (mRefCount >= 10) {
102             Log.w("BackingStore", "High ref-count of " + mRefCount + " on " + this + "!");
103         }
104         if (mRefCount <= 0) {
105             throw new RuntimeException("RETAINING RELEASED");
106         }
107         ++mRefCount;
108         return this;
109     }
110 
release()111     public BackingStore release() {
112         if (mRefCount <= 0) {
113             throw new RuntimeException("DOUBLE-RELEASE");
114         }
115         --mRefCount;
116         if (mRefCount == 0) {
117             releaseBackings();
118             return null;
119         }
120         return this;
121     }
122 
123     /**
124      * Resizes the backing store. This invalidates all data in the store.
125      */
resize(int[] newDimensions)126     public void resize(int[] newDimensions) {
127         Vector<Backing> resized = new Vector<Backing>();
128         for (Backing backing : mBackings) {
129             if (backing.resize(newDimensions)) {
130                 resized.add(backing);
131             } else {
132                 releaseBacking(backing);
133             }
134         }
135         mBackings = resized;
136         mDimensions = newDimensions;
137     }
138 
getDimensions()139     public int[] getDimensions() {
140         return mDimensions;
141     }
142 
getElementCount()143     public int getElementCount() {
144         int result = 1;
145         if (mDimensions != null) {
146             for (int dim : mDimensions) {
147                 result *= dim;
148             }
149         }
150         return result;
151     }
152 
importStore(BackingStore store)153     public void importStore(BackingStore store) {
154         // TODO: Better backing selection?
155         if (store.mBackings.size() > 0) {
156             importBacking(store.mBackings.firstElement());
157         }
158         mTimestamp = store.mTimestamp;
159     }
160 
161     /**
162      * @return the timestamp
163      */
getTimestamp()164     public long getTimestamp() {
165         return mTimestamp;
166     }
167 
168     /**
169      * @param timestamp the timestamp to set
170      */
setTimestamp(long timestamp)171     public void setTimestamp(long timestamp) {
172         mTimestamp = timestamp;
173     }
174 
175     // Internal Methods ////////////////////////////////////////////////////////////////////////////
fetchBacking(int mode, int access)176     private Backing fetchBacking(int mode, int access) {
177         Backing backing = getBacking(mode, access);
178         if (backing == null) {
179             backing = attachNewBacking(mode, access);
180         }
181         syncBacking(backing);
182         return backing;
183     }
184 
syncBacking(Backing backing)185     private void syncBacking(Backing backing) {
186         if (backing != null && backing.isDirty() && mCurrentBacking != null) {
187             backing.syncTo(mCurrentBacking);
188         }
189     }
190 
getBacking(int mode, int access)191     private Backing getBacking(int mode, int access) {
192         // [Non-iterator looping]
193         for (int i = 0; i < mBackings.size(); ++i) {
194             final Backing backing = mBackings.get(i);
195 
196             int backingAccess =
197                     (mode == Frame.MODE_WRITE) ? backing.writeAccess() : backing.readAccess();
198             if ((backingAccess & access) == access) {
199                 return backing;
200             }
201         }
202         return null;
203     }
204 
attachNewBacking(int mode, int access)205     private Backing attachNewBacking(int mode, int access) {
206         Backing backing = createBacking(mode, access);
207         if (mBackings.size() > 0) {
208             backing.markDirty();
209         }
210         mBackings.add(backing);
211         return backing;
212     }
213 
createBacking(int mode, int access)214     private Backing createBacking(int mode, int access) {
215         // TODO: If the read/write access flags indicate, make/fetch a GraphicBuffer backing.
216         Backing backing = null;
217         int elemSize = mType.getElementSize();
218         if (shouldFetchCached(access)) {
219             backing = mFrameManager.fetchBacking(mode, access, mDimensions, elemSize);
220         }
221         if (backing == null) {
222             switch (access) {
223                 case ACCESS_BYTES:
224                     backing = new ByteBufferBacking();
225                     break;
226                 case ACCESS_TEXTURE:
227                 case ACCESS_RENDERTARGET:
228                     backing = new TextureBacking();
229                     break;
230                 case ACCESS_OBJECT:
231                     backing = new ObjectBacking();
232                     break;
233                 case ACCESS_BITMAP:
234                     backing = new BitmapBacking();
235                     break;
236             }
237             if (backing == null) {
238                 throw new RuntimeException(
239                         "Could not create backing for access type " + access + "!");
240             }
241             if (backing.requiresGpu() && !mFrameManager.getRunner().isOpenGLSupported()) {
242                 throw new RuntimeException(
243                         "Cannot create backing that requires GPU in a runner that does not " +
244                         "support OpenGL!");
245             }
246             backing.setDimensions(mDimensions);
247             backing.setElementSize(elemSize);
248             backing.setElementId(mType.getElementId());
249             backing.allocate(mType);
250             mFrameManager.onBackingCreated(backing);
251         }
252         return backing;
253     }
254 
importBacking(Backing backing)255     private void importBacking(Backing backing) {
256         // TODO: This actually needs synchronization between the two BackingStore threads for the
257         // general case
258         int access = backing.requiresGpu() ? ACCESS_BYTES : backing.readAccess();
259         Backing newBacking = createBacking(Frame.MODE_READ, access);
260         newBacking.syncTo(backing);
261         mBackings.add(newBacking);
262         mCurrentBacking = newBacking;
263     }
264 
releaseBackings()265     private void releaseBackings() {
266         // [Non-iterator looping]
267         for (int i = 0; i < mBackings.size(); ++i) {
268             releaseBacking(mBackings.get(i));
269         }
270         mBackings.clear();
271         mCurrentBacking = null;
272     }
273 
releaseBacking(Backing backing)274     private void releaseBacking(Backing backing) {
275         mFrameManager.onBackingAvailable(backing);
276     }
277 
lock(Backing backingToLock, int mode)278     private void lock(Backing backingToLock, int mode) {
279         if (mode == Frame.MODE_WRITE) {
280             // Make sure frame is not read-locked
281             if (mReadLocks > 0) {
282                 throw new RuntimeException(
283                         "Attempting to write-lock the read-locked frame " + this + "!");
284             } else if (mWriteLocked) {
285                 throw new RuntimeException(
286                         "Attempting to write-lock the write-locked frame " + this + "!");
287             }
288             // Mark all other backings dirty
289             // [Non-iterator looping]
290             for (int i = 0; i < mBackings.size(); ++i) {
291                 final Backing backing = mBackings.get(i);
292                 if (backing != backingToLock) {
293                     backing.markDirty();
294                 }
295             }
296             mWriteLocked = true;
297             mCurrentBacking = backingToLock;
298         } else {
299             if (mWriteLocked) {
300                 throw new RuntimeException("Attempting to read-lock locked frame " + this + "!");
301             }
302             ++mReadLocks;
303         }
304         mLockedBacking = backingToLock;
305     }
306 
shouldFetchCached(int access)307     private static boolean shouldFetchCached(int access) {
308         return access != ACCESS_OBJECT;
309     }
310 
311 
312     // Backings ////////////////////////////////////////////////////////////////////////////////////
313     static abstract class Backing {
314         protected int[] mDimensions = null;
315         private int mElementSize;
316         private int mElementID;
317         protected boolean mIsDirty = false;
318 
319         int cachePriority = 0;
320 
allocate(FrameType frameType)321         public abstract void allocate(FrameType frameType);
322 
readAccess()323         public abstract int readAccess();
324 
writeAccess()325         public abstract int writeAccess();
326 
syncTo(Backing backing)327         public abstract void syncTo(Backing backing);
328 
lock(int accessType)329         public abstract Object lock(int accessType);
330 
getType()331         public abstract int getType();
332 
shouldCache()333         public abstract boolean shouldCache();
334 
requiresGpu()335         public abstract boolean requiresGpu();
336 
destroy()337         public abstract void destroy();
338 
getSize()339         public abstract int getSize();
340 
unlock()341         public void unlock() {
342             // Default implementation does nothing.
343         }
344 
setData(Object data)345         public void setData(Object data) {
346             throw new RuntimeException("Internal error: Setting data on frame backing " + this
347                     + ", which does not support setting data directly!");
348         }
349 
setDimensions(int[] dimensions)350         public void setDimensions(int[] dimensions) {
351             mDimensions = dimensions;
352         }
353 
setElementSize(int elemSize)354         public void setElementSize(int elemSize) {
355             mElementSize = elemSize;
356         }
357 
setElementId(int elemId)358         public void setElementId(int elemId) {
359             mElementID = elemId;
360         }
361 
getDimensions()362         public int[] getDimensions() {
363             return mDimensions;
364         }
365 
getElementSize()366         public int getElementSize() {
367             return mElementSize;
368         }
369 
getElementId()370         public int getElementId() {
371             return mElementID;
372         }
373 
resize(int[] newDimensions)374         public boolean resize(int[] newDimensions) {
375             return false;
376         }
377 
markDirty()378         public void markDirty() {
379             mIsDirty = true;
380         }
381 
isDirty()382         public boolean isDirty() {
383             return mIsDirty;
384         }
385 
assertImageCompatible(FrameType type)386         protected void assertImageCompatible(FrameType type) {
387             if (type.getElementId() != FrameType.ELEMENT_RGBA8888) {
388                 throw new RuntimeException("Cannot allocate texture with non-RGBA data type!");
389             } else if (mDimensions == null || mDimensions.length != 2) {
390                 throw new RuntimeException("Cannot allocate non 2-dimensional texture!");
391             }
392         }
393 
394     }
395 
396     static class ObjectBacking extends Backing {
397 
398         private Object mObject = null;
399 
400         @Override
allocate(FrameType frameType)401         public void allocate(FrameType frameType) {
402             mObject = null;
403         }
404 
405         @Override
readAccess()406         public int readAccess() {
407             return ACCESS_OBJECT;
408         }
409 
410         @Override
writeAccess()411         public int writeAccess() {
412             return ACCESS_OBJECT;
413         }
414 
415         @Override
syncTo(Backing backing)416         public void syncTo(Backing backing) {
417             switch (backing.getType()) {
418                 case BACKING_OBJECT:
419                     mObject = backing.lock(ACCESS_OBJECT);
420                     backing.unlock();
421                     break;
422                 case BACKING_BITMAP:
423                     mObject = backing.lock(ACCESS_BITMAP);
424                     backing.unlock();
425                     break;
426                 default:
427                     mObject = null;
428             }
429             mIsDirty = false;
430         }
431 
432         @Override
lock(int accessType)433         public Object lock(int accessType) {
434             return mObject;
435         }
436 
437         @Override
getType()438         public int getType() {
439             return BACKING_OBJECT;
440         }
441 
442         @Override
shouldCache()443         public boolean shouldCache() {
444             return false;
445         }
446 
447         @Override
requiresGpu()448         public boolean requiresGpu() {
449             return false;
450         }
451 
452         @Override
destroy()453         public void destroy() {
454             mObject = null;
455         }
456 
457         @Override
getSize()458         public int getSize() {
459             return 0;
460         }
461 
462         @Override
setData(Object data)463         public void setData(Object data) {
464             mObject = data;
465         }
466 
467     }
468 
469     static class BitmapBacking extends Backing {
470 
471         private Bitmap mBitmap = null;
472 
473         @Override
allocate(FrameType frameType)474         public void allocate(FrameType frameType) {
475             assertImageCompatible(frameType);
476         }
477 
478         @Override
readAccess()479         public int readAccess() {
480             return ACCESS_BITMAP;
481         }
482 
483         @Override
writeAccess()484         public int writeAccess() {
485             return ACCESS_BITMAP;
486         }
487 
488         @Override
syncTo(Backing backing)489         public void syncTo(Backing backing) {
490             int access = backing.readAccess();
491             if ((access & ACCESS_BITMAP) != 0) {
492                 mBitmap = (Bitmap) backing.lock(ACCESS_BITMAP);
493             } else if ((access & ACCESS_BYTES) != 0) {
494                 createBitmap();
495                 ByteBuffer buffer = (ByteBuffer) backing.lock(ACCESS_BYTES);
496                 mBitmap.copyPixelsFromBuffer(buffer);
497                 buffer.rewind();
498             } else if ((access & ACCESS_TEXTURE) != 0) {
499                 createBitmap();
500                 RenderTarget renderTarget = (RenderTarget) backing.lock(ACCESS_RENDERTARGET);
501                 mBitmap.copyPixelsFromBuffer(
502                         renderTarget.getPixelData(mDimensions[0], mDimensions[1]));
503             } else {
504                 throw new RuntimeException("Cannot sync bytebuffer backing!");
505             }
506             backing.unlock();
507             mIsDirty = false;
508         }
509 
510         @Override
lock(int accessType)511         public Object lock(int accessType) {
512             return mBitmap;
513         }
514 
515         @Override
getType()516         public int getType() {
517             return BACKING_BITMAP;
518         }
519 
520         @Override
shouldCache()521         public boolean shouldCache() {
522             return false;
523         }
524 
525         @Override
requiresGpu()526         public boolean requiresGpu() {
527             return false;
528         }
529 
530         @Override
destroy()531         public void destroy() {
532             // As we share the bitmap with other backings (such as object backings), we must not
533             // recycle it here.
534             mBitmap = null;
535         }
536 
537         @Override
getSize()538         public int getSize() {
539             return 4 * mDimensions[0] * mDimensions[1];
540         }
541 
542         @Override
setData(Object data)543         public void setData(Object data) {
544             // We can assume that data will always be a Bitmap instance.
545             mBitmap = (Bitmap) data;
546         }
547 
createBitmap()548         private void createBitmap() {
549             mBitmap = Bitmap.createBitmap(mDimensions[0], mDimensions[1], Bitmap.Config.ARGB_8888);
550         }
551     }
552 
553     static class TextureBacking extends Backing {
554 
555         private RenderTarget mRenderTarget = null;
556         private TextureSource mTexture = null;
557 
558         @Override
allocate(FrameType frameType)559         public void allocate(FrameType frameType) {
560             assertImageCompatible(frameType);
561             mTexture = TextureSource.newTexture();
562         }
563 
564         @Override
readAccess()565         public int readAccess() {
566             return ACCESS_TEXTURE;
567         }
568 
569         @Override
writeAccess()570         public int writeAccess() {
571             return ACCESS_RENDERTARGET;
572         }
573 
574         @Override
syncTo(Backing backing)575         public void syncTo(Backing backing) {
576             int access = backing.readAccess();
577             if ((access & ACCESS_BYTES) != 0) {
578                 ByteBuffer pixels = (ByteBuffer) backing.lock(ACCESS_BYTES);
579                 mTexture.allocateWithPixels(pixels, mDimensions[0], mDimensions[1]);
580             } else if ((access & ACCESS_BITMAP) != 0) {
581                 Bitmap bitmap = (Bitmap) backing.lock(ACCESS_BITMAP);
582                 mTexture.allocateWithBitmapPixels(bitmap);
583             } else if ((access & ACCESS_TEXTURE) != 0) {
584                 TextureSource texture = (TextureSource) backing.lock(ACCESS_TEXTURE);
585                 int w = mDimensions[0];
586                 int h = mDimensions[1];
587                 ImageShader.renderTextureToTarget(texture, getRenderTarget(), w, h);
588             } else {
589                 throw new RuntimeException("Cannot sync bytebuffer backing!");
590             }
591             backing.unlock();
592             mIsDirty = false;
593         }
594 
595         @Override
lock(int accessType)596         public Object lock(int accessType) {
597             switch (accessType) {
598                 case ACCESS_TEXTURE:
599                     return getTexture();
600 
601                 case ACCESS_RENDERTARGET:
602                     return getRenderTarget();
603 
604                 default:
605                     throw new RuntimeException("Illegal access to texture!");
606             }
607         }
608 
609         @Override
getType()610         public int getType() {
611             return BACKING_TEXTURE;
612         }
613 
614         @Override
shouldCache()615         public boolean shouldCache() {
616             return true;
617         }
618 
619         @Override
requiresGpu()620         public boolean requiresGpu() {
621             return true;
622         }
623 
624         @Override
destroy()625         public void destroy() {
626             if (mRenderTarget != null) {
627                 mRenderTarget.release();
628             }
629             if (mTexture.isAllocated()) {
630                 mTexture.release();
631             }
632         }
633 
634         @Override
getSize()635         public int getSize() {
636             return 4 * mDimensions[0] * mDimensions[1];
637         }
638 
getTexture()639         private TextureSource getTexture() {
640             if (!mTexture.isAllocated()) {
641                 mTexture.allocate(mDimensions[0], mDimensions[1]);
642             }
643             return mTexture;
644         }
645 
getRenderTarget()646         private RenderTarget getRenderTarget() {
647             if (mRenderTarget == null) {
648                 int w = mDimensions[0];
649                 int h = mDimensions[1];
650                 mRenderTarget = RenderTarget.currentTarget().forTexture(getTexture(), w, h);
651             }
652             return mRenderTarget;
653         }
654 
655     }
656 
657     static class ByteBufferBacking extends Backing {
658 
659         ByteBuffer mBuffer = null;
660 
661         @Override
allocate(FrameType frameType)662         public void allocate(FrameType frameType) {
663             int size = frameType.getElementSize();
664             for (int dim : mDimensions) {
665                 size *= dim;
666             }
667             mBuffer = ByteBuffer.allocateDirect(size);
668         }
669 
670         @Override
readAccess()671         public int readAccess() {
672             return ACCESS_BYTES;
673         }
674 
675         @Override
writeAccess()676         public int writeAccess() {
677             return ACCESS_BYTES;
678         }
679 
680         @Override
requiresGpu()681         public boolean requiresGpu() {
682             return false;
683         }
684 
685         @Override
syncTo(Backing backing)686         public void syncTo(Backing backing) {
687             int access = backing.readAccess();
688             if ((access & ACCESS_TEXTURE) != 0) {
689                 RenderTarget target = (RenderTarget) backing.lock(ACCESS_RENDERTARGET);
690                 GLToolbox.readTarget(target, mBuffer, mDimensions[0], mDimensions[1]);
691             } else if ((access & ACCESS_BITMAP) != 0) {
692                 Bitmap bitmap = (Bitmap) backing.lock(ACCESS_BITMAP);
693                 bitmap.copyPixelsToBuffer(mBuffer);
694                 mBuffer.rewind();
695             } else if ((access & ACCESS_BYTES) != 0) {
696                 ByteBuffer otherBuffer = (ByteBuffer) backing.lock(ACCESS_BYTES);
697                 mBuffer.put(otherBuffer);
698                 otherBuffer.rewind();
699             } else {
700                 throw new RuntimeException("Cannot sync bytebuffer backing!");
701             }
702             backing.unlock();
703             mBuffer.rewind();
704             mIsDirty = false;
705         }
706 
707         @Override
lock(int accessType)708         public Object lock(int accessType) {
709             return mBuffer.rewind();
710         }
711 
712         @Override
unlock()713         public void unlock() {
714             mBuffer.rewind();
715         }
716 
717         @Override
getType()718         public int getType() {
719             return BACKING_BYTEBUFFER;
720         }
721 
722         @Override
shouldCache()723         public boolean shouldCache() {
724             return true;
725         }
726 
727         @Override
destroy()728         public void destroy() {
729             mBuffer = null;
730         }
731 
732         @Override
getSize()733         public int getSize() {
734             return mBuffer.remaining();
735         }
736 
737     }
738 }
739