123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158 |
- // 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/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<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_);
- 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<DWORD>(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
|