summaryrefslogtreecommitdiffstats
path: root/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx
diff options
context:
space:
mode:
Diffstat (limited to 'mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx')
-rw-r--r--mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/AndroidVsync.java72
-rw-r--r--mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/CompositorSurfaceManager.java26
-rw-r--r--mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/GeckoSurface.java151
-rw-r--r--mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/GeckoSurfaceTexture.java314
-rw-r--r--mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/PanningPerfAPI.java71
-rw-r--r--mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/RemoteSurfaceAllocator.java77
-rw-r--r--mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/SurfaceAllocator.java139
-rw-r--r--mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/SurfaceControlManager.java111
-rw-r--r--mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/SurfaceTextureListener.java38
-rw-r--r--mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/SyncConfig.java59
10 files changed, 1058 insertions, 0 deletions
diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/AndroidVsync.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/AndroidVsync.java
new file mode 100644
index 0000000000..c87bf466d0
--- /dev/null
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/AndroidVsync.java
@@ -0,0 +1,72 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.gfx;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.view.Choreographer;
+import org.mozilla.gecko.annotation.WrapForJNI;
+import org.mozilla.gecko.mozglue.JNIObject;
+
+/** This class receives HW vsync events through a {@link Choreographer}. */
+@WrapForJNI
+/* package */ final class AndroidVsync extends JNIObject implements Choreographer.FrameCallback {
+ @WrapForJNI
+ @Override // JNIObject
+ protected native void disposeNative();
+
+ private static final String LOGTAG = "AndroidVsync";
+
+ /* package */ Choreographer mChoreographer;
+ private volatile boolean mObservingVsync;
+
+ public AndroidVsync() {
+ final Handler mainHandler = new Handler(Looper.getMainLooper());
+ mainHandler.post(
+ new Runnable() {
+ @Override
+ public void run() {
+ mChoreographer = Choreographer.getInstance();
+ if (mObservingVsync) {
+ mChoreographer.postFrameCallback(AndroidVsync.this);
+ }
+ }
+ });
+ }
+
+ @WrapForJNI(stubName = "NotifyVsync")
+ private native void nativeNotifyVsync(final long frameTimeNanos);
+
+ // Choreographer callback implementation.
+ public void doFrame(final long frameTimeNanos) {
+ if (mObservingVsync) {
+ mChoreographer.postFrameCallback(this);
+ nativeNotifyVsync(frameTimeNanos);
+ }
+ }
+
+ /**
+ * Start/stop observing Vsync event.
+ *
+ * @param enable true to start observing; false to stop.
+ * @return true if observing and false if not.
+ */
+ @WrapForJNI
+ public synchronized boolean observeVsync(final boolean enable) {
+ if (mObservingVsync != enable) {
+ mObservingVsync = enable;
+
+ if (mChoreographer != null) {
+ if (enable) {
+ mChoreographer.postFrameCallback(this);
+ } else {
+ mChoreographer.removeFrameCallback(this);
+ }
+ }
+ }
+ return mObservingVsync;
+ }
+}
diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/CompositorSurfaceManager.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/CompositorSurfaceManager.java
new file mode 100644
index 0000000000..1378a284b7
--- /dev/null
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/CompositorSurfaceManager.java
@@ -0,0 +1,26 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.gfx;
+
+import android.os.RemoteException;
+import android.view.Surface;
+import org.mozilla.gecko.annotation.WrapForJNI;
+
+public final class CompositorSurfaceManager {
+ private static final String LOGTAG = "CompSurfManager";
+
+ private ICompositorSurfaceManager mManager;
+
+ public CompositorSurfaceManager(final ICompositorSurfaceManager aManager) {
+ mManager = aManager;
+ }
+
+ @WrapForJNI(exceptionMode = "nsresult")
+ public synchronized void onSurfaceChanged(final int widgetId, final Surface surface)
+ throws RemoteException {
+ mManager.onSurfaceChanged(widgetId, surface);
+ }
+}
diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/GeckoSurface.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/GeckoSurface.java
new file mode 100644
index 0000000000..d533d2ad39
--- /dev/null
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/GeckoSurface.java
@@ -0,0 +1,151 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.gfx;
+
+import static org.mozilla.geckoview.BuildConfig.DEBUG_BUILD;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.Surface;
+import org.mozilla.gecko.annotation.WrapForJNI;
+
+public final class GeckoSurface implements Parcelable {
+ private static final String LOGTAG = "GeckoSurface";
+
+ private Surface mSurface;
+ private long mHandle;
+ private boolean mIsSingleBuffer;
+ private volatile boolean mIsAvailable;
+ private boolean mOwned = true;
+ private volatile boolean mIsReleased = false;
+
+ private int mMyPid;
+ // Locally allocated surface/texture. Do not pass it over IPC.
+ private GeckoSurface mSyncSurface;
+
+ @WrapForJNI(exceptionMode = "nsresult")
+ public GeckoSurface(final GeckoSurfaceTexture gst) {
+ mSurface = new Surface(gst);
+ mHandle = gst.getHandle();
+ mIsSingleBuffer = gst.isSingleBuffer();
+ mIsAvailable = true;
+ mMyPid = android.os.Process.myPid();
+ }
+
+ public GeckoSurface(final Parcel p) {
+ mSurface = Surface.CREATOR.createFromParcel(p);
+ mHandle = p.readLong();
+ mIsSingleBuffer = p.readByte() == 1;
+ mIsAvailable = p.readByte() == 1;
+ mMyPid = p.readInt();
+ }
+
+ public static final Parcelable.Creator<GeckoSurface> CREATOR =
+ new Parcelable.Creator<GeckoSurface>() {
+ public GeckoSurface createFromParcel(final Parcel p) {
+ return new GeckoSurface(p);
+ }
+
+ public GeckoSurface[] newArray(final int size) {
+ return new GeckoSurface[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(final Parcel out, final int flags) {
+ mSurface.writeToParcel(out, flags);
+ if ((flags & Parcelable.PARCELABLE_WRITE_RETURN_VALUE) == 0) {
+ // GeckoSurface can be passed across processes as a return value or
+ // an argument, and should always tranfers its ownership (move) to
+ // the receiver of parcel. On the other hand, Surface is moved only
+ // when passed as a return value and releases itself when corresponding
+ // write flags is provided. (See Surface.writeToParcel().)
+ // The superclass method must be called here to ensure the local instance
+ // is truely forgotten.
+ mSurface.release();
+ }
+ mOwned = false;
+
+ out.writeLong(mHandle);
+ out.writeByte((byte) (mIsSingleBuffer ? 1 : 0));
+ out.writeByte((byte) (mIsAvailable ? 1 : 0));
+ out.writeInt(mMyPid);
+ }
+
+ public void release() {
+ if (mIsReleased) {
+ return;
+ }
+ mIsReleased = true;
+
+ if (mSyncSurface != null) {
+ mSyncSurface.release();
+ final GeckoSurfaceTexture gst = GeckoSurfaceTexture.lookup(mSyncSurface.getHandle());
+ if (gst != null) {
+ gst.decrementUse();
+ }
+ mSyncSurface = null;
+ }
+
+ if (mOwned) {
+ mSurface.release();
+ }
+ }
+
+ @WrapForJNI
+ public long getHandle() {
+ return mHandle;
+ }
+
+ @WrapForJNI
+ public Surface getSurface() {
+ return mSurface;
+ }
+
+ @WrapForJNI
+ public boolean getAvailable() {
+ return mIsAvailable;
+ }
+
+ @WrapForJNI
+ public boolean isReleased() {
+ return mIsReleased;
+ }
+
+ @WrapForJNI
+ public void setAvailable(final boolean available) {
+ mIsAvailable = available;
+ }
+
+ /* package */ boolean inProcess() {
+ return android.os.Process.myPid() == mMyPid;
+ }
+
+ /* package */ SyncConfig initSyncSurface(final int width, final int height) {
+ if (DEBUG_BUILD) {
+ if (inProcess()) {
+ throw new AssertionError("no need for sync when allocated in process");
+ }
+ }
+ if (GeckoSurfaceTexture.lookup(mHandle) != null) {
+ throw new AssertionError("texture#" + mHandle + " already in use.");
+ }
+ final GeckoSurfaceTexture texture = GeckoSurfaceTexture.acquire(true, mHandle);
+ if (texture != null) {
+ texture.setDefaultBufferSize(width, height);
+ texture.track(mHandle);
+ mSyncSurface = new GeckoSurface(texture);
+ return new SyncConfig(mHandle, mSyncSurface, width, height);
+ }
+
+ return null;
+ }
+}
diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/GeckoSurfaceTexture.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/GeckoSurfaceTexture.java
new file mode 100644
index 0000000000..b063fc9c2c
--- /dev/null
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/GeckoSurfaceTexture.java
@@ -0,0 +1,314 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.gfx;
+
+import android.graphics.SurfaceTexture;
+import android.os.Build;
+import android.util.Log;
+import android.util.LongSparseArray;
+import java.util.LinkedList;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.mozilla.gecko.GeckoAppShell;
+import org.mozilla.gecko.annotation.WrapForJNI;
+import org.mozilla.gecko.mozglue.JNIObject;
+
+/* package */ final class GeckoSurfaceTexture extends SurfaceTexture {
+ private static final String LOGTAG = "GeckoSurfaceTexture";
+ private static final int MAX_SURFACE_TEXTURES = 200;
+ private static final LongSparseArray<GeckoSurfaceTexture> sSurfaceTextures =
+ new LongSparseArray<GeckoSurfaceTexture>();
+
+ private static LongSparseArray<LinkedList<GeckoSurfaceTexture>> sUnusedTextures =
+ new LongSparseArray<LinkedList<GeckoSurfaceTexture>>();
+
+ private long mHandle;
+ private boolean mIsSingleBuffer;
+
+ private long mAttachedContext;
+ private int mTexName;
+
+ private GeckoSurfaceTexture.Callbacks mListener;
+ private AtomicInteger mUseCount;
+ private boolean mFinalized;
+
+ private long mUpstream;
+ private NativeGLBlitHelper mBlitter;
+
+ private GeckoSurfaceTexture(final long handle) {
+ super(0);
+ init(handle, false);
+ }
+
+ private GeckoSurfaceTexture(final long handle, final boolean singleBufferMode) {
+ super(0, singleBufferMode);
+ init(handle, singleBufferMode);
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ // We only want finalize() to be called once
+ if (mFinalized) {
+ return;
+ }
+
+ mFinalized = true;
+ super.finalize();
+ }
+
+ private void init(final long handle, final boolean singleBufferMode) {
+ mHandle = handle;
+ mIsSingleBuffer = singleBufferMode;
+ mUseCount = new AtomicInteger(1);
+
+ // Start off detached
+ detachFromGLContext();
+ }
+
+ @WrapForJNI
+ public long getHandle() {
+ return mHandle;
+ }
+
+ @WrapForJNI
+ public int getTexName() {
+ return mTexName;
+ }
+
+ @WrapForJNI(exceptionMode = "nsresult")
+ public synchronized void attachToGLContext(final long context, final int texName) {
+ if (context == mAttachedContext && texName == mTexName) {
+ return;
+ }
+
+ attachToGLContext(texName);
+
+ mAttachedContext = context;
+ mTexName = texName;
+ }
+
+ @Override
+ @WrapForJNI(exceptionMode = "nsresult")
+ public synchronized void detachFromGLContext() {
+ super.detachFromGLContext();
+
+ mAttachedContext = mTexName = 0;
+ }
+
+ @WrapForJNI
+ public synchronized boolean isAttachedToGLContext(final long context) {
+ return mAttachedContext == context;
+ }
+
+ @WrapForJNI
+ public boolean isSingleBuffer() {
+ return mIsSingleBuffer;
+ }
+
+ @Override
+ @WrapForJNI
+ public synchronized void updateTexImage() {
+ try {
+ if (mUpstream != 0) {
+ SurfaceAllocator.sync(mUpstream);
+ }
+ super.updateTexImage();
+ if (mListener != null) {
+ mListener.onUpdateTexImage();
+ }
+ } catch (final Exception e) {
+ Log.w(LOGTAG, "updateTexImage() failed", e);
+ }
+ }
+
+ @Override
+ public synchronized void release() {
+ mUpstream = 0;
+ if (mBlitter != null) {
+ mBlitter.close();
+ }
+ try {
+ super.release();
+ synchronized (sSurfaceTextures) {
+ sSurfaceTextures.remove(mHandle);
+ }
+ } catch (final Exception e) {
+ Log.w(LOGTAG, "release() failed", e);
+ }
+ }
+
+ @Override
+ @WrapForJNI
+ public synchronized void releaseTexImage() {
+ if (!mIsSingleBuffer) {
+ return;
+ }
+
+ try {
+ super.releaseTexImage();
+ if (mListener != null) {
+ mListener.onReleaseTexImage();
+ }
+ } catch (final Exception e) {
+ Log.w(LOGTAG, "releaseTexImage() failed", e);
+ }
+ }
+
+ public synchronized void setListener(final GeckoSurfaceTexture.Callbacks listener) {
+ mListener = listener;
+ }
+
+ @WrapForJNI
+ public synchronized void incrementUse() {
+ mUseCount.incrementAndGet();
+ }
+
+ @WrapForJNI
+ public synchronized void decrementUse() {
+ final int useCount = mUseCount.decrementAndGet();
+
+ if (useCount == 0) {
+ setListener(null);
+
+ if (mAttachedContext == 0) {
+ release();
+ synchronized (sUnusedTextures) {
+ sSurfaceTextures.remove(mHandle);
+ }
+ return;
+ }
+
+ synchronized (sUnusedTextures) {
+ LinkedList<GeckoSurfaceTexture> list = sUnusedTextures.get(mAttachedContext);
+ if (list == null) {
+ list = new LinkedList<GeckoSurfaceTexture>();
+ sUnusedTextures.put(mAttachedContext, list);
+ }
+ list.addFirst(this);
+ }
+ }
+ }
+
+ @WrapForJNI
+ public static void destroyUnused(final long context) {
+ final LinkedList<GeckoSurfaceTexture> list;
+ synchronized (sUnusedTextures) {
+ list = sUnusedTextures.get(context);
+ sUnusedTextures.delete(context);
+ }
+
+ if (list == null) {
+ return;
+ }
+
+ for (final GeckoSurfaceTexture tex : list) {
+ try {
+ if (tex.isSingleBuffer()) {
+ tex.releaseTexImage();
+ }
+
+ tex.detachFromGLContext();
+ tex.release();
+
+ // We need to manually call finalize here, otherwise we can run out
+ // of file descriptors if the GC doesn't kick in soon enough. Bug 1416015.
+ try {
+ tex.finalize();
+ } catch (final Throwable t) {
+ Log.e(LOGTAG, "Failed to finalize SurfaceTexture", t);
+ }
+ } catch (final Exception e) {
+ Log.e(LOGTAG, "Failed to destroy SurfaceTexture", e);
+ }
+ }
+ }
+
+ public static GeckoSurfaceTexture acquire(final boolean singleBufferMode, final long handle) {
+ // Attempting to create a SurfaceTexture from an isolated process on Android versions prior to
+ // 8.0 results in an indefinite hang. See bug 1706656.
+ if (GeckoAppShell.isIsolatedProcess() && Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
+ return null;
+ }
+
+ synchronized (sSurfaceTextures) {
+ // We want to limit the maximum number of SurfaceTextures at any one time.
+ // This is because they use a large number of fds, and once the process' limit
+ // is reached bad things happen. See bug 1421586.
+ if (sSurfaceTextures.size() >= MAX_SURFACE_TEXTURES) {
+ return null;
+ }
+
+ if (sSurfaceTextures.indexOfKey(handle) >= 0) {
+ throw new IllegalArgumentException("Already have a GeckoSurfaceTexture with that handle");
+ }
+
+ final GeckoSurfaceTexture gst = new GeckoSurfaceTexture(handle, singleBufferMode);
+
+ sSurfaceTextures.put(handle, gst);
+
+ return gst;
+ }
+ }
+
+ @WrapForJNI
+ public static GeckoSurfaceTexture lookup(final long handle) {
+ synchronized (sSurfaceTextures) {
+ return sSurfaceTextures.get(handle);
+ }
+ }
+
+ /* package */ synchronized void track(final long upstream) {
+ mUpstream = upstream;
+ }
+
+ /* package */ synchronized void configureSnapshot(
+ final GeckoSurface target, final int width, final int height) {
+ mBlitter = NativeGLBlitHelper.create(mHandle, target, width, height);
+ }
+
+ /* package */ synchronized void takeSnapshot() {
+ mBlitter.blit();
+ }
+
+ public interface Callbacks {
+ void onUpdateTexImage();
+
+ void onReleaseTexImage();
+ }
+
+ @WrapForJNI
+ public static final class NativeGLBlitHelper extends JNIObject {
+ public static NativeGLBlitHelper create(
+ final long textureHandle,
+ final GeckoSurface targetSurface,
+ final int width,
+ final int height) {
+ final NativeGLBlitHelper helper = nativeCreate(textureHandle, targetSurface, width, height);
+ helper.mTargetSurface = targetSurface; // Take ownership of surface.
+ return helper;
+ }
+
+ public static native NativeGLBlitHelper nativeCreate(
+ final long textureHandle,
+ final GeckoSurface targetSurface,
+ final int width,
+ final int height);
+
+ public native void blit();
+
+ public void close() {
+ disposeNative();
+ if (mTargetSurface != null) {
+ mTargetSurface.release();
+ mTargetSurface = null;
+ }
+ }
+
+ @Override
+ protected native void disposeNative();
+
+ private GeckoSurface mTargetSurface;
+ }
+}
diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/PanningPerfAPI.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/PanningPerfAPI.java
new file mode 100644
index 0000000000..b8ceb74f0b
--- /dev/null
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/PanningPerfAPI.java
@@ -0,0 +1,71 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.gfx;
+
+import android.os.SystemClock;
+import android.util.Log;
+import java.util.ArrayList;
+import java.util.List;
+import org.mozilla.gecko.annotation.RobocopTarget;
+
+public final class PanningPerfAPI {
+ private static final String LOGTAG = "GeckoPanningPerfAPI";
+
+ // make this large enough to avoid having to resize the frame time
+ // list, as that may be expensive and impact the thing we're trying
+ // to measure.
+ private static final int EXPECTED_FRAME_COUNT = 2048;
+
+ private static boolean mRecordingFrames;
+ private static List<Long> mFrameTimes;
+ private static long mFrameStartTime;
+
+ private static void initialiseRecordingArrays() {
+ if (mFrameTimes == null) {
+ mFrameTimes = new ArrayList<Long>(EXPECTED_FRAME_COUNT);
+ } else {
+ mFrameTimes.clear();
+ }
+ }
+
+ @RobocopTarget
+ public static void startFrameTimeRecording() {
+ if (mRecordingFrames) {
+ Log.e(LOGTAG, "Error: startFrameTimeRecording() called while already recording!");
+ return;
+ }
+ mRecordingFrames = true;
+ initialiseRecordingArrays();
+ mFrameStartTime = SystemClock.uptimeMillis();
+ }
+
+ @RobocopTarget
+ public static List<Long> stopFrameTimeRecording() {
+ if (!mRecordingFrames) {
+ Log.e(LOGTAG, "Error: stopFrameTimeRecording() called when not recording!");
+ return null;
+ }
+ mRecordingFrames = false;
+ return mFrameTimes;
+ }
+
+ public static void recordFrameTime() {
+ // this will be called often, so try to make it as quick as possible
+ if (mRecordingFrames) {
+ mFrameTimes.add(SystemClock.uptimeMillis() - mFrameStartTime);
+ }
+ }
+
+ @RobocopTarget
+ public static void startCheckerboardRecording() {
+ throw new UnsupportedOperationException();
+ }
+
+ @RobocopTarget
+ public static List<Float> stopCheckerboardRecording() {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/RemoteSurfaceAllocator.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/RemoteSurfaceAllocator.java
new file mode 100644
index 0000000000..3244519da1
--- /dev/null
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/RemoteSurfaceAllocator.java
@@ -0,0 +1,77 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.gfx;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+public final class RemoteSurfaceAllocator extends ISurfaceAllocator.Stub {
+ private static final String LOGTAG = "RemoteSurfaceAllocator";
+
+ private static RemoteSurfaceAllocator mInstance;
+
+ private final int mAllocatorId;
+ /// Monotonically increasing counter used to generate unique handles
+ /// for each SurfaceTexture by combining with mAllocatorId.
+ private static AtomicInteger sNextHandle = new AtomicInteger(1);
+
+ /**
+ * Retrieves the singleton allocator instance for this process.
+ *
+ * @param allocatorId A unique ID identifying the process this instance belongs to, which must be
+ * 0 for the parent process instance.
+ */
+ public static synchronized RemoteSurfaceAllocator getInstance(final int allocatorId) {
+ if (mInstance == null) {
+ mInstance = new RemoteSurfaceAllocator(allocatorId);
+ }
+ return mInstance;
+ }
+
+ private RemoteSurfaceAllocator(final int allocatorId) {
+ mAllocatorId = allocatorId;
+ }
+
+ @Override
+ public GeckoSurface acquireSurface(
+ final int width, final int height, final boolean singleBufferMode) {
+ final long handle = ((long) mAllocatorId << 32) | sNextHandle.getAndIncrement();
+ final GeckoSurfaceTexture gst = GeckoSurfaceTexture.acquire(singleBufferMode, handle);
+
+ if (gst == null) {
+ return null;
+ }
+
+ if (width > 0 && height > 0) {
+ gst.setDefaultBufferSize(width, height);
+ }
+
+ return new GeckoSurface(gst);
+ }
+
+ @Override
+ public void releaseSurface(final long handle) {
+ final GeckoSurfaceTexture gst = GeckoSurfaceTexture.lookup(handle);
+ if (gst != null) {
+ gst.decrementUse();
+ }
+ }
+
+ @Override
+ public void configureSync(final SyncConfig config) {
+ final GeckoSurfaceTexture gst = GeckoSurfaceTexture.lookup(config.sourceTextureHandle);
+ if (gst != null) {
+ gst.configureSnapshot(config.targetSurface, config.width, config.height);
+ }
+ }
+
+ @Override
+ public void sync(final long handle) {
+ final GeckoSurfaceTexture gst = GeckoSurfaceTexture.lookup(handle);
+ if (gst != null) {
+ gst.takeSnapshot();
+ }
+ }
+}
diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/SurfaceAllocator.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/SurfaceAllocator.java
new file mode 100644
index 0000000000..f3cca81a81
--- /dev/null
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/SurfaceAllocator.java
@@ -0,0 +1,139 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.gfx;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.LongSparseArray;
+import org.mozilla.gecko.GeckoAppShell;
+import org.mozilla.gecko.annotation.WrapForJNI;
+import org.mozilla.gecko.process.GeckoProcessManager;
+import org.mozilla.gecko.process.GeckoServiceChildProcess;
+
+/* package */ final class SurfaceAllocator {
+ private static final String LOGTAG = "SurfaceAllocator";
+
+ private static ISurfaceAllocator sAllocator;
+
+ // Keep a reference to all allocated Surfaces, so that we can release them if we lose the
+ // connection to the allocator service.
+ private static final LongSparseArray<GeckoSurface> sSurfaces =
+ new LongSparseArray<GeckoSurface>();
+
+ private static synchronized void ensureConnection() {
+ if (sAllocator != null) {
+ return;
+ }
+
+ try {
+ if (GeckoAppShell.isParentProcess()) {
+ sAllocator = GeckoProcessManager.getInstance().getSurfaceAllocator();
+ } else {
+ sAllocator = GeckoServiceChildProcess.getSurfaceAllocator();
+ }
+
+ if (sAllocator == null) {
+ Log.w(LOGTAG, "Failed to connect to RemoteSurfaceAllocator");
+ return;
+ }
+ sAllocator
+ .asBinder()
+ .linkToDeath(
+ new IBinder.DeathRecipient() {
+ @Override
+ public void binderDied() {
+ Log.w(LOGTAG, "RemoteSurfaceAllocator died");
+ synchronized (SurfaceAllocator.class) {
+ // Our connection to the remote allocator has died, so all our surfaces are
+ // invalid. Release them all now. When their owners attempt to render in to
+ // them they can detect they have been released and allocate new ones instead.
+ for (int i = 0; i < sSurfaces.size(); i++) {
+ sSurfaces.valueAt(i).release();
+ }
+ sSurfaces.clear();
+ sAllocator = null;
+ }
+ }
+ },
+ 0);
+ } catch (final RemoteException e) {
+ Log.w(LOGTAG, "Failed to connect to RemoteSurfaceAllocator", e);
+ sAllocator = null;
+ }
+ }
+
+ @WrapForJNI
+ public static synchronized GeckoSurface acquireSurface(
+ final int width, final int height, final boolean singleBufferMode) {
+ try {
+ ensureConnection();
+
+ if (sAllocator == null) {
+ Log.w(LOGTAG, "Failed to acquire GeckoSurface: not connected");
+ return null;
+ }
+
+ final GeckoSurface surface = sAllocator.acquireSurface(width, height, singleBufferMode);
+ if (surface == null) {
+ Log.w(LOGTAG, "Failed to acquire GeckoSurface: RemoteSurfaceAllocator returned null");
+ return null;
+ }
+ sSurfaces.put(surface.getHandle(), surface);
+
+ if (!surface.inProcess()) {
+ final SyncConfig config = surface.initSyncSurface(width, height);
+ if (config != null) {
+ sAllocator.configureSync(config);
+ }
+ }
+ return surface;
+ } catch (final RemoteException e) {
+ Log.w(LOGTAG, "Failed to acquire GeckoSurface", e);
+ return null;
+ }
+ }
+
+ @WrapForJNI
+ public static synchronized void disposeSurface(final GeckoSurface surface) {
+ // If the surface has already been released (probably due to losing connection to the remote
+ // allocator) then there is nothing to do here.
+ if (surface.isReleased()) {
+ return;
+ }
+
+ sSurfaces.remove(surface.getHandle());
+
+ // Release our Surface
+ surface.release();
+
+ if (sAllocator == null) {
+ return;
+ }
+
+ // Release the SurfaceTexture on the other side. If we have lost connection then do nothing, as
+ // there is nothing on the other side to release.
+ try {
+ if (sAllocator != null) {
+ sAllocator.releaseSurface(surface.getHandle());
+ }
+ } catch (final RemoteException e) {
+ Log.w(LOGTAG, "Failed to release surface texture", e);
+ }
+ }
+
+ public static synchronized void sync(final long upstream) {
+ // Sync from the SurfaceTexture on the other side. If we have lost connection then do nothing,
+ // as there is nothing on the other side to sync from.
+ try {
+ if (sAllocator != null) {
+ sAllocator.sync(upstream);
+ }
+ } catch (final RemoteException e) {
+ Log.w(LOGTAG, "Failed to sync texture", e);
+ }
+ }
+}
diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/SurfaceControlManager.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/SurfaceControlManager.java
new file mode 100644
index 0000000000..7732cc3bc9
--- /dev/null
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/SurfaceControlManager.java
@@ -0,0 +1,111 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.gfx;
+
+import android.os.Build;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import androidx.annotation.RequiresApi;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.WeakHashMap;
+import org.mozilla.gecko.annotation.WrapForJNI;
+
+// A helper class that creates Surfaces from SurfaceControl objects, for the widget to render in to.
+// Unlike the Surfaces provided to the widget directly from the application, these are suitable for
+// use in the GPU process as well as the main process.
+//
+// The reason we must not render directly in to the Surface provided by the application from the GPU
+// process is because of a bug on Android versions 12 and later: when the GPU process dies the
+// Surface is not detached from the dead process' EGL surface, and any subsequent attempts to
+// attach another EGL surface to the Surface will fail.
+//
+// The application is therefore required to provide the SurfaceControl object to a GeckoDisplay
+// whenever rendering in to a SurfaceView. The widget will then obtain a Surface from that
+// SurfaceControl using getChildSurface(). Internally, this creates another SurfaceControl as a
+// child of the provided SurfaceControl, then creates the Surface from that child. If the GPU
+// process dies we are able to simply destroy and recreate the child SurfaceControl objects, thereby
+// avoiding the bug.
+public class SurfaceControlManager {
+ private static final String LOGTAG = "SurfaceControlManager";
+
+ private static final SurfaceControlManager sInstance = new SurfaceControlManager();
+
+ private final WeakHashMap<SurfaceControl, SurfaceControl> mChildSurfaceControls =
+ new WeakHashMap<>();
+
+ @WrapForJNI
+ public static SurfaceControlManager getInstance() {
+ return sInstance;
+ }
+
+ // Returns a Surface of the requested size that will be composited in to the specified
+ // SurfaceControl.
+ @RequiresApi(api = Build.VERSION_CODES.Q)
+ @WrapForJNI(exceptionMode = "abort")
+ public synchronized Surface getChildSurface(
+ final SurfaceControl parent, final int width, final int height) {
+ SurfaceControl child = mChildSurfaceControls.get(parent);
+ if (child == null) {
+ // We must periodically check if any of the SurfaceControls we are managing have been
+ // destroyed, as we are unable to directly listen to their SurfaceViews' surfaceDestroyed
+ // callbacks, and they may not be attached to any compositor when they are destroyed meaning
+ // we cannot perform cleanup in response to the compositor being paused.
+ // Doing so here, when we encounter a new SurfaceControl instance, is a reasonable guess as to
+ // when a previous instance may have been released.
+ final Iterator<Map.Entry<SurfaceControl, SurfaceControl>> it =
+ mChildSurfaceControls.entrySet().iterator();
+ while (it.hasNext()) {
+ final Map.Entry<SurfaceControl, SurfaceControl> entry = it.next();
+ if (!entry.getKey().isValid()) {
+ it.remove();
+ }
+ }
+
+ child = new SurfaceControl.Builder().setParent(parent).setName("GeckoSurface").build();
+ mChildSurfaceControls.put(parent, child);
+ }
+
+ final SurfaceControl.Transaction transaction =
+ new SurfaceControl.Transaction()
+ .setVisibility(child, true)
+ .setBufferSize(child, width, height);
+ transaction.apply();
+ transaction.close();
+
+ return new Surface(child);
+ }
+
+ // Removes an existing parent SurfaceControl and its corresponding child from the manager. This
+ // can be used when we require the next call to getChildSurface() for the specified parent to
+ // create a new child rather than return the existing one.
+ @RequiresApi(api = Build.VERSION_CODES.Q)
+ @WrapForJNI(exceptionMode = "abort")
+ public synchronized void removeSurface(final SurfaceControl parent) {
+ final SurfaceControl child = mChildSurfaceControls.remove(parent);
+ if (child != null) {
+ child.release();
+ }
+ }
+
+ // Must be called whenever the GPU process has died. This destroys all the child SurfaceControls
+ // that have been created, meaning subsequent calls to getChildSurface() will create new ones.
+ @RequiresApi(api = Build.VERSION_CODES.Q)
+ @WrapForJNI(exceptionMode = "abort")
+ public synchronized void onGpuProcessLoss() {
+ for (final SurfaceControl child : mChildSurfaceControls.values()) {
+ // Explicitly reparenting the old SurfaceControl to null ensures SurfaceFlinger does not hold
+ // on to it. We used to not do this in order to avoid a blank screen until we resume rendering
+ // in to a new SurfaceControl, but on some devices this was causing glitches.
+ final SurfaceControl.Transaction transaction =
+ new SurfaceControl.Transaction().reparent(child, null);
+ transaction.apply();
+ transaction.close();
+ child.release();
+ }
+ mChildSurfaceControls.clear();
+ }
+}
diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/SurfaceTextureListener.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/SurfaceTextureListener.java
new file mode 100644
index 0000000000..0ba79d1f42
--- /dev/null
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/SurfaceTextureListener.java
@@ -0,0 +1,38 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.gfx;
+
+import android.graphics.SurfaceTexture;
+import org.mozilla.gecko.annotation.WrapForJNI;
+import org.mozilla.gecko.mozglue.JNIObject;
+
+/* package */ final class SurfaceTextureListener extends JNIObject
+ implements SurfaceTexture.OnFrameAvailableListener {
+ @WrapForJNI(calledFrom = "gecko")
+ private SurfaceTextureListener() {}
+
+ @WrapForJNI(dispatchTo = "gecko")
+ @Override // JNIObject
+ protected native void disposeNative();
+
+ @Override
+ protected void finalize() {
+ disposeNative();
+ }
+
+ @WrapForJNI(stubName = "OnFrameAvailable")
+ private native void nativeOnFrameAvailable();
+
+ @Override // SurfaceTexture.OnFrameAvailableListener
+ public void onFrameAvailable(final SurfaceTexture surfaceTexture) {
+ try {
+ nativeOnFrameAvailable();
+ } catch (final NullPointerException e) {
+ // Ignore exceptions caused by a disposed object, i.e.
+ // getting a callback after this listener is no longer in use.
+ }
+ }
+}
diff --git a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/SyncConfig.java b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/SyncConfig.java
new file mode 100644
index 0000000000..d8e2099ddc
--- /dev/null
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/SyncConfig.java
@@ -0,0 +1,59 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.gfx;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/* package */ final class SyncConfig implements Parcelable {
+ final long sourceTextureHandle;
+ final GeckoSurface targetSurface;
+ final int width;
+ final int height;
+
+ /* package */ SyncConfig(
+ final long sourceTextureHandle,
+ final GeckoSurface targetSurface,
+ final int width,
+ final int height) {
+ this.sourceTextureHandle = sourceTextureHandle;
+ this.targetSurface = targetSurface;
+ this.width = width;
+ this.height = height;
+ }
+
+ public static final Creator<SyncConfig> CREATOR =
+ new Creator<SyncConfig>() {
+ @Override
+ public SyncConfig createFromParcel(final Parcel parcel) {
+ return new SyncConfig(parcel);
+ }
+
+ @Override
+ public SyncConfig[] newArray(final int i) {
+ return new SyncConfig[i];
+ }
+ };
+
+ private SyncConfig(final Parcel parcel) {
+ sourceTextureHandle = parcel.readLong();
+ targetSurface = GeckoSurface.CREATOR.createFromParcel(parcel);
+ width = parcel.readInt();
+ height = parcel.readInt();
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(final Parcel parcel, final int flags) {
+ parcel.writeLong(sourceTextureHandle);
+ targetSurface.writeToParcel(parcel, flags);
+ parcel.writeInt(width);
+ parcel.writeInt(height);
+ }
+}