Bug 947781 - Part 2: Add the ability to readback texture data to TextureClient. r=nical
authorBas Schouten <bschouten@mozilla.com>
Wed, 30 Jul 2014 15:38:46 +0200
changeset 196981 6346e96ceac008d657c8e071ec102a7c2a8b1d0d
parent 196980 436cc8abfda3ba2f27c889379b293bb0b73f1528
child 196982 881f358bc39e7393294295f9677f07c54eff93eb
push id27228
push usercbook@mozilla.com
push dateThu, 31 Jul 2014 10:58:52 +0000
treeherdermozilla-central@aa176fcc56b8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnical
bugs947781
milestone34.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 947781 - Part 2: Add the ability to readback texture data to TextureClient. r=nical
gfx/layers/TextureDIB.cpp
gfx/layers/client/TextureClient.cpp
gfx/layers/client/TextureClient.h
gfx/layers/d3d11/ReadbackManagerD3D11.cpp
gfx/layers/d3d11/ReadbackManagerD3D11.h
gfx/layers/d3d11/TextureD3D11.cpp
gfx/layers/moz.build
gfx/thebes/gfxWindowsPlatform.cpp
gfx/thebes/gfxWindowsPlatform.h
--- a/gfx/layers/TextureDIB.cpp
+++ b/gfx/layers/TextureDIB.cpp
@@ -49,16 +49,22 @@ DIBTextureClient::Lock(OpenMode)
   return true;
 }
 
 void
 DIBTextureClient::Unlock()
 {
   MOZ_ASSERT(mIsLocked, "Unlocked called while the texture is not locked!");
   if (mDrawTarget) {
+    if (mReadbackSink) {
+      RefPtr<SourceSurface> snapshot = mDrawTarget->Snapshot();
+      RefPtr<DataSourceSurface> dataSurf = snapshot->GetDataSurface();
+      mReadbackSink->ProcessReadback(dataSurf);
+    }
+
     mDrawTarget->Flush();
     mDrawTarget = nullptr;
   }
 
   mIsLocked = false;
 }
 
 bool
--- a/gfx/layers/client/TextureClient.cpp
+++ b/gfx/layers/client/TextureClient.cpp
@@ -726,16 +726,22 @@ BufferTextureClient::Unlock()
   }
 
   // see the comment on TextureClient::BorrowDrawTarget.
   // This DrawTarget is internal to the TextureClient and is only exposed to the
   // outside world between Lock() and Unlock(). This assertion checks that no outside
   // reference remains by the time Unlock() is called.
   MOZ_ASSERT(mDrawTarget->refCount() == 1);
 
+  if (mReadbackSink) {
+    RefPtr<SourceSurface> snapshot = mDrawTarget->Snapshot();
+    RefPtr<DataSourceSurface> dataSurf = snapshot->GetDataSurface();
+    mReadbackSink->ProcessReadback(dataSurf);
+  }
+
   mDrawTarget->Flush();
   mDrawTarget = nullptr;
 }
 
 bool
 BufferTextureClient::UpdateYCbCr(const PlanarYCbCrData& aData)
 {
   MOZ_ASSERT(mLocked);
--- a/gfx/layers/client/TextureClient.h
+++ b/gfx/layers/client/TextureClient.h
@@ -85,16 +85,37 @@ public:
    * be painted entirely with opaque content.
    */
   virtual bool AllocateForYCbCr(gfx::IntSize aYSize,
                                 gfx::IntSize aCbCrSize,
                                 StereoMode aStereoMode) = 0;
 };
 
 /**
+ * This class may be used to asynchronously receive an update when the content
+ * drawn to this texture client is available for reading in CPU memory. This
+ * can only be used on texture clients that support draw target creation.
+ */
+class TextureReadbackSink
+{
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TextureReadbackSink)
+public:
+  /**
+   * Callback function to implement in order to receive a DataSourceSurface
+   * containing the data read back from the texture client. This will always
+   * be called on the main thread, and this may not hold on to the
+   * DataSourceSurface beyond the execution of this function.
+   */
+  virtual void ProcessReadback(gfx::DataSourceSurface *aSourceSurface) = 0;
+
+protected:
+  virtual ~TextureReadbackSink() {}
+};
+
+/**
  * TextureClient is a thin abstraction over texture data that need to be shared
  * between the content process and the compositor process. It is the
  * content-side half of a TextureClient/TextureHost pair. A corresponding
  * TextureHost lives on the compositor-side.
  *
  * TextureClient's primary purpose is to present texture data in a way that is
  * understood by the IPC system. There are two ways to use it:
  * - Use it to serialize image data that is not IPC-friendly (most likely
@@ -368,16 +389,25 @@ public:
   /**
    * Track how much of this texture is wasted.
    * For example we might allocate a 256x256 tile but only use 10x10.
    */
    void SetWaste(int aWasteArea) {
      mWasteTracker.Update(aWasteArea, BytesPerPixel(GetFormat()));
    }
 
+   /**
+    * This sets the readback sink that this texture is to use. This will
+    * receive the data for this texture as soon as it becomes available after
+    * texture unlock.
+    */
+   virtual void SetReadbackSink(TextureReadbackSink* aReadbackSink) {
+     mReadbackSink = aReadbackSink;
+   }
+
 private:
   /**
    * Called once, just before the destructor.
    *
    * Here goes the shut-down code that uses virtual methods.
    * Must only be called by Release().
    */
   void Finalize();
@@ -416,16 +446,18 @@ protected:
   RefPtr<ISurfaceAllocator> mAllocator;
   TextureFlags mFlags;
   FenceHandle mReleaseFenceHandle;
   FenceHandle mAcquireFenceHandle;
   gl::GfxTextureWasteTracker mWasteTracker;
   bool mShared;
   bool mValid;
 
+  RefPtr<TextureReadbackSink> mReadbackSink;
+
   friend class TextureChild;
   friend class RemoveTextureFromCompositableTracker;
   friend void TestTextureClientSurface(TextureClient*, gfxImageSurface*);
   friend void TestTextureClientYCbCr(TextureClient*, PlanarYCbCrData&);
 };
 
 /**
  * Task that releases TextureClient pointer on a specified thread.
new file mode 100644
--- /dev/null
+++ b/gfx/layers/d3d11/ReadbackManagerD3D11.cpp
@@ -0,0 +1,159 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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/. */
+
+#include "ReadbackManagerD3D11.h"
+#include "ReadbackProcessor.h"
+#include "ReadbackLayer.h"
+#include "mozilla/layers/TextureClient.h"
+#include "mozilla/gfx/2D.h"
+
+#include "nsIThread.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+
+using namespace gfx;
+
+namespace layers {
+
+// Structure that contains the information required to execute a readback task,
+// the only member accessed off the main thread here is mReadbackTexture. Since
+// mSink may be released only on the main thread this object should always be
+// destroyed on the main thread!
+struct ReadbackTask {
+  // The texture that we copied the contents of the thebeslayer to.
+  nsRefPtr<ID3D10Texture2D> mReadbackTexture;
+  // The sink that we're trying to read back to.
+  RefPtr<TextureReadbackSink> mSink;
+};
+
+// This class is created and dispatched from the Readback thread but it must be
+// destroyed by the main thread.
+class ReadbackResultWriterD3D11 MOZ_FINAL : public nsIRunnable
+{
+  ~ReadbackResultWriterD3D11() {}
+  NS_DECL_THREADSAFE_ISUPPORTS
+public:
+  ReadbackResultWriterD3D11(ReadbackTask *aTask) : mTask(aTask) {}
+
+  NS_IMETHODIMP Run()
+  {
+    D3D10_TEXTURE2D_DESC desc;
+    mTask->mReadbackTexture->GetDesc(&desc);
+
+    D3D10_MAPPED_TEXTURE2D mappedTex;
+    // We know this map will immediately succeed, as we've already mapped this
+    // copied data on our task thread.
+    HRESULT hr = mTask->mReadbackTexture->Map(0, D3D10_MAP_READ, 0, &mappedTex);
+
+    if (FAILED(hr)) {
+      mTask->mSink->ProcessReadback(nullptr);
+    }
+
+    {
+      RefPtr<DataSourceSurface> surf =
+        Factory::CreateWrappingDataSourceSurface((uint8_t*)mappedTex.pData, mappedTex.RowPitch,
+                                                 IntSize(desc.Width, desc.Height),
+                                                 SurfaceFormat::B8G8R8X8);
+
+      mTask->mSink->ProcessReadback(surf);
+
+      MOZ_ASSERT(surf->hasOneRef());
+    }
+
+    mTask->mReadbackTexture->Unmap(0);
+
+    return NS_OK;
+  }
+
+private:
+  nsAutoPtr<ReadbackTask> mTask;
+};
+
+NS_IMPL_ISUPPORTS(ReadbackResultWriterD3D11, nsIRunnable)
+
+static DWORD WINAPI StartTaskThread(void *aManager)
+{
+  static_cast<ReadbackManagerD3D11*>(aManager)->ProcessTasks();
+
+  return 0;
+}
+
+ReadbackManagerD3D11::ReadbackManagerD3D11()
+  : mRefCnt(0)
+{
+  ::InitializeCriticalSection(&mTaskMutex);
+  mShutdownEvent = ::CreateEventA(nullptr, FALSE, FALSE, nullptr);
+  mTaskSemaphore = ::CreateSemaphoreA(nullptr, 0, 1000000, nullptr);
+  mTaskThread = ::CreateThread(nullptr, 0, StartTaskThread, this, 0, 0);
+}
+
+ReadbackManagerD3D11::~ReadbackManagerD3D11()
+{
+  ::SetEvent(mShutdownEvent);
+
+  // This shouldn't take longer than 5 seconds, if it does we're going to choose
+  // to leak the thread and its synchronisation in favor of crashing or freezing
+  DWORD result = ::WaitForSingleObject(mTaskThread, 5000);
+  if (result != WAIT_TIMEOUT) {
+    ::DeleteCriticalSection(&mTaskMutex);
+    ::CloseHandle(mShutdownEvent);
+    ::CloseHandle(mTaskSemaphore);
+    ::CloseHandle(mTaskThread);
+  } else {
+    NS_RUNTIMEABORT("ReadbackManager: Task thread did not shutdown in 5 seconds.");
+  }
+}
+
+void
+ReadbackManagerD3D11::PostTask(ID3D10Texture2D *aTexture, TextureReadbackSink* aSink)
+{
+  ReadbackTask *task = new ReadbackTask;
+  task->mReadbackTexture = aTexture;
+  task->mSink = aSink;
+
+  ::EnterCriticalSection(&mTaskMutex);
+  mPendingReadbackTasks.AppendElement(task);
+  ::LeaveCriticalSection(&mTaskMutex);
+
+  ::ReleaseSemaphore(mTaskSemaphore, 1, nullptr);
+}
+
+void
+ReadbackManagerD3D11::ProcessTasks()
+{
+  HANDLE handles[] = { mTaskSemaphore, mShutdownEvent };
+
+  while (true) {
+    DWORD result = ::WaitForMultipleObjects(2, handles, FALSE, INFINITE);
+    if (result != WAIT_OBJECT_0) {
+      return;
+    }
+
+    ::EnterCriticalSection(&mTaskMutex);
+    if (mPendingReadbackTasks.Length() == 0) {
+      NS_RUNTIMEABORT("Trying to read from an empty array, bad bad bad");
+    }
+    ReadbackTask *nextReadbackTask = mPendingReadbackTasks[0].forget();
+    mPendingReadbackTasks.RemoveElementAt(0);
+    ::LeaveCriticalSection(&mTaskMutex);
+
+    // We want to block here until the texture contents are available, the
+    // easiest thing is to simply map and unmap.
+    D3D10_MAPPED_TEXTURE2D mappedTex;
+    nextReadbackTask->mReadbackTexture->Map(0, D3D10_MAP_READ, 0, &mappedTex);
+    nextReadbackTask->mReadbackTexture->Unmap(0);
+
+    // We can only send the update to the sink on the main thread, so post an
+    // event there to do so. Ownership of the task is passed from
+    // mPendingReadbackTasks to ReadbackResultWriter here.
+    nsCOMPtr<nsIThread> thread = do_GetMainThread();
+    thread->Dispatch(new ReadbackResultWriterD3D11(nextReadbackTask),
+                     nsIEventTarget::DISPATCH_NORMAL);
+  }
+}
+
+}
+}
new file mode 100644
--- /dev/null
+++ b/gfx/layers/d3d11/ReadbackManagerD3D11.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * 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 GFX_READBACKMANAGERD3D11_H
+#define GFX_READBACKMANAGERD3D11_H
+
+#include <windows.h>
+#include <d3d10_1.h>
+
+#include "nsTArray.h"
+#include "nsAutoPtr.h"
+
+namespace mozilla {
+namespace layers {
+
+class TextureReadbackSink;
+struct ReadbackTask;
+
+class ReadbackManagerD3D11 MOZ_FINAL
+{
+  NS_INLINE_DECL_REFCOUNTING(ReadbackManagerD3D11)
+public:
+  ReadbackManagerD3D11();
+  ~ReadbackManagerD3D11();
+
+  /**
+   * Tell the readback manager to post a readback task.
+   *
+   * @param aTexture D3D10_USAGE_STAGING texture that will contain the data that
+   *                 was readback.
+   * @param aSink    TextureReadbackSink that the resulting DataSourceSurface
+   *                 should be dispatched to.
+   */
+  void PostTask(ID3D10Texture2D* aTexture, TextureReadbackSink* aSink);
+
+private:
+  friend DWORD WINAPI StartTaskThread(void *aManager);
+
+  void ProcessTasks();
+
+  // The invariant maintained by |mTaskSemaphore| is that the readback thread
+  // will awaken from WaitForMultipleObjects() at least once per readback
+  // task enqueued by the main thread.  Since the readback thread processes
+  // exactly one task per wakeup (with one exception), no tasks are lost.  The
+  // exception is when the readback thread is shut down, which orphans the
+  // remaining tasks, on purpose.
+  HANDLE mTaskSemaphore;
+  // Event signaled when the task thread should shutdown
+  HANDLE mShutdownEvent;
+  // Handle to the task thread
+  HANDLE mTaskThread;
+
+  // FiFo list of readback tasks that are to be executed. Access is synchronized
+  // by mTaskMutex.
+  CRITICAL_SECTION mTaskMutex;
+  nsTArray<nsAutoPtr<ReadbackTask>> mPendingReadbackTasks;
+};
+
+}
+}
+
+#endif /* GFX_READBACKMANAGERD3D11_H */
--- a/gfx/layers/d3d11/TextureD3D11.cpp
+++ b/gfx/layers/d3d11/TextureD3D11.cpp
@@ -6,16 +6,17 @@
 #include "TextureD3D11.h"
 #include "CompositorD3D11.h"
 #include "gfxContext.h"
 #include "Effects.h"
 #include "mozilla/layers/YCbCrImageDataSerializer.h"
 #include "gfxWindowsPlatform.h"
 #include "gfxD2DSurface.h"
 #include "gfx2DGlue.h"
+#include "ReadbackManagerD3D11.h"
 
 namespace mozilla {
 
 using namespace gfx;
 
 namespace layers {
 
 static DXGI_FORMAT
@@ -244,16 +245,38 @@ TextureClientD3D11::Unlock()
     // see the comment on TextureClient::BorrowDrawTarget.
     // This DrawTarget is internal to the TextureClient and is only exposed to the
     // outside world between Lock() and Unlock(). This assertion checks that no outside
     // reference remains by the time Unlock() is called.
     MOZ_ASSERT(mDrawTarget->refCount() == 1);
     mDrawTarget->Flush();
   }
 
+  if (mReadbackSink) {
+    ID3D10Device* device = gfxWindowsPlatform::GetPlatform()->GetD3D10Device();
+
+    D3D10_TEXTURE2D_DESC desc;
+    mTexture->GetDesc(&desc);
+    desc.BindFlags = 0;
+    desc.Usage = D3D10_USAGE_STAGING;
+    desc.CPUAccessFlags = D3D10_CPU_ACCESS_READ;
+    desc.MiscFlags = 0;
+
+    RefPtr<ID3D10Texture2D> tex;
+    HRESULT hr = device->CreateTexture2D(&desc, nullptr, byRef(tex));
+
+    if (SUCCEEDED(hr)) {
+      device->CopyResource(tex, mTexture);
+
+      gfxWindowsPlatform::GetPlatform()->GetReadbackManager()->PostTask(tex, mReadbackSink);
+    } else {
+      mReadbackSink->ProcessReadback(nullptr);
+    }
+  }
+
   // The DrawTarget is created only once, and is only usable between calls
   // to Lock and Unlock.
   UnlockD3DTexture(mTexture.get());
   mIsLocked = false;
 }
 
 DrawTarget*
 TextureClientD3D11::BorrowDrawTarget()
--- a/gfx/layers/moz.build
+++ b/gfx/layers/moz.build
@@ -75,32 +75,34 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'wind
         ]
     if CONFIG['MOZ_ENABLE_D3D10_LAYER']:
         EXPORTS += [
             'd3d10/LayerManagerD3D10.h',
             'd3d10/ReadbackManagerD3D10.h',
         ]
         EXPORTS.mozilla.layers += [
             'd3d11/CompositorD3D11.h',
+            'd3d11/ReadbackManagerD3D11.h',
             'd3d11/TextureD3D11.h',
             'ipc/ShadowLayerUtilsD3D10.h',
         ]
         UNIFIED_SOURCES += [
             'd3d10/CanvasLayerD3D10.cpp',
             'd3d10/ColorLayerD3D10.cpp',
             'd3d10/ContainerLayerD3D10.cpp',
             'd3d10/ImageLayerD3D10.cpp',
             'd3d10/LayerManagerD3D10.cpp',
             'd3d10/ReadbackManagerD3D10.cpp',
             'd3d10/ThebesLayerD3D10.cpp',
             'd3d11/TextureD3D11.cpp',
             'ipc/ShadowLayerUtilsD3D10.cpp',
         ]
         SOURCES += [
             'd3d11/CompositorD3D11.cpp',
+            'd3d11/ReadbackManagerD3D11.cpp',
         ]
 
 EXPORTS.gfxipc += [
     'ipc/ShadowLayerUtils.h',
 ]
 
 EXPORTS.mozilla.layers += [
     'apz/public/GeckoContentController.h',
--- a/gfx/thebes/gfxWindowsPlatform.cpp
+++ b/gfx/thebes/gfxWindowsPlatform.cpp
@@ -27,16 +27,17 @@
 
 #include "gfxCrashReporterUtils.h"
 
 #include "gfxGDIFontList.h"
 #include "gfxGDIFont.h"
 
 #include "mozilla/layers/CompositorParent.h"   // for CompositorParent::IsInCompositorThread
 #include "DeviceManagerD3D9.h"
+#include "mozilla/layers/ReadbackManagerD3D11.h"
 
 #include "WinUtils.h"
 
 #ifdef CAIRO_HAS_DWRITE_FONT
 #include "gfxDWriteFontList.h"
 #include "gfxDWriteFonts.h"
 #include "gfxDWriteCommon.h"
 #include <dwrite.h>
@@ -1386,16 +1387,26 @@ gfxWindowsPlatform::GetD3D11Device()
 
   // We leak these everywhere and we need them our entire runtime anyway, let's
   // leak it here as well.
   d3d11Module.disown();
 
   return mD3D11Device;
 }
 
+ReadbackManagerD3D11*
+gfxWindowsPlatform::GetReadbackManager()
+{
+  if (!mD3D11ReadbackManager) {
+    mD3D11ReadbackManager = new ReadbackManagerD3D11();
+  }
+
+  return mD3D11ReadbackManager;
+}
+
 bool
 gfxWindowsPlatform::IsOptimus()
 {
     static int knowIsOptimus = -1;
     if (knowIsOptimus == -1) {
         // other potential optimus -- nvd3d9wrapx.dll & nvdxgiwrap.dll
         if (GetModuleHandleA("nvumdshim.dll") ||
             GetModuleHandleA("nvumdshimx.dll"))
--- a/gfx/thebes/gfxWindowsPlatform.h
+++ b/gfx/thebes/gfxWindowsPlatform.h
@@ -41,16 +41,17 @@
 #define D3D_FEATURE_LEVEL_11_1 static_cast<D3D_FEATURE_LEVEL>(0xb100)
 #define D3D_FL9_1_REQ_TEXTURE2D_U_OR_V_DIMENSION 2048
 #define D3D_FL9_3_REQ_TEXTURE2D_U_OR_V_DIMENSION 4096
 #endif
 
 namespace mozilla {
 namespace layers {
 class DeviceManagerD3D9;
+class ReadbackManagerD3D11;
 }
 }
 struct IDirect3DDevice9;
 struct ID3D11Device;
 struct IDXGIAdapter1;
 
 class nsIMemoryReporter;
 
@@ -251,16 +252,18 @@ public:
     mozilla::layers::DeviceManagerD3D9* GetD3D9DeviceManager();
     IDirect3DDevice9* GetD3D9Device();
 #ifdef CAIRO_HAS_D2D_SURFACE
     cairo_device_t *GetD2DDevice() { return mD2DDevice; }
     ID3D10Device1 *GetD3D10Device() { return mD2DDevice ? cairo_d2d_device_get_device(mD2DDevice) : nullptr; }
 #endif
     ID3D11Device *GetD3D11Device();
 
+    mozilla::layers::ReadbackManagerD3D11* GetReadbackManager();
+
     static bool IsOptimus();
 
 protected:
     RenderMode mRenderMode;
 
     int8_t mUseClearTypeForDownloadableFonts;
     int8_t mUseClearTypeAlways;
 
@@ -279,16 +282,17 @@ private:
 #endif
 #ifdef CAIRO_HAS_D2D_SURFACE
     cairo_device_t *mD2DDevice;
 #endif
     mozilla::RefPtr<IDXGIAdapter1> mAdapter;
     nsRefPtr<mozilla::layers::DeviceManagerD3D9> mDeviceManager;
     mozilla::RefPtr<ID3D11Device> mD3D11Device;
     bool mD3D11DeviceInitialized;
+    mozilla::RefPtr<mozilla::layers::ReadbackManagerD3D11> mD3D11ReadbackManager;
 
     virtual void GetPlatformCMSOutputProfile(void* &mem, size_t &size);
 
     // TODO: unify this with mPrefFonts (NB: holds families, not fonts) in gfxPlatformFontList
     nsDataHashtable<nsCStringHashKey, nsTArray<nsRefPtr<gfxFontEntry> > > mPrefFonts;
 };
 
 #endif /* GFX_WINDOWS_PLATFORM_H */