summaryrefslogtreecommitdiffstats
path: root/gfx/thebes/VsyncSource.cpp
blob: d7cfb339aa64d89ed2bcff9e0d14507d044d57c4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "VsyncSource.h"
#include "nsThreadUtils.h"
#include "nsXULAppAPI.h"
#include "mozilla/VsyncDispatcher.h"
#include "MainThreadUtils.h"
#include "gfxPlatform.h"

#ifdef MOZ_WAYLAND
#  include "WaylandVsyncSource.h"
#endif

namespace mozilla {
namespace gfx {

VsyncSource::VsyncSource() : mState("VsyncSource::State") {
  MOZ_ASSERT(NS_IsMainThread());
}

VsyncSource::~VsyncSource() { MOZ_ASSERT(NS_IsMainThread()); }

// Called on the vsync thread
void VsyncSource::NotifyVsync(const TimeStamp& aVsyncTimestamp,
                              const TimeStamp& aOutputTimestamp) {
  VsyncId vsyncId;
  nsTArray<DispatcherRefWithCount> dispatchers;

  {
    auto state = mState.Lock();
    vsyncId = state->mVsyncId.Next();
    dispatchers = state->mDispatchers.Clone();
    state->mVsyncId = vsyncId;
  }

  // Notify our listeners, outside of the lock.
  const VsyncEvent event(vsyncId, aVsyncTimestamp, aOutputTimestamp);
  for (const auto& dispatcher : dispatchers) {
    dispatcher.mDispatcher->NotifyVsync(event);
  }
}

void VsyncSource::AddVsyncDispatcher(VsyncDispatcher* aVsyncDispatcher) {
  MOZ_ASSERT(aVsyncDispatcher);
  {
    auto state = mState.Lock();

    // Find the dispatcher in mDispatchers. If it is already present, increment
    // the count. If not, add it with a count of 1.
    bool found = false;
    for (auto& dispatcherRefWithCount : state->mDispatchers) {
      if (dispatcherRefWithCount.mDispatcher == aVsyncDispatcher) {
        dispatcherRefWithCount.mCount++;
        found = true;
        break;
      }
    }
    if (!found) {
      state->mDispatchers.AppendElement(
          DispatcherRefWithCount{aVsyncDispatcher, 1});
    }
  }

  UpdateVsyncStatus();
}

void VsyncSource::RemoveVsyncDispatcher(VsyncDispatcher* aVsyncDispatcher) {
  MOZ_ASSERT(aVsyncDispatcher);
  {
    auto state = mState.Lock();

    // Find the dispatcher in mDispatchers. If found, decrement the count.
    // If the count becomes zero, remove it from mDispatchers.
    for (auto it = state->mDispatchers.begin(); it != state->mDispatchers.end();
         ++it) {
      if (it->mDispatcher == aVsyncDispatcher) {
        it->mCount--;
        if (it->mCount == 0) {
          state->mDispatchers.RemoveElementAt(it);
        }
        break;
      }
    }

    // In the future we should probably MOZ_RELEASE_ASSERT here that we don't
    // try to remove a dispatcher which isn't in mDispatchers.
  }

  UpdateVsyncStatus();
}

// This is the base class implementation. Subclasses override this method.
TimeDuration VsyncSource::GetVsyncRate() {
  // If hardware queries fail / are unsupported, we have to just guess.
  return TimeDuration::FromMilliseconds(1000.0 / 60.0);
}

void VsyncSource::UpdateVsyncStatus() {
  if (!NS_IsMainThread()) {
    NS_DispatchToMainThread(NS_NewRunnableFunction(
        "VsyncSource::UpdateVsyncStatus",
        [self = RefPtr{this}] { self->UpdateVsyncStatus(); }));
    return;
  }

  MOZ_ASSERT(NS_IsMainThread());
  // WARNING: This function SHOULD NOT BE CALLED WHILE HOLDING LOCKS
  // NotifyVsync grabs a lock to dispatch vsync events
  // When disabling vsync, we wait for the underlying thread to stop on some
  // platforms We can deadlock if we wait for the underlying vsync thread to
  // stop while the vsync thread is in NotifyVsync.
  bool enableVsync = false;
  {  // scope lock
    auto state = mState.Lock();
    enableVsync = !state->mDispatchers.IsEmpty();
  }

  if (enableVsync) {
    EnableVsync();
  } else {
    DisableVsync();
  }

  if (IsVsyncEnabled() != enableVsync) {
    NS_WARNING("Vsync status did not change.");
  }
}

// static
Maybe<TimeDuration> VsyncSource::GetFastestVsyncRate() {
  Maybe<TimeDuration> retVal;
  if (!gfxPlatform::Initialized()) {
    return retVal;
  }

  RefPtr<VsyncDispatcher> vsyncDispatcher =
      gfxPlatform::GetPlatform()->GetGlobalVsyncDispatcher();
  RefPtr<VsyncSource> vsyncSource = vsyncDispatcher->GetCurrentVsyncSource();
  if (vsyncSource->IsVsyncEnabled()) {
    retVal.emplace(vsyncSource->GetVsyncRate());
#ifdef MOZ_WAYLAND
    Maybe<TimeDuration> waylandRate = WaylandVsyncSource::GetFastestVsyncRate();
    if (waylandRate) {
      if (!retVal) {
        retVal.emplace(*waylandRate);
      } else if (*waylandRate < *retVal) {
        retVal = waylandRate;
      }
    }
#endif
  }

  return retVal;
}

}  // namespace gfx
}  // namespace mozilla