Bug 1592150 - Make NativeLayer::NextSurfaceAs* copy existing drawing from previous buffers so that only the update region needs to be drawn. r=jrmuizel
authorMarkus Stange <mstange@themasta.com>
Tue, 19 Nov 2019 03:11:13 +0000
changeset 502527 ffcddfbaf321cb2f7ca87fe3ab68a1347b4328aa
parent 502526 292ede718577ae84b3856c72dae29989803e764b
child 502528 1af8d457ec9937c9d59746905bbbd30e32ee57c4
push id114172
push userdluca@mozilla.com
push dateTue, 19 Nov 2019 11:31:10 +0000
treeherdermozilla-inbound@b5c5ba07d3db [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjrmuizel
bugs1592150
milestone72.0a1
Bug 1592150 - Make NativeLayer::NextSurfaceAs* copy existing drawing from previous buffers so that only the update region needs to be drawn. r=jrmuizel There are three reasons for doing this. 1. It makes the NativeLayer API more compatible with DirectComposition. 2. Copying existing content may be faster than redrawing those pixels. Redrawing might have some amount of overdraw which takes up more memory bandwidth. 3. Most importantly: Partial updates now have "unidirectional flow of information": The renderer decides which area to redraw, and it redraws exactly that area. In the past, partial updates required the following dance: - Figure out what area changed in this frame. Call that area A. - Invalidate that area in the NativeLayer. - Get the next surface for drawing from the layer. - Request the actual invalid area in the current surface. Call that area B. - Redraw B. Now with this change, the renderer no longer needs to care about B, and can just redraw what changed in the current frame (A). This is useful for WebRender because WebRender prepares drawing commands on a separate thread before it executes them on the render thread. And at the time of preparation, WebRender does not have access to the native layer. It needs to know what to draw ahead of time. Differential Revision: https://phabricator.services.mozilla.com/D51760
gfx/layers/NativeLayerCA.h
gfx/layers/NativeLayerCA.mm
--- a/gfx/layers/NativeLayerCA.h
+++ b/gfx/layers/NativeLayerCA.h
@@ -105,40 +105,50 @@ class NativeLayerCA : public NativeLayer
   bool SurfaceIsFlipped() override;
 
  protected:
   friend class NativeLayerRootCA;
 
   NativeLayerCA(const gfx::IntSize& aSize, bool aIsOpaque);
   ~NativeLayerCA() override;
 
-  // Returns an IOSurface that can be drawn to. The size of the IOSurface will
-  // be the same as the size of this layer.
-  // The returned surface is guaranteed to be not in use by the window server.
+  // Gets the next surface for drawing from our swap chain and stores it in
+  // mInProgressSurface. Returns whether this was successful.
+  // mInProgressSurface is guaranteed to be not in use by the window server.
   // After a call to NextSurface, NextSurface must not be called again until
   // after NotifySurfaceReady has been called. Can be called on any thread. When
   // used from multiple threads, callers need to make sure that they still only
   // call NextSurface and NotifySurfaceReady alternatingly and not in any other
   // order.
-  CFTypeRefPtr<IOSurfaceRef> NextSurface(const MutexAutoLock&);
+  bool NextSurface(const MutexAutoLock&);
 
   // To be called by NativeLayerRootCA:
   CALayer* UnderlyingCALayer() { return mWrappingCALayer; }
   void ApplyChanges();
   void SetBackingScale(float aBackingScale);
 
   // Invalidates the specified region in all surfaces that are tracked by this
   // layer.
   void InvalidateRegionThroughoutSwapchain(const MutexAutoLock&,
                                            const gfx::IntRegion& aRegion);
 
   GLuint GetOrCreateFramebufferForSurface(const MutexAutoLock&,
                                           CFTypeRefPtr<IOSurfaceRef> aSurface,
                                           bool aNeedsDepth);
 
+  // Invalidate aUpdateRegion and make sure that mInProgressSurface has valid
+  // content everywhere outside aUpdateRegion, so that only aUpdateRegion needs
+  // to be drawn. If content needs to be copied from a previous surface, aCopyFn
+  // is called to do the copying.
+  // aCopyFn: Fn(CFTypeRefPtr<IOSurfaceRef> aValidSourceIOSurface,
+  //             const gfx::IntRegion& aCopyRegion) -> void
+  template <typename F>
+  void HandlePartialUpdate(const MutexAutoLock&,
+                           const gfx::IntRegion& aUpdateRegion, F&& aCopyFn);
+
   struct SurfaceWithInvalidRegion {
     CFTypeRefPtr<IOSurfaceRef> mSurface;
     gfx::IntRegion mInvalidRegion;
   };
 
   std::vector<SurfaceWithInvalidRegion> RemoveExcessUnusedSurfaces(
       const MutexAutoLock&);
 
--- a/gfx/layers/NativeLayerCA.mm
+++ b/gfx/layers/NativeLayerCA.mm
@@ -6,16 +6,17 @@
 #import "mozilla/layers/NativeLayerCA.h"
 
 #import <QuartzCore/QuartzCore.h>
 #import <CoreVideo/CVPixelBuffer.h>
 
 #include <utility>
 #include <algorithm>
 
+#include "GLBlitHelper.h"
 #include "GLContextCGL.h"
 #include "MozFramebuffer.h"
 #include "ScopedGLHelpers.h"
 
 @interface CALayer (PrivateSetContentsOpaque)
 - (void)setContentsOpaque:(BOOL)opaque;
 @end
 
@@ -242,21 +243,21 @@ void NativeLayerCA::InvalidateRegionThro
   if (mFrontSurface) {
     mFrontSurface->mInvalidRegion.OrWith(r);
   }
   for (auto& surf : mSurfaces) {
     surf.mInvalidRegion.OrWith(r);
   }
 }
 
-CFTypeRefPtr<IOSurfaceRef> NativeLayerCA::NextSurface(const MutexAutoLock& aLock) {
+bool NativeLayerCA::NextSurface(const MutexAutoLock& aLock) {
   if (mSize.IsEmpty()) {
-    NSLog(@"NextSurface returning nullptr because of invalid mSize (%d, %d).", mSize.width,
+    NSLog(@"NextSurface returning false because of invalid mSize (%d, %d).", mSize.width,
           mSize.height);
-    return nullptr;
+    return false;
   }
 
   MOZ_RELEASE_ASSERT(
       !mInProgressSurface,
       "ERROR: Do not call NextSurface twice in sequence. Call NotifySurfaceReady before the "
       "next call to NextSurface.");
 
   // Find the last surface in unusedSurfaces. If such
@@ -274,48 +275,99 @@ CFTypeRefPtr<IOSurfaceRef> NativeLayerCA
     CFTypeRefPtr<IOSurfaceRef> newSurf = CFTypeRefPtr<IOSurfaceRef>::WrapUnderCreateRule(
         IOSurfaceCreate((__bridge CFDictionaryRef) @{
           (__bridge NSString*)kIOSurfaceWidth : @(mSize.width),
           (__bridge NSString*)kIOSurfaceHeight : @(mSize.height),
           (__bridge NSString*)kIOSurfacePixelFormat : @(kCVPixelFormatType_32BGRA),
           (__bridge NSString*)kIOSurfaceBytesPerElement : @(4),
         }));
     if (!newSurf) {
-      NSLog(@"NextSurface returning nullptr because IOSurfaceCreate failed to create the surface.");
-      return nullptr;
+      NSLog(@"NextSurface returning false because IOSurfaceCreate failed to create the surface.");
+      return false;
     }
     surf = Some(SurfaceWithInvalidRegion{newSurf, IntRect({}, mSize)});
   }
 
   // Delete all other unused surfaces.
   for (auto unusedSurf : unusedSurfaces) {
     mFramebuffers.erase(unusedSurf.mSurface);
   }
   unusedSurfaces.clear();
 
   MOZ_RELEASE_ASSERT(surf);
   mInProgressSurface = std::move(surf);
   IOSurfaceIncrementUseCount(mInProgressSurface->mSurface.get());
-  return mInProgressSurface->mSurface;
+  return true;
+}
+
+template <typename F>
+void NativeLayerCA::HandlePartialUpdate(const MutexAutoLock& aLock,
+                                        const gfx::IntRegion& aUpdateRegion, F&& aCopyFn) {
+  MOZ_RELEASE_ASSERT(IntRect({}, mSize).Contains(aUpdateRegion.GetBounds()),
+                     "The update region should be within the surface bounds.");
+
+  InvalidateRegionThroughoutSwapchain(aLock, aUpdateRegion);
+
+  gfx::IntRegion copyRegion;
+  copyRegion.Sub(mInProgressSurface->mInvalidRegion, aUpdateRegion);
+  if (!copyRegion.IsEmpty()) {
+    // There are parts in mInProgressSurface which are invalid but which are not included in
+    // aUpdateRegion. We will obtain valid content for those parts by copying from a previous
+    // surface.
+    MOZ_RELEASE_ASSERT(
+        mReadySurface || mFrontSurface,
+        "The first call to NextSurface* must always update the entire layer. If this "
+        "is the second call, mReadySurface or mFrontSurface will be Some().");
+
+    // NotifySurfaceReady marks the entire surface as valid. The valid surface is then stored in
+    // mReadySurface, and later moves to mFrontSurface. Get the surface that NotifySurfaceReady was
+    // called on most recently.
+    SurfaceWithInvalidRegion& copySource = mReadySurface ? *mReadySurface : *mFrontSurface;
+    MOZ_RELEASE_ASSERT(copySource.mInvalidRegion.Intersect(copyRegion).IsEmpty(),
+                       "copySource should have valid content in the entire copy region, because "
+                       "the only invalidation since NotifySurfaceReady was aUpdateRegion, and "
+                       "aUpdateRegion has no overlap with copyRegion.");
+
+    // Now copy the valid content, using a callar-provided copy function.
+    aCopyFn(copySource.mSurface, copyRegion);
+    mInProgressSurface->mInvalidRegion.SubOut(copyRegion);
+  }
+
+  MOZ_RELEASE_ASSERT(mInProgressSurface->mInvalidRegion == aUpdateRegion);
 }
 
 RefPtr<gfx::DrawTarget> NativeLayerCA::NextSurfaceAsDrawTarget(const gfx::IntRegion& aUpdateRegion,
                                                                gfx::BackendType aBackendType) {
   MutexAutoLock lock(mMutex);
-  CFTypeRefPtr<IOSurfaceRef> surface = NextSurface(lock);
-  if (!surface) {
+  if (!NextSurface(lock)) {
     return nullptr;
   }
 
-  MOZ_RELEASE_ASSERT(IntRect({}, mSize).Contains(aUpdateRegion.GetBounds()));
-  InvalidateRegionThroughoutSwapchain(lock, aUpdateRegion);
+  mInProgressLockedIOSurface = new MacIOSurface(mInProgressSurface->mSurface);
+  mInProgressLockedIOSurface->Lock(false);
+  RefPtr<gfx::DrawTarget> dt = mInProgressLockedIOSurface->GetAsDrawTargetLocked(aBackendType);
 
-  mInProgressLockedIOSurface = new MacIOSurface(std::move(surface));
-  mInProgressLockedIOSurface->Lock(false);
-  return mInProgressLockedIOSurface->GetAsDrawTargetLocked(aBackendType);
+  HandlePartialUpdate(
+      lock, aUpdateRegion,
+      [&](CFTypeRefPtr<IOSurfaceRef> validSource, const gfx::IntRegion& copyRegion) {
+        RefPtr<MacIOSurface> source = new MacIOSurface(validSource);
+        source->Lock(true);
+        {
+          RefPtr<gfx::DrawTarget> sourceDT = source->GetAsDrawTargetLocked(aBackendType);
+          RefPtr<gfx::SourceSurface> sourceSurface = sourceDT->Snapshot();
+
+          for (auto iter = copyRegion.RectIter(); !iter.Done(); iter.Next()) {
+            const gfx::IntRect& r = iter.Get();
+            dt->CopySurface(sourceSurface, r, r.TopLeft());
+          }
+        }
+        source->Unlock(true);
+      });
+
+  return dt;
 }
 
 void NativeLayerCA::SetGLContext(gl::GLContext* aContext) {
   MutexAutoLock lock(mMutex);
 
   RefPtr<gl::GLContextCGL> glContextCGL = gl::GLContextCGL::Cast(aContext);
   MOZ_RELEASE_ASSERT(glContextCGL, "Unexpected GLContext type");
 
@@ -328,24 +380,40 @@ void NativeLayerCA::SetGLContext(gl::GLC
 gl::GLContext* NativeLayerCA::GetGLContext() {
   MutexAutoLock lock(mMutex);
   return mGLContext;
 }
 
 Maybe<GLuint> NativeLayerCA::NextSurfaceAsFramebuffer(const gfx::IntRegion& aUpdateRegion,
                                                       bool aNeedsDepth) {
   MutexAutoLock lock(mMutex);
-  CFTypeRefPtr<IOSurfaceRef> surface = NextSurface(lock);
-  if (!surface) {
+  if (!NextSurface(lock)) {
     return Nothing();
   }
 
-  MOZ_RELEASE_ASSERT(IntRect({}, mSize).Contains(aUpdateRegion.GetBounds()));
-  InvalidateRegionThroughoutSwapchain(lock, aUpdateRegion);
-  return Some(GetOrCreateFramebufferForSurface(lock, std::move(surface), aNeedsDepth));
+  GLuint fbo = GetOrCreateFramebufferForSurface(lock, mInProgressSurface->mSurface, aNeedsDepth);
+
+  HandlePartialUpdate(
+      lock, aUpdateRegion,
+      [&](CFTypeRefPtr<IOSurfaceRef> validSource, const gfx::IntRegion& copyRegion) {
+        // Copy copyRegion from validSource to fbo.
+        MOZ_RELEASE_ASSERT(mGLContext);
+        mGLContext->MakeCurrent();
+        GLuint sourceFBO = GetOrCreateFramebufferForSurface(lock, validSource, false);
+        for (auto iter = copyRegion.RectIter(); !iter.Done(); iter.Next()) {
+          gfx::IntRect r = iter.Get();
+          if (mSurfaceIsFlipped) {
+            r.y = mSize.height - r.YMost();
+          }
+          mGLContext->BlitHelper()->BlitFramebufferToFramebuffer(sourceFBO, fbo, r, r,
+                                                                 LOCAL_GL_NEAREST);
+        }
+      });
+
+  return Some(fbo);
 }
 
 GLuint NativeLayerCA::GetOrCreateFramebufferForSurface(const MutexAutoLock&,
                                                        CFTypeRefPtr<IOSurfaceRef> aSurface,
                                                        bool aNeedsDepth) {
   auto fbCursor = mFramebuffers.find(aSurface);
   if (fbCursor != mFramebuffers.end()) {
     return fbCursor->second->mFB;