Bug 1322650 - Add GeckoSurfaceTexture, GeckoSurface and associated Android Service r=jchen draft
authorJames Willcox <snorp@snorp.net>
Fri, 03 Mar 2017 15:02:53 -0600
changeset 560645 f322befbc3946406a546780f9c59eb24e36f1708
parent 560609 649702f31585e9ca8714905378750214fcf8f46f
child 560657 e198c582f84273290260ab1c8dc87d2c3e53df04
push id53496
push userbmo:snorp@snorp.net
push dateTue, 11 Apr 2017 18:50:20 +0000
reviewersjchen
bugs1322650
milestone55.0a1
Bug 1322650 - Add GeckoSurfaceTexture, GeckoSurface and associated Android Service r=jchen This allows us to allocate an Android SurfaceTexture in the compositor process as well as an accompanying Surface. We can then transfer the Surface back to the content process via binder, where it can be used for things like WebGL and video decoding. Each SurfaceTexture/Surface pair has a unique handle. We use this handle in layer transactions to locate the SurfaceTexture for a given Surface and composite it appropriately. MozReview-Commit-ID: 68VSbXdfsMH
mobile/android/base/AndroidManifest.xml.in
mobile/android/base/Makefile.in
mobile/android/base/moz.build
mobile/android/geckoview/src/main/AndroidManifest.xml
mobile/android/geckoview/src/main/aidl/org/mozilla/gecko/gfx/GeckoSurface.aidl
mobile/android/geckoview/src/main/aidl/org/mozilla/gecko/gfx/ISurfaceAllocator.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
--- a/mobile/android/base/AndroidManifest.xml.in
+++ b/mobile/android/base/AndroidManifest.xml.in
@@ -464,10 +464,17 @@
         <service
             android:name="org.mozilla.gecko.process.GeckoServiceChildProcess$tab"
             android:enabled="true"
             android:exported="false"
             android:process=":tab"
             android:isolatedProcess="false">
         </service>
 
+        <service
+            android:name="org.mozilla.gecko.gfx.SurfaceAllocatorService"
+            android:enabled="true"
+            android:exported="false"
+            android:isolatedProcess="false">
+        </service>
+
     </application>
 </manifest>
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -565,16 +565,18 @@ endif
 
 libs:: classes.dex
 	$(INSTALL) classes.dex $(FINAL_TARGET)
 
 # Generate Java binder interfaces from AIDL files.
 GECKOVIEW_AIDLS = \
   org/mozilla/gecko/IGeckoEditableChild.aidl \
   org/mozilla/gecko/IGeckoEditableParent.aidl \
+  org/mozilla/gecko/gfx/GeckoSurface.aidl \
+  org/mozilla/gecko/gfx/ISurfaceAllocator.aidl \
   org/mozilla/gecko/media/ICodec.aidl \
   org/mozilla/gecko/media/ICodecCallbacks.aidl \
   org/mozilla/gecko/media/IMediaDrmBridge.aidl \
   org/mozilla/gecko/media/IMediaDrmBridgeCallbacks.aidl \
   org/mozilla/gecko/media/IMediaManager.aidl \
   org/mozilla/gecko/process/IChildProcess.aidl \
   org/mozilla/gecko/process/IProcessManager.aidl \
   $(NULL)
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -374,30 +374,34 @@ gvjar.sources += [geckoview_source_dir +
     'GeckoViewSettings.java',
     'gfx/BitmapUtils.java',
     'gfx/BufferedImage.java',
     'gfx/BufferedImageGLInfo.java',
     'gfx/DynamicToolbarAnimator.java',
     'gfx/FloatSize.java',
     'gfx/FullScreenState.java',
     'gfx/GeckoLayerClient.java',
+    'gfx/GeckoSurface.java',
+    'gfx/GeckoSurfaceTexture.java',
     'gfx/ImmutableViewportMetrics.java',
     'gfx/IntSize.java',
     'gfx/LayerRenderer.java',
     'gfx/LayerView.java',
     'gfx/NativePanZoomController.java',
     'gfx/Overscroll.java',
     'gfx/OverscrollEdgeEffect.java',
     'gfx/PanningPerfAPI.java',
     'gfx/PanZoomController.java',
     'gfx/PanZoomTarget.java',
     'gfx/PointUtils.java',
     'gfx/RectUtils.java',
     'gfx/RenderTask.java',
     'gfx/StackScroller.java',
+    'gfx/SurfaceAllocator.java',
+    'gfx/SurfaceAllocatorService.java',
     'gfx/SurfaceTextureListener.java',
     'gfx/ViewTransform.java',
     'gfx/VsyncSource.java',
     'InputConnectionListener.java',
     'InputMethods.java',
     'media/AsyncCodec.java',
     'media/AsyncCodecFactory.java',
     'media/Codec.java',
@@ -1295,16 +1299,17 @@ FINAL_TARGET_PP_FILES += ['package-name.
 DEFINES['OBJDIR'] = OBJDIR
 DEFINES['TOPOBJDIR'] = TOPOBJDIR
 
 OBJDIR_PP_FILES.mobile.android.base += [
     'AndroidManifest.xml.in',
 ]
 
 gvjar.sources += ['generated/org/mozilla/gecko/' + x for x in [
+    'gfx/ISurfaceAllocator.java',
     'IGeckoEditableChild.java',
     'IGeckoEditableParent.java',
     'media/ICodec.java',
     'media/ICodecCallbacks.java',
     'media/IMediaDrmBridge.java',
     'media/IMediaDrmBridgeCallbacks.java',
     'media/IMediaManager.java',
     'process/IChildProcess.java',
--- a/mobile/android/geckoview/src/main/AndroidManifest.xml
+++ b/mobile/android/geckoview/src/main/AndroidManifest.xml
@@ -48,11 +48,18 @@
 
         <service
             android:name="org.mozilla.gecko.process.GeckoServiceChildProcess$tab"
             android:enabled="true"
             android:exported="false"
             android:process=":tab"
             android:isolatedProcess="false">
         </service>
+
+        <service
+            android:name="org.mozilla.gecko.gfx.SurfaceAllocatorService"
+            android:enabled="true"
+            android:exported="false"
+            android:isolatedProcess="false">
+        </service>
    </application>
 
 </manifest>
new file mode 100644
--- /dev/null
+++ b/mobile/android/geckoview/src/main/aidl/org/mozilla/gecko/gfx/GeckoSurface.aidl
@@ -0,0 +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;
+
+parcelable GeckoSurface;
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/mobile/android/geckoview/src/main/aidl/org/mozilla/gecko/gfx/ISurfaceAllocator.aidl
@@ -0,0 +1,12 @@
+/* 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 long handle);
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/GeckoSurface.java
@@ -0,0 +1,98 @@
+/* -*- 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.Parcel;
+import android.os.Parcelable;
+import android.view.Surface;
+import android.util.Log;
+
+import java.util.HashMap;
+
+import org.mozilla.gecko.annotation.WrapForJNI;
+import org.mozilla.gecko.AppConstants.Versions;
+
+public final class GeckoSurface extends Surface {
+    private static final String LOGTAG = "GeckoSurface";
+
+    private static HashMap<Long, GeckoSurfaceTexture> sSurfaceTextures = new HashMap<Long, GeckoSurfaceTexture>();
+
+    private long mHandle;
+    private boolean mIsSingleBuffer;
+    private volatile boolean mIsAvailable;
+
+    private SurfaceTexture mDummySurfaceTexture;
+
+    @WrapForJNI(exceptionMode = "nsresult")
+    public GeckoSurface(GeckoSurfaceTexture gst) {
+        super(gst);
+        mHandle = gst.getHandle();
+        mIsSingleBuffer = gst.isSingleBuffer();
+        mIsAvailable = true;
+    }
+
+    public GeckoSurface(SurfaceTexture st) {
+        super(st);
+        mDummySurfaceTexture = st;
+    }
+
+    public GeckoSurface() {
+        // 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(new SurfaceTexture(0));
+    }
+
+    @Override
+    public void readFromParcel(Parcel p) {
+        super.readFromParcel(p);
+        mHandle = p.readLong();
+        mIsSingleBuffer = p.readByte() == 1 ? true : false;
+        mIsAvailable = (p.readByte() == 1 ? true : false);
+
+        if (mDummySurfaceTexture != null) {
+            mDummySurfaceTexture.release();
+            mDummySurfaceTexture = null;
+        }
+    }
+
+    public static final Parcelable.Creator<GeckoSurface> CREATOR = new Parcelable.Creator<GeckoSurface>() {
+        public GeckoSurface createFromParcel(Parcel p) {
+            GeckoSurface surf = new GeckoSurface();
+            surf.readFromParcel(p);
+            return surf;
+        }
+
+        public GeckoSurface[] newArray(int size) {
+            return new GeckoSurface[size];
+        }
+    };
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        super.writeToParcel(out, flags);
+        out.writeLong(mHandle);
+        out.writeByte((byte) (mIsSingleBuffer ? 1 : 0));
+        out.writeByte((byte) (mIsAvailable ? 1 : 0));
+    }
+
+    @WrapForJNI
+    public long getHandle() {
+        return mHandle;
+    }
+
+    @WrapForJNI
+    public boolean getAvailable() {
+        return mIsAvailable;
+    }
+
+    @WrapForJNI
+    public void setAvailable(boolean available) {
+        mIsAvailable = available;
+    }
+}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/GeckoSurfaceTexture.java
@@ -0,0 +1,141 @@
+/* -*- 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.util.Log;
+
+import java.util.HashMap;
+import java.util.concurrent.atomic.AtomicLong;
+
+import org.mozilla.gecko.annotation.WrapForJNI;
+import org.mozilla.gecko.AppConstants.Versions;
+
+public final class GeckoSurfaceTexture extends SurfaceTexture {
+    private static final String LOGTAG = "GeckoSurfaceTexture";
+    private static AtomicLong sNextHandle = new AtomicLong(1);
+    private static HashMap<Long, GeckoSurfaceTexture> sSurfaceTextures = new HashMap<Long, GeckoSurfaceTexture>();
+
+    private long mHandle;
+    private boolean mIsSingleBuffer;
+    private int mTexName;
+    private GeckoSurfaceTexture.Callbacks mListener;
+
+    @WrapForJNI(dispatchTo = "current")
+    private static native int nativeAcquireTexture();
+
+    private GeckoSurfaceTexture(long handle, int texName) {
+        super(texName);
+        mHandle = handle;
+        mIsSingleBuffer = false;
+        mTexName = texName;
+    }
+
+    private GeckoSurfaceTexture(long handle, int texName, boolean singleBufferMode) {
+        super(texName, singleBufferMode);
+        mHandle = handle;
+        mIsSingleBuffer = singleBufferMode;
+        mTexName = texName;
+    }
+
+    @WrapForJNI
+    public long getHandle() {
+        return mHandle;
+    }
+
+    @WrapForJNI
+    public int getTexName() {
+        return mTexName;
+    }
+
+    @WrapForJNI
+    public boolean isSingleBuffer() {
+        return mIsSingleBuffer;
+    }
+
+    @Override
+    @WrapForJNI
+    public synchronized void updateTexImage() {
+        super.updateTexImage();
+        if (mListener != null) {
+            mListener.onUpdateTexImage();
+        }
+    }
+
+    @Override
+    @WrapForJNI
+    public synchronized void releaseTexImage() {
+        if (!mIsSingleBuffer) {
+            return;
+        }
+
+        super.releaseTexImage();
+        if (mListener != null) {
+            mListener.onReleaseTexImage();
+        }
+    }
+
+    public synchronized void setListener(GeckoSurfaceTexture.Callbacks listener) {
+        mListener = listener;
+    }
+
+    @WrapForJNI
+    public static boolean isSingleBufferSupported() {
+        return Versions.feature19Plus;
+    }
+
+    public static GeckoSurfaceTexture acquire(boolean singleBufferMode) {
+        if (singleBufferMode && !isSingleBufferSupported()) {
+            throw new IllegalArgumentException("single buffer mode not supported on API version < 19");
+        }
+
+        long handle = sNextHandle.getAndIncrement();
+        int texName = nativeAcquireTexture();
+
+        final GeckoSurfaceTexture gst;
+        if (isSingleBufferSupported()) {
+            gst = new GeckoSurfaceTexture(handle, texName, singleBufferMode);
+        } else {
+            gst = new GeckoSurfaceTexture(handle, texName);
+        }
+
+        synchronized (sSurfaceTextures) {
+            if (sSurfaceTextures.containsKey(handle)) {
+                gst.release();
+                throw new IllegalArgumentException("Already have a GeckoSurfaceTexture with that handle");
+            }
+
+            sSurfaceTextures.put(handle, gst);
+        }
+
+
+        return gst;
+    }
+
+    public static void dispose(long handle) {
+        final GeckoSurfaceTexture gst;
+        synchronized (sSurfaceTextures) {
+            gst = sSurfaceTextures.remove(handle);
+        }
+
+        if (gst != null) {
+            gst.setListener(null);
+            gst.release();
+        }
+    }
+
+    @WrapForJNI
+    public static GeckoSurfaceTexture lookup(long handle) {
+        synchronized (sSurfaceTextures) {
+            return sSurfaceTextures.get(handle);
+        }
+    }
+
+    public interface Callbacks {
+        void onUpdateTexImage();
+        void onReleaseTexImage();
+    }
+}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/SurfaceAllocator.java
@@ -0,0 +1,103 @@
+/* -*- 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.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;
+
+public final class SurfaceAllocator {
+    private static final String LOGTAG = "SurfaceAllocator";
+
+    private static SurfaceAllocatorConnection sConnection;
+
+    private static synchronized void ensureConnection() throws Exception {
+        if (sConnection != null) {
+            return;
+        }
+
+        sConnection = new SurfaceAllocatorConnection();
+        Intent intent = new Intent();
+        intent.setClassName(GeckoAppShell.getApplicationContext(),
+                            "org.mozilla.gecko.gfx.SurfaceAllocatorService");
+
+        // FIXME: may not want to auto create
+        if (!GeckoAppShell.getApplicationContext().bindService(intent, sConnection, Context.BIND_AUTO_CREATE)) {
+            throw new Exception("Failed to connect to surface allocator service!");
+        }
+    }
+
+    @WrapForJNI
+    public static GeckoSurface acquireSurface(int width, int height, boolean singleBufferMode) throws Exception {
+        ensureConnection();
+
+        try {
+            return sConnection.getAllocator().acquireSurface(width, height, singleBufferMode);
+        } catch (RemoteException e) {
+            throw new Exception("Failed to acquire GeckoSurface", e);
+        }
+    }
+
+    @WrapForJNI
+    public static void disposeSurface(GeckoSurface surface) {
+        try {
+            ensureConnection();
+        } catch (Exception e) {
+            Log.w(LOGTAG, "Failed to dispose surface, no connection");
+            return;
+        }
+
+        // Release the SurfaceTexture on the other side
+        try {
+            sConnection.getAllocator().releaseSurface(surface.getHandle());
+        } catch (RemoteException e) {
+            Log.w(LOGTAG, "Failed to release surface texture", e);
+        }
+
+        // And now our Surface
+        try {
+            surface.release();
+        } catch (Exception e) {
+            Log.w(LOGTAG, "Failed to release surface", e);
+        }
+    }
+
+    private static final class SurfaceAllocatorConnection implements ServiceConnection {
+        private ISurfaceAllocator mAllocator;
+
+        public synchronized ISurfaceAllocator getAllocator() {
+            while (mAllocator == null) {
+                try {
+                    this.wait();
+                } catch(InterruptedException e) {}
+            }
+
+            return mAllocator;
+        }
+
+        @Override
+        public synchronized void onServiceConnected(ComponentName name, IBinder service) {
+            mAllocator = ISurfaceAllocator.Stub.asInterface(service);
+            this.notifyAll();
+        }
+
+        @Override
+        public synchronized void onServiceDisconnected(ComponentName name) {
+            mAllocator = null;
+        }
+    }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/SurfaceAllocatorService.java
@@ -0,0 +1,45 @@
+/* -*- 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.app.Service;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+public class SurfaceAllocatorService extends Service {
+
+    static private 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);
+            if (width > 0 && height > 0) {
+                gst.setDefaultBufferSize(width, height);
+            }
+
+            return new GeckoSurface(gst);
+        }
+
+        public void releaseSurface(long handle) {
+            GeckoSurfaceTexture.dispose(handle);
+        }
+    };
+
+    public IBinder onBind(final Intent intent) {
+        return mBinder;
+    }
+
+    public boolean onUnbind(Intent intent) {
+        return false;
+    }
+}