Bug 1628137 - Switch to using WaitForVBlank for vsync on Windows. r=jrmuizel, a=RyanVM
authorBert Peers <bpeers@mozilla.com>
Fri, 10 Apr 2020 02:24:07 +0000
changeset 585536 8138cfc9f25ffcc429440f998af55c15a5567215
parent 585535 be3b947cd28d6ef428e1c4414b8f888c8d7f6659
child 585537 d13fcd0a2d453bce8fdaa0a650a84775da89e9db
push id13004
push userryanvm@gmail.com
push dateWed, 15 Apr 2020 20:35:43 +0000
treeherdermozilla-beta@9d27f85c23cb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjrmuizel, RyanVM
bugs1628137
milestone76.0
Bug 1628137 - Switch to using WaitForVBlank for vsync on Windows. r=jrmuizel, a=RyanVM Differential Revision: https://phabricator.services.mozilla.com/D70463
gfx/thebes/DeviceManagerDx.cpp
gfx/thebes/DeviceManagerDx.h
gfx/thebes/gfxWindowsPlatform.cpp
modules/libpref/init/StaticPrefList.yaml
--- a/gfx/thebes/DeviceManagerDx.cpp
+++ b/gfx/thebes/DeviceManagerDx.cpp
@@ -165,16 +165,44 @@ nsTArray<DXGI_OUTPUT_DESC1> DeviceManage
       break;
     }
 
     outputs.AppendElement(desc);
   }
   return outputs;
 }
 
+bool DeviceManagerDx::GetOutputFromMonitor(HMONITOR monitor,
+                                           RefPtr<IDXGIOutput>* aOutOutput) {
+  RefPtr<IDXGIAdapter> adapter = GetDXGIAdapter();
+
+  if (!adapter) {
+    NS_WARNING("Failed to acquire a DXGI adapter for GetOutputFromMonitor.");
+    return false;
+  }
+
+  for (UINT i = 0;; ++i) {
+    RefPtr<IDXGIOutput> output = nullptr;
+    if (FAILED(adapter->EnumOutputs(i, getter_AddRefs(output)))) {
+      break;
+    }
+
+    DXGI_OUTPUT_DESC desc;
+    if (FAILED(output->GetDesc(&desc))) {
+      continue;
+    }
+
+    if (desc.Monitor == monitor) {
+      *aOutOutput = output;
+      return true;
+    }
+  }
+  return false;
+}
+
 bool DeviceManagerDx::CheckHardwareStretchingSupport() {
   RefPtr<IDXGIAdapter> adapter = GetDXGIAdapter();
 
   if (!adapter) {
     NS_WARNING(
         "Failed to acquire a DXGI adapter for checking hardware stretching "
         "support.");
     return false;
--- a/gfx/thebes/DeviceManagerDx.h
+++ b/gfx/thebes/DeviceManagerDx.h
@@ -82,16 +82,20 @@ class DeviceManagerDx final {
 
   // Intel devices on older windows versions seem to occasionally have
   // stability issues when supplying InitData to CreateTexture2D.
   bool HasCrashyInitData();
 
   // Enumerate and return all outputs on the current adapter.
   nsTArray<DXGI_OUTPUT_DESC1> EnumerateOutputs();
 
+  // find the IDXGIOutput with a description.Monitor matching
+  // 'monitor'; returns false if not found or some error occurred.
+  bool GetOutputFromMonitor(HMONITOR monitor, RefPtr<IDXGIOutput>* aOutOutput);
+
   // Check if the current adapter supports hardware stretching
   bool CheckHardwareStretchingSupport();
 
   bool CreateCompositorDevices();
   void CreateContentDevices();
   void CreateDirectCompositionDevice();
   bool CreateCanvasDevice();
 
--- a/gfx/thebes/gfxWindowsPlatform.cpp
+++ b/gfx/thebes/gfxWindowsPlatform.cpp
@@ -1586,17 +1586,18 @@ bool gfxWindowsPlatform::DwmCompositionE
 class D3DVsyncSource final : public VsyncSource {
  public:
   class D3DVsyncDisplay final : public VsyncSource::Display {
     NS_INLINE_DECL_THREADSAFE_REFCOUNTING(D3DVsyncDisplay)
    public:
     D3DVsyncDisplay()
         : mPrevVsync(TimeStamp::Now()),
           mVsyncEnabledLock("D3DVsyncEnabledLock"),
-          mVsyncEnabled(false) {
+          mVsyncEnabled(false),
+          mWaitVBlankMonitor(NULL) {
       mVsyncThread = new base::Thread("WindowsVsyncThread");
       MOZ_RELEASE_ASSERT(mVsyncThread->Start(),
                          "GFX: Could not start Windows vsync thread");
       SetVsyncRate();
     }
 
     void SetVsyncRate() {
       if (!gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled()) {
@@ -1761,19 +1762,26 @@ class D3DVsyncSource final : public Vsyn
         // so we have to check every time that it's available.
         // When it is unavailable, we fallback to software but will try
         // to get back to dwm rendering once it's re-enabled
         if (!gfxWindowsPlatform::GetPlatform()->DwmCompositionEnabled()) {
           ScheduleSoftwareVsync(vsync);
           return;
         }
 
-        // Using WaitForVBlank, the whole system dies because WaitForVBlank
-        // only works if it's run on the same thread as the Present();
-        HRESULT hr = DwmFlush();
+        HRESULT hr = E_FAIL;
+        if (StaticPrefs::gfx_vsync_use_waitforvblank()) {
+          UpdateVBlankOutput();
+          if (mWaitVBlankOutput) {
+            hr = mWaitVBlankOutput->WaitForVBlank();
+          }
+        }
+        if (!SUCCEEDED(hr)) {
+          hr = DwmFlush();
+        }
         if (!SUCCEEDED(hr)) {
           // DWMFlush isn't working, fallback to software vsync.
           ScheduleSoftwareVsync(TimeStamp::Now());
           return;
         }
 
         TimeStamp now = TimeStamp::Now();
         TimeDuration flushDiff = now - flushTime;
@@ -1811,21 +1819,45 @@ class D3DVsyncSource final : public Vsyn
 
    private:
     virtual ~D3DVsyncDisplay() { MOZ_ASSERT(NS_IsMainThread()); }
 
     bool IsInVsyncThread() {
       return mVsyncThread->thread_id() == PlatformThread::CurrentId();
     }
 
+    void UpdateVBlankOutput() {
+      HMONITOR primary_monitor =
+          MonitorFromWindow(nullptr, MONITOR_DEFAULTTOPRIMARY);
+      if (primary_monitor == mWaitVBlankMonitor && mWaitVBlankOutput) {
+        return;
+      }
+
+      mWaitVBlankMonitor = primary_monitor;
+
+      RefPtr<IDXGIOutput> output = nullptr;
+      if (DeviceManagerDx* dx = DeviceManagerDx::Get()) {
+        if (dx->GetOutputFromMonitor(mWaitVBlankMonitor, &output)) {
+          mWaitVBlankOutput = output;
+          return;
+        }
+      }
+
+      // failed to convert a monitor to an output so keep trying
+      mWaitVBlankOutput = nullptr;
+    }
+
     TimeStamp mPrevVsync;
     Monitor mVsyncEnabledLock;
     base::Thread* mVsyncThread;
     TimeDuration mVsyncRate;
     bool mVsyncEnabled;
+
+    HMONITOR mWaitVBlankMonitor;
+    RefPtr<IDXGIOutput> mWaitVBlankOutput;
   };  // end d3dvsyncdisplay
 
   D3DVsyncSource() { mPrimaryDisplay = new D3DVsyncDisplay(); }
 
   virtual Display& GetGlobalDisplay() override { return *mPrimaryDisplay; }
 
  private:
   virtual ~D3DVsyncSource() = default;
--- a/modules/libpref/init/StaticPrefList.yaml
+++ b/modules/libpref/init/StaticPrefList.yaml
@@ -3971,16 +3971,21 @@
   value: false
   mirror: always
 
 - name: gfx.vsync.compositor.unobserve-count
   type: int32_t
   value: 10
   mirror: once
 
+- name: gfx.vsync.use-waitforvblank
+  type: RelaxedAtomicBool
+  value: false
+  mirror: always
+
 # We expose two prefs: gfx.webrender.all and gfx.webrender.enabled.
 # The first enables WR+additional features, and the second just enables WR.
 # For developer convenience, building with --enable-webrender=true or just
 # --enable-webrender will set gfx.webrender.enabled to true by default.
 #
 # We also have a pref gfx.webrender.all.qualified which is not exposed via
 # about:config. That pref enables WR but only on qualified hardware. This is
 # the pref we'll eventually flip to deploy WebRender to the target population.