1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2  * This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5  */
6 
7 #include "SoftwareVsyncSource.h"
8 #include "base/task.h"
9 #include "gfxPlatform.h"
10 #include "nsThreadUtils.h"
11 
12 using namespace mozilla;
13 
SoftwareVsyncSource()14 SoftwareVsyncSource::SoftwareVsyncSource()
15 {
16   MOZ_ASSERT(NS_IsMainThread());
17   mGlobalDisplay = new SoftwareDisplay();
18 }
19 
~SoftwareVsyncSource()20 SoftwareVsyncSource::~SoftwareVsyncSource()
21 {
22   MOZ_ASSERT(NS_IsMainThread());
23   mGlobalDisplay = nullptr;
24 }
25 
SoftwareDisplay()26 SoftwareDisplay::SoftwareDisplay()
27   : mVsyncEnabled(false)
28 {
29   // Mimic 60 fps
30   MOZ_ASSERT(NS_IsMainThread());
31   const double rate = 1000.0 / (double) gfxPlatform::GetSoftwareVsyncRate();
32   mVsyncRate = mozilla::TimeDuration::FromMilliseconds(rate);
33   mVsyncThread = new base::Thread("SoftwareVsyncThread");
34   MOZ_RELEASE_ASSERT(mVsyncThread->Start(), "GFX: Could not start software vsync thread");
35 }
36 
~SoftwareDisplay()37 SoftwareDisplay::~SoftwareDisplay() {}
38 
39 void
EnableVsync()40 SoftwareDisplay::EnableVsync()
41 {
42   MOZ_ASSERT(mVsyncThread->IsRunning());
43   if (NS_IsMainThread()) {
44     if (mVsyncEnabled) {
45       return;
46     }
47     mVsyncEnabled = true;
48 
49     mVsyncThread->message_loop()->PostTask(
50       NewRunnableMethod(this, &SoftwareDisplay::EnableVsync));
51     return;
52   }
53 
54   MOZ_ASSERT(IsInSoftwareVsyncThread());
55   NotifyVsync(mozilla::TimeStamp::Now());
56 }
57 
58 void
DisableVsync()59 SoftwareDisplay::DisableVsync()
60 {
61   MOZ_ASSERT(mVsyncThread->IsRunning());
62   if (NS_IsMainThread()) {
63     if (!mVsyncEnabled) {
64       return;
65     }
66     mVsyncEnabled = false;
67 
68     mVsyncThread->message_loop()->PostTask(
69       NewRunnableMethod(this, &SoftwareDisplay::DisableVsync));
70     return;
71   }
72 
73   MOZ_ASSERT(IsInSoftwareVsyncThread());
74   if (mCurrentVsyncTask) {
75     mCurrentVsyncTask->Cancel();
76     mCurrentVsyncTask = nullptr;
77   }
78 }
79 
80 bool
IsVsyncEnabled()81 SoftwareDisplay::IsVsyncEnabled()
82 {
83   MOZ_ASSERT(NS_IsMainThread());
84   return mVsyncEnabled;
85 }
86 
87 bool
IsInSoftwareVsyncThread()88 SoftwareDisplay::IsInSoftwareVsyncThread()
89 {
90   return mVsyncThread->thread_id() == PlatformThread::CurrentId();
91 }
92 
93 void
NotifyVsync(mozilla::TimeStamp aVsyncTimestamp)94 SoftwareDisplay::NotifyVsync(mozilla::TimeStamp aVsyncTimestamp)
95 {
96   MOZ_ASSERT(IsInSoftwareVsyncThread());
97 
98   mozilla::TimeStamp displayVsyncTime = aVsyncTimestamp;
99   mozilla::TimeStamp now = mozilla::TimeStamp::Now();
100   // Posted tasks can only have integer millisecond delays
101   // whereas TimeDurations can have floating point delays.
102   // Thus the vsync timestamp can be in the future, which large parts
103   // of the system can't handle, including animations. Force the timestamp to be now.
104   if (aVsyncTimestamp > now) {
105     displayVsyncTime = now;
106   }
107 
108   Display::NotifyVsync(displayVsyncTime);
109 
110   // Prevent skew by still scheduling based on the original
111   // vsync timestamp
112   ScheduleNextVsync(aVsyncTimestamp);
113 }
114 
115 mozilla::TimeDuration
GetVsyncRate()116 SoftwareDisplay::GetVsyncRate()
117 {
118   return mVsyncRate;
119 }
120 
121 void
ScheduleNextVsync(mozilla::TimeStamp aVsyncTimestamp)122 SoftwareDisplay::ScheduleNextVsync(mozilla::TimeStamp aVsyncTimestamp)
123 {
124   MOZ_ASSERT(IsInSoftwareVsyncThread());
125   mozilla::TimeStamp nextVsync = aVsyncTimestamp + mVsyncRate;
126   mozilla::TimeDuration delay = nextVsync - mozilla::TimeStamp::Now();
127   if (delay.ToMilliseconds() < 0) {
128     delay = mozilla::TimeDuration::FromMilliseconds(0);
129     nextVsync = mozilla::TimeStamp::Now();
130   }
131 
132   mCurrentVsyncTask =
133     NewCancelableRunnableMethod<mozilla::TimeStamp>(this,
134                                                     &SoftwareDisplay::NotifyVsync,
135                                                     nextVsync);
136 
137   RefPtr<Runnable> addrefedTask = mCurrentVsyncTask;
138   mVsyncThread->message_loop()->PostDelayedTask(
139     addrefedTask.forget(),
140     delay.ToMilliseconds());
141 }
142 
143 void
Shutdown()144 SoftwareDisplay::Shutdown()
145 {
146   MOZ_ASSERT(NS_IsMainThread());
147   DisableVsync();
148   mVsyncThread->Stop();
149   delete mVsyncThread;
150 }
151