1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
2  * This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 package org.mozilla.gecko.gfx;
7 
8 import android.graphics.SurfaceTexture;
9 import android.os.Build;
10 import android.support.annotation.RequiresApi;
11 import android.util.Log;
12 
13 import java.util.concurrent.atomic.AtomicInteger;
14 import java.util.HashMap;
15 import java.util.LinkedList;
16 
17 import org.mozilla.gecko.annotation.WrapForJNI;
18 import org.mozilla.gecko.mozglue.JNIObject;
19 
20 /* package */ final class GeckoSurfaceTexture extends SurfaceTexture {
21     private static final String LOGTAG = "GeckoSurfaceTexture";
22     private static final int MAX_SURFACE_TEXTURES = 200;
23     private static volatile int sNextHandle = 1;
24     private static final HashMap<Integer, GeckoSurfaceTexture> sSurfaceTextures = new HashMap<Integer, GeckoSurfaceTexture>();
25 
26 
27     private static HashMap<Long, LinkedList<GeckoSurfaceTexture>> sUnusedTextures =
28         new HashMap<Long, LinkedList<GeckoSurfaceTexture>>();
29 
30     private int mHandle;
31     private boolean mIsSingleBuffer;
32 
33     private long mAttachedContext;
34     private int mTexName;
35 
36     private GeckoSurfaceTexture.Callbacks mListener;
37     private AtomicInteger mUseCount;
38     private boolean mFinalized;
39 
40     private int mUpstream;
41     private NativeGLBlitHelper mBlitter;
42 
GeckoSurfaceTexture(final int handle)43     private GeckoSurfaceTexture(final int handle) {
44         super(0);
45         init(handle, false);
46     }
47 
48     @RequiresApi(api = Build.VERSION_CODES.KITKAT)
GeckoSurfaceTexture(final int handle, final boolean singleBufferMode)49     private GeckoSurfaceTexture(final int handle, final boolean singleBufferMode) {
50         super(0, singleBufferMode);
51         init(handle, singleBufferMode);
52     }
53 
54     @Override
finalize()55     protected void finalize() throws Throwable {
56         // We only want finalize() to be called once
57         if (mFinalized) {
58             return;
59         }
60 
61         mFinalized = true;
62         super.finalize();
63     }
64 
init(final int handle, final boolean singleBufferMode)65     private void init(final int handle, final boolean singleBufferMode) {
66         mHandle = handle;
67         mIsSingleBuffer = singleBufferMode;
68         mUseCount = new AtomicInteger(1);
69 
70         // Start off detached
71         detachFromGLContext();
72     }
73 
74     @WrapForJNI
getHandle()75     public int getHandle() {
76         return mHandle;
77     }
78 
79     @WrapForJNI
getTexName()80     public int getTexName() {
81         return mTexName;
82     }
83 
84     @WrapForJNI(exceptionMode = "nsresult")
attachToGLContext(final long context, final int texName)85     public synchronized void attachToGLContext(final long context, final int texName) {
86         if (context == mAttachedContext && texName == mTexName) {
87             return;
88         }
89 
90         attachToGLContext(texName);
91 
92         mAttachedContext = context;
93         mTexName = texName;
94     }
95 
96     @Override
97     @WrapForJNI(exceptionMode = "nsresult")
detachFromGLContext()98     public synchronized void detachFromGLContext() {
99         super.detachFromGLContext();
100 
101         mAttachedContext = mTexName = 0;
102     }
103 
104     @WrapForJNI
isAttachedToGLContext(final long context)105     public synchronized boolean isAttachedToGLContext(final long context) {
106         return mAttachedContext == context;
107     }
108 
109     @WrapForJNI
isSingleBuffer()110     public boolean isSingleBuffer() {
111         return mIsSingleBuffer;
112     }
113 
114     @Override
115     @WrapForJNI
updateTexImage()116     public synchronized void updateTexImage() {
117         try {
118             if (mUpstream != 0) {
119                 SurfaceAllocator.sync(mUpstream);
120             }
121             super.updateTexImage();
122             if (mListener != null) {
123                 mListener.onUpdateTexImage();
124             }
125         } catch (Exception e) {
126             Log.w(LOGTAG, "updateTexImage() failed", e);
127         }
128     }
129 
130     @Override
release()131     public synchronized void release() {
132         mUpstream = 0;
133         if (mBlitter != null) {
134             mBlitter.disposeNative();
135         }
136         try {
137             super.release();
138             synchronized (sSurfaceTextures) {
139                 sSurfaceTextures.remove(mHandle);
140             }
141         } catch (Exception e) {
142             Log.w(LOGTAG, "release() failed", e);
143         }
144     }
145 
146     @Override
147     @WrapForJNI
releaseTexImage()148     public synchronized void releaseTexImage() {
149         if (!mIsSingleBuffer) {
150             return;
151         }
152 
153         try {
154             super.releaseTexImage();
155             if (mListener != null) {
156                 mListener.onReleaseTexImage();
157             }
158         } catch (Exception e) {
159             Log.w(LOGTAG, "releaseTexImage() failed", e);
160         }
161     }
162 
setListener(final GeckoSurfaceTexture.Callbacks listener)163     public synchronized void setListener(final GeckoSurfaceTexture.Callbacks listener) {
164         mListener = listener;
165     }
166 
167     @WrapForJNI
isSingleBufferSupported()168     public static boolean isSingleBufferSupported() {
169         return Build.VERSION.SDK_INT >= 19;
170     }
171 
172     @WrapForJNI
incrementUse()173     public synchronized void incrementUse() {
174         mUseCount.incrementAndGet();
175     }
176 
177     @WrapForJNI
decrementUse()178     public synchronized void decrementUse() {
179         int useCount = mUseCount.decrementAndGet();
180 
181         if (useCount == 0) {
182             setListener(null);
183 
184             if (mAttachedContext == 0) {
185                 release();
186                 synchronized (sUnusedTextures) {
187                     sSurfaceTextures.remove(mHandle);
188                 }
189                 return;
190             }
191 
192             synchronized (sUnusedTextures) {
193                 LinkedList<GeckoSurfaceTexture> list = sUnusedTextures.get(mAttachedContext);
194                 if (list == null) {
195                     list = new LinkedList<GeckoSurfaceTexture>();
196                     sUnusedTextures.put(mAttachedContext, list);
197                 }
198                 list.addFirst(this);
199             }
200         }
201     }
202 
203     @WrapForJNI
destroyUnused(final long context)204     public static void destroyUnused(final long context) {
205         LinkedList<GeckoSurfaceTexture> list;
206         synchronized (sUnusedTextures) {
207             list = sUnusedTextures.remove(context);
208         }
209 
210         if (list == null) {
211             return;
212         }
213 
214         for (GeckoSurfaceTexture tex : list) {
215             try {
216                 if (tex.isSingleBuffer()) {
217                     tex.releaseTexImage();
218                 }
219 
220                 tex.detachFromGLContext();
221                 tex.release();
222 
223                 // We need to manually call finalize here, otherwise we can run out
224                 // of file descriptors if the GC doesn't kick in soon enough. Bug 1416015.
225                 try {
226                     tex.finalize();
227                 } catch (Throwable t) {
228                     Log.e(LOGTAG, "Failed to finalize SurfaceTexture", t);
229                 }
230             } catch (Exception e) {
231                 Log.e(LOGTAG, "Failed to destroy SurfaceTexture", e);
232             }
233         }
234     }
235 
acquire(final boolean singleBufferMode, final int handle)236     public static GeckoSurfaceTexture acquire(final boolean singleBufferMode, final int handle) {
237         if (singleBufferMode && !isSingleBufferSupported()) {
238             throw new IllegalArgumentException("single buffer mode not supported on API version < 19");
239         }
240 
241         synchronized (sSurfaceTextures) {
242             // We want to limit the maximum number of SurfaceTextures at any one time.
243             // This is because they use a large number of fds, and once the process' limit
244             // is reached bad things happen. See bug 1421586.
245             if (sSurfaceTextures.size() >= MAX_SURFACE_TEXTURES) {
246                 return null;
247             }
248 
249             int resolvedHandle = handle;
250             if (resolvedHandle == 0) {
251                 // Generate new handle value when none specified.
252                 resolvedHandle = sNextHandle++;
253             }
254 
255             final GeckoSurfaceTexture gst;
256             if (isSingleBufferSupported()) {
257                 gst = new GeckoSurfaceTexture(resolvedHandle, singleBufferMode);
258             } else {
259                 gst = new GeckoSurfaceTexture(resolvedHandle);
260             }
261 
262             if (sSurfaceTextures.containsKey(resolvedHandle)) {
263                 gst.release();
264                 throw new IllegalArgumentException("Already have a GeckoSurfaceTexture with that handle");
265             }
266 
267             sSurfaceTextures.put(resolvedHandle, gst);
268             return gst;
269         }
270     }
271 
272     @WrapForJNI
lookup(final int handle)273     public static GeckoSurfaceTexture lookup(final int handle) {
274         synchronized (sSurfaceTextures) {
275             return sSurfaceTextures.get(handle);
276         }
277     }
278 
track(final int upstream)279     /* package */ synchronized void track(final int upstream) {
280         mUpstream = upstream;
281     }
282 
configureSnapshot(final GeckoSurface target, final int width, final int height)283     /* package */ synchronized void configureSnapshot(final GeckoSurface target,
284                                                       final int width, final int height) {
285         mBlitter = NativeGLBlitHelper.create(mHandle, target, width, height);
286     }
287 
takeSnapshot()288     /* package */ synchronized void takeSnapshot() {
289         mBlitter.blit();
290     }
291 
292     public interface Callbacks {
onUpdateTexImage()293         void onUpdateTexImage();
onReleaseTexImage()294         void onReleaseTexImage();
295     }
296 
297     @WrapForJNI
298     public static final class NativeGLBlitHelper extends JNIObject {
create(int textureHandle, GeckoSurface targetSurface, int width, int height)299         public native static NativeGLBlitHelper create(int textureHandle,
300                                                        GeckoSurface targetSurface,
301                                                        int width,
302                                                        int height);
blit()303         public native void blit();
304 
305         @Override
disposeNative()306         protected native void disposeNative();
307     }
308 }
309