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(×tamp)) { |
// 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(); |
} |
} |