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 572876 cbb24ea09a46a25760b3133bb6915a115893205b
parent 571027 f4ad40ab582e74e1ae811a4f974370227fef7e69
child 572877 1fa382bb06a366ec2a096e5cd15ce52c512bd135
push id57212
push userbmo:snorp@snorp.net
push dateThu, 04 May 2017 21:08:01 +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
gfx/gl/AndroidSurfaceTexture.cpp
gfx/gl/AndroidSurfaceTexture.h
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/gfx/gl/AndroidSurfaceTexture.cpp
+++ b/gfx/gl/AndroidSurfaceTexture.cpp
@@ -1,209 +1,25 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-// vim:set ts=2 sts=2 sw=2 et cin:
-/* 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/. */
-
 #ifdef MOZ_WIDGET_ANDROID
 
-#include <map>
-#include <android/native_window_jni.h>
-#include <android/log.h>
 #include "AndroidSurfaceTexture.h"
-#include "gfxImageSurface.h"
-#include "gfxPrefs.h"
-#include "AndroidBridge.h"
-#include "nsThreadUtils.h"
-#include "mozilla/gfx/Matrix.h"
-#include "GeneratedJNINatives.h"
-#include "GLContext.h"
 
 using namespace mozilla;
 
 namespace mozilla {
 namespace gl {
 
-class AndroidSurfaceTexture::Listener
-  : public java::SurfaceTextureListener::Natives<Listener>
-{
-  using Base = java::SurfaceTextureListener::Natives<Listener>;
-
-  const nsCOMPtr<nsIRunnable> mCallback;
-
-public:
-  using Base::AttachNative;
-  using Base::DisposeNative;
-
-  Listener(nsIRunnable* aCallback) : mCallback(aCallback) {}
-
-  void OnFrameAvailable()
-  {
-    if (NS_IsMainThread()) {
-      mCallback->Run();
-      return;
-    }
-    NS_DispatchToMainThread(mCallback);
-  }
-};
-
-already_AddRefed<AndroidSurfaceTexture>
-AndroidSurfaceTexture::Create()
-{
-  return Create(nullptr, 0);
-}
-
-already_AddRefed<AndroidSurfaceTexture>
-AndroidSurfaceTexture::Create(GLContext* aContext, GLuint aTexture)
-{
-  RefPtr<AndroidSurfaceTexture> st = new AndroidSurfaceTexture();
-  if (!st->Init(aContext, aTexture)) {
-    printf_stderr("Failed to initialize AndroidSurfaceTexture");
-    st = nullptr;
-  }
-
-  return st.forget();
-}
-
-nsresult
-AndroidSurfaceTexture::Attach(GLContext* aContext, PRIntervalTime aTimeout)
-{
-  MonitorAutoLock lock(mMonitor);
-
-  if (mAttachedContext == aContext) {
-    NS_WARNING("Tried to attach same GLContext to AndroidSurfaceTexture");
-    return NS_OK;
-  }
-
-  if (!CanDetach()) {
-    return NS_ERROR_NOT_AVAILABLE;
-  }
-
-  while (mAttachedContext) {
-    // Wait until it's detached (or we time out)
-    if (NS_FAILED(lock.Wait(aTimeout))) {
-      return NS_ERROR_NOT_AVAILABLE;
-    }
-  }
-
-  MOZ_ASSERT(aContext->IsOwningThreadCurrent(), "Trying to attach GLContext from different thread");
-
-  aContext->fGenTextures(1, &mTexture);
-
-  if (NS_FAILED(mSurfaceTexture->AttachToGLContext(mTexture))) {
-    return NS_ERROR_NOT_AVAILABLE;
-  }
-  mAttachedContext = aContext;
-  mAttachedContext->MakeCurrent();
-
-  return NS_OK;
-}
-
-nsresult
-AndroidSurfaceTexture::Detach()
-{
-  MonitorAutoLock lock(mMonitor);
-
-  if (!CanDetach() ||
-      !mAttachedContext ||
-      !mAttachedContext->IsOwningThreadCurrent())
-  {
-    return NS_ERROR_FAILURE;
-  }
-
-  mAttachedContext->MakeCurrent();
-
-  mSurfaceTexture->DetachFromGLContext();
-
-  mTexture = 0;
-  mAttachedContext = nullptr;
-  lock.NotifyAll();
-  return NS_OK;
-}
-
-bool
-AndroidSurfaceTexture::CanDetach() const
-{
-  // The API for attach/detach only exists on 16+, and PowerVR has some sort of
-  // fencing issue. Additionally, attach/detach seems to be busted on at least
-  // some Mali adapters (400MP2 for sure, bug 1131793)
-  return AndroidBridge::Bridge()->GetAPIVersion() >= 16 &&
-    (!mAttachedContext || mAttachedContext->Vendor() != GLVendor::Imagination) &&
-    (!mAttachedContext || mAttachedContext->Vendor() != GLVendor::ARM /* Mali */) &&
-    gfxPrefs::SurfaceTextureDetachEnabled();
-}
-
-bool
-AndroidSurfaceTexture::Init(GLContext* aContext, GLuint aTexture)
-{
-
-  if (!aTexture && !CanDetach()) {
-    // We have no texture and cannot initialize detached, bail out
-    return false;
-  }
-
-  if (NS_WARN_IF(NS_FAILED(
-      java::sdk::SurfaceTexture::New(aTexture, ReturnTo(&mSurfaceTexture))))) {
-    return false;
-  }
-
-  if (!aTexture) {
-    mSurfaceTexture->DetachFromGLContext();
-  }
-
-  mAttachedContext = aContext;
-
-  if (NS_WARN_IF(NS_FAILED(
-      java::sdk::Surface::New(mSurfaceTexture, ReturnTo(&mSurface))))) {
-    return false;
-  }
-
-  mNativeWindow = ANativeWindow_fromSurface(jni::GetEnvForThread(),
-                                            mSurface.Get());
-  MOZ_ASSERT(mNativeWindow, "Failed to create native window from surface");
-
-  return true;
-}
-
-AndroidSurfaceTexture::AndroidSurfaceTexture()
-  : mTexture(0)
-  , mSurfaceTexture()
-  , mSurface()
-  , mAttachedContext(nullptr)
-  , mMonitor("AndroidSurfaceTexture")
-{
-}
-
-AndroidSurfaceTexture::~AndroidSurfaceTexture()
-{
-  if (mSurfaceTexture) {
-    SetFrameAvailableCallback(nullptr);
-    mSurfaceTexture = nullptr;
-  }
-
-  if (mNativeWindow) {
-    ANativeWindow_release(mNativeWindow);
-    mNativeWindow = nullptr;
-  }
-}
-
 void
-AndroidSurfaceTexture::UpdateTexImage()
-{
-  mSurfaceTexture->UpdateTexImage();
-}
-
-void
-AndroidSurfaceTexture::GetTransformMatrix(gfx::Matrix4x4& aMatrix) const
+AndroidSurfaceTexture::GetTransformMatrix(java::sdk::SurfaceTexture::LocalRef aSurfaceTexture,
+                                          gfx::Matrix4x4& aMatrix)
 {
   JNIEnv* const env = jni::GetEnvForThread();
 
   auto jarray = jni::FloatArray::LocalRef::Adopt(env, env->NewFloatArray(16));
-  mSurfaceTexture->GetTransformMatrix(jarray);
+  aSurfaceTexture->GetTransformMatrix(jarray);
 
   jfloat* array = env->GetFloatArrayElements(jarray.Get(), nullptr);
 
   aMatrix._11 = array[0];
   aMatrix._12 = array[1];
   aMatrix._13 = array[2];
   aMatrix._14 = array[3];
 
@@ -220,41 +36,11 @@ AndroidSurfaceTexture::GetTransformMatri
   aMatrix._41 = array[12];
   aMatrix._42 = array[13];
   aMatrix._43 = array[14];
   aMatrix._44 = array[15];
 
   env->ReleaseFloatArrayElements(jarray.Get(), array, 0);
 }
 
-void
-AndroidSurfaceTexture::SetFrameAvailableCallback(nsIRunnable* aRunnable)
-{
-  java::SurfaceTextureListener::LocalRef newListener;
-
-  if (aRunnable) {
-    newListener = java::SurfaceTextureListener::New();
-    Listener::AttachNative(newListener, MakeUnique<Listener>(aRunnable));
-  }
-
-  if (aRunnable || mListener) {
-    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
-        mSurfaceTexture->SetOnFrameAvailableListener(newListener)));
-  }
-
-  if (mListener) {
-    Listener::DisposeNative(java::SurfaceTextureListener::LocalRef(
-        newListener.Env(), mListener));
-  }
-
-  mListener = newListener;
-}
-
-void
-AndroidSurfaceTexture::SetDefaultSize(mozilla::gfx::IntSize size)
-{
-  mSurfaceTexture->SetDefaultBufferSize(size.width, size.height);
-}
-
 } // gl
 } // mozilla
-
 #endif // MOZ_WIDGET_ANDROID
--- a/gfx/gl/AndroidSurfaceTexture.h
+++ b/gfx/gl/AndroidSurfaceTexture.h
@@ -3,105 +3,28 @@
 /* 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/. */
 
 #ifndef AndroidSurfaceTexture_h__
 #define AndroidSurfaceTexture_h__
 #ifdef MOZ_WIDGET_ANDROID
 
-#include <jni.h>
-#include <android/native_window.h>
-#include "nsIRunnable.h"
-#include "gfxPlatform.h"
-#include "GLDefs.h"
-#include "mozilla/gfx/2D.h"
-#include "mozilla/gfx/MatrixFwd.h"
-#include "mozilla/Monitor.h"
+#include "mozilla/gfx/Matrix.h"
+#include "SurfaceTexture.h"
 
-#include "GeneratedJNIWrappers.h"
-#include "SurfaceTexture.h"
+typedef uint32_t AndroidSurfaceTextureHandle;
 
 namespace mozilla {
 namespace gl {
 
-class GLContext;
-
-/**
- * This class is a wrapper around Android's SurfaceTexture class.
- * Usage is pretty much exactly like the Java class, so see
- * the Android documentation for details.
- */
 class AndroidSurfaceTexture {
-  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AndroidSurfaceTexture)
-
 public:
-
-  // The SurfaceTexture is created in an attached state. This method requires
-  // Android Ice Cream Sandwich.
-  static already_AddRefed<AndroidSurfaceTexture> Create(GLContext* aGLContext, GLuint aTexture);
-
-  // Here the SurfaceTexture will be created in a detached state. You must call
-  // Attach() with the GLContext you wish to composite with. It must be done
-  // on the thread where that GLContext is current. This method requires
-  // Android Jelly Bean.
-  static already_AddRefed<AndroidSurfaceTexture> Create();
-
-  // If we are on Jelly Bean, the SurfaceTexture can be detached and reattached
-  // to allow consumption from different GLContexts. It is recommended to only
-  // attach while you are consuming in order to allow this.
-  //
-  // Only one GLContext may be attached at any given time. If another is already
-  // attached, we try to wait for it to become detached.
-  nsresult Attach(GLContext* aContext, PRIntervalTime aTiemout = PR_INTERVAL_NO_TIMEOUT);
-
-  nsresult Detach();
-
-  // Ability to detach is based on API version (16+), and we also block PowerVR
-  // since it has some type of fencing problem. Bug 1100126.
-  bool CanDetach() const;
+  static void GetTransformMatrix(java::sdk::SurfaceTexture::LocalRef aSurfaceTexture,
+                                 mozilla::gfx::Matrix4x4& aMatrix);
 
-  GLContext* AttachedContext() const { return mAttachedContext; }
-
-  ANativeWindow* NativeWindow() const {
-    return mNativeWindow;
-  }
-
-  // This attaches the updated data to the TEXTURE_EXTERNAL target
-  void UpdateTexImage();
-
-  void GetTransformMatrix(mozilla::gfx::Matrix4x4& aMatrix) const;
-
-  void SetDefaultSize(mozilla::gfx::IntSize size);
-
-  // The callback is guaranteed to be called on the main thread even
-  // if the upstream callback is received on a different thread
-  void SetFrameAvailableCallback(nsIRunnable* aRunnable);
-
-  GLuint Texture() const { return mTexture; }
-  const java::sdk::Surface::Ref& JavaSurface() const { return mSurface; }
-
-private:
-  class Listener;
-
-  AndroidSurfaceTexture();
-  ~AndroidSurfaceTexture();
-
-  bool Init(GLContext* aContext, GLuint aTexture);
-
-  GLuint mTexture;
-  java::sdk::SurfaceTexture::GlobalRef mSurfaceTexture;
-  java::sdk::Surface::GlobalRef mSurface;
-  java::SurfaceTextureListener::GlobalRef mListener;
-
-  GLContext* mAttachedContext;
-
-  ANativeWindow* mNativeWindow;
-
-  Monitor mMonitor;
 };
 
-}
-}
+} // gl
+} // mozilla
 
-
-#endif
-#endif
+#endif // MOZ_WIDGET_ANDROID
+#endif // AndroidSurfaceTexture_h__
--- a/mobile/android/base/AndroidManifest.xml.in
+++ b/mobile/android/base/AndroidManifest.xml.in
@@ -467,10 +467,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
@@ -543,16 +543,17 @@ 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/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
@@ -377,28 +377,32 @@ 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/LayerView.java',
     'gfx/NativePanZoomController.java',
     'gfx/Overscroll.java',
     'gfx/OverscrollEdgeEffect.java',
     'gfx/PanningPerfAPI.java',
     'gfx/PanZoomController.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',
@@ -1257,16 +1261,17 @@ if CONFIG['MOZ_ANDROID_SEARCH_ACTIVITY']
         'gecko-util.jar',
         'gecko-view.jar',
     ]
 
 DEFINES['ANDROID_PACKAGE_NAME'] = CONFIG['ANDROID_PACKAGE_NAME']
 FINAL_TARGET_PP_FILES += ['package-name.txt.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 int 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<Integer, GeckoSurfaceTexture> sSurfaceTextures = new HashMap<Integer, GeckoSurfaceTexture>();
+
+    private int 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.readInt();
+        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.writeInt(mHandle);
+        out.writeByte((byte) (mIsSingleBuffer ? 1 : 0));
+        out.writeByte((byte) (mIsAvailable ? 1 : 0));
+    }
+
+    @WrapForJNI
+    public int 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,140 @@
+/* -*- 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 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 volatile int sNextHandle = 1;
+    private static HashMap<Integer, GeckoSurfaceTexture> sSurfaceTextures = new HashMap<Integer, GeckoSurfaceTexture>();
+
+    private int mHandle;
+    private boolean mIsSingleBuffer;
+    private int mTexName;
+    private GeckoSurfaceTexture.Callbacks mListener;
+
+    @WrapForJNI(dispatchTo = "current")
+    private static native int nativeAcquireTexture();
+
+    private GeckoSurfaceTexture(int handle, int texName) {
+        super(texName);
+        mHandle = handle;
+        mIsSingleBuffer = false;
+        mTexName = texName;
+    }
+
+    private GeckoSurfaceTexture(int handle, int texName, boolean singleBufferMode) {
+        super(texName, singleBufferMode);
+        mHandle = handle;
+        mIsSingleBuffer = singleBufferMode;
+        mTexName = texName;
+    }
+
+    @WrapForJNI
+    public int 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");
+        }
+
+        int handle = sNextHandle++;
+        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(int handle) {
+        final GeckoSurfaceTexture gst;
+        synchronized (sSurfaceTextures) {
+            gst = sSurfaceTextures.remove(handle);
+        }
+
+        if (gst != null) {
+            gst.setListener(null);
+            gst.release();
+        }
+    }
+
+    @WrapForJNI
+    public static GeckoSurfaceTexture lookup(int 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(int handle) {
+            GeckoSurfaceTexture.dispose(handle);
+        }
+    };
+
+    public IBinder onBind(final Intent intent) {
+        return mBinder;
+    }
+
+    public boolean onUnbind(Intent intent) {
+        return false;
+    }
+}