| // 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 "base/stl_util.h" |
| #include "ui/gl/gl_angle_util_win.h" |
| #include "ui/gl/vsync_observer.h" |
| |
| namespace gl { |
| namespace { |
| Microsoft::WRL::ComPtr<IDXGIOutput> DXGIOutputFromMonitor( |
| HMONITOR monitor, |
| const Microsoft::WRL::ComPtr<ID3D11Device>& d3d11_device) { |
| Microsoft::WRL::ComPtr<IDXGIDevice> dxgi_device; |
| if (FAILED(d3d11_device.As(&dxgi_device))) { |
| DLOG(ERROR) << "Failed to retrieve DXGI device"; |
| return nullptr; |
| } |
| |
| Microsoft::WRL::ComPtr<IDXGIAdapter> 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<IDXGIOutput> 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<VSyncThreadWin>::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<DWORD>(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 |