зеркало из https://github.com/mozilla/gecko-dev.git
454 строки
15 KiB
C++
454 строки
15 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
|
/* 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 "VRDisplayHost.h"
|
|
#include "VRThread.h"
|
|
#include "gfxVR.h"
|
|
#include "ipc/VRLayerParent.h"
|
|
#include "mozilla/StaticPrefs.h"
|
|
#include "mozilla/dom/GamepadBinding.h" // For GamepadMappingType
|
|
#include "mozilla/layers/CompositorThread.h" // for CompositorThreadHolder
|
|
#include "mozilla/layers/TextureHost.h"
|
|
|
|
#if defined(XP_WIN)
|
|
|
|
# include <d3d11.h>
|
|
# include "gfxWindowsPlatform.h"
|
|
# include "../layers/d3d11/CompositorD3D11.h"
|
|
# include "mozilla/gfx/DeviceManagerDx.h"
|
|
# include "mozilla/layers/TextureD3D11.h"
|
|
|
|
#elif defined(XP_MACOSX)
|
|
|
|
# include "mozilla/gfx/MacIOSurface.h"
|
|
|
|
#endif
|
|
|
|
#if defined(MOZ_WIDGET_ANDROID)
|
|
# include "mozilla/layers/CompositorThread.h"
|
|
// Max frame duration on Android before the watchdog submits a new one.
|
|
// Probably we can get rid of this when we enforce that SubmitFrame can only be
|
|
// called in a VRDisplay loop.
|
|
# define ANDROID_MAX_FRAME_DURATION 4000
|
|
#endif // defined(MOZ_WIDGET_ANDROID)
|
|
|
|
using namespace mozilla;
|
|
using namespace mozilla::gfx;
|
|
using namespace mozilla::layers;
|
|
|
|
VRDisplayHost::AutoRestoreRenderState::AutoRestoreRenderState(
|
|
VRDisplayHost* aDisplay)
|
|
: mDisplay(aDisplay), mSuccess(true) {
|
|
#if defined(XP_WIN)
|
|
ID3D11DeviceContext1* context = mDisplay->GetD3DDeviceContext();
|
|
ID3DDeviceContextState* state = mDisplay->GetD3DDeviceContextState();
|
|
if (!context || !state) {
|
|
mSuccess = false;
|
|
return;
|
|
}
|
|
context->SwapDeviceContextState(state,
|
|
getter_AddRefs(mPrevDeviceContextState));
|
|
#endif
|
|
}
|
|
|
|
VRDisplayHost::AutoRestoreRenderState::~AutoRestoreRenderState() {
|
|
#if defined(XP_WIN)
|
|
ID3D11DeviceContext1* context = mDisplay->GetD3DDeviceContext();
|
|
if (context && mSuccess) {
|
|
context->SwapDeviceContextState(mPrevDeviceContextState, nullptr);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
bool VRDisplayHost::AutoRestoreRenderState::IsSuccess() { return mSuccess; }
|
|
|
|
VRDisplayHost::VRDisplayHost(VRDeviceType aType)
|
|
: mDisplayInfo{},
|
|
mLastUpdateDisplayInfo{},
|
|
mCurrentSubmitTaskMonitor("CurrentSubmitTaskMonitor"),
|
|
mCurrentSubmitTask(nullptr),
|
|
mFrameStarted(false) {
|
|
MOZ_COUNT_CTOR(VRDisplayHost);
|
|
mDisplayInfo.mType = aType;
|
|
mDisplayInfo.mDisplayID = VRSystemManager::AllocateDisplayID();
|
|
mDisplayInfo.mPresentingGroups = 0;
|
|
mDisplayInfo.mGroupMask = kVRGroupContent;
|
|
mDisplayInfo.mFrameId = 0;
|
|
mDisplayInfo.mDisplayState.presentingGeneration = 0;
|
|
mDisplayInfo.mDisplayState.displayName[0] = '\0';
|
|
|
|
#if defined(MOZ_WIDGET_ANDROID)
|
|
mLastSubmittedFrameId = 0;
|
|
mLastStartedFrame = 0;
|
|
#endif // defined(MOZ_WIDGET_ANDROID)
|
|
}
|
|
|
|
VRDisplayHost::~VRDisplayHost() {
|
|
CancelCurrentSubmitTask();
|
|
|
|
if (mSubmitThread) {
|
|
mSubmitThread->Shutdown();
|
|
mSubmitThread = nullptr;
|
|
}
|
|
MOZ_COUNT_DTOR(VRDisplayHost);
|
|
}
|
|
|
|
void VRDisplayHost::ShutdownSubmitThread() {
|
|
if (mSubmitThread) {
|
|
mSubmitThread->Shutdown();
|
|
mSubmitThread = nullptr;
|
|
}
|
|
}
|
|
|
|
#if defined(XP_WIN)
|
|
bool VRDisplayHost::CreateD3DObjects() {
|
|
if (!mDevice) {
|
|
RefPtr<ID3D11Device> device = gfx::DeviceManagerDx::Get()->GetVRDevice();
|
|
if (!device) {
|
|
NS_WARNING("VRDisplayHost::CreateD3DObjects failed to get a D3D11Device");
|
|
return false;
|
|
}
|
|
if (FAILED(device->QueryInterface(__uuidof(ID3D11Device1),
|
|
getter_AddRefs(mDevice)))) {
|
|
NS_WARNING(
|
|
"VRDisplayHost::CreateD3DObjects failed to get a D3D11Device1");
|
|
return false;
|
|
}
|
|
}
|
|
if (!mContext) {
|
|
mDevice->GetImmediateContext1(getter_AddRefs(mContext));
|
|
if (!mContext) {
|
|
NS_WARNING(
|
|
"VRDisplayHost::CreateD3DObjects failed to get an immediate context");
|
|
return false;
|
|
}
|
|
}
|
|
if (!mDeviceContextState) {
|
|
D3D_FEATURE_LEVEL featureLevels[]{D3D_FEATURE_LEVEL_11_1,
|
|
D3D_FEATURE_LEVEL_11_0};
|
|
mDevice->CreateDeviceContextState(0, featureLevels, 2, D3D11_SDK_VERSION,
|
|
__uuidof(ID3D11Device1), nullptr,
|
|
getter_AddRefs(mDeviceContextState));
|
|
}
|
|
if (!mDeviceContextState) {
|
|
NS_WARNING(
|
|
"VRDisplayHost::CreateD3DObjects failed to get a "
|
|
"D3D11DeviceContextState");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
ID3D11Device1* VRDisplayHost::GetD3DDevice() { return mDevice; }
|
|
|
|
ID3D11DeviceContext1* VRDisplayHost::GetD3DDeviceContext() { return mContext; }
|
|
|
|
ID3DDeviceContextState* VRDisplayHost::GetD3DDeviceContextState() {
|
|
return mDeviceContextState;
|
|
}
|
|
|
|
#endif // defined(XP_WIN)
|
|
|
|
void VRDisplayHost::SetGroupMask(uint32_t aGroupMask) {
|
|
mDisplayInfo.mGroupMask = aGroupMask;
|
|
}
|
|
|
|
bool VRDisplayHost::GetIsConnected() {
|
|
return mDisplayInfo.mDisplayState.isConnected;
|
|
}
|
|
|
|
void VRDisplayHost::AddLayer(VRLayerParent* aLayer) {
|
|
mLayers.AppendElement(aLayer);
|
|
mDisplayInfo.mPresentingGroups |= aLayer->GetGroup();
|
|
if (mLayers.Length() == 1) {
|
|
StartPresentation();
|
|
}
|
|
|
|
// Ensure that the content process receives the change immediately
|
|
VRManager* vm = VRManager::Get();
|
|
vm->RefreshVRDisplays();
|
|
}
|
|
|
|
void VRDisplayHost::RemoveLayer(VRLayerParent* aLayer) {
|
|
mLayers.RemoveElement(aLayer);
|
|
if (mLayers.Length() == 0) {
|
|
StopPresentation();
|
|
}
|
|
mDisplayInfo.mPresentingGroups = 0;
|
|
for (auto layer : mLayers) {
|
|
mDisplayInfo.mPresentingGroups |= layer->GetGroup();
|
|
}
|
|
|
|
// Ensure that the content process receives the change immediately
|
|
VRManager* vm = VRManager::Get();
|
|
vm->RefreshVRDisplays();
|
|
}
|
|
|
|
void VRDisplayHost::StartFrame() {
|
|
AUTO_PROFILER_TRACING("VR", "GetSensorState", OTHER);
|
|
|
|
TimeStamp now = TimeStamp::Now();
|
|
#if defined(MOZ_WIDGET_ANDROID)
|
|
const TimeStamp lastFrameStart =
|
|
mLastFrameStart[mDisplayInfo.mFrameId % kVRMaxLatencyFrames];
|
|
const bool isPresenting = mLastUpdateDisplayInfo.GetPresentingGroups() != 0;
|
|
double duration =
|
|
lastFrameStart.IsNull() ? 0.0 : (now - lastFrameStart).ToMilliseconds();
|
|
/**
|
|
* Do not start more VR frames until the last submitted frame is already
|
|
* processed.
|
|
*/
|
|
if (isPresenting && mLastStartedFrame > 0 &&
|
|
mDisplayInfo.mDisplayState.lastSubmittedFrameId < mLastStartedFrame &&
|
|
duration < (double)ANDROID_MAX_FRAME_DURATION) {
|
|
return;
|
|
}
|
|
#endif // !defined(MOZ_WIDGET_ANDROID)
|
|
|
|
++mDisplayInfo.mFrameId;
|
|
size_t bufferIndex = mDisplayInfo.mFrameId % kVRMaxLatencyFrames;
|
|
mDisplayInfo.mLastSensorState[bufferIndex] = GetSensorState();
|
|
mLastFrameStart[bufferIndex] = now;
|
|
mFrameStarted = true;
|
|
#if defined(MOZ_WIDGET_ANDROID)
|
|
mLastStartedFrame = mDisplayInfo.mFrameId;
|
|
#endif // !defined(MOZ_WIDGET_ANDROID)
|
|
}
|
|
|
|
void VRDisplayHost::NotifyVSync() {
|
|
/**
|
|
* If this display isn't presenting, refresh the sensors and trigger
|
|
* VRDisplay.requestAnimationFrame at the normal 2d display refresh rate.
|
|
*/
|
|
if (mDisplayInfo.mPresentingGroups == 0) {
|
|
VRManager* vm = VRManager::Get();
|
|
MOZ_ASSERT(vm);
|
|
vm->NotifyVRVsync(mDisplayInfo.mDisplayID);
|
|
}
|
|
}
|
|
|
|
void VRDisplayHost::CheckWatchDog() {
|
|
/**
|
|
* We will trigger a new frame immediately after a successful frame texture
|
|
* submission. If content fails to call VRDisplay.submitFrame after
|
|
* dom.vr.display.rafMaxDuration milliseconds has elapsed since the last
|
|
* VRDisplay.requestAnimationFrame, we act as a "watchdog" and kick-off
|
|
* a new VRDisplay.requestAnimationFrame to avoid a render loop stall and
|
|
* to give content a chance to recover.
|
|
*
|
|
* If the lower level VR platform API's are rejecting submitted frames,
|
|
* such as when the Oculus "Health and Safety Warning" is displayed,
|
|
* we will not kick off the next frame immediately after VRDisplay.submitFrame
|
|
* as it would result in an unthrottled render loop that would free run at
|
|
* potentially extreme frame rates. To ensure that content has a chance to
|
|
* resume its presentation when the frames are accepted once again, we rely
|
|
* on this "watchdog" to act as a VR refresh driver cycling at a rate defined
|
|
* by dom.vr.display.rafMaxDuration.
|
|
*
|
|
* This number must be larger than the slowest expected frame time during
|
|
* normal VR presentation, but small enough not to break content that
|
|
* makes assumptions of reasonably minimal VSync rate.
|
|
*
|
|
* The slowest expected refresh rate for a VR display currently is an
|
|
* Oculus CV1 when ASW (Asynchronous Space Warp) is enabled, at 45hz.
|
|
* A dom.vr.display.rafMaxDuration value of 50 milliseconds results in a 20hz
|
|
* rate, which avoids inadvertent triggering of the watchdog during
|
|
* Oculus ASW even if every second frame is dropped.
|
|
*/
|
|
bool bShouldStartFrame = false;
|
|
|
|
// If content fails to call VRDisplay.submitFrame, we must eventually
|
|
// time-out and trigger a new frame.
|
|
TimeStamp lastFrameStart =
|
|
mLastFrameStart[mDisplayInfo.mFrameId % kVRMaxLatencyFrames];
|
|
if (lastFrameStart.IsNull()) {
|
|
bShouldStartFrame = true;
|
|
} else {
|
|
TimeDuration duration = TimeStamp::Now() - lastFrameStart;
|
|
if (duration.ToMilliseconds() > StaticPrefs::VRDisplayRafMaxDuration()) {
|
|
bShouldStartFrame = true;
|
|
}
|
|
}
|
|
|
|
if (bShouldStartFrame) {
|
|
VRManager* vm = VRManager::Get();
|
|
MOZ_ASSERT(vm);
|
|
vm->NotifyVRVsync(mDisplayInfo.mDisplayID);
|
|
}
|
|
}
|
|
|
|
void VRDisplayHost::Run1msTasks(double aDeltaTime) {
|
|
// To override in children
|
|
}
|
|
|
|
void VRDisplayHost::Run10msTasks() { CheckWatchDog(); }
|
|
|
|
void VRDisplayHost::Run100msTasks() {
|
|
// to override in children
|
|
}
|
|
|
|
void VRDisplayHost::SubmitFrameInternal(
|
|
const layers::SurfaceDescriptor& aTexture, uint64_t aFrameId,
|
|
const gfx::Rect& aLeftEyeRect, const gfx::Rect& aRightEyeRect) {
|
|
#if !defined(MOZ_WIDGET_ANDROID)
|
|
MOZ_ASSERT(mSubmitThread->GetThread() == NS_GetCurrentThread());
|
|
#endif // !defined(MOZ_WIDGET_ANDROID)
|
|
AUTO_PROFILER_TRACING("VR", "SubmitFrameAtVRDisplayHost", OTHER);
|
|
|
|
{ // scope lock
|
|
MonitorAutoLock lock(mCurrentSubmitTaskMonitor);
|
|
|
|
if (!SubmitFrame(aTexture, aFrameId, aLeftEyeRect, aRightEyeRect)) {
|
|
mCurrentSubmitTask = nullptr;
|
|
return;
|
|
}
|
|
mCurrentSubmitTask = nullptr;
|
|
}
|
|
|
|
#if defined(XP_WIN) || defined(XP_MACOSX) || defined(MOZ_WIDGET_ANDROID)
|
|
|
|
/**
|
|
* Trigger the next VSync immediately after we are successfully
|
|
* submitting frames. As SubmitFrame is responsible for throttling
|
|
* the render loop, if we don't successfully call it, we shouldn't trigger
|
|
* NotifyVRVsync immediately, as it will run unbounded.
|
|
* If NotifyVRVsync is not called here due to SubmitFrame failing, the
|
|
* fallback "watchdog" code in VRDisplayHost::NotifyVSync() will cause
|
|
* frames to continue at a lower refresh rate until frame submission
|
|
* succeeds again.
|
|
*/
|
|
VRManager* vm = VRManager::Get();
|
|
MessageLoop* loop = CompositorThreadHolder::Loop();
|
|
|
|
loop->PostTask(NewRunnableMethod<const uint32_t>(
|
|
"gfx::VRManager::NotifyVRVsync", vm, &VRManager::NotifyVRVsync,
|
|
mDisplayInfo.mDisplayID));
|
|
#endif
|
|
}
|
|
|
|
void VRDisplayHost::SubmitFrame(VRLayerParent* aLayer,
|
|
const layers::SurfaceDescriptor& aTexture,
|
|
uint64_t aFrameId,
|
|
const gfx::Rect& aLeftEyeRect,
|
|
const gfx::Rect& aRightEyeRect) {
|
|
MonitorAutoLock lock(mCurrentSubmitTaskMonitor);
|
|
if ((mDisplayInfo.mGroupMask & aLayer->GetGroup()) == 0) {
|
|
// Suppress layers hidden by the group mask
|
|
return;
|
|
}
|
|
|
|
// Ensure that we only accept the first SubmitFrame call per RAF cycle.
|
|
if (!mFrameStarted || aFrameId != mDisplayInfo.mFrameId) {
|
|
return;
|
|
}
|
|
|
|
#if defined(MOZ_WIDGET_ANDROID)
|
|
/**
|
|
* Do not queue more submit frames until the last submitted frame is already
|
|
* processed and the new WebGL texture is ready.
|
|
*/
|
|
if (mLastSubmittedFrameId > 0 &&
|
|
mLastSubmittedFrameId !=
|
|
mDisplayInfo.mDisplayState.lastSubmittedFrameId) {
|
|
mLastStartedFrame = 0;
|
|
return;
|
|
}
|
|
|
|
mLastSubmittedFrameId = aFrameId;
|
|
#endif // !defined(MOZ_WIDGET_ANDROID)
|
|
|
|
mFrameStarted = false;
|
|
|
|
RefPtr<CancelableRunnable> task = NewCancelableRunnableMethod<
|
|
StoreCopyPassByConstLRef<layers::SurfaceDescriptor>, uint64_t,
|
|
StoreCopyPassByConstLRef<gfx::Rect>, StoreCopyPassByConstLRef<gfx::Rect>>(
|
|
"gfx::VRDisplayHost::SubmitFrameInternal", this,
|
|
&VRDisplayHost::SubmitFrameInternal, aTexture, aFrameId, aLeftEyeRect,
|
|
aRightEyeRect);
|
|
|
|
if (!mCurrentSubmitTask) {
|
|
mCurrentSubmitTask = task;
|
|
#if !defined(MOZ_WIDGET_ANDROID)
|
|
if (!mSubmitThread) {
|
|
mSubmitThread = new VRThread(NS_LITERAL_CSTRING("VR_SubmitFrame"));
|
|
}
|
|
mSubmitThread->Start();
|
|
mSubmitThread->PostTask(task.forget());
|
|
#else
|
|
CompositorThreadHolder::Loop()->PostTask(task.forget());
|
|
#endif // defined(MOZ_WIDGET_ANDROID)
|
|
}
|
|
}
|
|
|
|
void VRDisplayHost::CancelCurrentSubmitTask() {
|
|
MonitorAutoLock lock(mCurrentSubmitTaskMonitor);
|
|
if (mCurrentSubmitTask) {
|
|
mCurrentSubmitTask->Cancel();
|
|
mCurrentSubmitTask = nullptr;
|
|
}
|
|
}
|
|
|
|
bool VRDisplayHost::CheckClearDisplayInfoDirty() {
|
|
if (mDisplayInfo == mLastUpdateDisplayInfo) {
|
|
return false;
|
|
}
|
|
mLastUpdateDisplayInfo = mDisplayInfo;
|
|
return true;
|
|
}
|
|
|
|
void VRDisplayHost::StartVRNavigation() {}
|
|
|
|
void VRDisplayHost::StopVRNavigation(const TimeDuration& aTimeout) {}
|
|
|
|
VRControllerHost::VRControllerHost(VRDeviceType aType, dom::GamepadHand aHand,
|
|
uint32_t aDisplayID)
|
|
: mControllerInfo{}, mVibrateIndex(0) {
|
|
MOZ_COUNT_CTOR(VRControllerHost);
|
|
mControllerInfo.mType = aType;
|
|
mControllerInfo.mControllerState.hand = aHand;
|
|
mControllerInfo.mMappingType = dom::GamepadMappingType::_empty;
|
|
mControllerInfo.mDisplayID = aDisplayID;
|
|
mControllerInfo.mControllerID = VRSystemManager::AllocateControllerID();
|
|
}
|
|
|
|
VRControllerHost::~VRControllerHost() { MOZ_COUNT_DTOR(VRControllerHost); }
|
|
|
|
const VRControllerInfo& VRControllerHost::GetControllerInfo() const {
|
|
return mControllerInfo;
|
|
}
|
|
|
|
void VRControllerHost::SetButtonPressed(uint64_t aBit) {
|
|
mControllerInfo.mControllerState.buttonPressed = aBit;
|
|
}
|
|
|
|
uint64_t VRControllerHost::GetButtonPressed() {
|
|
return mControllerInfo.mControllerState.buttonPressed;
|
|
}
|
|
|
|
void VRControllerHost::SetButtonTouched(uint64_t aBit) {
|
|
mControllerInfo.mControllerState.buttonTouched = aBit;
|
|
}
|
|
|
|
uint64_t VRControllerHost::GetButtonTouched() {
|
|
return mControllerInfo.mControllerState.buttonTouched;
|
|
}
|
|
|
|
void VRControllerHost::SetPose(const dom::GamepadPoseState& aPose) {
|
|
mPose = aPose;
|
|
}
|
|
|
|
const dom::GamepadPoseState& VRControllerHost::GetPose() { return mPose; }
|
|
|
|
dom::GamepadHand VRControllerHost::GetHand() {
|
|
return mControllerInfo.mControllerState.hand;
|
|
}
|
|
|
|
void VRControllerHost::SetVibrateIndex(uint64_t aIndex) {
|
|
mVibrateIndex = aIndex;
|
|
}
|
|
|
|
uint64_t VRControllerHost::GetVibrateIndex() { return mVibrateIndex; }
|