| 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();
|
| }
|
| }
|
|
|
|
|