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