Bug 1562812 - For powerPreference:Default, only keep dGPU active for consistently active contexts. r=lsalzman
authorJeff Gilbert <jgilbert@mozilla.com>
Tue, 02 Jul 2019 16:45:22 +0000
changeset 540615 6d492faea9ecc22e09ea2758d9fc0bee830ccc16
parent 540614 a14daf0a53cf85cee27285acf396aca6fb03bb99
child 540616 3e6f208a59d30ef4028e1b305b85a08aac2fabc9
push id11529
push userarchaeopteryx@coole-files.de
push dateThu, 04 Jul 2019 15:22:33 +0000
treeherdermozilla-beta@ebb510a784b8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerslsalzman
bugs1562812
milestone69.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 1562812 - For powerPreference:Default, only keep dGPU active for consistently active contexts. r=lsalzman Differential Revision: https://phabricator.services.mozilla.com/D36565
dom/canvas/WebGLContext.cpp
dom/canvas/WebGLContext.h
dom/canvas/WebGLExtensions.cpp
gfx/thebes/gfxEnv.h
--- a/dom/canvas/WebGLContext.cpp
+++ b/dom/canvas/WebGLContext.cpp
@@ -7,16 +7,17 @@
 
 #include <algorithm>
 #include <queue>
 
 #include "AccessCheck.h"
 #include "gfxConfig.h"
 #include "gfxContext.h"
 #include "gfxCrashReporterUtils.h"
+#include "gfxEnv.h"
 #include "gfxPattern.h"
 #include "gfxUtils.h"
 #include "MozFramebuffer.h"
 #include "GLBlitHelper.h"
 #include "GLContext.h"
 #include "GLContextProvider.h"
 #include "GLReadTexImageHelper.h"
 #include "GLScreenBuffer.h"
@@ -284,16 +285,18 @@ void WebGLContext::DestroyResourcesAndCo
   if (GLContext::ShouldSpew()) {
     printf_stderr("--- WebGL context destroyed: %p\n", gl.get());
   }
 
   MOZ_ASSERT(gl);
   gl->MarkDestroyed();
   mGL_OnlyClearInDestroyResourcesAndContext = nullptr;
   MOZ_ASSERT(!gl);
+
+  mDynDGpuManager = nullptr;
 }
 
 void WebGLContext::Invalidate() {
   if (!mCanvasElement) return;
 
   mCapturedFrameInvalidated = true;
 
   if (mInvalidated) return;
@@ -479,18 +482,30 @@ bool WebGLContext::CreateAndInitGL(
 
   if (IsWebGL2()) {
     flags |= gl::CreateContextFlags::PREFER_ES3;
   } else if (!StaticPrefs::webgl_1_allow_core_profiles()) {
     flags |= gl::CreateContextFlags::REQUIRE_COMPAT_PROFILE;
   }
 
   {
-    bool highPower = false;
-    switch (mOptions.powerPreference) {
+    auto powerPref = mOptions.powerPreference;
+
+    // If "Use hardware acceleration when available" option is disabled:
+    if (!gfxConfig::IsEnabled(Feature::HW_COMPOSITING)) {
+      powerPref = dom::WebGLPowerPreference::Low_power;
+    }
+
+    if (StaticPrefs::webgl_default_low_power() &&
+        powerPref == dom::WebGLPowerPreference::Default) {
+      powerPref = dom::WebGLPowerPreference::Low_power;
+    }
+
+    bool highPower;
+    switch (powerPref) {
       case dom::WebGLPowerPreference::Low_power:
         highPower = false;
         break;
 
       case dom::WebGLPowerPreference::High_performance:
         highPower = true;
         break;
 
@@ -498,33 +513,24 @@ bool WebGLContext::CreateAndInitGL(
         // We can even make it dynamic by holding on to a
         // ForceDiscreteGPUHelperCGL iff we decide it's a high-performance
         // application:
         // - Non-trivial canvas size
         // - Many draw calls
         // - Same origin with root page (try to stem bleeding from WebGL
         // ads/trackers)
       default:
-        highPower = true;
-        if (StaticPrefs::webgl_default_low_power()) {
-          highPower = false;
-        } else if (mCanvasElement && !mCanvasElement->GetParentNode()) {
-          GenerateWarning(
-              "WebGLContextAttributes.powerPreference: 'default' when <canvas>"
-              " has no parent Element defaults to 'low-power'.");
-          highPower = false;
+        highPower = false;
+        mDynDGpuManager = webgl::DynDGpuManager::Get();
+        if (!mDynDGpuManager) {
+          highPower = true;
         }
         break;
     }
 
-    // If "Use hardware acceleration when available" option is disabled:
-    if (!gfxConfig::IsEnabled(Feature::HW_COMPOSITING)) {
-      highPower = false;
-    }
-
     if (highPower) {
       flags |= gl::CreateContextFlags::HIGH_POWER;
     }
   }
 
 #ifdef XP_MACOSX
   const nsCOMPtr<nsIGfxInfo> gfxInfo = services::GetGfxInfo();
   nsString vendorID, deviceID;
@@ -1392,16 +1398,17 @@ void WebGLContext::BlitBackbufferToCurDr
 
 // For an overview of how WebGL compositing works, see:
 // https://wiki.mozilla.org/Platform/GFX/WebGL/Compositing
 bool WebGLContext::PresentScreenBuffer(GLScreenBuffer* const targetScreen) {
   const FuncScope funcScope(*this, "<PresentScreenBuffer>");
   if (IsContextLost()) return false;
 
   if (!mShouldPresent) return false;
+  ReportActivity();
 
   if (!ValidateAndInitFB(nullptr)) return false;
 
   const auto& screen = targetScreen ? targetScreen : gl->Screen();
   if ((!screen->IsReadBufferReady() || screen->Size() != mDefaultFB->mSize) &&
       !screen->Resize(mDefaultFB->mSize)) {
     GenerateWarning("screen->Resize failed. Losing context.");
     ForceLoseContext();
@@ -2402,16 +2409,111 @@ nsresult webgl::AvailabilityRunnable::Ru
     cur->mCanBeAvailable = true;
   }
   mSyncs.clear();
 
   mWebGL->mAvailabilityRunnable = nullptr;
   return NS_OK;
 }
 
+// ---------------
+
+namespace webgl {
+
+/*static*/
+std::shared_ptr<DynDGpuManager> DynDGpuManager::Get() {
+#ifndef XP_MACOSX
+  if (true) return nullptr;
+#endif
+
+  static std::weak_ptr<DynDGpuManager> sCurrent;
+
+  auto ret = sCurrent.lock();
+  if (!ret) {
+    ret.reset(new DynDGpuManager);
+    sCurrent = ret;
+  }
+  return ret;
+}
+
+DynDGpuManager::DynDGpuManager() : mMutex("DynDGpuManager") {}
+DynDGpuManager::~DynDGpuManager() = default;
+
+void DynDGpuManager::SetState(const MutexAutoLock&, const State newState) {
+  if (gfxEnv::GpuSwitchingSpew()) {
+    printf_stderr(
+        "[MOZ_GPU_SWITCHING_SPEW] DynDGpuManager::SetState(%u -> %u)\n",
+        uint32_t(mState), uint32_t(newState));
+  }
+
+  if (newState == State::Active) {
+    if (!mDGpuContext) {
+      const auto flags = gl::CreateContextFlags::HIGH_POWER;
+      nsCString failureId;
+      mDGpuContext = gl::GLContextProvider::CreateHeadless(flags, &failureId);
+    }
+  } else {
+    mDGpuContext = nullptr;
+  }
+
+  mState = newState;
+}
+
+void DynDGpuManager::ReportActivity(
+    const std::shared_ptr<DynDGpuManager>& strong) {
+  MOZ_ASSERT(strong.get() == this);
+  const MutexAutoLock lock(mMutex);
+
+  if (mActivityThisTick) return;
+  mActivityThisTick = true;
+
+  // Promote!
+  switch (mState) {
+    case State::Inactive:
+      SetState(lock, State::Primed);
+      DispatchTick(strong);  // Initial tick
+      break;
+
+    case State::Primed:
+      SetState(lock, State::Active);
+      break;
+    case State::Active:
+      if (!mDGpuContext) {
+        SetState(lock, State::Active);
+      }
+      break;
+  }
+}
+
+void DynDGpuManager::Tick(const std::shared_ptr<DynDGpuManager>& strong) {
+  MOZ_ASSERT(strong.get() == this);
+  const MutexAutoLock lock(mMutex);
+  MOZ_ASSERT(mState != State::Inactive);
+
+  if (!mActivityThisTick) {
+    SetState(lock, State::Inactive);
+    return;
+  }
+  mActivityThisTick = false;  // reset
+
+  DispatchTick(strong);
+}
+
+void DynDGpuManager::DispatchTick(
+    const std::shared_ptr<DynDGpuManager>& strong) {
+  MOZ_ASSERT(strong.get() == this);
+
+  const auto fnTick = [strong]() { strong->Tick(strong); };
+  already_AddRefed<mozilla::Runnable> event =
+      NS_NewRunnableFunction("DynDGpuManager fnWeakTick", fnTick);
+  NS_DelayedDispatchToCurrentThread(std::move(event), TICK_MS);
+}
+
+}  // namespace webgl
+
 ////////////////////////////////////////////////////////////////////////////////
 // XPCOM goop
 
 void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& callback,
                                  const std::vector<IndexedBufferBinding>& field,
                                  const char* name, uint32_t flags) {
   for (const auto& cur : field) {
     ImplCycleCollectionTraverse(callback, cur.mBufferBinding, name, flags);
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -16,16 +16,17 @@
 #include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/dom/HTMLCanvasElement.h"
 #include "mozilla/dom/Nullable.h"
 #include "mozilla/dom/TypedArray.h"
 #include "mozilla/EnumeratedArray.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/LinkedList.h"
+#include "mozilla/Mutex.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/WeakPtr.h"
 #include "nsCycleCollectionNoteChild.h"
 #include "nsICanvasRenderingContextInternal.h"
 #include "nsLayoutUtils.h"
 #include "nsTArray.h"
 #include "nsWrapperCache.h"
 #include "SurfaceTypes.h"
@@ -267,16 +268,46 @@ class AvailabilityRunnable final : publi
   NS_IMETHOD Run() override;
 };
 
 struct BufferAndIndex final {
   const WebGLBuffer* buffer = nullptr;
   uint32_t id = -1;
 };
 
+// -
+
+class DynDGpuManager final {
+  static constexpr uint32_t TICK_MS = 3000;
+
+  enum class State {
+    Inactive,
+    Primed,
+    Active,
+  };
+
+  Mutex mMutex;
+  bool mActivityThisTick = false;
+  State mState = State::Inactive;
+  RefPtr<gl::GLContext> mDGpuContext;
+
+ public:
+  static std::shared_ptr<DynDGpuManager> Get();
+
+  DynDGpuManager();
+  ~DynDGpuManager();
+
+  void ReportActivity(const std::shared_ptr<DynDGpuManager>& strong);
+
+ private:
+  void SetState(const MutexAutoLock&, State);
+  void Tick(const std::shared_ptr<DynDGpuManager>& strong);
+  void DispatchTick(const std::shared_ptr<DynDGpuManager>& strong);
+};
+
 }  // namespace webgl
 
 ////////////////////////////////////////////////////////////////////////////////
 
 class WebGLContext : public nsICanvasRenderingContextInternal,
                      public nsSupportsWeakReference,
                      public nsWrapperCache,
                      public SupportsWeakPtr<WebGLContext> {
@@ -327,16 +358,28 @@ class WebGLContext : public nsICanvasRen
   // all of our resources. This construction ensures that we are aware that we
   // should only null `gl` in DestroyResourcesAndContext.
   RefPtr<gl::GLContext> mGL_OnlyClearInDestroyResourcesAndContext;
 
  public:
   // Grab a const reference so we can see changes, but can't make changes.
   const decltype(mGL_OnlyClearInDestroyResourcesAndContext)& gl;
 
+ private:
+  std::shared_ptr<webgl::DynDGpuManager> mDynDGpuManager;
+
+  void ReportActivity() const {
+    if (mDynDGpuManager) {
+      mDynDGpuManager->ReportActivity(mDynDGpuManager);
+    }
+  }
+
+ public:
+  void CheckForInactivity();
+
  protected:
   const uint32_t mMaxPerfWarnings;
   mutable uint64_t mNumPerfWarnings;
   const uint32_t mMaxAcceptableFBStatusInvals;
 
   uint64_t mNextFenceId = 1;
   uint64_t mCompletedFenceId = 0;
 
--- a/dom/canvas/WebGLExtensions.cpp
+++ b/dom/canvas/WebGLExtensions.cpp
@@ -25,22 +25,24 @@ void WebGLExtensionBase::MarkLost() {
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLExtensionBase)
 
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(WebGLExtensionBase, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(WebGLExtensionBase, Release)
 
 // -
 
-WebGLExtensionExplicitPresent::WebGLExtensionExplicitPresent(WebGLContext* const webgl)
+WebGLExtensionExplicitPresent::WebGLExtensionExplicitPresent(
+    WebGLContext* const webgl)
     : WebGLExtensionBase(webgl) {
   MOZ_ASSERT(IsSupported(webgl), "Don't construct extension if unsupported.");
 }
 
-bool WebGLExtensionExplicitPresent::IsSupported(const WebGLContext* const webgl) {
+bool WebGLExtensionExplicitPresent::IsSupported(
+    const WebGLContext* const webgl) {
   return StaticPrefs::webgl_enable_draft_extensions();
 }
 
 void WebGLExtensionExplicitPresent::Present() const {
   if (mIsLost || !mContext) return;
   mContext->PresentScreenBuffer();
 }
 
--- a/gfx/thebes/gfxEnv.h
+++ b/gfx/thebes/gfxEnv.h
@@ -84,16 +84,19 @@ class gfxEnv final {
   DECL_GFX_ENV("MOZ_GL_DEBUG_ABORT_ON_ERROR", GlDebugAbortOnError);
 
   // Count GL extensions
   DECL_GFX_ENV("MOZ_GL_DUMP_EXTS", GlDumpExtensions);
 
   // Very noisy GLContext and GLContextProviderEGL
   DECL_GFX_ENV("MOZ_GL_SPEW", GlSpew);
 
+  //
+  DECL_GFX_ENV("MOZ_GPU_SWITCHING_SPEW", GpuSwitchingSpew);
+
   // Do extra work before and after each GLX call in GLContextProviderGLX
   DECL_GFX_ENV("MOZ_GLX_DEBUG", GlxDebug);
 
   // Use X compositing
   DECL_GFX_ENV("MOZ_LAYERS_ENABLE_XLIB_SURFACES", LayersEnableXlibSurfaces);
 
   // GL compositing on Windows
   DECL_GFX_ENV("MOZ_LAYERS_PREFER_EGL", LayersPreferEGL);