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.util.Log;
11 import android.util.LongSparseArray;
12 import android.util.SparseArray;
13 import androidx.annotation.RequiresApi;
14 import java.util.LinkedList;
15 import java.util.concurrent.atomic.AtomicInteger;
16 import org.mozilla.gecko.annotation.WrapForJNI;
17 import org.mozilla.gecko.mozglue.JNIObject;
18 
19 /* package */ final class GeckoSurfaceTexture extends SurfaceTexture {
20   private static final String LOGTAG = "GeckoSurfaceTexture";
21   private static final int MAX_SURFACE_TEXTURES = 200;
22   private static volatile int sNextHandle = 1;
23   private static final SparseArray<GeckoSurfaceTexture> sSurfaceTextures =
24       new SparseArray<GeckoSurfaceTexture>();
25 
26   private static LongSparseArray<LinkedList<GeckoSurfaceTexture>> sUnusedTextures =
27       new LongSparseArray<LinkedList<GeckoSurfaceTexture>>();
28 
29   private int mHandle;
30   private boolean mIsSingleBuffer;
31 
32   private long mAttachedContext;
33   private int mTexName;
34 
35   private GeckoSurfaceTexture.Callbacks mListener;
36   private AtomicInteger mUseCount;
37   private boolean mFinalized;
38 
39   private int mUpstream;
40   private NativeGLBlitHelper mBlitter;
41 
GeckoSurfaceTexture(final int handle)42   private GeckoSurfaceTexture(final int handle) {
43     super(0);
44     init(handle, false);
45   }
46 
47   @RequiresApi(api = Build.VERSION_CODES.KITKAT)
GeckoSurfaceTexture(final int handle, final boolean singleBufferMode)48   private GeckoSurfaceTexture(final int handle, final boolean singleBufferMode) {
49     super(0, singleBufferMode);
50     init(handle, singleBufferMode);
51   }
52 
53   @Override
finalize()54   protected void finalize() throws Throwable {
55     // We only want finalize() to be called once
56     if (mFinalized) {
57       return;
58     }
59 
60     mFinalized = true;
61     super.finalize();
62   }
63 
init(final int handle, final boolean singleBufferMode)64   private void init(final int handle, final boolean singleBufferMode) {
65     mHandle = handle;
66     mIsSingleBuffer = singleBufferMode;
67     mUseCount = new AtomicInteger(1);
68 
69     // Start off detached
70     detachFromGLContext();
71   }
72 
73   @WrapForJNI
getHandle()74   public int getHandle() {
75     return mHandle;
76   }
77 
78   @WrapForJNI
getTexName()79   public int getTexName() {
80     return mTexName;
81   }
82 
83   @WrapForJNI(exceptionMode = "nsresult")
attachToGLContext(final long context, final int texName)84   public synchronized void attachToGLContext(final long context, final int texName) {
85     if (context == mAttachedContext && texName == mTexName) {
86       return;
87     }
88 
89     attachToGLContext(texName);
90 
91     mAttachedContext = context;
92     mTexName = texName;
93   }
94 
95   @Override
96   @WrapForJNI(exceptionMode = "nsresult")
detachFromGLContext()97   public synchronized void detachFromGLContext() {
98     super.detachFromGLContext();
99 
100     mAttachedContext = mTexName = 0;
101   }
102 
103   @WrapForJNI
isAttachedToGLContext(final long context)104   public synchronized boolean isAttachedToGLContext(final long context) {
105     return mAttachedContext == context;
106   }
107 
108   @WrapForJNI
isSingleBuffer()109   public boolean isSingleBuffer() {
110     return mIsSingleBuffer;
111   }
112 
113   @Override
114   @WrapForJNI
updateTexImage()115   public synchronized void updateTexImage() {
116     try {
117       if (mUpstream != 0) {
118         SurfaceAllocator.sync(mUpstream);
119       }
120       super.updateTexImage();
121       if (mListener != null) {
122         mListener.onUpdateTexImage();
123       }
124     } catch (final Exception e) {
125       Log.w(LOGTAG, "updateTexImage() failed", e);
126     }
127   }
128 
129   @Override
release()130   public synchronized void release() {
131     mUpstream = 0;
132     if (mBlitter != null) {
133       mBlitter.close();
134     }
135     try {
136       super.release();
137       synchronized (sSurfaceTextures) {
138         sSurfaceTextures.remove(mHandle);
139       }
140     } catch (final Exception e) {
141       Log.w(LOGTAG, "release() failed", e);
142     }
143   }
144 
145   @Override
146   @WrapForJNI
releaseTexImage()147   public synchronized void releaseTexImage() {
148     if (!mIsSingleBuffer) {
149       return;
150     }
151 
152     try {
153       super.releaseTexImage();
154       if (mListener != null) {
155         mListener.onReleaseTexImage();
156       }
157     } catch (final Exception e) {
158       Log.w(LOGTAG, "releaseTexImage() failed", e);
159     }
160   }
161 
setListener(final GeckoSurfaceTexture.Callbacks listener)162   public synchronized void setListener(final GeckoSurfaceTexture.Callbacks listener) {
163     mListener = listener;
164   }
165 
166   @WrapForJNI
isSingleBufferSupported()167   public static boolean isSingleBufferSupported() {
168     return Build.VERSION.SDK_INT >= 19;
169   }
170 
171   @WrapForJNI
incrementUse()172   public synchronized void incrementUse() {
173     mUseCount.incrementAndGet();
174   }
175 
176   @WrapForJNI
decrementUse()177   public synchronized void decrementUse() {
178     final int useCount = mUseCount.decrementAndGet();
179 
180     if (useCount == 0) {
181       setListener(null);
182 
183       if (mAttachedContext == 0) {
184         release();
185         synchronized (sUnusedTextures) {
186           sSurfaceTextures.remove(mHandle);
187         }
188         return;
189       }
190 
191       synchronized (sUnusedTextures) {
192         LinkedList<GeckoSurfaceTexture> list = sUnusedTextures.get(mAttachedContext);
193         if (list == null) {
194           list = new LinkedList<GeckoSurfaceTexture>();
195           sUnusedTextures.put(mAttachedContext, list);
196         }
197         list.addFirst(this);
198       }
199     }
200   }
201 
202   @WrapForJNI
destroyUnused(final long context)203   public static void destroyUnused(final long context) {
204     final LinkedList<GeckoSurfaceTexture> list;
205     synchronized (sUnusedTextures) {
206       list = sUnusedTextures.get(context);
207       sUnusedTextures.delete(context);
208     }
209 
210     if (list == null) {
211       return;
212     }
213 
214     for (final 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 (final Throwable t) {
228           Log.e(LOGTAG, "Failed to finalize SurfaceTexture", t);
229         }
230       } catch (final 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.indexOfKey(resolvedHandle) >= 0) {
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(
284       final GeckoSurface target, 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();
294 
onReleaseTexImage()295     void onReleaseTexImage();
296   }
297 
298   @WrapForJNI
299   public static final class NativeGLBlitHelper extends JNIObject {
create( final int textureHandle, final GeckoSurface targetSurface, final int width, final int height)300     public static NativeGLBlitHelper create(
301         final int textureHandle,
302         final GeckoSurface targetSurface,
303         final int width,
304         final int height) {
305       final NativeGLBlitHelper helper = nativeCreate(textureHandle, targetSurface, width, height);
306       helper.mTargetSurface = targetSurface; // Take ownership of surface.
307       return helper;
308     }
309 
nativeCreate( final int textureHandle, final GeckoSurface targetSurface, final int width, final int height)310     public static native NativeGLBlitHelper nativeCreate(
311         final int textureHandle,
312         final GeckoSurface targetSurface,
313         final int width,
314         final int height);
315 
blit()316     public native void blit();
317 
close()318     public void close() {
319       disposeNative();
320       if (mTargetSurface != null) {
321         mTargetSurface.release();
322         mTargetSurface = null;
323       }
324     }
325 
326     @Override
disposeNative()327     protected native void disposeNative();
328 
329     private GeckoSurface mTargetSurface;
330   }
331 }
332