Bug 1014614 - Support attach/detach of GLContext to AndroidSurfaceTexture r=jgilbert
authorJames Willcox <snorp@snorp.net>
Fri, 17 Oct 2014 10:35:11 -0500
changeset 210951 f2b504bdd7c5
parent 210950 c878e29fbef9
child 210952 40dbd7c6ce65
push id50598
push userjwillcox@mozilla.com
push date2014-10-17 15:36 +0000
treeherdermozilla-inbound@40f99ba7f616 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjgilbert
bugs1014614
milestone36.0a1
Bug 1014614 - Support attach/detach of GLContext to AndroidSurfaceTexture r=jgilbert
dom/plugins/base/nsNPAPIPluginInstance.cpp
gfx/gl/AndroidSurfaceTexture.cpp
gfx/gl/AndroidSurfaceTexture.h
gfx/layers/opengl/TextureHostOGL.cpp
gfx/layers/opengl/TexturePoolOGL.cpp
gfx/layers/opengl/TexturePoolOGL.h
--- a/dom/plugins/base/nsNPAPIPluginInstance.cpp
+++ b/dom/plugins/base/nsNPAPIPluginInstance.cpp
@@ -956,17 +956,18 @@ AndroidSurfaceTexture* nsNPAPIPluginInst
 {
   if (!EnsureGLContext())
     return nullptr;
 
   GLuint texture = TexturePoolOGL::AcquireTexture();
   if (!texture)
     return nullptr;
 
-  AndroidSurfaceTexture* surface = AndroidSurfaceTexture::Create(texture);
+  AndroidSurfaceTexture* surface = AndroidSurfaceTexture::Create(TexturePoolOGL::GetGLContext(),
+                                                                 texture);
   if (!surface)
     return nullptr;
 
   nsCOMPtr<nsIRunnable> frameCallback = NS_NewRunnableMethod(this, &nsNPAPIPluginInstance::OnSurfaceTextureFrameAvailable);
   surface->SetFrameAvailableCallback(frameCallback);
   return surface;
 }
 
--- a/gfx/gl/AndroidSurfaceTexture.cpp
+++ b/gfx/gl/AndroidSurfaceTexture.cpp
@@ -10,46 +10,71 @@
 #include <map>
 #include <android/log.h>
 #include "AndroidSurfaceTexture.h"
 #include "gfxImageSurface.h"
 #include "AndroidBridge.h"
 #include "nsThreadUtils.h"
 #include "mozilla/gfx/Matrix.h"
 #include "GeneratedJNIWrappers.h"
+#include "GLContext.h"
 
 using namespace mozilla;
 using namespace mozilla::widget::android;
 
 // UGH
 static std::map<int, AndroidSurfaceTexture*> sInstances;
 static int sNextID = 0;
 
+static bool
+IsDetachSupported()
+{
+  return AndroidBridge::Bridge()->GetAPIVersion() >= 16; /* Jelly Bean */
+}
+
+static bool
+IsSTSupported()
+{
+  return AndroidBridge::Bridge()->GetAPIVersion() >= 14; /* ICS */
+}
+
 static class JNIFunctions {
 public:
 
   JNIFunctions() : mInitialized(false)
   {
   }
 
   bool EnsureInitialized()
   {
-    if (mInitialized)
+    if (mInitialized) {
       return true;
+    }
+
+    if (!IsSTSupported()) {
+      return false;
+    }
 
     JNIEnv* env = GetJNIForThread();
 
     AutoLocalJNIFrame jniFrame(env);
 
     jSurfaceTextureClass = (jclass)env->NewGlobalRef(env->FindClass("android/graphics/SurfaceTexture"));
     jSurfaceTexture_Ctor = env->GetMethodID(jSurfaceTextureClass, "<init>", "(I)V");
     jSurfaceTexture_updateTexImage = env->GetMethodID(jSurfaceTextureClass, "updateTexImage", "()V");
     jSurfaceTexture_getTransformMatrix = env->GetMethodID(jSurfaceTextureClass, "getTransformMatrix", "([F)V");
     jSurfaceTexture_setDefaultBufferSize = env->GetMethodID(jSurfaceTextureClass, "setDefaultBufferSize", "(II)V");
 
+    if (IsDetachSupported()) {
+      jSurfaceTexture_attachToGLContext = env->GetMethodID(jSurfaceTextureClass, "attachToGLContext", "(I)V");
+      jSurfaceTexture_detachFromGLContext = env->GetMethodID(jSurfaceTextureClass, "detachFromGLContext", "()V");
+    } else {
+      jSurfaceTexture_attachToGLContext = jSurfaceTexture_detachFromGLContext = 0;
+    }
+
     jSurfaceClass = (jclass)env->NewGlobalRef(env->FindClass("android/view/Surface"));
     jSurface_Ctor = env->GetMethodID(jSurfaceClass, "<init>", "(Landroid/graphics/SurfaceTexture;)V");
 
     mInitialized = true;
     return true;
   }
 
   jobject CreateSurfaceTexture(GLuint aTexture)
@@ -128,39 +153,74 @@ public:
   void SetDefaultBufferSize(jobject aSurfaceTexture, int32_t width, int32_t height)
   {
     JNIEnv* env = GetJNIForThread();
 
     AutoLocalJNIFrame jniFrame(env);
     env->CallVoidMethod(aSurfaceTexture, jSurfaceTexture_setDefaultBufferSize, width, height);
   }
 
+  void AttachToGLContext(jobject aSurfaceTexture, int32_t texName)
+  {
+    MOZ_ASSERT(jSurfaceTexture_attachToGLContext);
+
+    JNIEnv* env = GetJNIForThread();
+
+    env->CallVoidMethod(aSurfaceTexture, jSurfaceTexture_attachToGLContext, texName);
+    if (env->ExceptionCheck()) {
+      env->ExceptionDescribe();
+      env->ExceptionClear();
+    }
+  }
+
+  void DetachFromGLContext(jobject aSurfaceTexture)
+  {
+    MOZ_ASSERT(jSurfaceTexture_detachFromGLContext);
+
+    JNIEnv* env = GetJNIForThread();
+
+    env->CallVoidMethod(aSurfaceTexture, jSurfaceTexture_detachFromGLContext);
+    if (env->ExceptionCheck()) {
+      env->ExceptionDescribe();
+      env->ExceptionClear();
+    }
+  }
+
 private:
   bool mInitialized;
 
   jclass jSurfaceTextureClass;
   jmethodID jSurfaceTexture_Ctor;
   jmethodID jSurfaceTexture_updateTexImage;
   jmethodID jSurfaceTexture_getTransformMatrix;
   jmethodID jSurfaceTexture_setDefaultBufferSize;
 
+  jmethodID jSurfaceTexture_attachToGLContext;
+  jmethodID jSurfaceTexture_detachFromGLContext;
+
   jclass jSurfaceClass;
   jmethodID jSurface_Ctor;
 
 } sJNIFunctions;
 
 AndroidSurfaceTexture*
-AndroidSurfaceTexture::Create(GLuint aTexture)
+AndroidSurfaceTexture::Create()
 {
-  if (AndroidBridge::Bridge()->GetAPIVersion() < 14 /* Ice Cream Sandwich */) {
+  return Create(nullptr, 0);
+}
+
+AndroidSurfaceTexture*
+AndroidSurfaceTexture::Create(GLContext* aContext, GLuint aTexture)
+{
+  if (!IsSTSupported()) {
     return nullptr;
   }
 
   AndroidSurfaceTexture* st = new AndroidSurfaceTexture();
-  if (!st->Init(aTexture)) {
+  if (!st->Init(aContext, aTexture)) {
     printf_stderr("Failed to initialize AndroidSurfaceTexture");
     delete st;
     st = nullptr;
   }
 
   return st;
 }
 
@@ -178,45 +238,110 @@ AndroidSurfaceTexture::Find(int id)
 
 bool
 AndroidSurfaceTexture::Check()
 {
   return sJNIFunctions.EnsureInitialized();
 }
 
 bool
-AndroidSurfaceTexture::Init(GLuint aTexture)
+AndroidSurfaceTexture::Attach(GLContext* aContext, PRIntervalTime aTimeout)
 {
+  MonitorAutoLock lock(mMonitor);
+
+  if (mAttachedContext == aContext) {
+    NS_WARNING("Tried to attach same GLContext to AndroidSurfaceTexture");
+    return true;
+  }
+
+  if (!IsDetachSupported()) {
+    return false;
+  }
+
+  while (mAttachedContext) {
+    // Wait until it's detached (or we time out)
+    if (NS_FAILED(lock.Wait(aTimeout))) {
+      return false;
+    }
+  }
+
+  MOZ_ASSERT(aContext->IsOwningThreadCurrent(), "Trying to attach GLContext from different thread");
+
+  mAttachedContext = aContext;
+  mAttachedContext->MakeCurrent();
+  aContext->fGenTextures(1, &mTexture);
+
+  sJNIFunctions.AttachToGLContext(mSurfaceTexture, mTexture);
+  return true;
+}
+
+bool
+AndroidSurfaceTexture::Detach()
+{
+  MonitorAutoLock lock(mMonitor);
+
+  if (!IsDetachSupported() ||
+      !mAttachedContext || !mAttachedContext->IsOwningThreadCurrent()) {
+    return false;
+  }
+
+  mAttachedContext->MakeCurrent();
+
+  // This call takes care of deleting the texture
+  sJNIFunctions.DetachFromGLContext(mSurfaceTexture);
+
+  mTexture = 0;
+  mAttachedContext = nullptr;
+  lock.NotifyAll();
+  return true;
+}
+
+bool
+AndroidSurfaceTexture::Init(GLContext* aContext, GLuint aTexture)
+{
+  if (!aTexture && !IsDetachSupported()) {
+    // We have no texture and cannot initialize detached, bail out
+    return false;
+  }
+
   if (!sJNIFunctions.EnsureInitialized())
     return false;
 
   JNIEnv* env = GetJNIForThread();
 
   mSurfaceTexture = sJNIFunctions.CreateSurfaceTexture(aTexture);
   if (!mSurfaceTexture) {
     return false;
   }
 
+  if (!aTexture) {
+    sJNIFunctions.DetachFromGLContext(mSurfaceTexture);
+  }
+
+  mAttachedContext = aContext;
+
   mSurface = sJNIFunctions.CreateSurface(mSurfaceTexture);
   if (!mSurface) {
     return false;
   }
 
   mNativeWindow = AndroidNativeWindow::CreateFromSurface(env, mSurface);
 
   mID = ++sNextID;
   sInstances.insert(std::pair<int, AndroidSurfaceTexture*>(mID, this));
 
-  mTexture = aTexture;
-
   return true;
 }
 
 AndroidSurfaceTexture::AndroidSurfaceTexture()
-  : mTexture(0), mSurfaceTexture(nullptr), mSurface(nullptr)
+  : mTexture(0)
+  , mSurfaceTexture(nullptr)
+  , mSurface(nullptr)
+  , mMonitor("AndroidSurfaceTexture::mContextMonitor")
+  , mAttachedContext(nullptr)
 {
 }
 
 AndroidSurfaceTexture::~AndroidSurfaceTexture()
 {
   sInstances.erase(mID);
 
   mFrameAvailableCallback = nullptr;
--- a/gfx/gl/AndroidSurfaceTexture.h
+++ b/gfx/gl/AndroidSurfaceTexture.h
@@ -7,46 +7,73 @@
 #ifndef AndroidSurfaceTexture_h__
 #define AndroidSurfaceTexture_h__
 #ifdef MOZ_WIDGET_ANDROID
 
 #include <jni.h>
 #include "nsIRunnable.h"
 #include "gfxPlatform.h"
 #include "GLDefs.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/Monitor.h"
 
 #include "AndroidNativeWindow.h"
 
 class gfxASurface;
 
 namespace mozilla {
 namespace gfx {
 class Matrix4x4;
 }
 }
 
 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:
-  static AndroidSurfaceTexture* Create(GLuint aTexture);
+
+  // The SurfaceTexture is created in an attached state. This method requires
+  // Android Ice Cream Sandwich.
+  static 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 AndroidSurfaceTexture* Create();
+
   static AndroidSurfaceTexture* Find(int id);
 
   // Returns with reasonable certainty whether or not we'll
   // be able to create and use a SurfaceTexture
   static bool Check();
 
+  // 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.
+  bool Attach(GLContext* aContext, PRIntervalTime aTiemout = PR_INTERVAL_NO_TIMEOUT);
+
+  // This is a noop on ICS, and will always fail
+  bool Detach();
+
+  GLContext* GetAttachedContext() { return mAttachedContext; }
+
   AndroidNativeWindow* NativeWindow() {
     return mNativeWindow;
   }
 
   // This attaches the updated data to the TEXTURE_EXTERNAL target
   void UpdateTexImage();
 
   bool GetTransformMatrix(mozilla::gfx::Matrix4x4& aMatrix);
@@ -63,25 +90,28 @@ public:
   void NotifyFrameAvailable();
 
   GLuint Texture() { return mTexture; }
   jobject JavaSurface() { return mSurface; }
 private:
   AndroidSurfaceTexture();
   ~AndroidSurfaceTexture();
 
-  bool Init(GLuint aTexture);
+  bool Init(GLContext* aContext, GLuint aTexture);
 
   GLuint mTexture;
   jobject mSurfaceTexture;
   jobject mSurface;
 
+  Monitor mMonitor;
+  GLContext* mAttachedContext;
+
   RefPtr<AndroidNativeWindow> mNativeWindow;
   int mID;
   nsRefPtr<nsIRunnable> mFrameAvailableCallback;
 };
-  
+
 }
 }
 
 
 #endif
 #endif
--- a/gfx/layers/opengl/TextureHostOGL.cpp
+++ b/gfx/layers/opengl/TextureHostOGL.cpp
@@ -529,22 +529,25 @@ SurfaceTextureHost::Lock()
     mTextureSource = new SurfaceTextureSource(mCompositor,
                                               mSurfTex,
                                               format,
                                               target,
                                               wrapMode,
                                               mSize);
   }
 
+  mSurfTex->Attach(gl());
+
   return true;
 }
 
 void
 SurfaceTextureHost::Unlock()
 {
+  mSurfTex->Detach();
 }
 
 void
 SurfaceTextureHost::SetCompositor(Compositor* aCompositor)
 {
   CompositorOGL* glCompositor = static_cast<CompositorOGL*>(aCompositor);
   mCompositor = glCompositor;
   if (mTextureSource) {
--- a/gfx/layers/opengl/TexturePoolOGL.cpp
+++ b/gfx/layers/opengl/TexturePoolOGL.cpp
@@ -97,16 +97,21 @@ void TexturePoolOGL::Fill(GLContext* aCo
     texture = (GLuint*)malloc(sizeof(GLuint));
     sActiveContext->fGenTextures(1, texture);
     sTextures->Push((void*) texture);
   }
 
   sMonitor->NotifyAll();
 }
 
+GLContext* TexturePoolOGL::GetGLContext()
+{
+  return sActiveContext;
+}
+
 void TexturePoolOGL::Init()
 {
   sMonitor = new Monitor("TexturePoolOGL.sMonitor");
   sTextures = new nsDeque();
 }
 
 void TexturePoolOGL::Shutdown()
 {
--- a/gfx/layers/opengl/TexturePoolOGL.h
+++ b/gfx/layers/opengl/TexturePoolOGL.h
@@ -20,16 +20,18 @@ public:
   // Get a new texture from the pool. Will block
   // and wait for one to be created if necessary
   static GLuint AcquireTexture();
 
   // Called by the active LayerManagerOGL to fill
   // the pool
   static void Fill(GLContext* aContext);
 
+  static GLContext* GetGLContext();
+
   // Initializes the pool, but does not fill it. Called by gfxPlatform init.
   static void Init();
 
   // Clears all internal data structures in preparation for shutdown
   static void Shutdown();
 };
 
 } // gl