// 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. #include "ui/gl/vsync_thread_win.h" #include "base/bind.h" #include "base/logging.h" #include "base/memory/singleton.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_); base::Thread::Options options; options.priority = base::ThreadPriority::DISPLAY; vsync_thread_.StartWithOptions(std::move(options)); } VSyncThreadWin::~VSyncThreadWin() { { base::AutoLock auto_lock(lock_); observers_.clear(); } vsync_thread_.Stop(); } void VSyncThreadWin::AddObserver(VSyncObserver* obs) { base::AutoLock auto_lock(lock_); 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() { 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::TimeDelta::FromMilliseconds(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_); if (!observers_.empty()) { vsync_thread_.task_runner()->PostTask( FROM_HERE, base::BindOnce(&VSyncThreadWin::WaitForVSync, base::Unretained(this))); const base::TimeTicks vsync_time = base::TimeTicks::Now(); for (auto* obs : observers_) obs->OnVSync(vsync_time, vsync_interval); } else { is_idle_ = true; } } } // namespace gl