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 490280 9b5b68080ee6987a11779871bc606b1e4d5dd139
parent 490279 2ba7db69225b588c14915c8609cf82cb5f433a01
child 490281 ed0463d80c34acd808558758e8a5467cb59e8ab1
push id247
push userfmarier@mozilla.com
push dateSat, 27 Oct 2018 01:06:44 +0000
reviewersnical
bugs1488808
milestone64.0a1
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