Bug 1444432 - Create a Compositor abstraction called AsyncReadbackBuffer and implement it for CompositorOGL. r?jrmuizel draft
authorMarkus Stange <mstange@themasta.com>
Wed, 28 Mar 2018 15:46:38 -0400
changeset 781548 069b79ea3abb0f897472144bc2b57a8dcfe9ce13
parent 781547 fb0181e1cdb254b78b4c1041601ec95f01b1624b
child 781549 744d10d9f0d774e92d7b4d994700793cb66a1f9f
push id106334
push userbmo:mstange@themasta.com
push dateFri, 13 Apr 2018 04:36:02 +0000
reviewersjrmuizel
bugs1444432
milestone61.0a1
Bug 1444432 - Create a Compositor abstraction called AsyncReadbackBuffer and implement it for CompositorOGL. r?jrmuizel MozReview-Commit-ID: Jx1RFFoKypz
gfx/layers/Compositor.h
gfx/layers/opengl/CompositorOGL.cpp
gfx/layers/opengl/CompositorOGL.h
--- a/gfx/layers/Compositor.h
+++ b/gfx/layers/Compositor.h
@@ -130,16 +130,17 @@ class DataTextureSource;
 class CompositingRenderTarget;
 class CompositorBridgeParent;
 class LayerManagerComposite;
 class CompositorOGL;
 class CompositorD3D11;
 class BasicCompositor;
 class TextureReadLock;
 struct GPUStats;
+class AsyncReadbackBuffer;
 
 enum SurfaceInitMode
 {
   INIT_MODE_NONE,
   INIT_MODE_CLEAR
 };
 
 /**
@@ -258,16 +259,33 @@ public:
    * aSourcePoint specifies the point in aSource to copy data from.
    */
   virtual already_AddRefed<CompositingRenderTarget>
   CreateRenderTargetFromSource(const gfx::IntRect& aRect,
                                const CompositingRenderTarget* aSource,
                                const gfx::IntPoint& aSourcePoint) = 0;
 
   /**
+   * Grab a snapshot of aSource and store it in aDest, so that the pixels can
+   * be read on the CPU by mapping aDest at some point in the future.
+   * aSource and aDest must have the same size.
+   * If this is a GPU compositor, this call must not block on the GPU.
+   * Returns whether the operation was successful.
+   */
+  virtual bool
+  ReadbackRenderTarget(CompositingRenderTarget* aSource,
+                       AsyncReadbackBuffer* aDest) { return false; }
+
+  /**
+   * Create an AsyncReadbackBuffer of the specified size. Can return null.
+   */
+  virtual already_AddRefed<AsyncReadbackBuffer>
+  CreateAsyncReadbackBuffer(const gfx::IntSize& aSize) { return nullptr; }
+
+  /**
    * Sets the given surface as the target for subsequent calls to DrawQuad.
    * Passing null as aSurface sets the screen as the target.
    */
   virtual void SetRenderTarget(CompositingRenderTarget* aSurface) = 0;
 
   /**
    * Returns the current target for rendering. Will return null if we are
    * rendering to the screen.
@@ -620,16 +638,32 @@ BlendOpIsMixBlendMode(gfx::CompositionOp
   case gfx::CompositionOp::OP_COLOR:
   case gfx::CompositionOp::OP_LUMINOSITY:
     return true;
   default:
     return false;
   }
 }
 
+class AsyncReadbackBuffer
+{
+public:
+  NS_INLINE_DECL_REFCOUNTING(AsyncReadbackBuffer)
+
+  gfx::IntSize GetSize() const { return mSize; }
+  virtual bool MapAndCopyInto(gfx::DataSourceSurface* aSurface,
+                              const gfx::IntSize& aReadSize) const=0;
+
+protected:
+  explicit AsyncReadbackBuffer(const gfx::IntSize& aSize) : mSize(aSize) {}
+  virtual ~AsyncReadbackBuffer() {}
+
+  gfx::IntSize mSize;
+};
+
 struct TexturedVertex
 {
   float position[2];
   float texCoords[2];
 };
 
 nsTArray<TexturedVertex>
 TexturedTrianglesToVertexArray(const nsTArray<gfx::TexturedTriangle>& aTriangles);
--- a/gfx/layers/opengl/CompositorOGL.cpp
+++ b/gfx/layers/opengl/CompositorOGL.cpp
@@ -39,16 +39,17 @@
 #include "nsMathUtils.h"                // for NS_roundf
 #include "nsRect.h"                     // for mozilla::gfx::IntRect
 #include "nsServiceManagerUtils.h"      // for do_GetService
 #include "nsString.h"                   // for nsString, nsAutoCString, etc
 #include "ScopedGLHelpers.h"
 #include "GLReadTexImageHelper.h"
 #include "GLBlitTextureImageHelper.h"
 #include "HeapCopyOfStackArray.h"
+#include "mozilla/gfx/Swizzle.h"
 
 #if MOZ_WIDGET_ANDROID
 #include "GeneratedJNIWrappers.h"
 #endif
 
 #include "GeckoProfiler.h"
 
 namespace mozilla {
@@ -58,16 +59,97 @@ using namespace gfx;
 
 namespace layers {
 
 using namespace mozilla::gl;
 
 static const GLuint kCoordinateAttributeIndex = 0;
 static const GLuint kTexCoordinateAttributeIndex = 1;
 
+class AsyncReadbackBufferOGL final : public AsyncReadbackBuffer
+{
+public:
+  AsyncReadbackBufferOGL(GLContext* aGL, const IntSize& aSize);
+
+  bool MapAndCopyInto(DataSourceSurface* aSurface,
+                      const IntSize& aReadSize) const override;
+
+  void Bind() const
+  {
+    mGL->fBindBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, mBufferHandle);
+    mGL->fPixelStorei(LOCAL_GL_PACK_ALIGNMENT, 1);
+  }
+
+protected:
+  ~AsyncReadbackBufferOGL() override;
+
+private:
+  GLContext* mGL;
+  GLuint mBufferHandle;
+};
+
+AsyncReadbackBufferOGL::AsyncReadbackBufferOGL(GLContext* aGL,
+                                               const IntSize& aSize)
+  : AsyncReadbackBuffer(aSize)
+  , mGL(aGL)
+{
+  size_t bufferByteCount = mSize.width * mSize.height * 4;
+  mGL->fGenBuffers(1, &mBufferHandle);
+
+  ScopedPackState scopedPackState(mGL);
+  Bind();
+  mGL->fBufferData(LOCAL_GL_PIXEL_PACK_BUFFER, bufferByteCount, nullptr,
+                   LOCAL_GL_STREAM_READ);
+}
+
+AsyncReadbackBufferOGL::~AsyncReadbackBufferOGL()
+{
+  if (mGL && mGL->MakeCurrent()) {
+    mGL->fDeleteBuffers(1, &mBufferHandle);
+  }
+}
+
+bool
+AsyncReadbackBufferOGL::MapAndCopyInto(DataSourceSurface* aSurface,
+                                       const IntSize& aReadSize) const
+{
+  MOZ_RELEASE_ASSERT(aReadSize <= aSurface->GetSize());
+
+  if (!mGL || !mGL->MakeCurrent()) {
+    return false;
+  }
+
+  ScopedPackState scopedPackState(mGL);
+  Bind();
+  uint8_t* srcData = static_cast<uint8_t*>(
+    mGL->fMapBuffer(LOCAL_GL_PIXEL_PACK_BUFFER, LOCAL_GL_READ_ONLY));
+
+  if (!srcData) {
+    return false;
+  }
+
+  int32_t srcStride = mSize.width * 4; // Bind() sets an alignment of 1
+  DataSourceSurface::ScopedMap map(aSurface, DataSourceSurface::WRITE);
+  uint8_t* destData = map.GetData();
+  int32_t destStride = map.GetStride();
+  SurfaceFormat destFormat = aSurface->GetFormat();
+  for (int32_t destRow = 0; destRow < aReadSize.height; destRow++) {
+    // Turn srcData upside down during the copy.
+    int32_t srcRow = aReadSize.height - 1 - destRow;
+    uint8_t* src = &srcData[srcRow * srcStride];
+    uint8_t* dest = &destData[destRow * destStride];
+    SwizzleData(src, srcStride, SurfaceFormat::R8G8B8A8,
+                dest, destStride, destFormat, IntSize(aReadSize.width, 1));
+  }
+
+  mGL->fUnmapBuffer(LOCAL_GL_PIXEL_PACK_BUFFER);
+
+  return true;
+}
+
 static void
 BindMaskForProgram(ShaderProgramOGL* aProgram, TextureSourceOGL* aSourceMask,
                    GLenum aTexUnit, const gfx::Matrix4x4& aTransform)
 {
   MOZ_ASSERT(LOCAL_GL_TEXTURE0 <= aTexUnit && aTexUnit <= LOCAL_GL_TEXTURE31);
   aSourceMask->BindTexture(aTexUnit, gfx::SamplingFilter::LINEAR);
   aProgram->SetMaskTextureUnit(aTexUnit - LOCAL_GL_TEXTURE0);
   aProgram->SetMaskLayerTransform(aTransform);
@@ -598,16 +680,46 @@ CompositorOGL::SetRenderTarget(Compositi
 }
 
 CompositingRenderTarget*
 CompositorOGL::GetCurrentRenderTarget() const
 {
   return mCurrentRenderTarget;
 }
 
+already_AddRefed<AsyncReadbackBuffer>
+CompositorOGL::CreateAsyncReadbackBuffer(const IntSize& aSize)
+{
+  return MakeAndAddRef<AsyncReadbackBufferOGL>(mGLContext, aSize);
+}
+
+bool
+CompositorOGL::ReadbackRenderTarget(CompositingRenderTarget* aSource,
+                                    AsyncReadbackBuffer* aDest)
+{
+  IntSize size = aSource->GetSize();
+  MOZ_RELEASE_ASSERT(aDest->GetSize() == size);
+
+  RefPtr<CompositingRenderTarget> previousTarget = GetCurrentRenderTarget();
+  if (previousTarget != aSource) {
+    SetRenderTarget(aSource);
+  }
+
+  ScopedPackState scopedPackState(mGLContext);
+  static_cast<AsyncReadbackBufferOGL*>(aDest)->Bind();
+
+  mGLContext->fReadPixels(0, 0, size.width, size.height,
+                          LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE, 0);
+
+  if (previousTarget != aSource) {
+    SetRenderTarget(previousTarget);
+  }
+  return true;
+}
+
 static GLenum
 GetFrameBufferInternalFormat(GLContext* gl,
                              GLuint aFrameBuffer,
                              mozilla::widget::CompositorWidget* aWidget)
 {
   if (aFrameBuffer == 0) { // default framebuffer
     return aWidget->GetGLFrameBufferFormat();
   }
--- a/gfx/layers/opengl/CompositorOGL.h
+++ b/gfx/layers/opengl/CompositorOGL.h
@@ -153,16 +153,23 @@ public:
   virtual already_AddRefed<CompositingRenderTarget>
   CreateRenderTargetFromSource(const gfx::IntRect &aRect,
                                const CompositingRenderTarget *aSource,
                                const gfx::IntPoint &aSourcePoint) override;
 
   virtual void SetRenderTarget(CompositingRenderTarget *aSurface) override;
   virtual CompositingRenderTarget* GetCurrentRenderTarget() const override;
 
+  virtual bool
+  ReadbackRenderTarget(CompositingRenderTarget* aSource,
+                       AsyncReadbackBuffer* aDest) override;
+
+  virtual already_AddRefed<AsyncReadbackBuffer>
+  CreateAsyncReadbackBuffer(const gfx::IntSize& aSize) override;
+
   virtual void DrawQuad(const gfx::Rect& aRect,
                         const gfx::IntRect& aClipRect,
                         const EffectChain &aEffectChain,
                         gfx::Float aOpacity,
                         const gfx::Matrix4x4& aTransform,
                         const gfx::Rect& aVisibleRect) override;
 
   virtual void DrawTriangles(const nsTArray<gfx::TexturedTriangle>& aTriangles,