Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(325)

Unified Diff: gpu/ipc/service/gpu_vsync_provider_win.cc

Issue 2874833003: D3D V-sync: prevent timestamp drift on a secondary monitor (Closed)
Patch Set: Merged and fixed a typo in a comment. Created 3 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « gpu/ipc/service/BUILD.gn ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: gpu/ipc/service/gpu_vsync_provider_win.cc
diff --git a/gpu/ipc/service/gpu_vsync_provider_win.cc b/gpu/ipc/service/gpu_vsync_provider_win.cc
index 47287ce7b8b044f1ba356976e2427f2b591a1088..093f5e0eb96a55196018733cf8f7fcba1a648a0b 100644
--- a/gpu/ipc/service/gpu_vsync_provider_win.cc
+++ b/gpu/ipc/service/gpu_vsync_provider_win.cc
@@ -4,6 +4,9 @@
#include "gpu/ipc/service/gpu_vsync_provider_win.h"
+#include <dwmapi.h>
+#include <windows.h>
+
#include <string>
#include "base/atomicops.h"
@@ -14,15 +17,12 @@
#include "gpu/ipc/common/gpu_messages.h"
#include "ipc/ipc_message_macros.h"
#include "ipc/message_filter.h"
-#include "ui/gl/vsync_provider_win.h"
-
-#include <windows.h>
namespace gpu {
namespace {
-// Default interval used when no v-sync interval comes from DWM.
-const int kDefaultTimerBasedInterval = 16666;
+// Default v-sync interval used when there is no history of v-sync timestamps.
+const int kDefaultInterval = 16666;
// from <D3dkmthk.h>
typedef LONG NTSTATUS;
@@ -79,14 +79,20 @@ class GpuVSyncWorker : public base::Thread,
void CloseAdapter();
NTSTATUS WaitForVBlankEvent();
- void SendGpuVSyncUpdate(base::TimeTicks now,
- base::TimeTicks timestamp,
- base::TimeDelta interval);
+ void AddTimestamp(base::TimeTicks timestamp);
+ void AddInterval(base::TimeDelta interval);
+ base::TimeDelta GetAverageInterval() const;
+ void ClearIntervalHistory();
+
+ bool GetDisplayFrequency(const wchar_t* device_name, DWORD* frequency);
+ void UpdateCurrentDisplayFrequency();
+ bool GetDwmVBlankTimestamp(base::TimeTicks* timestamp);
+
+ void SendGpuVSyncUpdate(base::TimeTicks now, bool use_dwm);
+
void InvokeCallbackAndReschedule(base::TimeTicks timestamp,
base::TimeDelta interval);
void UseDelayBasedVSyncOnError();
- void ScheduleDelayBasedVSync(base::TimeTicks timebase,
- base::TimeDelta interval);
// Specifies whether background tasks are running.
// This can be set on background thread only.
@@ -99,9 +105,6 @@ class GpuVSyncWorker : public base::Thread,
const gfx::VSyncProvider::UpdateVSyncCallback callback_;
const SurfaceHandle surface_handle_;
- // The actual timing and interval comes from the nested provider.
- std::unique_ptr<gl::VSyncProviderWin> vsync_provider_;
-
PFND3DKMTOPENADAPTERFROMHDC open_adapter_from_hdc_ptr_;
PFND3DKMTCLOSEADAPTER close_adapter_ptr_;
PFND3DKMTWAITFORVERTICALBLANKEVENT wait_for_vertical_blank_event_ptr_;
@@ -109,6 +112,30 @@ class GpuVSyncWorker : public base::Thread,
std::wstring current_device_name_;
D3DKMT_HANDLE current_adapter_handle_ = 0;
D3DDDI_VIDEO_PRESENT_SOURCE_ID current_source_id_ = 0;
+
+ // Last known v-sync timestamp.
+ base::TimeTicks last_timestamp_;
+
+ // Current display refresh frequency in Hz which is used to detect
+ // when the frequency changes and and to update the accepted interval
+ // range below.
+ DWORD current_display_frequency_ = 0;
+
+ // Range of intervals accepted for the average calculation which is
+ // +/-20% from the interval corresponding to the display frequency above.
+ // This is used to filter out outliers.
+ base::TimeDelta min_accepted_interval_;
+ base::TimeDelta max_accepted_interval_;
+
+ // History of recent deltas between timestamps which is used to calculate the
+ // average v-sync interval and organized as a circular buffer.
+ static const size_t kIntervalHistorySize = 60;
+ base::TimeDelta interval_history_[kIntervalHistorySize];
+ size_t history_index_ = 0;
+ size_t history_size_ = 0;
+
+ // Rolling sum of intervals in the circular buffer above.
+ base::TimeDelta rolling_interval_sum_;
};
GpuVSyncWorker::GpuVSyncWorker(
@@ -116,8 +143,7 @@ GpuVSyncWorker::GpuVSyncWorker(
SurfaceHandle surface_handle)
: base::Thread(base::StringPrintf("VSync-%d", surface_handle)),
callback_(callback),
- surface_handle_(surface_handle),
- vsync_provider_(new gl::VSyncProviderWin(surface_handle)) {
+ surface_handle_(surface_handle) {
HMODULE gdi32 = GetModuleHandle(L"gdi32");
if (!gdi32) {
NOTREACHED() << "Can't open gdi32.dll";
@@ -209,6 +235,14 @@ void GpuVSyncWorker::WaitForVSyncOnThread() {
}
}
+ UpdateCurrentDisplayFrequency();
+
+ // Use DWM timing only when running on the primary monitor which DWM
+ // is synchronized with and only if we can get accurate high resulution
+ // timestamps.
+ bool use_dwm = (monitor_info.dwFlags & MONITORINFOF_PRIMARY) != 0 &&
+ base::TimeTicks::IsHighResolution();
+
NTSTATUS wait_result = WaitForVBlankEvent();
if (wait_result != STATUS_SUCCESS) {
if (wait_result == STATUS_GRAPHICS_PRESENT_OCCLUDED) {
@@ -221,34 +255,119 @@ void GpuVSyncWorker::WaitForVSyncOnThread() {
}
}
- vsync_provider_->GetVSyncParameters(
- base::Bind(&GpuVSyncWorker::SendGpuVSyncUpdate, base::Unretained(this),
- base::TimeTicks::Now()));
+ SendGpuVSyncUpdate(base::TimeTicks::Now(), use_dwm);
+}
+
+void GpuVSyncWorker::AddTimestamp(base::TimeTicks timestamp) {
+ if (!last_timestamp_.is_null()) {
+ AddInterval(timestamp - last_timestamp_);
+ }
+
+ last_timestamp_ = timestamp;
+}
+
+void GpuVSyncWorker::AddInterval(base::TimeDelta interval) {
+ if (interval < min_accepted_interval_ || interval > max_accepted_interval_)
+ return;
+
+ if (history_size_ == kIntervalHistorySize) {
+ rolling_interval_sum_ -= interval_history_[history_index_];
+ } else {
+ history_size_++;
+ }
+
+ interval_history_[history_index_] = interval;
+ rolling_interval_sum_ += interval;
+ history_index_ = (history_index_ + 1) % kIntervalHistorySize;
+}
+
+void GpuVSyncWorker::ClearIntervalHistory() {
+ last_timestamp_ = base::TimeTicks();
+ rolling_interval_sum_ = base::TimeDelta();
+ history_index_ = 0;
+ history_size_ = 0;
+}
+
+base::TimeDelta GpuVSyncWorker::GetAverageInterval() const {
+ return !rolling_interval_sum_.is_zero()
+ ? rolling_interval_sum_ / history_size_
+ : base::TimeDelta::FromMicroseconds(kDefaultInterval);
+}
+
+bool GpuVSyncWorker::GetDisplayFrequency(const wchar_t* device_name,
+ DWORD* frequency) {
+ DEVMODE display_info;
+ display_info.dmSize = sizeof(DEVMODE);
+ display_info.dmDriverExtra = 0;
+
+ BOOL result =
+ EnumDisplaySettings(device_name, ENUM_CURRENT_SETTINGS, &display_info);
+ if (result && display_info.dmDisplayFrequency > 1) {
+ *frequency = display_info.dmDisplayFrequency;
+ return true;
+ }
+
+ return false;
}
-void GpuVSyncWorker::SendGpuVSyncUpdate(base::TimeTicks now,
- base::TimeTicks timestamp,
- base::TimeDelta interval) {
+void GpuVSyncWorker::UpdateCurrentDisplayFrequency() {
+ DWORD frequency;
+ DCHECK(!current_device_name_.empty());
+ if (!GetDisplayFrequency(current_device_name_.c_str(), &frequency)) {
+ current_display_frequency_ = 0;
+ return;
+ }
+
+ if (frequency != current_display_frequency_) {
+ current_display_frequency_ = frequency;
+ base::TimeDelta interval = base::TimeDelta::FromMicroseconds(
+ base::Time::kMicrosecondsPerSecond / static_cast<double>(frequency));
+ ClearIntervalHistory();
+
+ min_accepted_interval_ = interval * 0.8;
+ max_accepted_interval_ = interval * 1.2;
+ AddInterval(interval);
+ }
+}
+
+bool GpuVSyncWorker::GetDwmVBlankTimestamp(base::TimeTicks* timestamp) {
+ DWM_TIMING_INFO timing_info;
+ timing_info.cbSize = sizeof(timing_info);
+ HRESULT result = DwmGetCompositionTimingInfo(nullptr, &timing_info);
+ if (result != S_OK)
+ return false;
+
+ *timestamp = base::TimeTicks::FromQPCValue(
+ static_cast<LONGLONG>(timing_info.qpcVBlank));
+ return true;
+}
+
+void GpuVSyncWorker::SendGpuVSyncUpdate(base::TimeTicks now, bool use_dwm) {
+ base::TimeTicks timestamp;
base::TimeDelta adjustment;
- if (!(timestamp.is_null() || interval.is_zero())) {
+ if (use_dwm && GetDwmVBlankTimestamp(&timestamp)) {
// Timestamp comes from DwmGetCompositionTimingInfo and apparently it might
// be up to 2-3 vsync cycles in the past or in the future.
// The adjustment formula was suggested here:
// http://www.vsynctester.com/firefoxisbroken.html
+ base::TimeDelta interval = GetAverageInterval();
adjustment =
((now - timestamp + interval / 8) % interval + interval) % interval -
interval / 8;
timestamp = now - adjustment;
} else {
- // DWM must be disabled.
+ // Not using DWM.
timestamp = now;
}
+ AddTimestamp(timestamp);
+
TRACE_EVENT1("gpu", "GpuVSyncWorker::SendGpuVSyncUpdate", "adjustment",
adjustment.ToInternalValue());
- InvokeCallbackAndReschedule(timestamp, interval);
+ DCHECK_GT(GetAverageInterval().InMillisecondsF(), 0);
+ InvokeCallbackAndReschedule(timestamp, GetAverageInterval());
}
void GpuVSyncWorker::InvokeCallbackAndReschedule(base::TimeTicks timestamp,
@@ -261,6 +380,8 @@ void GpuVSyncWorker::InvokeCallbackAndReschedule(base::TimeTicks timestamp,
base::Unretained(this)));
} else {
running_ = false;
+ // Clear last_timestamp_ to avoid a long interval when the worker restarts.
+ last_timestamp_ = base::TimeTicks();
}
}
@@ -269,18 +390,11 @@ void GpuVSyncWorker::UseDelayBasedVSyncOnError() {
// Use timer based mechanism as a backup for one v-sync cycle, start with
// getting VSync parameters to determine timebase and interval.
// TODO(stanisc): Consider a slower v-sync rate in this particular case.
- vsync_provider_->GetVSyncParameters(base::Bind(
- &GpuVSyncWorker::ScheduleDelayBasedVSync, base::Unretained(this)));
-}
-void GpuVSyncWorker::ScheduleDelayBasedVSync(base::TimeTicks timebase,
- base::TimeDelta interval) {
- // This is called only when WaitForVBlankEvent fails due to monitor going to
- // sleep. Use a delay based v-sync as a back-up.
- if (interval.is_zero()) {
- interval = base::TimeDelta::FromMicroseconds(kDefaultTimerBasedInterval);
- }
+ base::TimeTicks timebase;
+ GetDwmVBlankTimestamp(&timebase);
+ base::TimeDelta interval = GetAverageInterval();
base::TimeTicks now = base::TimeTicks::Now();
base::TimeTicks next_vsync = now.SnappedToNextTick(timebase, interval);
@@ -327,6 +441,8 @@ void GpuVSyncWorker::CloseAdapter() {
current_adapter_handle_ = 0;
current_device_name_.clear();
+
+ ClearIntervalHistory();
}
}
« no previous file with comments | « gpu/ipc/service/BUILD.gn ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698