/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 | } |