зеркало из https://github.com/mozilla/gecko-dev.git
500 строки
14 KiB
C++
500 строки
14 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 "gfxPrefs.h"
|
|
#include "gfxVR.h"
|
|
#include "ipc/VRLayerParent.h"
|
|
#include "mozilla/layers/TextureHost.h"
|
|
#include "mozilla/dom/GamepadBinding.h" // For GamepadMappingType
|
|
#include "VRThread.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_ANDROID_GOOGLE_VR)
|
|
#include "mozilla/layers/CompositorThread.h"
|
|
#endif // defined(MOZ_ANDROID_GOOGLE_VR)
|
|
|
|
|
|
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{}
|
|
, mFrameStarted(false)
|
|
{
|
|
MOZ_COUNT_CTOR(VRDisplayHost);
|
|
mDisplayInfo.mType = aType;
|
|
mDisplayInfo.mDisplayID = VRSystemManager::AllocateDisplayID();
|
|
mDisplayInfo.mPresentingGroups = 0;
|
|
mDisplayInfo.mGroupMask = kVRGroupContent;
|
|
mDisplayInfo.mFrameId = 0;
|
|
mDisplayInfo.mPresentingGeneration = 0;
|
|
mDisplayInfo.mDisplayState.mDisplayName[0] = '\0';
|
|
}
|
|
|
|
VRDisplayHost::~VRDisplayHost()
|
|
{
|
|
if (mSubmitThread) {
|
|
mSubmitThread->Shutdown();
|
|
mSubmitThread = nullptr;
|
|
}
|
|
MOZ_COUNT_DTOR(VRDisplayHost);
|
|
}
|
|
|
|
#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.mIsConnected;
|
|
}
|
|
|
|
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");
|
|
|
|
mLastFrameStart = TimeStamp::Now();
|
|
++mDisplayInfo.mFrameId;
|
|
mDisplayInfo.mLastSensorState[mDisplayInfo.mFrameId % kVRMaxLatencyFrames] = GetSensorState();
|
|
mFrameStarted = true;
|
|
}
|
|
|
|
void
|
|
VRDisplayHost::NotifyVSync()
|
|
{
|
|
/**
|
|
* 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 (mDisplayInfo.mPresentingGroups == 0) {
|
|
// If this display isn't presenting, refresh the sensors and trigger
|
|
// VRDisplay.requestAnimationFrame at the normal 2d display refresh rate.
|
|
bShouldStartFrame = true;
|
|
} else {
|
|
// If content fails to call VRDisplay.submitFrame, we must eventually
|
|
// time-out and trigger a new frame.
|
|
if (mLastFrameStart.IsNull()) {
|
|
bShouldStartFrame = true;
|
|
} else {
|
|
TimeDuration duration = TimeStamp::Now() - mLastFrameStart;
|
|
if (duration.ToMilliseconds() > gfxPrefs::VRDisplayRafMaxDuration()) {
|
|
bShouldStartFrame = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bShouldStartFrame) {
|
|
VRManager *vm = VRManager::Get();
|
|
MOZ_ASSERT(vm);
|
|
vm->NotifyVRVsync(mDisplayInfo.mDisplayID);
|
|
}
|
|
}
|
|
|
|
void
|
|
VRDisplayHost::SubmitFrameInternal(const layers::SurfaceDescriptor &aTexture,
|
|
uint64_t aFrameId,
|
|
const gfx::Rect& aLeftEyeRect,
|
|
const gfx::Rect& aRightEyeRect)
|
|
{
|
|
#if !defined(MOZ_ANDROID_GOOGLE_VR)
|
|
MOZ_ASSERT(mSubmitThread->GetThread() == NS_GetCurrentThread());
|
|
#endif // !defined(MOZ_ANDROID_GOOGLE_VR)
|
|
AUTO_PROFILER_TRACING("VR", "SubmitFrameAtVRDisplayHost");
|
|
|
|
mFrameStarted = false;
|
|
switch (aTexture.type()) {
|
|
|
|
#if defined(XP_WIN)
|
|
case SurfaceDescriptor::TSurfaceDescriptorD3D10: {
|
|
if (!CreateD3DObjects()) {
|
|
return;
|
|
}
|
|
const SurfaceDescriptorD3D10& surf = aTexture.get_SurfaceDescriptorD3D10();
|
|
RefPtr<ID3D11Texture2D> dxTexture;
|
|
HRESULT hr = mDevice->OpenSharedResource((HANDLE)surf.handle(),
|
|
__uuidof(ID3D11Texture2D),
|
|
(void**)(ID3D11Texture2D**)getter_AddRefs(dxTexture));
|
|
if (FAILED(hr) || !dxTexture) {
|
|
NS_WARNING("Failed to open shared texture");
|
|
return;
|
|
}
|
|
|
|
// Similar to LockD3DTexture in TextureD3D11.cpp
|
|
RefPtr<IDXGIKeyedMutex> mutex;
|
|
dxTexture->QueryInterface((IDXGIKeyedMutex**)getter_AddRefs(mutex));
|
|
if (mutex) {
|
|
HRESULT hr = mutex->AcquireSync(0, 1000);
|
|
if (hr == WAIT_TIMEOUT) {
|
|
gfxDevCrash(LogReason::D3DLockTimeout) << "D3D lock mutex timeout";
|
|
}
|
|
else if (hr == WAIT_ABANDONED) {
|
|
gfxCriticalNote << "GFX: D3D11 lock mutex abandoned";
|
|
}
|
|
if (FAILED(hr)) {
|
|
NS_WARNING("Failed to lock the texture");
|
|
return;
|
|
}
|
|
}
|
|
bool success = SubmitFrame(dxTexture, surf.size(),
|
|
aLeftEyeRect, aRightEyeRect);
|
|
if (mutex) {
|
|
HRESULT hr = mutex->ReleaseSync(0);
|
|
if (FAILED(hr)) {
|
|
NS_WARNING("Failed to unlock the texture");
|
|
}
|
|
}
|
|
if (!success) {
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
#elif defined(XP_MACOSX)
|
|
case SurfaceDescriptor::TSurfaceDescriptorMacIOSurface: {
|
|
const auto& desc = aTexture.get_SurfaceDescriptorMacIOSurface();
|
|
RefPtr<MacIOSurface> surf = MacIOSurface::LookupSurface(desc.surfaceId(),
|
|
desc.scaleFactor(),
|
|
!desc.isOpaque());
|
|
if (!surf) {
|
|
NS_WARNING("VRDisplayHost::SubmitFrame failed to get a MacIOSurface");
|
|
return;
|
|
}
|
|
IntSize texSize = gfx::IntSize(surf->GetDevicePixelWidth(),
|
|
surf->GetDevicePixelHeight());
|
|
if (!SubmitFrame(surf, texSize, aLeftEyeRect, aRightEyeRect)) {
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
#elif defined(MOZ_ANDROID_GOOGLE_VR)
|
|
case SurfaceDescriptor::TEGLImageDescriptor: {
|
|
const EGLImageDescriptor& desc = aTexture.get_EGLImageDescriptor();
|
|
if (!SubmitFrame(&desc, aLeftEyeRect, aRightEyeRect)) {
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
#endif
|
|
default: {
|
|
NS_WARNING("Unsupported SurfaceDescriptor type for VR layer texture");
|
|
return;
|
|
}
|
|
}
|
|
|
|
#if defined(XP_WIN) || defined(XP_MACOSX) || defined(MOZ_ANDROID_GOOGLE_VR)
|
|
|
|
/**
|
|
* 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 = VRListenerThreadHolder::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)
|
|
{
|
|
#if !defined(MOZ_ANDROID_GOOGLE_VR)
|
|
if (!mSubmitThread) {
|
|
mSubmitThread = new VRThread(NS_LITERAL_CSTRING("VR_SubmitFrame"));
|
|
}
|
|
#endif // !defined(MOZ_ANDROID_GOOGLE_VR)
|
|
|
|
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;
|
|
}
|
|
|
|
RefPtr<Runnable> submit =
|
|
NewRunnableMethod<StoreCopyPassByConstLRef<layers::SurfaceDescriptor>, uint64_t,
|
|
StoreCopyPassByConstLRef<gfx::Rect>, StoreCopyPassByConstLRef<gfx::Rect>>(
|
|
"gfx::VRDisplayHost::SubmitFrameInternal", this, &VRDisplayHost::SubmitFrameInternal,
|
|
aTexture, aFrameId, aLeftEyeRect, aRightEyeRect);
|
|
#if !defined(MOZ_ANDROID_GOOGLE_VR)
|
|
mSubmitThread->Start();
|
|
mSubmitThread->PostTask(submit.forget());
|
|
#else
|
|
CompositorThreadHolder::Loop()->PostTask(submit.forget());
|
|
#endif // defined(MOZ_ANDROID_GOOGLE_VR)
|
|
}
|
|
|
|
bool
|
|
VRDisplayHost::CheckClearDisplayInfoDirty()
|
|
{
|
|
if (mDisplayInfo == mLastUpdateDisplayInfo) {
|
|
return false;
|
|
}
|
|
mLastUpdateDisplayInfo = mDisplayInfo;
|
|
return true;
|
|
}
|
|
|
|
VRControllerHost::VRControllerHost(VRDeviceType aType, dom::GamepadHand aHand,
|
|
uint32_t aDisplayID)
|
|
: mControllerInfo{}
|
|
, mVibrateIndex(0)
|
|
{
|
|
MOZ_COUNT_CTOR(VRControllerHost);
|
|
mControllerInfo.mType = aType;
|
|
mControllerInfo.mControllerState.mHand = 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.mButtonPressed = aBit;
|
|
}
|
|
|
|
uint64_t
|
|
VRControllerHost::GetButtonPressed()
|
|
{
|
|
return mControllerInfo.mControllerState.mButtonPressed;
|
|
}
|
|
|
|
void
|
|
VRControllerHost::SetButtonTouched(uint64_t aBit)
|
|
{
|
|
mControllerInfo.mControllerState.mButtonTouched = aBit;
|
|
}
|
|
|
|
uint64_t
|
|
VRControllerHost::GetButtonTouched()
|
|
{
|
|
return mControllerInfo.mControllerState.mButtonTouched;
|
|
}
|
|
|
|
void
|
|
VRControllerHost::SetPose(const dom::GamepadPoseState& aPose)
|
|
{
|
|
mPose = aPose;
|
|
}
|
|
|
|
const dom::GamepadPoseState&
|
|
VRControllerHost::GetPose()
|
|
{
|
|
return mPose;
|
|
}
|
|
|
|
dom::GamepadHand
|
|
VRControllerHost::GetHand()
|
|
{
|
|
return mControllerInfo.mControllerState.mHand;
|
|
}
|
|
|
|
void
|
|
VRControllerHost::SetVibrateIndex(uint64_t aIndex)
|
|
{
|
|
mVibrateIndex = aIndex;
|
|
}
|
|
|
|
uint64_t
|
|
VRControllerHost::GetVibrateIndex()
|
|
{
|
|
return mVibrateIndex;
|
|
}
|