Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/gfx/vr/service/VRService.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
3
/* This Source Code Form is subject to the terms of the Mozilla Public
4
 * License, v. 2.0. If a copy of the MPL was not distributed with this
5
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
#include "VRService.h"
8
#include "OpenVRSession.h"
9
#include "gfxPrefs.h"
10
#include "base/thread.h"                // for Thread
11
12
using namespace mozilla;
13
using namespace mozilla::gfx;
14
using namespace std;
15
16
namespace {
17
18
int64_t
19
FrameIDFromBrowserState(const mozilla::gfx::VRBrowserState& aState)
20
0
{
21
0
  for (int iLayer=0; iLayer < kVRLayerMaxCount; iLayer++) {
22
0
    const VRLayerState& layer = aState.layerState[iLayer];
23
0
    if (layer.type == VRLayerType::LayerType_Stereo_Immersive) {
24
0
      return layer.layer_stereo_immersive.mFrameId;
25
0
    }
26
0
  }
27
0
  return 0;
28
0
}
29
30
bool
31
IsImmersiveContentActive(const mozilla::gfx::VRBrowserState& aState)
32
0
{
33
0
  for (int iLayer=0; iLayer < kVRLayerMaxCount; iLayer++) {
34
0
    const VRLayerState& layer = aState.layerState[iLayer];
35
0
    if (layer.type == VRLayerType::LayerType_Stereo_Immersive) {
36
0
      return true;
37
0
    }
38
0
  }
39
0
  return false;
40
0
}
41
42
} // anonymous namespace
43
44
/*static*/ already_AddRefed<VRService>
45
VRService::Create()
46
0
{
47
0
  MOZ_ASSERT(NS_IsMainThread());
48
0
49
0
  if (!gfxPrefs::VRServiceEnabled()) {
50
0
    return nullptr;
51
0
  }
52
0
53
0
  RefPtr<VRService> service = new VRService();
54
0
  return service.forget();
55
0
}
56
57
VRService::VRService()
58
 : mSystemState{}
59
 , mBrowserState{}
60
 , mServiceThread(nullptr)
61
 , mShutdownRequested(false)
62
 , mAPIShmem(nullptr)
63
 , mTargetShmemFile(0)
64
0
{
65
0
  // When we have the VR process, we map the memory
66
0
  // of mAPIShmem from GPU process.
67
0
  // If we don't have the VR process, we will instantiate
68
0
  // mAPIShmem in VRService.
69
0
  if (!gfxPrefs::VRProcessEnabled()) {
70
0
    mAPIShmem = new VRExternalShmem();
71
0
    memset(mAPIShmem, 0, sizeof(VRExternalShmem));
72
0
  }
73
0
}
74
75
VRService::~VRService()
76
0
{
77
0
  Stop();
78
0
79
0
  if (!gfxPrefs::VRProcessEnabled() && mAPIShmem) {
80
0
    delete mAPIShmem;
81
0
    mAPIShmem = nullptr;
82
0
  }
83
0
}
84
85
void
86
VRService::Start()
87
0
{
88
0
  if (!mServiceThread) {
89
0
    /**
90
0
     * We must ensure that any time the service is re-started, that
91
0
     * the VRSystemState is reset, including mSystemState.enumerationCompleted
92
0
     * This must happen before VRService::Start returns to the caller, in order
93
0
     * to prevent the WebVR/WebXR promises from being resolved before the
94
0
     * enumeration has been completed.
95
0
     */
96
0
    memset(&mSystemState, 0, sizeof(mSystemState));
97
0
    PushState(mSystemState);
98
0
99
0
    mServiceThread = new base::Thread("VRService");
100
0
    base::Thread::Options options;
101
0
    /* Timeout values are powers-of-two to enable us get better data.
102
0
       128ms is chosen for transient hangs because 8Hz should be the minimally
103
0
       acceptable goal for Compositor responsiveness (normal goal is 60Hz). */
104
0
    options.transient_hang_timeout = 128; // milliseconds
105
0
    /* 2048ms is chosen for permanent hangs because it's longer than most
106
0
     * Compositor hangs seen in the wild, but is short enough to not miss getting
107
0
     * native hang stacks. */
108
0
    options.permanent_hang_timeout = 2048; // milliseconds
109
0
110
0
    if (!mServiceThread->StartWithOptions(options)) {
111
0
      delete mServiceThread;
112
0
      mServiceThread = nullptr;
113
0
      return;
114
0
    }
115
0
116
0
    mServiceThread->message_loop()->PostTask(NewRunnableMethod(
117
0
      "gfx::VRService::ServiceInitialize",
118
0
      this, &VRService::ServiceInitialize
119
0
    ));
120
0
  }
121
0
}
122
123
void
124
VRService::Stop()
125
0
{
126
0
  if (mServiceThread) {
127
0
    mShutdownRequested = true;
128
0
    delete mServiceThread;
129
0
    mServiceThread = nullptr;
130
0
  }
131
0
  if (mTargetShmemFile) {
132
#if defined(XP_WIN)
133
    CloseHandle(mTargetShmemFile);
134
#endif
135
    mTargetShmemFile = 0;
136
0
  }
137
0
  if (gfxPrefs::VRProcessEnabled() && mAPIShmem) {
138
#if defined(XP_WIN)
139
    UnmapViewOfFile((void *)mAPIShmem);
140
#endif
141
    mAPIShmem = nullptr;
142
0
  }
143
0
  mSession = nullptr;
144
0
}
145
146
bool
147
VRService::InitShmem()
148
0
{
149
0
  if (!gfxPrefs::VRProcessEnabled()) {
150
0
    return true;
151
0
  }
152
0
153
#if defined(XP_WIN)
154
  const char* kShmemName = "moz.gecko.vr_ext.0.0.1";
155
  base::ProcessHandle targetHandle = 0;
156
157
  // Opening a file-mapping object by name
158
  targetHandle = OpenFileMappingA(
159
                  FILE_MAP_ALL_ACCESS,   // read/write access
160
                  FALSE,                 // do not inherit the name
161
                  kShmemName);           // name of mapping object
162
163
  MOZ_ASSERT(GetLastError() == 0);
164
165
  LARGE_INTEGER length;
166
  length.QuadPart = sizeof(VRExternalShmem);
167
  mAPIShmem = (VRExternalShmem *)MapViewOfFile(reinterpret_cast<base::ProcessHandle>(targetHandle), // handle to map object
168
                                               FILE_MAP_ALL_ACCESS,  // read/write permission
169
                                               0,
170
                                               0,
171
                                               length.QuadPart);
172
  MOZ_ASSERT(GetLastError() == 0);
173
  // TODO - Implement logging
174
  mTargetShmemFile = targetHandle;
175
  if (!mAPIShmem) {
176
    MOZ_ASSERT(mAPIShmem);
177
    return false;
178
  }
179
#else
180
  // TODO: Implement shmem for other platforms.
181
0
#endif
182
0
183
0
 return true;
184
0
}
185
186
bool
187
VRService::IsInServiceThread()
188
0
{
189
0
  return mServiceThread && mServiceThread->thread_id() == PlatformThread::CurrentId();
190
0
}
191
192
void
193
VRService::ServiceInitialize()
194
0
{
195
0
  MOZ_ASSERT(IsInServiceThread());
196
0
197
0
  if (!InitShmem()) {
198
0
    return;
199
0
  }
200
0
201
0
  mShutdownRequested = false;
202
0
  memset(&mBrowserState, 0, sizeof(mBrowserState));
203
0
204
0
  // Try to start a VRSession
205
0
  UniquePtr<VRSession> session;
206
0
207
0
  // Try OpenVR
208
0
  session = MakeUnique<OpenVRSession>();
209
0
  if (!session->Initialize(mSystemState)) {
210
0
    session = nullptr;
211
0
  }
212
0
  if (session) {
213
0
    mSession = std::move(session);
214
0
    // Setting enumerationCompleted to true indicates to the browser
215
0
    // that it should resolve any promises in the WebVR/WebXR API
216
0
    // waiting for hardware detection.
217
0
    mSystemState.enumerationCompleted = true;
218
0
    PushState(mSystemState);
219
0
220
0
    MessageLoop::current()->PostTask(NewRunnableMethod(
221
0
      "gfx::VRService::ServiceWaitForImmersive",
222
0
      this, &VRService::ServiceWaitForImmersive
223
0
    ));
224
0
  } else {
225
0
    // VR hardware was not detected.
226
0
    // We must inform the browser of the failure so it may try again
227
0
    // later and resolve WebVR promises.  A failure or shutdown is
228
0
    // indicated by enumerationCompleted being set to true, with all
229
0
    // other fields remaining zeroed out.
230
0
    memset(&mSystemState, 0, sizeof(mSystemState));
231
0
    mSystemState.enumerationCompleted = true;
232
0
    PushState(mSystemState);
233
0
  }
234
0
}
235
236
void
237
VRService::ServiceShutdown()
238
0
{
239
0
  MOZ_ASSERT(IsInServiceThread());
240
0
241
0
  mSession = nullptr;
242
0
243
0
  // Notify the browser that we have shut down.
244
0
  // This is indicated by enumerationCompleted being set
245
0
  // to true, with all other fields remaining zeroed out.
246
0
  memset(&mSystemState, 0, sizeof(mSystemState));
247
0
  mSystemState.enumerationCompleted = true;
248
0
  PushState(mSystemState);
249
0
}
250
251
void
252
VRService::ServiceWaitForImmersive()
253
0
{
254
0
  MOZ_ASSERT(IsInServiceThread());
255
0
  MOZ_ASSERT(mSession);
256
0
257
0
  mSession->ProcessEvents(mSystemState);
258
0
  PushState(mSystemState);
259
0
  PullState(mBrowserState);
260
0
261
0
  if (mSession->ShouldQuit() || mShutdownRequested) {
262
0
    // Shut down
263
0
    MessageLoop::current()->PostTask(NewRunnableMethod(
264
0
      "gfx::VRService::ServiceShutdown",
265
0
      this, &VRService::ServiceShutdown
266
0
    ));
267
0
  } else if (IsImmersiveContentActive(mBrowserState)) {
268
0
    // Enter Immersive Mode
269
0
    mSession->StartPresentation();
270
0
    mSession->StartFrame(mSystemState);
271
0
    PushState(mSystemState);
272
0
273
0
    MessageLoop::current()->PostTask(NewRunnableMethod(
274
0
      "gfx::VRService::ServiceImmersiveMode",
275
0
      this, &VRService::ServiceImmersiveMode
276
0
    ));
277
0
  } else {
278
0
    // Continue waiting for immersive mode
279
0
    MessageLoop::current()->PostTask(NewRunnableMethod(
280
0
      "gfx::VRService::ServiceWaitForImmersive",
281
0
      this, &VRService::ServiceWaitForImmersive
282
0
    ));
283
0
  }
284
0
}
285
286
void
287
VRService::ServiceImmersiveMode()
288
0
{
289
0
  MOZ_ASSERT(IsInServiceThread());
290
0
  MOZ_ASSERT(mSession);
291
0
292
0
  mSession->ProcessEvents(mSystemState);
293
0
  PushState(mSystemState);
294
0
  PullState(mBrowserState);
295
0
296
0
  if (mSession->ShouldQuit() || mShutdownRequested) {
297
0
    // Shut down
298
0
    MessageLoop::current()->PostTask(NewRunnableMethod(
299
0
      "gfx::VRService::ServiceShutdown",
300
0
      this, &VRService::ServiceShutdown
301
0
    ));
302
0
    return;
303
0
  } else if (!IsImmersiveContentActive(mBrowserState)) {
304
0
    // Exit immersive mode
305
0
    mSession->StopPresentation();
306
0
    MessageLoop::current()->PostTask(NewRunnableMethod(
307
0
      "gfx::VRService::ServiceWaitForImmersive",
308
0
      this, &VRService::ServiceWaitForImmersive
309
0
    ));
310
0
    return;
311
0
  }
312
0
313
0
  uint64_t newFrameId = FrameIDFromBrowserState(mBrowserState);
314
0
  if (newFrameId != mSystemState.displayState.mLastSubmittedFrameId) {
315
0
    // A new immersive frame has been received.
316
0
    // Submit the textures to the VR system compositor.
317
0
    bool success = false;
318
0
    for (int iLayer=0; iLayer < kVRLayerMaxCount; iLayer++) {
319
0
      const VRLayerState& layer = mBrowserState.layerState[iLayer];
320
0
      if (layer.type == VRLayerType::LayerType_Stereo_Immersive) {
321
0
        success = mSession->SubmitFrame(layer.layer_stereo_immersive);
322
0
        break;
323
0
      }
324
0
    }
325
0
326
0
    // Changing mLastSubmittedFrameId triggers a new frame to start
327
0
    // rendering.  Changes to mLastSubmittedFrameId and the values
328
0
    // used for rendering, such as headset pose, must be pushed
329
0
    // atomically to the browser.
330
0
    mSystemState.displayState.mLastSubmittedFrameId = newFrameId;
331
0
    mSystemState.displayState.mLastSubmittedFrameSuccessful = success;
332
0
    mSession->StartFrame(mSystemState);
333
0
    PushState(mSystemState);
334
0
  }
335
0
336
0
  // Continue immersive mode
337
0
  MessageLoop::current()->PostTask(NewRunnableMethod(
338
0
    "gfx::VRService::ServiceImmersiveMode",
339
0
    this, &VRService::ServiceImmersiveMode
340
0
  ));
341
0
}
342
343
void
344
VRService::PushState(const mozilla::gfx::VRSystemState& aState)
345
0
{
346
0
  if (!mAPIShmem) {
347
0
    return;
348
0
  }
349
0
  // Copying the VR service state to the shmem is atomic, infallable,
350
0
  // and non-blocking on x86/x64 architectures.  Arm requires a mutex
351
0
  // that is locked for the duration of the memcpy to and from shmem on
352
0
  // both sides.
353
0
354
#if defined(MOZ_WIDGET_ANDROID)
355
    if (pthread_mutex_lock((pthread_mutex_t*)&(mExternalShmem->systemMutex)) == 0) {
356
      memcpy((void *)&mAPIShmem->state, &aState, sizeof(VRSystemState));
357
      pthread_mutex_unlock((pthread_mutex_t*)&(mExternalShmem->systemMutex));
358
    }
359
#else
360
0
  mAPIShmem->generationA++;
361
0
  memcpy((void *)&mAPIShmem->state, &aState, sizeof(VRSystemState));
362
0
  mAPIShmem->generationB++;
363
0
#endif
364
0
}
365
366
void
367
VRService::PullState(mozilla::gfx::VRBrowserState& aState)
368
0
{
369
0
  if (!mAPIShmem) {
370
0
    return;
371
0
  }
372
0
  // Copying the browser state from the shmem is non-blocking
373
0
  // on x86/x64 architectures.  Arm requires a mutex that is
374
0
  // locked for the duration of the memcpy to and from shmem on
375
0
  // both sides.
376
0
  // On x86/x64 It is fallable -- If a dirty copy is detected by
377
0
  // a mismatch of browserGenerationA and browserGenerationB,
378
0
  // the copy is discarded and will not replace the last known
379
0
  // browser state.
380
0
381
#if defined(MOZ_WIDGET_ANDROID)
382
    if (pthread_mutex_lock((pthread_mutex_t*)&(mExternalShmem->browserMutex)) == 0) {
383
      memcpy(&aState, &tmp.browserState, sizeof(VRBrowserState));
384
      pthread_mutex_unlock((pthread_mutex_t*)&(mExternalShmem->browserMutex));
385
    }
386
#else
387
0
  VRExternalShmem tmp;
388
0
  memcpy(&tmp, mAPIShmem, sizeof(VRExternalShmem));
389
0
  if (tmp.browserGenerationA == tmp.browserGenerationB && tmp.browserGenerationA != 0 && tmp.browserGenerationA != -1) {
390
0
    memcpy(&aState, &tmp.browserState, sizeof(VRBrowserState));
391
0
  }
392
0
#endif
393
0
}
394
395
VRExternalShmem*
396
VRService::GetAPIShmem()
397
0
{
398
0
  return mAPIShmem;
399
0
}