Commit bd0cae50 authored by Sunny Sachanandani's avatar Sunny Sachanandani Committed by Commit Bot

gpu: Sync to primary monitor's vblank (windows)

DWM composition is limited to primary monitor's refresh rate.  Prior to
GPU vsync we would use DWM composition rate to drive begin frames.

With GPU vsync we started using per monitor vblank to limit begin frames
in case primary monitor had higher refresh rate, but this might be
causing draw duration and omnibar latency regressions.  MS platform team
also suggested syncing to primary monitor only.  In any case the correct
approach would be to further limit begin frames based on event queries
from 2 frames back.

This CL makes the GPU vsync begin frames sync to primary monitor, and
also makes the vsync thread a singleton since it doesn't have per-window
state any more.

Bug: 953970
Change-Id: Id3d3f043cb847172b1d6ba4bd38b087ceb2d8631
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1711215Reviewed-by: default avatarZhenyao Mo <zmo@chromium.org>
Commit-Queue: Sunny Sachanandani <sunnyps@chromium.org>
Cr-Commit-Position: refs/heads/master@{#679832}
parent 72946ec8
......@@ -307,6 +307,7 @@ jumbo_component("gl") {
"gl_wgl_api_implementation.h",
"swap_chain_presenter.cc",
"swap_chain_presenter.h",
"vsync_observer.h",
"vsync_provider_win.cc",
"vsync_provider_win.h",
"vsync_thread_win.cc",
......
......@@ -107,7 +107,9 @@ bool DirectCompositionChildSurfaceWin::UseSwapChainFrameStatistics() {
return base::FeatureList::IsEnabled(features::kSwapChainFrameStatistics);
}
DirectCompositionChildSurfaceWin::DirectCompositionChildSurfaceWin() {}
DirectCompositionChildSurfaceWin::DirectCompositionChildSurfaceWin(
gfx::VSyncProvider* vsync_provider)
: vsync_provider_(vsync_provider) {}
DirectCompositionChildSurfaceWin::~DirectCompositionChildSurfaceWin() {
Destroy();
......@@ -258,6 +260,8 @@ gfx::SwapResult DirectCompositionChildSurfaceWin::SwapBuffers(
bool succeeded = ReleaseDrawTexture(false /* will_discard */);
if (UseSwapChainFrameStatistics()) {
vsync_provider_->GetVSyncParametersIfAvailable(&last_vsync_time_,
&last_vsync_interval_);
CheckPendingFrames();
// Enqueue callback after retiring previous callbacks so that it's called
// after SwapBuffers() returns.
......@@ -503,17 +507,6 @@ bool DirectCompositionChildSurfaceWin::SetEnableDCLayers(bool enable) {
return true;
}
void DirectCompositionChildSurfaceWin::UpdateVSyncParameters(
base::TimeTicks vsync_time,
base::TimeDelta vsync_interval) {
last_vsync_time_ = vsync_time;
last_vsync_interval_ = vsync_interval;
}
bool DirectCompositionChildSurfaceWin::HasPendingFrames() const {
return !pending_frames_.empty();
}
void DirectCompositionChildSurfaceWin::CheckPendingFrames() {
DCHECK(UseSwapChainFrameStatistics());
......
......@@ -12,11 +12,15 @@
#include "ui/gl/gl_surface_egl.h"
namespace gfx {
class VSyncProvider;
} // namespace gfx
namespace gl {
class DirectCompositionChildSurfaceWin : public GLSurfaceEGL {
public:
DirectCompositionChildSurfaceWin();
explicit DirectCompositionChildSurfaceWin(gfx::VSyncProvider* vsync_provider);
static bool UseSwapChainFrameStatistics();
......@@ -40,11 +44,6 @@ class DirectCompositionChildSurfaceWin : public GLSurfaceEGL {
bool has_alpha) override;
bool SetEnableDCLayers(bool enable) override;
void UpdateVSyncParameters(base::TimeTicks vsync_time,
base::TimeDelta vsync_interval);
bool HasPendingFrames() const;
void CheckPendingFrames();
const Microsoft::WRL::ComPtr<IDCompositionSurface>& dcomp_surface() const {
return dcomp_surface_;
}
......@@ -76,6 +75,7 @@ class DirectCompositionChildSurfaceWin : public GLSurfaceEGL {
// Presentation callback enqueued in SwapBuffers().
PresentationCallback callback;
};
void CheckPendingFrames();
void EnqueuePendingFrame(PresentationCallback callback);
void ClearPendingFrames();
......@@ -109,6 +109,7 @@ class DirectCompositionChildSurfaceWin : public GLSurfaceEGL {
// be called on the device.
uint64_t dcomp_surface_serial_ = 0;
gfx::VSyncProvider* const vsync_provider_;
base::TimeTicks last_vsync_time_;
base::TimeDelta last_vsync_interval_ = base::TimeDelta::FromSecondsD(1. / 60);
......
......@@ -172,15 +172,14 @@ DirectCompositionSurfaceWin::DirectCompositionSurfaceWin(
: GLSurfaceEGL(),
child_window_(parent_window),
task_runner_(base::ThreadTaskRunnerHandle::Get()),
root_surface_(new DirectCompositionChildSurfaceWin()),
root_surface_(new DirectCompositionChildSurfaceWin(vsync_provider.get())),
layer_tree_(std::make_unique<DCLayerTree>(
settings.disable_nv12_dynamic_textures,
settings.disable_larger_than_screen_overlays)),
vsync_provider_(std::move(vsync_provider)),
vsync_callback_(std::move(vsync_callback)),
presentation_helper_(
std::make_unique<GLSurfacePresentationHelper>(vsync_provider_.get())),
weak_ptr_factory_(this) {}
std::make_unique<GLSurfacePresentationHelper>(vsync_provider.get())),
vsync_provider_(std::move(vsync_provider)),
vsync_callback_(std::move(vsync_callback)) {}
DirectCompositionSurfaceWin::~DirectCompositionSurfaceWin() {
Destroy();
......@@ -396,27 +395,17 @@ bool DirectCompositionSurfaceWin::Initialize(GLSurfaceFormat format) {
if (!root_surface_->Initialize(GLSurfaceFormat()))
return false;
if (root_surface_->UseSwapChainFrameStatistics()) {
// Save weak ptr on main thread before any use on vsync thread.
main_thread_vsync_callback_ = base::BindRepeating(
&DirectCompositionSurfaceWin::HandleVSyncOnMainThread,
weak_ptr_factory_.GetWeakPtr());
}
if ((SupportsGpuVSync() && vsync_callback_) || main_thread_vsync_callback_) {
vsync_thread_ = std::make_unique<VSyncThreadWin>(
window_, d3d11_device_,
base::BindRepeating(
&DirectCompositionSurfaceWin::HandleVSyncOnVSyncThread,
base::Unretained(this)));
}
if (SupportsGpuVSync() && vsync_callback_)
vsync_thread_ = VSyncThreadWin::GetInstance();
return true;
}
void DirectCompositionSurfaceWin::Destroy() {
// Destroy vsync thread because joining it could issue callbacks.
vsync_thread_ = nullptr;
if (vsync_thread_) {
vsync_thread_->RemoveObserver(this);
vsync_thread_ = nullptr;
}
// Destroy presentation helper first because its dtor calls GetHandle.
presentation_helper_ = nullptr;
root_surface_->Destroy();
......@@ -459,14 +448,11 @@ gfx::SwapResult DirectCompositionSurfaceWin::SwapBuffers(
callback.Reset();
}
gfx::SwapResult swap_result = root_surface_->SwapBuffers(std::move(callback));
if (swap_result == gfx::SwapResult::SWAP_ACK &&
gfx::SwapResult swap_result;
if (root_surface_->SwapBuffers(std::move(callback)) ==
gfx::SwapResult::SWAP_ACK &&
layer_tree_->CommitAndClearPendingOverlays(root_surface_.get())) {
if (vsync_thread_) {
vsync_thread_->SetEnabled(root_surface_->HasPendingFrames() ||
vsync_callback_enabled_);
}
swap_result = gfx::SwapResult::SWAP_ACK;
} else {
swap_result = gfx::SwapResult::SWAP_FAILED;
}
......@@ -547,34 +533,17 @@ bool DirectCompositionSurfaceWin::SupportsGpuVSync() const {
void DirectCompositionSurfaceWin::SetGpuVSyncEnabled(bool enabled) {
DCHECK(vsync_thread_);
if (vsync_callback_enabled_ == enabled)
return;
vsync_callback_enabled_ = enabled;
vsync_thread_->SetEnabled(root_surface_->HasPendingFrames() ||
vsync_callback_enabled_);
}
void DirectCompositionSurfaceWin::HandleVSyncOnVSyncThread(
base::TimeTicks vsync_time,
base::TimeDelta vsync_interval) {
if (vsync_callback_)
vsync_callback_.Run(vsync_time, vsync_interval);
if (main_thread_vsync_callback_) {
task_runner_->PostTask(
FROM_HERE, base::BindOnce(main_thread_vsync_callback_, vsync_time,
vsync_interval));
if (enabled) {
vsync_thread_->AddObserver(this);
} else {
vsync_thread_->RemoveObserver(this);
}
}
void DirectCompositionSurfaceWin::HandleVSyncOnMainThread(
base::TimeTicks vsync_time,
base::TimeDelta vsync_interval) {
// Check pending frames in root surface in case client stops issuing swaps.
root_surface_->UpdateVSyncParameters(vsync_time, vsync_interval);
root_surface_->CheckPendingFrames();
vsync_thread_->SetEnabled(root_surface_->HasPendingFrames() ||
vsync_callback_enabled_);
void DirectCompositionSurfaceWin::OnVSync(base::TimeTicks vsync_time,
base::TimeDelta interval) {
DCHECK(vsync_callback_);
vsync_callback_.Run(vsync_time, interval);
}
scoped_refptr<base::TaskRunner>
......
......@@ -16,6 +16,7 @@
#include "ui/gl/child_window_win.h"
#include "ui/gl/gl_export.h"
#include "ui/gl/gl_surface_egl.h"
#include "ui/gl/vsync_observer.h"
namespace gl {
class DCLayerTree;
......@@ -23,7 +24,8 @@ class DirectCompositionChildSurfaceWin;
class GLSurfacePresentationHelper;
class VSyncThreadWin;
class GL_EXPORT DirectCompositionSurfaceWin : public GLSurfaceEGL {
class GL_EXPORT DirectCompositionSurfaceWin : public GLSurfaceEGL,
public VSyncObserver {
public:
using VSyncCallback =
base::RepeatingCallback<void(base::TimeTicks, base::TimeDelta)>;
......@@ -105,7 +107,6 @@ class GL_EXPORT DirectCompositionSurfaceWin : public GLSurfaceEGL {
gfx::Vector2d GetDrawOffset() const override;
bool SupportsGpuVSync() const override;
void SetGpuVSyncEnabled(bool enabled) override;
// This schedules an overlay plane to be displayed on the next SwapBuffers
// or PostSubBuffer call. Overlay planes must be scheduled before every swap
// to remain in the layer tree. This surface's backbuffer doesn't have to be
......@@ -113,6 +114,9 @@ class GL_EXPORT DirectCompositionSurfaceWin : public GLSurfaceEGL {
// tree at z-order 0.
bool ScheduleDCLayer(const ui::DCRendererLayerParams& params) override;
// VSyncObserver implementation.
void OnVSync(base::TimeTicks vsync_time, base::TimeDelta interval) override;
HWND window() const { return window_; }
scoped_refptr<base::TaskRunner> GetWindowTaskRunnerForTesting();
......@@ -127,33 +131,21 @@ class GL_EXPORT DirectCompositionSurfaceWin : public GLSurfaceEGL {
~DirectCompositionSurfaceWin() override;
private:
void HandleVSyncOnVSyncThread(base::TimeTicks vsync_time,
base::TimeDelta vsync_interval);
void HandleVSyncOnMainThread(base::TimeTicks vsync_time,
base::TimeDelta vsync_interval);
HWND window_ = nullptr;
ChildWindowWin child_window_;
scoped_refptr<base::SequencedTaskRunner> task_runner_;
scoped_refptr<DirectCompositionChildSurfaceWin> root_surface_;
std::unique_ptr<DCLayerTree> layer_tree_;
std::unique_ptr<GLSurfacePresentationHelper> presentation_helper_;
std::unique_ptr<VSyncThreadWin> vsync_thread_;
std::unique_ptr<gfx::VSyncProvider> vsync_provider_;
const VSyncCallback vsync_callback_;
bool vsync_callback_enabled_ = false;
std::unique_ptr<GLSurfacePresentationHelper> presentation_helper_;
VSyncThreadWin* vsync_thread_;
Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device_;
Microsoft::WRL::ComPtr<IDCompositionDevice2> dcomp_device_;
VSyncCallback main_thread_vsync_callback_;
base::WeakPtrFactory<DirectCompositionSurfaceWin> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(DirectCompositionSurfaceWin);
};
......
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef UI_GL_VSYNC_OBSERVER_H_
#define UI_GL_VSYNC_OBSERVER_H_
namespace gl {
class GL_EXPORT VSyncObserver {
public:
// Called on vsync thread.
virtual void OnVSync(base::TimeTicks vsync_time,
base::TimeDelta interval) = 0;
protected:
virtual ~VSyncObserver() {}
};
} // namespace gl
#endif // UI_GL_VSYNC_OBSERVER_H_
......@@ -5,6 +5,10 @@
#include "ui/gl/vsync_thread_win.h"
#include "base/bind.h"
#include "base/memory/singleton.h"
#include "base/stl_util.h"
#include "ui/gl/gl_angle_util_win.h"
#include "ui/gl/vsync_observer.h"
namespace gl {
namespace {
......@@ -43,45 +47,51 @@ Microsoft::WRL::ComPtr<IDXGIOutput> DXGIOutputFromMonitor(
}
} // namespace
VSyncThreadWin::VSyncThreadWin(
HWND window,
Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device,
VSyncCallback callback)
// static
VSyncThreadWin* VSyncThreadWin::GetInstance() {
return base::Singleton<VSyncThreadWin>::get();
}
VSyncThreadWin::VSyncThreadWin()
: vsync_thread_("GpuVSyncThread"),
window_(window),
d3d11_device_(std::move(d3d11_device)),
callback_(std::move(callback)) {
DCHECK(window_);
DCHECK(callback_);
d3d11_device_(QueryD3D11DeviceObjectFromANGLE()) {
DCHECK(d3d11_device_);
base::Thread::Options options;
// Inherit priority from GPU main thread which depends on finch flags.
options.priority = base::PlatformThread::GetCurrentThreadPriority();
options.priority = base::ThreadPriority::DISPLAY;
vsync_thread_.StartWithOptions(std::move(options));
}
VSyncThreadWin::~VSyncThreadWin() {
SetEnabled(false);
{
base::AutoLock auto_lock(lock_);
observers_.clear();
}
vsync_thread_.Stop();
}
void VSyncThreadWin::SetEnabled(bool enabled) {
void VSyncThreadWin::AddObserver(VSyncObserver* obs) {
base::AutoLock auto_lock(lock_);
if (enabled_ == enabled)
return;
enabled_ = enabled;
if (enabled_ && !started_) {
started_ = true;
observers_.insert(obs);
if (is_idle_) {
is_idle_ = false;
vsync_thread_.task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&VSyncThreadWin::WaitForVSync, base::Unretained(this)));
}
}
void VSyncThreadWin::RemoveObserver(VSyncObserver* obs) {
base::AutoLock auto_lock(lock_);
observers_.erase(obs);
}
void VSyncThreadWin::WaitForVSync() {
HMONITOR monitor = MonitorFromWindow(window_, MONITOR_DEFAULTTONEAREST);
if (window_monitor_ != monitor) {
window_monitor_ = monitor;
window_output_ = DXGIOutputFromMonitor(monitor, d3d11_device_);
// From Raymond Chen's blog "How do I get a handle to the primary monitor?"
// https://devblogs.microsoft.com/oldnewthing/20141106-00/?p=43683
HMONITOR monitor = MonitorFromWindow(nullptr, MONITOR_DEFAULTTOPRIMARY);
if (primary_monitor_ != monitor) {
primary_monitor_ = monitor;
primary_output_ = DXGIOutputFromMonitor(monitor, d3d11_device_);
}
base::TimeDelta interval = base::TimeDelta::FromSecondsD(1.0 / 60);
......@@ -102,13 +112,13 @@ void VSyncThreadWin::WaitForVSync() {
base::TimeTicks wait_for_vblank_start_time = base::TimeTicks::Now();
bool wait_for_vblank_succeeded =
window_output_ && SUCCEEDED(window_output_->WaitForVBlank());
primary_output_ && SUCCEEDED(primary_output_->WaitForVBlank());
// WaitForVBlank returns very early instead of waiting until vblank when the
// monitor goes to sleep. We use 1ms as a threshold for the duration of
// WaitForVBlank and fallback to Sleep() if it returns before that. This
// could happen during normal operation for the first call after the vsync
// callback is enabled, but it shouldn't happen often.
// thread becomes non-idle, but it shouldn't happen often.
const auto kVBlankIntervalThreshold = base::TimeDelta::FromMilliseconds(1);
base::TimeDelta wait_for_vblank_elapsed_time =
base::TimeTicks::Now() - wait_for_vblank_start_time;
......@@ -119,17 +129,15 @@ void VSyncThreadWin::WaitForVSync() {
}
base::AutoLock auto_lock(lock_);
DCHECK(started_);
if (enabled_) {
if (!observers_.empty()) {
vsync_thread_.task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&VSyncThreadWin::WaitForVSync, base::Unretained(this)));
// Release lock before running callback to guard against any reentracny
// deadlock.
base::AutoUnlock auto_unlock(lock_);
callback_.Run(base::TimeTicks::Now(), interval);
base::TimeTicks vsync_time = base::TimeTicks::Now();
for (auto* obs : observers_)
obs->OnVSync(vsync_time, interval);
} else {
started_ = false;
is_idle_ = true;
}
}
......
......@@ -9,48 +9,53 @@
#include <windows.h>
#include <wrl/client.h>
#include "base/containers/flat_set.h"
#include "base/threading/thread.h"
#include "ui/gl/gl_export.h"
namespace gl {
namespace base {
template <typename T>
struct DefaultSingletonTraits;
} // namespace base
// Helper class that manages a thread for calling IDXGIOutput::WaitForVBlank()
// for the output corresponding to the given |window|, and runs |callback| on
// the same thread. The callback can be enabled or disabled via SetEnabled().
// This is used by DirectCompositionSurfaceWin to plumb vsync signal back to the
// display compositor's BeginFrameSource.
namespace gl {
class VSyncObserver;
// Helper singleton that wraps a thread for calling IDXGIOutput::WaitForVBlank()
// for the primary monitor, and notifies observers on the same thread. Observers
// can be added or removed on the main thread, and the vsync thread goes to
// sleep if there are no observers. This is used by DirectCompositionSurfaceWin
// to plumb vsync signal back to the display compositor's BeginFrameSource.
class GL_EXPORT VSyncThreadWin {
public:
using VSyncCallback =
base::RepeatingCallback<void(base::TimeTicks, base::TimeDelta)>;
VSyncThreadWin(HWND window,
Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device,
VSyncCallback callback);
~VSyncThreadWin();
static VSyncThreadWin* GetInstance();
void SetEnabled(bool enabled);
// These methods are not rentrancy safe, and shouldn't be called inside
// VSyncObserver::OnVSync. It's safe to assume that these can be called only
// from the main thread.
void AddObserver(VSyncObserver* obs);
void RemoveObserver(VSyncObserver* obs);
private:
friend struct base::DefaultSingletonTraits<VSyncThreadWin>;
VSyncThreadWin();
~VSyncThreadWin();
void WaitForVSync();
base::Thread vsync_thread_;
// Used on vsync thread only after initialization.
const HWND window_;
const Microsoft::WRL::ComPtr<ID3D11Device> d3d11_device_;
const VSyncCallback callback_;
// Used on vsync thread exclusively.
HMONITOR window_monitor_ = nullptr;
Microsoft::WRL::ComPtr<IDXGIOutput> window_output_;
HMONITOR primary_monitor_ = nullptr;
Microsoft::WRL::ComPtr<IDXGIOutput> primary_output_;
base::Lock lock_;
bool GUARDED_BY(lock_) enabled_ = false;
bool GUARDED_BY(lock_) started_ = false;
bool GUARDED_BY(lock_) is_idle_ = true;
base::flat_set<VSyncObserver*> GUARDED_BY(lock_) observers_;
DISALLOW_COPY_AND_ASSIGN(VSyncThreadWin);
};
} // namespace gl
#endif // UI_GL_VSYNC_THREAD_WIN_H_
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment