Bug 1488808 Part 13 - Avoid blocking the main thread when painting, and allow repaints after diverging from the recording, r=nical.
authorBrian Hackett <bhackett1024@gmail.com>
Wed, 17 Oct 2018 10:06:43 -0600
changeset 500502 9b5b68080ee6987a11779871bc606b1e4d5dd139
parent 500501 2ba7db69225b588c14915c8609cf82cb5f433a01
child 500503 ed0463d80c34acd808558758e8a5467cb59e8ab1
push id1864
push userffxbld-merge
push dateMon, 03 Dec 2018 15:51:40 +0000
treeherdermozilla-release@f040763d99ad [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnical
bugs1488808
milestone64.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 1488808 Part 13 - Avoid blocking the main thread when painting, and allow repaints after diverging from the recording, r=nical.
toolkit/recordreplay/ipc/ChildIPC.cpp
toolkit/recordreplay/ipc/ChildIPC.h
toolkit/recordreplay/ipc/ChildInternal.h
toolkit/recordreplay/ipc/DisabledIPC.cpp
--- a/toolkit/recordreplay/ipc/ChildIPC.cpp
+++ b/toolkit/recordreplay/ipc/ChildIPC.cpp
@@ -193,16 +193,17 @@ ListenForCheckpointThreadMain(void*)
       NS_DispatchToMainThread(NewRunnableFunction("NewCheckpoint", NewCheckpoint,
                                                   /* aTemporary = */ false));
     } else {
       MOZ_RELEASE_ASSERT(errno == EINTR);
     }
   }
 }
 
+// Shared memory block for graphics data.
 void* gGraphicsShmem;
 
 void
 InitRecordingOrReplayingProcess(int* aArgc, char*** aArgv)
 {
   if (!IsRecordingOrReplaying()) {
     return;
   }
@@ -406,43 +407,54 @@ static VsyncObserver* gVsyncObserver;
 
 void
 SetVsyncObserver(VsyncObserver* aObserver)
 {
   MOZ_RELEASE_ASSERT(!gVsyncObserver || !aObserver);
   gVsyncObserver = aObserver;
 }
 
-void
+static void
 NotifyVsyncObserver()
 {
   if (gVsyncObserver) {
     gVsyncObserver->NotifyVsync(TimeStamp::Now());
   }
 }
 
 ///////////////////////////////////////////////////////////////////////////////
 // Painting
 ///////////////////////////////////////////////////////////////////////////////
 
 // Graphics memory is only written on the compositor thread and read on the
-// main thread and by the middleman. The gPendingPaint flag is used to
+// main thread and by the middleman. The gNumPendingPaints counter is used to
 // synchronize access, so that data is not read until the paint has completed.
 static Maybe<PaintMessage> gPaintMessage;
-static bool gPendingPaint;
+static size_t gNumPendingPaints;
 
 // Target buffer for the draw target created by the child process widget.
 static void* gDrawTargetBuffer;
 static size_t gDrawTargetBufferSize;
 
+// ID of the compositor thread.
+static Atomic<size_t, SequentiallyConsistent, Behavior::DontPreserve> gCompositorThreadId;
+
 already_AddRefed<gfx::DrawTarget>
 DrawTargetForRemoteDrawing(LayoutDeviceIntSize aSize)
 {
   MOZ_RELEASE_ASSERT(!NS_IsMainThread());
 
+  // Keep track of the compositor thread ID.
+  size_t threadId = Thread::Current()->Id();
+  if (gCompositorThreadId) {
+    MOZ_RELEASE_ASSERT(threadId == gCompositorThreadId);
+  } else {
+    gCompositorThreadId = threadId;
+  }
+
   if (aSize.IsEmpty()) {
     return nullptr;
   }
 
   gPaintMessage = Some(PaintMessage(aSize.width, aSize.height));
 
   gfx::IntSize size(aSize.width, aSize.height);
   size_t bufferSize = layers::ImageDataSerializer::ComputeRGBBufferSize(size, gSurfaceFormat);
@@ -461,50 +473,115 @@ DrawTargetForRemoteDrawing(LayoutDeviceI
                                           /* aUninitialized = */ true);
   if (!drawTarget) {
     MOZ_CRASH();
   }
 
   return drawTarget.forget();
 }
 
+static void
+FinishInProgressPaint(MonitorAutoLock&)
+{
+  while (gNumPendingPaints) {
+    gMonitor->Wait();
+  }
+}
+
 void
 NotifyPaintStart()
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 
-  NewCheckpoint(/* aTemporary = */ false);
-
-  gPendingPaint = true;
+  MonitorAutoLock lock(*gMonitor);
+  gNumPendingPaints++;
 }
 
-void
-WaitForPaintToComplete()
+static void
+PaintFromMainThread()
 {
   MOZ_RELEASE_ASSERT(NS_IsMainThread());
 
-  MonitorAutoLock lock(*gMonitor);
-  while (gPendingPaint) {
-    gMonitor->Wait();
+  // If another paint started before we got here, finish it first.
+  {
+    MonitorAutoLock lock(*gMonitor);
+    FinishInProgressPaint(lock);
   }
+
   if (IsActiveChild() && gPaintMessage.isSome()) {
     memcpy(gGraphicsShmem, gDrawTargetBuffer, gDrawTargetBufferSize);
     gChannel->SendMessage(gPaintMessage.ref());
   }
 }
 
 void
 NotifyPaintComplete()
 {
-  MOZ_RELEASE_ASSERT(!NS_IsMainThread());
+  MOZ_RELEASE_ASSERT(Thread::Current()->Id() == gCompositorThreadId);
+
+  // Notify the main thread in case it is waiting for this paint to complete.
+  {
+    MonitorAutoLock lock(*gMonitor);
+    MOZ_RELEASE_ASSERT(gNumPendingPaints);
+    if (--gNumPendingPaints == 0) {
+      gMonitor->Notify();
+    }
+  }
+
+  // Notify the middleman about the completed paint from the main thread.
+  NS_DispatchToMainThread(NewRunnableFunction("PaintFromMainThread", PaintFromMainThread));
+}
+
+void
+Repaint(size_t* aWidth, size_t* aHeight)
+{
+  MOZ_RELEASE_ASSERT(NS_IsMainThread());
+  MOZ_RELEASE_ASSERT(HasDivergedFromRecording());
+
+  // Don't try to repaint if the first normal paint hasn't occurred yet.
+  if (!gCompositorThreadId) {
+    *aWidth = 0;
+    *aHeight = 0;
+    return;
+  }
 
+  // Create an artifical vsync to see if graphics have changed since the last
+  // paint and a new paint is needed.
+  NotifyVsyncObserver();
+
+  {
+    MonitorAutoLock lock(*gMonitor);
+
+    if (gNumPendingPaints) {
+      // Allow the compositor to diverge from the recording so it can perform
+      // the paint we just triggered, or any in flight paint that that existed
+      // at the point we are paused at.
+      Thread::GetById(gCompositorThreadId)->SetShouldDivergeFromRecording();
+
+      FinishInProgressPaint(lock);
+    }
+  }
+
+  if (gPaintMessage.isSome()) {
+    memcpy(gGraphicsShmem, gDrawTargetBuffer, gDrawTargetBufferSize);
+    *aWidth = gPaintMessage.ref().mWidth;
+    *aHeight = gPaintMessage.ref().mHeight;
+  } else {
+    *aWidth = 0;
+    *aHeight = 0;
+  }
+}
+
+static bool
+CompositorCanPerformMiddlemanCalls()
+{
+  // After repainting finishes the compositor is not allowed to send call
+  // requests to the middleman anymore.
   MonitorAutoLock lock(*gMonitor);
-  MOZ_RELEASE_ASSERT(gPendingPaint);
-  gPendingPaint = false;
-  gMonitor->Notify();
+  return gNumPendingPaints != 0;
 }
 
 ///////////////////////////////////////////////////////////////////////////////
 // Checkpoint Messages
 ///////////////////////////////////////////////////////////////////////////////
 
 // When recording, the time when the last HitCheckpoint message was sent.
 static double gLastCheckpointTime;
--- a/toolkit/recordreplay/ipc/ChildIPC.h
+++ b/toolkit/recordreplay/ipc/ChildIPC.h
@@ -31,40 +31,25 @@ base::ProcessId ParentProcessId();
 
 // Create a normal checkpoint, if execution has not diverged from the recording.
 void CreateCheckpoint();
 
 ///////////////////////////////////////////////////////////////////////////////
 // Painting Coordination
 ///////////////////////////////////////////////////////////////////////////////
 
-// In child processes, paints do not occur in response to vsyncs from the UI
-// process: when a page is updating rapidly these events occur sporadically and
-// cause the tab's graphics to not accurately reflect the tab's state at that
-// point in time. When viewing a normal tab this is no problem because the tab
-// will be painted with the correct graphics momentarily, but when the tab can
-// be rewound and paused this behavior is visible.
-//
-// This API is used to trigger artificial vsyncs whenever the page is updated.
-// SetVsyncObserver is used to tell the child code about any singleton vsync
-// observer that currently exists, and NotifyVsyncObserver is used to trigger
-// a vsync on that observer at predictable points, e.g. the top of the main
-// thread's event loop.
+// Tell the child code about any singleton vsync observer that currently
+// exists. This is used to trigger artifical vsyncs that paint the current
+// graphics when paused.
 void SetVsyncObserver(VsyncObserver* aObserver);
-void NotifyVsyncObserver();
 
-// Similarly to the vsync handling above, in order to ensure that the tab's
-// graphics accurately reflect its state, we want to perform paints
-// synchronously after a vsync has occurred. When a paint is about to happen,
-// the main thread calls NotifyPaintStart, and after the compositor thread has
-// been informed about the update the main thread calls WaitForPaintToComplete
-// to block until the compositor thread has finished painting and called
-// NotifyPaintComplete.
+// Tell the child code about any ongoing painting activity. When a paint is
+// about to happen, the main thread calls NotifyPaintStart, and when the
+// compositor thread finishes the paint it calls NotifyPaintComplete.
 void NotifyPaintStart();
-void WaitForPaintToComplete();
 void NotifyPaintComplete();
 
 // Get a draw target which the compositor thread can paint to.
 already_AddRefed<gfx::DrawTarget> DrawTargetForRemoteDrawing(LayoutDeviceIntSize aSize);
 
 } // namespace child
 } // namespace recordreplay
 } // namespace mozilla
--- a/toolkit/recordreplay/ipc/ChildInternal.h
+++ b/toolkit/recordreplay/ipc/ChildInternal.h
@@ -59,16 +59,20 @@ void PositionHit(const js::BreakpointPos
 
 // Get an execution point for hitting the specified position right now.
 js::ExecutionPoint CurrentExecutionPoint(const js::BreakpointPosition& aPosition);
 
 // Convert an identifier from NewTimeWarpTarget() which we have seen while
 // executing into an ExecutionPoint.
 js::ExecutionPoint TimeWarpTargetExecutionPoint(ProgressCounter aTarget);
 
+// Synchronously paint the current contents into the graphics shared memory
+// object, returning the size of the painted area via aWidth/aHeight.
+void Repaint(size_t* aWidth, size_t* aHeight);
+
 // Called when running forward, immediately before hitting a normal or
 // temporary checkpoint.
 void BeforeCheckpoint();
 
 // Called immediately after hitting a normal or temporary checkpoint, either
 // when running forward or immediately after rewinding.
 void AfterCheckpoint(const CheckpointId& aCheckpoint);
 
--- a/toolkit/recordreplay/ipc/DisabledIPC.cpp
+++ b/toolkit/recordreplay/ipc/DisabledIPC.cpp
@@ -47,39 +47,27 @@ void CreateCheckpoint()
 
 void
 SetVsyncObserver(VsyncObserver* aObserver)
 {
   MOZ_CRASH();
 }
 
 void
-NotifyVsyncObserver()
-{
-  MOZ_CRASH();
-}
-
-void
 NotifyPaintStart()
 {
   MOZ_CRASH();
 }
 
 void
 NotifyPaintComplete()
 {
   MOZ_CRASH();
 }
 
-void
-WaitForPaintToComplete()
-{
-  MOZ_CRASH();
-}
-
 already_AddRefed<gfx::DrawTarget>
 DrawTargetForRemoteDrawing(LayoutDeviceIntSize aSize)
 {
   MOZ_CRASH();
 }
 
 } // namespace child