Bug 1486659 - p3: copy texture contents for remote allocated Surface. r=snorp
authorJohn Lin <jolin@mozilla.com>
Fri, 14 Dec 2018 21:34:55 +0000
changeset 510957 62064231dfbc0eacabd2f3ddc85033c704c4b7ca
parent 510956 6c89aae076dd9796a916d6b50611a7b2a7ee5d05
child 510958 b0604a326cde0e0bec29aa8e5da68bbfe365ac24
push id1953
push userffxbld-merge
push dateMon, 11 Mar 2019 12:10:20 +0000
treeherdermozilla-release@9c35dcbaa899 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssnorp
bugs1486659
milestone66.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1486659 - p3: copy texture contents for remote allocated Surface. r=snorp Child processes cannot access textures allocated in the parent process, which is needed by the compositor to render video elements efficiently. Unfortunately, Android doesn't expose Sufrace buffers (sharable across processes) in the SDK/NDK as other platforms, so we need to generate extra texture/surface in the child process and update texture images through the surface, which is passed to the parent process for the remote texture to copy its contents into. Differential Revision: https://phabricator.services.mozilla.com/D11939
mobile/android/geckoview/src/main/aidl/org/mozilla/gecko/gfx/ISurfaceAllocator.aidl
mobile/android/geckoview/src/main/aidl/org/mozilla/gecko/gfx/SyncConfig.aidl
mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/GeckoSurface.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/GeckoSurfaceTexture.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/SurfaceAllocator.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/SurfaceAllocatorService.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/SyncConfig.java
--- a/mobile/android/geckoview/src/main/aidl/org/mozilla/gecko/gfx/ISurfaceAllocator.aidl
+++ b/mobile/android/geckoview/src/main/aidl/org/mozilla/gecko/gfx/ISurfaceAllocator.aidl
@@ -1,12 +1,15 @@
 /* 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 org.mozilla.gecko.gfx.GeckoSurface;
+import org.mozilla.gecko.gfx.SyncConfig;
 
 interface ISurfaceAllocator {
     GeckoSurface acquireSurface(in int width, in int height, in boolean singleBufferMode);
     void releaseSurface(in int handle);
+    void configureSync(in SyncConfig config);
+    void sync(in int handle);
 }
copy from mobile/android/geckoview/src/main/aidl/org/mozilla/gecko/gfx/ISurfaceAllocator.aidl
copy to mobile/android/geckoview/src/main/aidl/org/mozilla/gecko/gfx/SyncConfig.aidl
--- a/mobile/android/geckoview/src/main/aidl/org/mozilla/gecko/gfx/ISurfaceAllocator.aidl
+++ b/mobile/android/geckoview/src/main/aidl/org/mozilla/gecko/gfx/SyncConfig.aidl
@@ -1,12 +1,7 @@
 /* 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 org.mozilla.gecko.gfx.GeckoSurface;
-
-interface ISurfaceAllocator {
-    GeckoSurface acquireSurface(in int width, in int height, in boolean singleBufferMode);
-    void releaseSurface(in int handle);
-}
+parcelable SyncConfig;
--- 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
@@ -5,50 +5,53 @@
 
 package org.mozilla.gecko.gfx;
 
 import android.graphics.SurfaceTexture;
 
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.view.Surface;
-import android.util.Log;
-
-import java.util.HashMap;
 
 import org.mozilla.gecko.annotation.WrapForJNI;
 
+import static org.mozilla.geckoview.BuildConfig.DEBUG_BUILD;
+
 public final class GeckoSurface extends Surface {
     private static final String LOGTAG = "GeckoSurface";
 
-    private static final HashMap<Integer, GeckoSurfaceTexture> sSurfaceTextures = new HashMap<Integer, GeckoSurfaceTexture>();
-
     private int mHandle;
     private boolean mIsSingleBuffer;
     private volatile boolean mIsAvailable;
     private boolean mOwned = true;
 
+    private int mMyPid;
+    // Locally allocated surface/texture. Do not pass it over IPC.
+    private GeckoSurface mSyncSurface;
+
     @WrapForJNI(exceptionMode = "nsresult")
     public GeckoSurface(GeckoSurfaceTexture gst) {
         super(gst);
         mHandle = gst.getHandle();
         mIsSingleBuffer = gst.isSingleBuffer();
         mIsAvailable = true;
+        mMyPid = android.os.Process.myPid();
     }
 
     public GeckoSurface(Parcel p, SurfaceTexture dummy) {
         // A no-arg constructor exists, but is hidden in the SDK. We need to create a dummy
         // SurfaceTexture here in order to create the instance. This is used to transfer the
         // GeckoSurface across binder.
         super(dummy);
 
         readFromParcel(p);
         mHandle = p.readInt();
         mIsSingleBuffer = p.readByte() == 1 ? true : false;
         mIsAvailable = (p.readByte() == 1 ? true : false);
+        mMyPid = p.readInt();
 
         dummy.release();
     }
 
     public static final Parcelable.Creator<GeckoSurface> CREATOR = new Parcelable.Creator<GeckoSurface>() {
         public GeckoSurface createFromParcel(Parcel p) {
             return new GeckoSurface(p, new SurfaceTexture(0));
         }
@@ -59,22 +62,32 @@ public final class GeckoSurface extends 
     };
 
     @Override
     public void writeToParcel(Parcel out, int flags) {
         super.writeToParcel(out, flags);
         out.writeInt(mHandle);
         out.writeByte((byte) (mIsSingleBuffer ? 1 : 0));
         out.writeByte((byte) (mIsAvailable ? 1 : 0));
+        out.writeInt(mMyPid);
 
         mOwned = false;
     }
 
     @Override
     public void release() {
+        if (mSyncSurface != null) {
+            mSyncSurface.release();
+            GeckoSurfaceTexture gst = GeckoSurfaceTexture.lookup(mSyncSurface.getHandle());
+            if (gst != null) {
+                gst.decrementUse();
+            }
+            mSyncSurface = null;
+        }
+
         if (mOwned) {
             super.release();
         }
     }
 
     @WrapForJNI
     public int getHandle() {
         return mHandle;
@@ -84,9 +97,30 @@ public final class GeckoSurface extends 
     public boolean getAvailable() {
         return mIsAvailable;
     }
 
     @WrapForJNI
     public void setAvailable(boolean available) {
         mIsAvailable = available;
     }
+
+    /* package */ boolean inProcess() {
+        return android.os.Process.myPid() == mMyPid;
+    }
+
+    /* package */ SyncConfig initSyncSurface(int width, 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.");
+        }
+        GeckoSurfaceTexture texture = GeckoSurfaceTexture.acquire(false, mHandle);
+        texture.setDefaultBufferSize(width, height);
+        texture.track(mHandle);
+        mSyncSurface = new GeckoSurface(texture);
+
+        return new SyncConfig(mHandle, mSyncSurface, width, height);
+    }
 }
--- 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
@@ -32,16 +32,17 @@ import org.mozilla.gecko.mozglue.JNIObje
 
     private long mAttachedContext;
     private int mTexName;
 
     private GeckoSurfaceTexture.Callbacks mListener;
     private AtomicInteger mUseCount;
     private boolean mFinalized;
 
+    private int mUpstream;
     private NativeGLBlitHelper mBlitter;
 
     private GeckoSurfaceTexture(int handle) {
         super(0);
         init(handle, false);
     }
 
     @RequiresApi(api = Build.VERSION_CODES.KITKAT)
@@ -109,27 +110,31 @@ import org.mozilla.gecko.mozglue.JNIObje
     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 (Exception e) {
             Log.w(LOGTAG, "updateTexImage() failed", e);
         }
     }
 
     @Override
     public synchronized void release() {
+        mUpstream = 0;
         if (mBlitter != null) {
             mBlitter.disposeNative();
         }
         try {
             super.release();
             synchronized (sSurfaceTextures) {
                 sSurfaceTextures.remove(mHandle);
             }
@@ -238,30 +243,33 @@ import org.mozilla.gecko.mozglue.JNIObje
                     }
                 } catch (Exception e) {
                     Log.e(LOGTAG, "Failed to detach SurfaceTexture with handle: " + tex.mHandle, e);
                 }
             }
         }
     }
 
-    public static GeckoSurfaceTexture acquire(boolean singleBufferMode) {
+    public static GeckoSurfaceTexture acquire(boolean singleBufferMode, int handle) {
         if (singleBufferMode && !isSingleBufferSupported()) {
             throw new IllegalArgumentException("single buffer mode not supported on API version < 19");
         }
 
         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;
             }
 
-            int handle = sNextHandle++;
+            if (handle == 0) {
+                // Generate new handle value when none specified.
+                handle = sNextHandle++;
+            }
 
             final GeckoSurfaceTexture gst;
             if (isSingleBufferSupported()) {
                 gst = new GeckoSurfaceTexture(handle, singleBufferMode);
             } else {
                 gst = new GeckoSurfaceTexture(handle);
             }
 
@@ -277,31 +285,35 @@ import org.mozilla.gecko.mozglue.JNIObje
 
     @WrapForJNI
     public static GeckoSurfaceTexture lookup(int handle) {
         synchronized (sSurfaceTextures) {
             return sSurfaceTextures.get(handle);
         }
     }
 
+    /* package */ synchronized void track(int upstream) {
+        mUpstream = upstream;
+    }
+
     /* package */ synchronized void configureSnapshot(GeckoSurface target, int width, 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 class NativeGLBlitHelper extends JNIObject {
+    public static final class NativeGLBlitHelper extends JNIObject {
         public native static NativeGLBlitHelper create(int textureHandle,
                                                        GeckoSurface targetSurface,
                                                        int width,
                                                        int height);
         public native void blit();
 
         @Override
         protected native void disposeNative();
--- 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
@@ -5,22 +5,19 @@
 
 package org.mozilla.gecko.gfx;
 
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
 
-import android.graphics.SurfaceTexture;
 import android.os.IBinder;
-import android.os.Process;
 import android.os.RemoteException;
 import android.util.Log;
-import android.view.Surface;
 
 import org.mozilla.gecko.annotation.WrapForJNI;
 import org.mozilla.gecko.GeckoAppShell;
 
 /* package */ final class SurfaceAllocator {
     private static final String LOGTAG = "SurfaceAllocator";
 
     private static SurfaceAllocatorConnection sConnection;
@@ -44,18 +41,22 @@ import org.mozilla.gecko.GeckoAppShell;
     @WrapForJNI
     public static GeckoSurface acquireSurface(int width, int height, boolean singleBufferMode) {
         try {
             ensureConnection();
 
             if (singleBufferMode && !GeckoSurfaceTexture.isSingleBufferSupported()) {
                 return null;
             }
-
-            return sConnection.getAllocator().acquireSurface(width, height, singleBufferMode);
+            ISurfaceAllocator allocator = sConnection.getAllocator();
+            GeckoSurface surface = allocator.acquireSurface(width, height, singleBufferMode);
+            if (surface != null && !surface.inProcess()) {
+                allocator.configureSync(surface.initSyncSurface(width, height));
+            }
+            return surface;
         } catch (Exception e) {
             Log.w(LOGTAG, "Failed to acquire GeckoSurface", e);
             return null;
         }
     }
 
     @WrapForJNI
     public static void disposeSurface(GeckoSurface surface) {
@@ -76,16 +77,32 @@ import org.mozilla.gecko.GeckoAppShell;
         // And now our Surface
         try {
             surface.release();
         } catch (Exception e) {
             Log.w(LOGTAG, "Failed to release surface", e);
         }
     }
 
+    public static void sync(int upstream) {
+        try {
+            ensureConnection();
+        } catch (Exception e) {
+            Log.w(LOGTAG, "Failed to sync texture, no connection");
+            return;
+        }
+
+        // Release the SurfaceTexture on the other side
+        try {
+            sConnection.getAllocator().sync(upstream);
+        } catch (RemoteException e) {
+            Log.w(LOGTAG, "Failed to sync texture", e);
+        }
+    }
+
     private static final class SurfaceAllocatorConnection implements ServiceConnection {
         private ISurfaceAllocator mAllocator;
 
         public synchronized ISurfaceAllocator getAllocator() {
             while (mAllocator == null) {
                 try {
                     this.wait();
                 } catch (InterruptedException e) { }
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/SurfaceAllocatorService.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/SurfaceAllocatorService.java
@@ -4,30 +4,28 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.gfx;
 
 import android.app.Service;
 import android.content.Intent;
 import android.os.Binder;
 import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Log;
 
 public final class SurfaceAllocatorService extends Service {
 
     private static final String LOGTAG = "SurfaceAllocatorService";
 
     public int onStartCommand(final Intent intent, final int flags, final int startId) {
         return Service.START_STICKY;
     }
 
     private Binder mBinder = new ISurfaceAllocator.Stub() {
         public GeckoSurface acquireSurface(int width, int height, boolean singleBufferMode) {
-            GeckoSurfaceTexture gst = GeckoSurfaceTexture.acquire(singleBufferMode);
+            GeckoSurfaceTexture gst = GeckoSurfaceTexture.acquire(singleBufferMode, 0);
 
             if (gst == null) {
                 return null;
             }
 
             if (width > 0 && height > 0) {
                 gst.setDefaultBufferSize(width, height);
             }
@@ -36,16 +34,30 @@ public final class SurfaceAllocatorServi
         }
 
         public void releaseSurface(int handle) {
             final GeckoSurfaceTexture gst = GeckoSurfaceTexture.lookup(handle);
             if (gst != null) {
                 gst.decrementUse();
             }
         }
+
+        public void configureSync(SyncConfig config) {
+            final GeckoSurfaceTexture gst = GeckoSurfaceTexture.lookup(config.sourceTextureHandle);
+            if (gst != null) {
+                gst.configureSnapshot(config.targetSurface, config.width, config.height);
+            }
+        }
+
+        public void sync(int handle) {
+            final GeckoSurfaceTexture gst = GeckoSurfaceTexture.lookup(handle);
+            if (gst != null) {
+                gst.takeSnapshot();
+            }
+        }
     };
 
     public IBinder onBind(final Intent intent) {
         return mBinder;
     }
 
     public boolean onUnbind(Intent intent) {
         return false;
new file mode 100644
--- /dev/null
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/SyncConfig.java
@@ -0,0 +1,54 @@
+package org.mozilla.gecko.gfx;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/* package */ final class SyncConfig implements Parcelable {
+    final int sourceTextureHandle;
+    final GeckoSurface targetSurface;
+    final int width;
+    final int height;
+
+    /* package */ SyncConfig(int sourceTextureHandle,
+                             GeckoSurface targetSurface,
+                             int width,
+                             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(Parcel parcel) {
+                return new SyncConfig(parcel);
+            }
+
+            @Override
+            public SyncConfig[] newArray(int i) {
+                return new SyncConfig[i];
+            }
+        };
+
+    private SyncConfig(Parcel parcel) {
+        sourceTextureHandle = parcel.readInt();
+        targetSurface = GeckoSurface.CREATOR.createFromParcel(parcel);
+        width = parcel.readInt();
+        height = parcel.readInt();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeInt(sourceTextureHandle);
+        targetSurface.writeToParcel(parcel, flags);
+        parcel.writeInt(width);
+        parcel.writeInt(height);
+    }
+}