/src/mozilla-central/gfx/vr/VRDisplayHost.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 "VRDisplayHost.h" |
8 | | #include "gfxPrefs.h" |
9 | | #include "gfxVR.h" |
10 | | #include "ipc/VRLayerParent.h" |
11 | | #include "mozilla/layers/TextureHost.h" |
12 | | #include "mozilla/dom/GamepadBinding.h" // For GamepadMappingType |
13 | | #include "VRThread.h" |
14 | | |
15 | | #if defined(XP_WIN) |
16 | | |
17 | | #include <d3d11.h> |
18 | | #include "gfxWindowsPlatform.h" |
19 | | #include "../layers/d3d11/CompositorD3D11.h" |
20 | | #include "mozilla/gfx/DeviceManagerDx.h" |
21 | | #include "mozilla/layers/TextureD3D11.h" |
22 | | |
23 | | #elif defined(XP_MACOSX) |
24 | | |
25 | | #include "mozilla/gfx/MacIOSurface.h" |
26 | | |
27 | | #endif |
28 | | |
29 | | #if defined(MOZ_WIDGET_ANDROID) |
30 | | #include "mozilla/layers/CompositorThread.h" |
31 | | // Max frame duration on Android before the watchdog submits a new one. |
32 | | // Probably we can get rid of this when we enforce that SubmitFrame can only be called in a VRDisplay loop. |
33 | | #define ANDROID_MAX_FRAME_DURATION 4000 |
34 | | #endif // defined(MOZ_WIDGET_ANDROID) |
35 | | |
36 | | |
37 | | using namespace mozilla; |
38 | | using namespace mozilla::gfx; |
39 | | using namespace mozilla::layers; |
40 | | |
41 | | VRDisplayHost::AutoRestoreRenderState::AutoRestoreRenderState(VRDisplayHost* aDisplay) |
42 | | : mDisplay(aDisplay) |
43 | | , mSuccess(true) |
44 | 0 | { |
45 | | #if defined(XP_WIN) |
46 | | ID3D11DeviceContext1* context = mDisplay->GetD3DDeviceContext(); |
47 | | ID3DDeviceContextState* state = mDisplay->GetD3DDeviceContextState(); |
48 | | if (!context || !state) { |
49 | | mSuccess = false; |
50 | | return; |
51 | | } |
52 | | context->SwapDeviceContextState(state, getter_AddRefs(mPrevDeviceContextState)); |
53 | | #endif |
54 | | } |
55 | | |
56 | | VRDisplayHost::AutoRestoreRenderState::~AutoRestoreRenderState() |
57 | 0 | { |
58 | | #if defined(XP_WIN) |
59 | | ID3D11DeviceContext1* context = mDisplay->GetD3DDeviceContext(); |
60 | | if (context && mSuccess) { |
61 | | context->SwapDeviceContextState(mPrevDeviceContextState, nullptr); |
62 | | } |
63 | | #endif |
64 | | } |
65 | | |
66 | | bool |
67 | | VRDisplayHost::AutoRestoreRenderState::IsSuccess() |
68 | 0 | { |
69 | 0 | return mSuccess; |
70 | 0 | } |
71 | | |
72 | | VRDisplayHost::VRDisplayHost(VRDeviceType aType) |
73 | | : mDisplayInfo{} |
74 | | , mLastUpdateDisplayInfo{} |
75 | | , mFrameStarted(false) |
76 | 0 | { |
77 | 0 | MOZ_COUNT_CTOR(VRDisplayHost); |
78 | 0 | mDisplayInfo.mType = aType; |
79 | 0 | mDisplayInfo.mDisplayID = VRSystemManager::AllocateDisplayID(); |
80 | 0 | mDisplayInfo.mPresentingGroups = 0; |
81 | 0 | mDisplayInfo.mGroupMask = kVRGroupContent; |
82 | 0 | mDisplayInfo.mFrameId = 0; |
83 | 0 | mDisplayInfo.mDisplayState.mPresentingGeneration = 0; |
84 | 0 | mDisplayInfo.mDisplayState.mDisplayName[0] = '\0'; |
85 | 0 |
|
86 | | #if defined(MOZ_WIDGET_ANDROID) |
87 | | mLastSubmittedFrameId = 0; |
88 | | mLastStartedFrame = 0; |
89 | | #endif // defined(MOZ_WIDGET_ANDROID) |
90 | | } |
91 | | |
92 | | VRDisplayHost::~VRDisplayHost() |
93 | 0 | { |
94 | 0 | if (mSubmitThread) { |
95 | 0 | mSubmitThread->Shutdown(); |
96 | 0 | mSubmitThread = nullptr; |
97 | 0 | } |
98 | 0 | MOZ_COUNT_DTOR(VRDisplayHost); |
99 | 0 | } |
100 | | |
101 | | #if defined(XP_WIN) |
102 | | bool |
103 | | VRDisplayHost::CreateD3DObjects() |
104 | | { |
105 | | if (!mDevice) { |
106 | | RefPtr<ID3D11Device> device = gfx::DeviceManagerDx::Get()->GetVRDevice(); |
107 | | if (!device) { |
108 | | NS_WARNING("VRDisplayHost::CreateD3DObjects failed to get a D3D11Device"); |
109 | | return false; |
110 | | } |
111 | | if (FAILED(device->QueryInterface(__uuidof(ID3D11Device1), getter_AddRefs(mDevice)))) { |
112 | | NS_WARNING("VRDisplayHost::CreateD3DObjects failed to get a D3D11Device1"); |
113 | | return false; |
114 | | } |
115 | | } |
116 | | if (!mContext) { |
117 | | mDevice->GetImmediateContext1(getter_AddRefs(mContext)); |
118 | | if (!mContext) { |
119 | | NS_WARNING("VRDisplayHost::CreateD3DObjects failed to get an immediate context"); |
120 | | return false; |
121 | | } |
122 | | } |
123 | | if (!mDeviceContextState) { |
124 | | D3D_FEATURE_LEVEL featureLevels[] { |
125 | | D3D_FEATURE_LEVEL_11_1, |
126 | | D3D_FEATURE_LEVEL_11_0 |
127 | | }; |
128 | | mDevice->CreateDeviceContextState(0, |
129 | | featureLevels, |
130 | | 2, |
131 | | D3D11_SDK_VERSION, |
132 | | __uuidof(ID3D11Device1), |
133 | | nullptr, |
134 | | getter_AddRefs(mDeviceContextState)); |
135 | | } |
136 | | if (!mDeviceContextState) { |
137 | | NS_WARNING("VRDisplayHost::CreateD3DObjects failed to get a D3D11DeviceContextState"); |
138 | | return false; |
139 | | } |
140 | | return true; |
141 | | } |
142 | | |
143 | | ID3D11Device1* |
144 | | VRDisplayHost::GetD3DDevice() |
145 | | { |
146 | | return mDevice; |
147 | | } |
148 | | |
149 | | ID3D11DeviceContext1* |
150 | | VRDisplayHost::GetD3DDeviceContext() |
151 | | { |
152 | | return mContext; |
153 | | } |
154 | | |
155 | | ID3DDeviceContextState* |
156 | | VRDisplayHost::GetD3DDeviceContextState() |
157 | | { |
158 | | return mDeviceContextState; |
159 | | } |
160 | | |
161 | | #endif // defined(XP_WIN) |
162 | | |
163 | | void |
164 | | VRDisplayHost::SetGroupMask(uint32_t aGroupMask) |
165 | 0 | { |
166 | 0 | mDisplayInfo.mGroupMask = aGroupMask; |
167 | 0 | } |
168 | | |
169 | | bool |
170 | | VRDisplayHost::GetIsConnected() |
171 | 0 | { |
172 | 0 | return mDisplayInfo.mDisplayState.mIsConnected; |
173 | 0 | } |
174 | | |
175 | | void |
176 | | VRDisplayHost::AddLayer(VRLayerParent *aLayer) |
177 | 0 | { |
178 | 0 | mLayers.AppendElement(aLayer); |
179 | 0 | mDisplayInfo.mPresentingGroups |= aLayer->GetGroup(); |
180 | 0 | if (mLayers.Length() == 1) { |
181 | 0 | StartPresentation(); |
182 | 0 | } |
183 | 0 |
|
184 | 0 | // Ensure that the content process receives the change immediately |
185 | 0 | VRManager* vm = VRManager::Get(); |
186 | 0 | vm->RefreshVRDisplays(); |
187 | 0 | } |
188 | | |
189 | | void |
190 | | VRDisplayHost::RemoveLayer(VRLayerParent *aLayer) |
191 | 0 | { |
192 | 0 | mLayers.RemoveElement(aLayer); |
193 | 0 | if (mLayers.Length() == 0) { |
194 | 0 | StopPresentation(); |
195 | 0 | } |
196 | 0 | mDisplayInfo.mPresentingGroups = 0; |
197 | 0 | for (auto layer : mLayers) { |
198 | 0 | mDisplayInfo.mPresentingGroups |= layer->GetGroup(); |
199 | 0 | } |
200 | 0 |
|
201 | 0 | // Ensure that the content process receives the change immediately |
202 | 0 | VRManager* vm = VRManager::Get(); |
203 | 0 | vm->RefreshVRDisplays(); |
204 | 0 | } |
205 | | |
206 | | void |
207 | | VRDisplayHost::StartFrame() |
208 | 0 | { |
209 | 0 | AUTO_PROFILER_TRACING("VR", "GetSensorState"); |
210 | 0 |
|
211 | | #if defined(MOZ_WIDGET_ANDROID) |
212 | | const bool isPresenting = mLastUpdateDisplayInfo.GetPresentingGroups() != 0; |
213 | | double duration = mLastFrameStart.IsNull() ? 0.0 : (TimeStamp::Now() - mLastFrameStart).ToMilliseconds(); |
214 | | /** |
215 | | * Do not start more VR frames until the last submitted frame is already processed. |
216 | | */ |
217 | | if (isPresenting && mLastStartedFrame > 0 && mDisplayInfo.mDisplayState.mLastSubmittedFrameId < mLastStartedFrame && duration < (double)ANDROID_MAX_FRAME_DURATION) { |
218 | | return; |
219 | | } |
220 | | #endif // !defined(MOZ_WIDGET_ANDROID) |
221 | |
|
222 | 0 | mLastFrameStart = TimeStamp::Now(); |
223 | 0 | ++mDisplayInfo.mFrameId; |
224 | 0 | mDisplayInfo.mLastSensorState[mDisplayInfo.mFrameId % kVRMaxLatencyFrames] = GetSensorState(); |
225 | 0 | mFrameStarted = true; |
226 | | #if defined(MOZ_WIDGET_ANDROID) |
227 | | mLastStartedFrame = mDisplayInfo.mFrameId; |
228 | | #endif // !defined(MOZ_WIDGET_ANDROID) |
229 | | } |
230 | | |
231 | | void |
232 | | VRDisplayHost::NotifyVSync() |
233 | 0 | { |
234 | 0 | /** |
235 | 0 | * We will trigger a new frame immediately after a successful frame texture |
236 | 0 | * submission. If content fails to call VRDisplay.submitFrame after |
237 | 0 | * dom.vr.display.rafMaxDuration milliseconds has elapsed since the last |
238 | 0 | * VRDisplay.requestAnimationFrame, we act as a "watchdog" and kick-off |
239 | 0 | * a new VRDisplay.requestAnimationFrame to avoid a render loop stall and |
240 | 0 | * to give content a chance to recover. |
241 | 0 | * |
242 | 0 | * If the lower level VR platform API's are rejecting submitted frames, |
243 | 0 | * such as when the Oculus "Health and Safety Warning" is displayed, |
244 | 0 | * we will not kick off the next frame immediately after VRDisplay.submitFrame |
245 | 0 | * as it would result in an unthrottled render loop that would free run at |
246 | 0 | * potentially extreme frame rates. To ensure that content has a chance to |
247 | 0 | * resume its presentation when the frames are accepted once again, we rely |
248 | 0 | * on this "watchdog" to act as a VR refresh driver cycling at a rate defined |
249 | 0 | * by dom.vr.display.rafMaxDuration. |
250 | 0 | * |
251 | 0 | * This number must be larger than the slowest expected frame time during |
252 | 0 | * normal VR presentation, but small enough not to break content that |
253 | 0 | * makes assumptions of reasonably minimal VSync rate. |
254 | 0 | * |
255 | 0 | * The slowest expected refresh rate for a VR display currently is an |
256 | 0 | * Oculus CV1 when ASW (Asynchronous Space Warp) is enabled, at 45hz. |
257 | 0 | * A dom.vr.display.rafMaxDuration value of 50 milliseconds results in a 20hz |
258 | 0 | * rate, which avoids inadvertent triggering of the watchdog during |
259 | 0 | * Oculus ASW even if every second frame is dropped. |
260 | 0 | */ |
261 | 0 | bool bShouldStartFrame = false; |
262 | 0 |
|
263 | 0 | if (mDisplayInfo.mPresentingGroups == 0) { |
264 | 0 | // If this display isn't presenting, refresh the sensors and trigger |
265 | 0 | // VRDisplay.requestAnimationFrame at the normal 2d display refresh rate. |
266 | 0 | bShouldStartFrame = true; |
267 | 0 | } else { |
268 | 0 | // If content fails to call VRDisplay.submitFrame, we must eventually |
269 | 0 | // time-out and trigger a new frame. |
270 | 0 | if (mLastFrameStart.IsNull()) { |
271 | 0 | bShouldStartFrame = true; |
272 | 0 | } else { |
273 | 0 | TimeDuration duration = TimeStamp::Now() - mLastFrameStart; |
274 | 0 | if (duration.ToMilliseconds() > gfxPrefs::VRDisplayRafMaxDuration()) { |
275 | 0 | bShouldStartFrame = true; |
276 | 0 | } |
277 | 0 | } |
278 | 0 | } |
279 | 0 |
|
280 | 0 | if (bShouldStartFrame) { |
281 | 0 | VRManager *vm = VRManager::Get(); |
282 | 0 | MOZ_ASSERT(vm); |
283 | 0 | vm->NotifyVRVsync(mDisplayInfo.mDisplayID); |
284 | 0 | } |
285 | 0 | } |
286 | | |
287 | | void |
288 | | VRDisplayHost::SubmitFrameInternal(const layers::SurfaceDescriptor &aTexture, |
289 | | uint64_t aFrameId, |
290 | | const gfx::Rect& aLeftEyeRect, |
291 | | const gfx::Rect& aRightEyeRect) |
292 | 0 | { |
293 | 0 | #if !defined(MOZ_WIDGET_ANDROID) |
294 | 0 | MOZ_ASSERT(mSubmitThread->GetThread() == NS_GetCurrentThread()); |
295 | 0 | #endif // !defined(MOZ_WIDGET_ANDROID) |
296 | 0 | AUTO_PROFILER_TRACING("VR", "SubmitFrameAtVRDisplayHost"); |
297 | 0 |
|
298 | 0 | if (!SubmitFrame(aTexture, aFrameId, aLeftEyeRect, aRightEyeRect)) { |
299 | 0 | return; |
300 | 0 | } |
301 | 0 |
|
302 | | #if defined(XP_WIN) || defined(XP_MACOSX) || defined(MOZ_WIDGET_ANDROID) |
303 | | |
304 | | /** |
305 | | * Trigger the next VSync immediately after we are successfully |
306 | | * submitting frames. As SubmitFrame is responsible for throttling |
307 | | * the render loop, if we don't successfully call it, we shouldn't trigger |
308 | | * NotifyVRVsync immediately, as it will run unbounded. |
309 | | * If NotifyVRVsync is not called here due to SubmitFrame failing, the |
310 | | * fallback "watchdog" code in VRDisplayHost::NotifyVSync() will cause |
311 | | * frames to continue at a lower refresh rate until frame submission |
312 | | * succeeds again. |
313 | | */ |
314 | | VRManager* vm = VRManager::Get(); |
315 | | MessageLoop* loop = VRListenerThreadHolder::Loop(); |
316 | | |
317 | | loop->PostTask(NewRunnableMethod<const uint32_t>( |
318 | | "gfx::VRManager::NotifyVRVsync", |
319 | | vm, &VRManager::NotifyVRVsync, mDisplayInfo.mDisplayID |
320 | | )); |
321 | | #endif |
322 | | } |
323 | | |
324 | | void |
325 | | VRDisplayHost::SubmitFrame(VRLayerParent* aLayer, |
326 | | const layers::SurfaceDescriptor &aTexture, |
327 | | uint64_t aFrameId, |
328 | | const gfx::Rect& aLeftEyeRect, |
329 | | const gfx::Rect& aRightEyeRect) |
330 | 0 | { |
331 | 0 | if ((mDisplayInfo.mGroupMask & aLayer->GetGroup()) == 0) { |
332 | 0 | // Suppress layers hidden by the group mask |
333 | 0 | return; |
334 | 0 | } |
335 | 0 | |
336 | 0 | // Ensure that we only accept the first SubmitFrame call per RAF cycle. |
337 | 0 | if (!mFrameStarted || aFrameId != mDisplayInfo.mFrameId) { |
338 | 0 | return; |
339 | 0 | } |
340 | 0 | |
341 | | #if defined(MOZ_WIDGET_ANDROID) |
342 | | /** |
343 | | * Do not queue more submit frames until the last submitted frame is already processed |
344 | | * and the new WebGL texture is ready. |
345 | | */ |
346 | | if (mLastSubmittedFrameId > 0 && mLastSubmittedFrameId != mDisplayInfo.mDisplayState.mLastSubmittedFrameId) { |
347 | | mLastStartedFrame = 0; |
348 | | return; |
349 | | } |
350 | | |
351 | | mLastSubmittedFrameId = aFrameId; |
352 | | #endif // !defined(MOZ_WIDGET_ANDROID) |
353 | | |
354 | 0 | mFrameStarted = false; |
355 | 0 |
|
356 | 0 | RefPtr<Runnable> submit = |
357 | 0 | NewRunnableMethod<StoreCopyPassByConstLRef<layers::SurfaceDescriptor>, uint64_t, |
358 | 0 | StoreCopyPassByConstLRef<gfx::Rect>, StoreCopyPassByConstLRef<gfx::Rect>>( |
359 | 0 | "gfx::VRDisplayHost::SubmitFrameInternal", this, &VRDisplayHost::SubmitFrameInternal, |
360 | 0 | aTexture, aFrameId, aLeftEyeRect, aRightEyeRect); |
361 | 0 |
|
362 | 0 | #if !defined(MOZ_WIDGET_ANDROID) |
363 | 0 | if (!mSubmitThread) { |
364 | 0 | mSubmitThread = new VRThread(NS_LITERAL_CSTRING("VR_SubmitFrame")); |
365 | 0 | } |
366 | 0 | mSubmitThread->Start(); |
367 | 0 | mSubmitThread->PostTask(submit.forget()); |
368 | | #else |
369 | | CompositorThreadHolder::Loop()->PostTask(submit.forget()); |
370 | | #endif // defined(MOZ_WIDGET_ANDROID) |
371 | | } |
372 | | |
373 | | bool |
374 | | VRDisplayHost::CheckClearDisplayInfoDirty() |
375 | 0 | { |
376 | 0 | if (mDisplayInfo == mLastUpdateDisplayInfo) { |
377 | 0 | return false; |
378 | 0 | } |
379 | 0 | mLastUpdateDisplayInfo = mDisplayInfo; |
380 | 0 | return true; |
381 | 0 | } |
382 | | |
383 | | void |
384 | | VRDisplayHost::StartVRNavigation() |
385 | 0 | { |
386 | 0 | |
387 | 0 | } |
388 | | |
389 | | void |
390 | | VRDisplayHost::StopVRNavigation(const TimeDuration& aTimeout) |
391 | 0 | { |
392 | 0 | |
393 | 0 | } |
394 | | |
395 | | VRControllerHost::VRControllerHost(VRDeviceType aType, dom::GamepadHand aHand, |
396 | | uint32_t aDisplayID) |
397 | | : mControllerInfo{} |
398 | | , mVibrateIndex(0) |
399 | 0 | { |
400 | 0 | MOZ_COUNT_CTOR(VRControllerHost); |
401 | 0 | mControllerInfo.mType = aType; |
402 | 0 | mControllerInfo.mControllerState.hand = aHand; |
403 | 0 | mControllerInfo.mMappingType = dom::GamepadMappingType::_empty; |
404 | 0 | mControllerInfo.mDisplayID = aDisplayID; |
405 | 0 | mControllerInfo.mControllerID = VRSystemManager::AllocateControllerID(); |
406 | 0 | } |
407 | | |
408 | | VRControllerHost::~VRControllerHost() |
409 | 0 | { |
410 | 0 | MOZ_COUNT_DTOR(VRControllerHost); |
411 | 0 | } |
412 | | |
413 | | const VRControllerInfo& |
414 | | VRControllerHost::GetControllerInfo() const |
415 | 0 | { |
416 | 0 | return mControllerInfo; |
417 | 0 | } |
418 | | |
419 | | void |
420 | | VRControllerHost::SetButtonPressed(uint64_t aBit) |
421 | 0 | { |
422 | 0 | mControllerInfo.mControllerState.buttonPressed = aBit; |
423 | 0 | } |
424 | | |
425 | | uint64_t |
426 | | VRControllerHost::GetButtonPressed() |
427 | 0 | { |
428 | 0 | return mControllerInfo.mControllerState.buttonPressed; |
429 | 0 | } |
430 | | |
431 | | void |
432 | | VRControllerHost::SetButtonTouched(uint64_t aBit) |
433 | 0 | { |
434 | 0 | mControllerInfo.mControllerState.buttonTouched = aBit; |
435 | 0 | } |
436 | | |
437 | | uint64_t |
438 | | VRControllerHost::GetButtonTouched() |
439 | 0 | { |
440 | 0 | return mControllerInfo.mControllerState.buttonTouched; |
441 | 0 | } |
442 | | |
443 | | void |
444 | | VRControllerHost::SetPose(const dom::GamepadPoseState& aPose) |
445 | 0 | { |
446 | 0 | mPose = aPose; |
447 | 0 | } |
448 | | |
449 | | const dom::GamepadPoseState& |
450 | | VRControllerHost::GetPose() |
451 | 0 | { |
452 | 0 | return mPose; |
453 | 0 | } |
454 | | |
455 | | dom::GamepadHand |
456 | | VRControllerHost::GetHand() |
457 | 0 | { |
458 | 0 | return mControllerInfo.mControllerState.hand; |
459 | 0 | } |
460 | | |
461 | | void |
462 | | VRControllerHost::SetVibrateIndex(uint64_t aIndex) |
463 | 0 | { |
464 | 0 | mVibrateIndex = aIndex; |
465 | 0 | } |
466 | | |
467 | | uint64_t |
468 | | VRControllerHost::GetVibrateIndex() |
469 | 0 | { |
470 | 0 | return mVibrateIndex; |
471 | 0 | } |
472 | | |