// Copyright 2019 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "ui/gl/vsync_thread_win.h" #include "base/bind.h" #include "base/logging.h" #include "base/memory/singleton.h" #include "base/power_monitor/power_monitor.h" #include "ui/gl/gl_angle_util_win.h" #include "ui/gl/vsync_observer.h" namespace gl { namespace { Microsoft::WRL::ComPtr DXGIOutputFromMonitor( HMONITOR monitor, const Microsoft::WRL::ComPtr& d3d11_device) { Microsoft::WRL::ComPtr dxgi_device; if (FAILED(d3d11_device.As(&dxgi_device))) { DLOG(ERROR) << "Failed to retrieve DXGI device"; return nullptr; } Microsoft::WRL::ComPtr dxgi_adapter; if (FAILED(dxgi_device->GetAdapter(&dxgi_adapter))) { DLOG(ERROR) << "Failed to retrieve DXGI adapter"; return nullptr; } size_t i = 0; while (true) { Microsoft::WRL::ComPtr output; if (FAILED(dxgi_adapter->EnumOutputs(i++, &output))) break; DXGI_OUTPUT_DESC desc = {}; if (FAILED(output->GetDesc(&desc))) { DLOG(ERROR) << "DXGI output GetDesc failed"; return nullptr; } if (desc.Monitor == monitor) return output; } return nullptr; } } // namespace // static VSyncThreadWin* VSyncThreadWin::GetInstance() { return base::Singleton::get(); } VSyncThreadWin::VSyncThreadWin() : vsync_thread_("GpuVSyncThread"), vsync_provider_(gfx::kNullAcceleratedWidget), d3d11_device_(QueryD3D11DeviceObjectFromANGLE()) { DCHECK(d3d11_device_); is_suspended_ = base::PowerMonitor::AddPowerSuspendObserverAndReturnSuspendedState(this); vsync_thread_.StartWithOptions( base::Thread::Options(base::ThreadType::kDisplayCritical)); } VSyncThreadWin::~VSyncThreadWin() { { base::AutoLock auto_lock(lock_); observers_.clear(); } vsync_thread_.Stop(); base::PowerMonitor::RemovePowerSuspendObserver(this); } void VSyncThreadWin::PostTaskIfNeeded() { lock_.AssertAcquired(); // PostTaskIfNeeded is called from AddObserver and OnResume. // Before queuing up a task, make sure that there are observers waiting for // VSync and that we're not already firing events to consumers. Avoid firing // events if we're suspended to conserve battery life. if (!is_vsync_task_posted_ && !observers_.empty() && !is_suspended_) { vsync_thread_.task_runner()->PostTask( FROM_HERE, base::BindOnce(&VSyncThreadWin::WaitForVSync, base::Unretained(this))); is_vsync_task_posted_ = true; } } void VSyncThreadWin::AddObserver(VSyncObserver* obs) { base::AutoLock auto_lock(lock_); observers_.insert(obs); PostTaskIfNeeded(); } void VSyncThreadWin::RemoveObserver(VSyncObserver* obs) { base::AutoLock auto_lock(lock_); observers_.erase(obs); } void VSyncThreadWin::OnSuspend() { base::AutoLock auto_lock(lock_); is_suspended_ = true; } void VSyncThreadWin::OnResume() { base::AutoLock auto_lock(lock_); is_suspended_ = false; PostTaskIfNeeded(); } void VSyncThreadWin::WaitForVSync() { base::TimeTicks vsync_phase; base::TimeDelta vsync_interval; const bool get_vsync_params_succeeded = vsync_provider_.GetVSyncParametersIfAvailable(&vsync_phase, &vsync_interval); DCHECK(get_vsync_params_succeeded); // From Raymond Chen's blog "How do I get a handle to the primary monitor?" // https://devblogs.microsoft.com/oldnewthing/20141106-00/?p=43683 const HMONITOR monitor = MonitorFromWindow(nullptr, MONITOR_DEFAULTTOPRIMARY); if (primary_monitor_ != monitor) { primary_monitor_ = monitor; primary_output_ = DXGIOutputFromMonitor(monitor, d3d11_device_); } const base::TimeTicks wait_for_vblank_start_time = base::TimeTicks::Now(); const bool wait_for_vblank_succeeded = 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 // thread becomes non-idle, but it shouldn't happen often. constexpr auto kVBlankIntervalThreshold = base::Milliseconds(1); const base::TimeDelta wait_for_vblank_elapsed_time = base::TimeTicks::Now() - wait_for_vblank_start_time; if (!wait_for_vblank_succeeded || wait_for_vblank_elapsed_time < kVBlankIntervalThreshold) { Sleep(static_cast(vsync_interval.InMillisecondsRoundedUp())); } base::AutoLock auto_lock(lock_); DCHECK(is_vsync_task_posted_); is_vsync_task_posted_ = false; PostTaskIfNeeded(); const base::TimeTicks vsync_time = base::TimeTicks::Now(); for (auto* obs : observers_) obs->OnVSync(vsync_time, vsync_interval); } } // namespace gl