diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /gfx/vr | |
parent | Initial commit. (diff) | |
download | firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
143 files changed, 30632 insertions, 0 deletions
diff --git a/gfx/vr/FxROutputHandler.cpp b/gfx/vr/FxROutputHandler.cpp new file mode 100644 index 0000000000..3bf2fb62ce --- /dev/null +++ b/gfx/vr/FxROutputHandler.cpp @@ -0,0 +1,96 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "FxROutputHandler.h" +#include "mozilla/Assertions.h" +#include "moz_external_vr.h" +#include "VRShMem.h" + +// TryInitialize is responsible for associating this output handler with the +// calling window's swapchain for subsequent updates. This also creates a +// texture that can be shared across processes and updates VRShMem with the +// shared texture handle. +// See nsFxrCommandLineHandler::Handle for more information about the +// bootstrap process. +bool FxROutputHandler::TryInitialize(IDXGISwapChain* aSwapChain, + ID3D11Device* aDevice) { + if (mSwapChain == nullptr) { + RefPtr<ID3D11Texture2D> texOrig = nullptr; + HRESULT hr = + aSwapChain->GetBuffer(0, IID_ID3D11Texture2D, getter_AddRefs(texOrig)); + if (hr != S_OK) { + return false; + } + + // Create shareable texture, which will be copied to + D3D11_TEXTURE2D_DESC descOrig = {0}; + texOrig->GetDesc(&descOrig); + descOrig.MiscFlags |= D3D11_RESOURCE_MISC_SHARED; + hr = aDevice->CreateTexture2D(&descOrig, nullptr, + mTexCopy.StartAssignment()); + if (hr != S_OK) { + return false; + } + + // Now, share the texture to a handle that can be marshaled to another + // process + HANDLE hCopy = nullptr; + RefPtr<IDXGIResource> texResource; + hr = mTexCopy->QueryInterface(IID_IDXGIResource, + getter_AddRefs(texResource)); + if (hr != S_OK) { + return false; + } + + hr = texResource->GetSharedHandle(&hCopy); + if (hr != S_OK) { + return false; + } + + // The texture is successfully created and shared, so cache a + // pointer to the swapchain to indicate this success. + mSwapChain = aSwapChain; + + // Finally, marshal the shared texture handle via VRShMem + mozilla::gfx::VRShMem shmem(nullptr, true /*aRequiresMutex*/); + if (shmem.JoinShMem()) { + mozilla::gfx::VRWindowState windowState = {0}; + shmem.PullWindowState(windowState); + + // The CLH should have populated hwndFx first + MOZ_ASSERT(windowState.hwndFx != 0); + MOZ_ASSERT(windowState.textureFx == nullptr); + + windowState.textureFx = (HANDLE)hCopy; + + shmem.PushWindowState(windowState); + shmem.LeaveShMem(); + + // Notify the waiting host process that the data is now available + HANDLE hSignal = ::OpenEventA(EVENT_ALL_ACCESS, // dwDesiredAccess + FALSE, // bInheritHandle + windowState.signalName // lpName + ); + ::SetEvent(hSignal); + ::CloseHandle(hSignal); + } + } else { + MOZ_ASSERT(aSwapChain == mSwapChain); + } + + return mSwapChain != nullptr && aSwapChain == mSwapChain; +} + +// Update the contents of the shared texture. +void FxROutputHandler::UpdateOutput(ID3D11DeviceContext* aCtx) { + MOZ_ASSERT(mSwapChain != nullptr); + + ID3D11Texture2D* texOrig = nullptr; + HRESULT hr = mSwapChain->GetBuffer(0, IID_PPV_ARGS(&texOrig)); + if (hr == S_OK) { + aCtx->CopyResource(mTexCopy, texOrig); + texOrig->Release(); + } +} diff --git a/gfx/vr/FxROutputHandler.h b/gfx/vr/FxROutputHandler.h new file mode 100644 index 0000000000..33e246aae5 --- /dev/null +++ b/gfx/vr/FxROutputHandler.h @@ -0,0 +1,30 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#pragma once + +struct ID3D11Texture2D; +struct IDXGISwapChain; +struct ID3D11DeviceContext; +struct ID3D11Device; + +#include <windows.h> +#include <d3d11_1.h> + +#include "mozilla/RefPtr.h" + +// FxROutputHandler is responsible for managing resources to share a Desktop +// browser window with a Firefox Reality VR window. +// Note: this object is created on the Compositor thread, but its usage should +// only be on the RenderThread. +class FxROutputHandler final { + public: + bool TryInitialize(IDXGISwapChain* aSwapChain, ID3D11Device* aDevice); + void UpdateOutput(ID3D11DeviceContext* aCtx); + + private: + RefPtr<IDXGISwapChain> mSwapChain = nullptr; + RefPtr<ID3D11Texture2D> mTexCopy = nullptr; +}; diff --git a/gfx/vr/FxRWindowManager.cpp b/gfx/vr/FxRWindowManager.cpp new file mode 100644 index 0000000000..becaf21447 --- /dev/null +++ b/gfx/vr/FxRWindowManager.cpp @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "FxRWindowManager.h" +#include "mozilla/Assertions.h" +#include "nsPIDOMWindow.h" +#include "mozilla/ClearOnShutdown.h" + +#include "nsWindow.h" + +static mozilla::StaticAutoPtr<FxRWindowManager> sFxrWinMgrInstance; + +FxRWindowManager* FxRWindowManager::GetInstance() { + if (sFxrWinMgrInstance == nullptr) { + sFxrWinMgrInstance = new FxRWindowManager(); + ClearOnShutdown(&sFxrWinMgrInstance); + } + + return sFxrWinMgrInstance; +} + +FxRWindowManager::FxRWindowManager() : mWindow(nullptr) {} + +// Track this new Firefox Reality window instance +void FxRWindowManager::AddWindow(nsPIDOMWindowOuter* aWindow) { + if (mWindow != nullptr) { + MOZ_CRASH("Only one window is supported"); + } + + mWindow = aWindow; +} + +// Returns true if the window at the provided ID was created for Firefox Reality +bool FxRWindowManager::IsFxRWindow(uint64_t aOuterWindowID) { + return (mWindow != nullptr) && (mWindow->WindowID() == aOuterWindowID); +} + +// Returns true if the window was created for Firefox Reality +bool FxRWindowManager::IsFxRWindow(const nsWindow* aWindow) const { + return (mWindow != nullptr) && + (aWindow == + mozilla::widget::WidgetUtils::DOMWindowToWidget(mWindow).take()); +} + +uint64_t FxRWindowManager::GetWindowID() const { + MOZ_ASSERT(mWindow); + return mWindow->WindowID(); +} diff --git a/gfx/vr/FxRWindowManager.h b/gfx/vr/FxRWindowManager.h new file mode 100644 index 0000000000..214ee1ecbf --- /dev/null +++ b/gfx/vr/FxRWindowManager.h @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#pragma once +#include <cstdint> + +class nsPIDOMWindowOuter; +class nsWindow; + +// FxRWindowManager is a singleton that is responsible for tracking all of +// the top-level windows created for Firefox Reality on Desktop. Only a +// single window is initially supported. +class FxRWindowManager final { + public: + static FxRWindowManager* GetInstance(); + + void AddWindow(nsPIDOMWindowOuter* aWindow); + bool IsFxRWindow(uint64_t aOuterWindowID); + bool IsFxRWindow(const nsWindow* aWindow) const; + uint64_t GetWindowID() const; + + private: + FxRWindowManager(); + + // Only a single window is supported for tracking. Support for multiple + // windows will require a data structure to collect windows as they are + // created. + nsPIDOMWindowOuter* mWindow; +}; diff --git a/gfx/vr/VRDisplayClient.cpp b/gfx/vr/VRDisplayClient.cpp new file mode 100644 index 0000000000..002bbefaef --- /dev/null +++ b/gfx/vr/VRDisplayClient.cpp @@ -0,0 +1,665 @@ +/* -*- 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 <math.h> + +#include "prlink.h" +#include "prenv.h" + +#include "nsIGlobalObject.h" +#include "nsRefPtrHashtable.h" +#include "nsString.h" +#include "mozilla/dom/GamepadHandle.h" +#include "mozilla/dom/GamepadManager.h" +#include "mozilla/dom/Gamepad.h" +#include "mozilla/dom/XRSession.h" +#include "mozilla/dom/XRInputSourceArray.h" +#include "mozilla/Preferences.h" +#include "mozilla/Unused.h" +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/Telemetry.h" +#include "mozilla/dom/WebXRBinding.h" +#include "nsServiceManagerUtils.h" + +#ifdef XP_WIN +# include "../layers/d3d11/CompositorD3D11.h" +#endif + +#include "VRDisplayClient.h" +#include "VRDisplayPresentation.h" +#include "VRManagerChild.h" +#include "VRLayerChild.h" + +using namespace mozilla; +using namespace mozilla::gfx; + +using mozilla::dom::GamepadHandle; +using mozilla::dom::GamepadHandleKind; + +VRDisplayClient::VRDisplayClient(const VRDisplayInfo& aDisplayInfo) + : mDisplayInfo(aDisplayInfo), + bLastEventWasMounted(false), + bLastEventWasPresenting(false), + mPresentationCount(0), + mLastEventFrameId(0), + mLastPresentingGeneration(0), + mLastEventControllerState{}, + // For now WebVR is default to prevent a VRDisplay restore bug in WebVR + // compatibility mode. See Bug 1630512 + mAPIMode(VRAPIMode::WebVR) { + MOZ_COUNT_CTOR(VRDisplayClient); +} + +VRDisplayClient::~VRDisplayClient() { MOZ_COUNT_DTOR(VRDisplayClient); } + +void VRDisplayClient::UpdateDisplayInfo(const VRDisplayInfo& aDisplayInfo) { + mDisplayInfo = aDisplayInfo; + FireEvents(); +} + +already_AddRefed<VRDisplayPresentation> VRDisplayClient::BeginPresentation( + const nsTArray<mozilla::dom::VRLayer>& aLayers, uint32_t aGroup) { + PresentationCreated(); + RefPtr<VRDisplayPresentation> presentation = + new VRDisplayPresentation(this, aLayers, aGroup); + return presentation.forget(); +} + +void VRDisplayClient::PresentationCreated() { ++mPresentationCount; } + +void VRDisplayClient::PresentationDestroyed() { --mPresentationCount; } + +void VRDisplayClient::SessionStarted(dom::XRSession* aSession) { + PresentationCreated(); + MakePresentationGenerationCurrent(); + mSessions.AppendElement(aSession); +} +void VRDisplayClient::SessionEnded(dom::XRSession* aSession) { + mSessions.RemoveElement(aSession); + PresentationDestroyed(); +} + +void VRDisplayClient::StartFrame() { + RefPtr<VRManagerChild> vm = VRManagerChild::Get(); + vm->RunFrameRequestCallbacks(); + + nsTArray<RefPtr<dom::XRSession>> sessions; + sessions.AppendElements(mSessions); + for (auto session : sessions) { + session->StartFrame(); + } +} + +void VRDisplayClient::SetGroupMask(uint32_t aGroupMask) { + VRManagerChild* vm = VRManagerChild::Get(); + vm->SendSetGroupMask(mDisplayInfo.mDisplayID, aGroupMask); +} + +bool VRDisplayClient::IsPresentationGenerationCurrent() const { + if (mLastPresentingGeneration != + mDisplayInfo.mDisplayState.presentingGeneration) { + return false; + } + + return true; +} + +void VRDisplayClient::MakePresentationGenerationCurrent() { + mLastPresentingGeneration = mDisplayInfo.mDisplayState.presentingGeneration; +} + +gfx::VRAPIMode VRDisplayClient::GetXRAPIMode() const { return mAPIMode; } + +void VRDisplayClient::SetXRAPIMode(gfx::VRAPIMode aMode) { + mAPIMode = aMode; + Telemetry::Accumulate(Telemetry::WEBXR_API_MODE, + static_cast<uint32_t>(mAPIMode)); +} + +void VRDisplayClient::FireEvents() { + RefPtr<VRManagerChild> vm = VRManagerChild::Get(); + // Only fire these events for non-chrome VR sessions + bool isPresenting = (mDisplayInfo.mPresentingGroups & kVRGroupContent) != 0; + + // Check if we need to trigger onVRDisplayPresentChange event + if (bLastEventWasPresenting != isPresenting) { + bLastEventWasPresenting = isPresenting; + vm->FireDOMVRDisplayPresentChangeEvent(mDisplayInfo.mDisplayID); + } + + // Check if we need to trigger onvrdisplayactivate event + if (!bLastEventWasMounted && mDisplayInfo.mDisplayState.isMounted) { + bLastEventWasMounted = true; + if (StaticPrefs::dom_vr_autoactivate_enabled()) { + vm->FireDOMVRDisplayMountedEvent(mDisplayInfo.mDisplayID); + } + } + + // Check if we need to trigger onvrdisplaydeactivate event + if (bLastEventWasMounted && !mDisplayInfo.mDisplayState.isMounted) { + bLastEventWasMounted = false; + if (StaticPrefs::dom_vr_autoactivate_enabled()) { + vm->FireDOMVRDisplayUnmountedEvent(mDisplayInfo.mDisplayID); + } + } + + if (mLastPresentingGeneration != + mDisplayInfo.mDisplayState.presentingGeneration) { + mLastPresentingGeneration = mDisplayInfo.mDisplayState.presentingGeneration; + vm->NotifyPresentationGenerationChanged(mDisplayInfo.mDisplayID); + } + + // In WebXR spec, Gamepad instances returned by an XRInputSource's gamepad + // attribute MUST NOT be included in the array returned by + // navigator.getGamepads(). + if (mAPIMode == VRAPIMode::WebVR) { + FireGamepadEvents(); + } + // Update controller states into XRInputSourceArray. + for (auto& session : mSessions) { + dom::XRInputSourceArray* inputs = session->InputSources(); + if (inputs) { + inputs->Update(session); + } + } + + // Check if we need to trigger VRDisplay.requestAnimationFrame + if (mLastEventFrameId != mDisplayInfo.mFrameId) { + mLastEventFrameId = mDisplayInfo.mFrameId; + StartFrame(); + } +} + +void VRDisplayClient::GamepadMappingForWebVR( + VRControllerState& aControllerState) { + float triggerValue[kVRControllerMaxButtons]; + memcpy(triggerValue, aControllerState.triggerValue, + sizeof(aControllerState.triggerValue)); + const uint64_t buttonPressed = aControllerState.buttonPressed; + const uint64_t buttonTouched = aControllerState.buttonTouched; + + auto SetTriggerValue = [&](uint64_t newSlot, uint64_t oldSlot) { + aControllerState.triggerValue[newSlot] = triggerValue[oldSlot]; + }; + auto ShiftButtonBitForNewSlot = [&](uint64_t newSlot, uint64_t oldSlot, + bool aIsTouch = false) { + if (aIsTouch) { + return ((buttonTouched & (1ULL << oldSlot)) != 0) * (1ULL << newSlot); + } + SetTriggerValue(newSlot, oldSlot); + return ((buttonPressed & (1ULL << oldSlot)) != 0) * (1ULL << newSlot); + }; + + switch (aControllerState.type) { + case VRControllerType::HTCVive: + aControllerState.buttonPressed = + ShiftButtonBitForNewSlot(1, 0) | ShiftButtonBitForNewSlot(2, 1) | + ShiftButtonBitForNewSlot(0, 2) | ShiftButtonBitForNewSlot(3, 4); + aControllerState.buttonTouched = ShiftButtonBitForNewSlot(0, 1, true) | + ShiftButtonBitForNewSlot(2, 1, true) | + ShiftButtonBitForNewSlot(0, 2, true) | + ShiftButtonBitForNewSlot(3, 4, true); + aControllerState.numButtons = 4; + aControllerState.numAxes = 2; + break; + case VRControllerType::MSMR: + aControllerState.buttonPressed = + ShiftButtonBitForNewSlot(1, 0) | ShiftButtonBitForNewSlot(2, 1) | + ShiftButtonBitForNewSlot(0, 2) | ShiftButtonBitForNewSlot(3, 3) | + ShiftButtonBitForNewSlot(4, 4); + aControllerState.buttonTouched = ShiftButtonBitForNewSlot(1, 0, true) | + ShiftButtonBitForNewSlot(2, 1, true) | + ShiftButtonBitForNewSlot(0, 2, true) | + ShiftButtonBitForNewSlot(3, 3, true) | + ShiftButtonBitForNewSlot(4, 4, true); + aControllerState.numButtons = 5; + aControllerState.numAxes = 4; + break; + case VRControllerType::HTCViveCosmos: + aControllerState.buttonPressed = + ShiftButtonBitForNewSlot(0, 0) | ShiftButtonBitForNewSlot(1, 1) | + ShiftButtonBitForNewSlot(4, 3) | ShiftButtonBitForNewSlot(2, 4) | + ShiftButtonBitForNewSlot(3, 5) | ShiftButtonBitForNewSlot(5, 6); + aControllerState.buttonTouched = ShiftButtonBitForNewSlot(0, 0, true) | + ShiftButtonBitForNewSlot(1, 1, true) | + ShiftButtonBitForNewSlot(4, 3, true) | + ShiftButtonBitForNewSlot(2, 4, true) | + ShiftButtonBitForNewSlot(3, 5, true) | + ShiftButtonBitForNewSlot(5, 6, true); + aControllerState.axisValue[0] = aControllerState.axisValue[2]; + aControllerState.axisValue[1] = aControllerState.axisValue[3]; + aControllerState.numButtons = 6; + aControllerState.numAxes = 2; + break; + case VRControllerType::HTCViveFocus: + aControllerState.buttonPressed = + ShiftButtonBitForNewSlot(0, 2) | ShiftButtonBitForNewSlot(1, 0); + aControllerState.buttonTouched = ShiftButtonBitForNewSlot(0, 2, true) | + ShiftButtonBitForNewSlot(1, 0, true); + aControllerState.numButtons = 2; + aControllerState.numAxes = 2; + break; + case VRControllerType::HTCViveFocusPlus: { + aControllerState.buttonPressed = ShiftButtonBitForNewSlot(0, 2) | + ShiftButtonBitForNewSlot(1, 0) | + ShiftButtonBitForNewSlot(2, 1); + aControllerState.buttonTouched = ShiftButtonBitForNewSlot(0, 2, true) | + ShiftButtonBitForNewSlot(1, 0, true) | + ShiftButtonBitForNewSlot(2, 1, true); + aControllerState.numButtons = 3; + aControllerState.numAxes = 2; + + static Matrix4x4 focusPlusTransform; + Matrix4x4 originalMtx; + if (focusPlusTransform.IsIdentity()) { + focusPlusTransform.RotateX(-0.70f); + focusPlusTransform.PostTranslate(0.0f, 0.0f, 0.01f); + focusPlusTransform.Inverse(); + } + gfx::Quaternion quat(aControllerState.pose.orientation[0], + aControllerState.pose.orientation[1], + aControllerState.pose.orientation[2], + aControllerState.pose.orientation[3]); + originalMtx.SetRotationFromQuaternion(quat); + originalMtx._41 = aControllerState.pose.position[0]; + originalMtx._42 = aControllerState.pose.position[1]; + originalMtx._43 = aControllerState.pose.position[2]; + originalMtx = focusPlusTransform * originalMtx; + + gfx::Point3D pos, scale; + originalMtx.Decompose(pos, quat, scale); + + aControllerState.pose.position[0] = pos.x; + aControllerState.pose.position[1] = pos.y; + aControllerState.pose.position[2] = pos.z; + + aControllerState.pose.orientation[0] = quat.x; + aControllerState.pose.orientation[1] = quat.y; + aControllerState.pose.orientation[2] = quat.z; + aControllerState.pose.orientation[3] = quat.w; + break; + } + case VRControllerType::OculusGo: { + aControllerState.buttonPressed = + ShiftButtonBitForNewSlot(0, 2) | ShiftButtonBitForNewSlot(1, 0); + aControllerState.buttonTouched = ShiftButtonBitForNewSlot(0, 2, true) | + ShiftButtonBitForNewSlot(1, 0, true); + aControllerState.numButtons = 2; + aControllerState.numAxes = 2; + + static Matrix4x4 goTransform; + Matrix4x4 originalMtx; + + if (goTransform.IsIdentity()) { + goTransform.RotateX(-0.60f); + goTransform.Inverse(); + } + gfx::Quaternion quat(aControllerState.pose.orientation[0], + aControllerState.pose.orientation[1], + aControllerState.pose.orientation[2], + aControllerState.pose.orientation[3]); + originalMtx.SetRotationFromQuaternion(quat); + originalMtx._41 = aControllerState.pose.position[0]; + originalMtx._42 = aControllerState.pose.position[1]; + originalMtx._43 = aControllerState.pose.position[2]; + originalMtx = goTransform * originalMtx; + + gfx::Point3D pos, scale; + originalMtx.Decompose(pos, quat, scale); + + aControllerState.pose.position[0] = pos.x; + aControllerState.pose.position[1] = pos.y; + aControllerState.pose.position[2] = pos.z; + + aControllerState.pose.orientation[0] = quat.x; + aControllerState.pose.orientation[1] = quat.y; + aControllerState.pose.orientation[2] = quat.z; + aControllerState.pose.orientation[3] = quat.w; + break; + } + case VRControllerType::OculusTouch: + aControllerState.buttonPressed = + ShiftButtonBitForNewSlot(0, 3) | ShiftButtonBitForNewSlot(1, 0) | + ShiftButtonBitForNewSlot(2, 1) | ShiftButtonBitForNewSlot(3, 4) | + ShiftButtonBitForNewSlot(4, 5) | ShiftButtonBitForNewSlot(5, 6); + aControllerState.buttonTouched = ShiftButtonBitForNewSlot(0, 3, true) | + ShiftButtonBitForNewSlot(1, 0, true) | + ShiftButtonBitForNewSlot(2, 1, true) | + ShiftButtonBitForNewSlot(3, 4, true) | + ShiftButtonBitForNewSlot(4, 5, true) | + ShiftButtonBitForNewSlot(5, 6, true); + aControllerState.axisValue[0] = aControllerState.axisValue[2]; + aControllerState.axisValue[1] = aControllerState.axisValue[3]; + aControllerState.numButtons = 6; + aControllerState.numAxes = 2; + break; + case VRControllerType::OculusTouch2: { + aControllerState.buttonPressed = + ShiftButtonBitForNewSlot(0, 3) | ShiftButtonBitForNewSlot(1, 0) | + ShiftButtonBitForNewSlot(2, 1) | ShiftButtonBitForNewSlot(3, 4) | + ShiftButtonBitForNewSlot(4, 5) | ShiftButtonBitForNewSlot(5, 6); + aControllerState.buttonTouched = ShiftButtonBitForNewSlot(0, 3, true) | + ShiftButtonBitForNewSlot(1, 0, true) | + ShiftButtonBitForNewSlot(2, 1, true) | + ShiftButtonBitForNewSlot(3, 4, true) | + ShiftButtonBitForNewSlot(4, 5, true) | + ShiftButtonBitForNewSlot(5, 6, true); + aControllerState.axisValue[0] = aControllerState.axisValue[2]; + aControllerState.axisValue[1] = aControllerState.axisValue[3]; + aControllerState.numButtons = 6; + aControllerState.numAxes = 2; + + static Matrix4x4 touch2Transform; + Matrix4x4 originalMtx; + + if (touch2Transform.IsIdentity()) { + touch2Transform.RotateX(-0.77f); + touch2Transform.PostTranslate(0.0f, 0.0f, -0.025f); + touch2Transform.Inverse(); + } + gfx::Quaternion quat(aControllerState.pose.orientation[0], + aControllerState.pose.orientation[1], + aControllerState.pose.orientation[2], + aControllerState.pose.orientation[3]); + originalMtx.SetRotationFromQuaternion(quat); + originalMtx._41 = aControllerState.pose.position[0]; + originalMtx._42 = aControllerState.pose.position[1]; + originalMtx._43 = aControllerState.pose.position[2]; + originalMtx = touch2Transform * originalMtx; + + gfx::Point3D pos, scale; + originalMtx.Decompose(pos, quat, scale); + + aControllerState.pose.position[0] = pos.x; + aControllerState.pose.position[1] = pos.y; + aControllerState.pose.position[2] = pos.z; + + aControllerState.pose.orientation[0] = quat.x; + aControllerState.pose.orientation[1] = quat.y; + aControllerState.pose.orientation[2] = quat.z; + aControllerState.pose.orientation[3] = quat.w; + break; + } + case VRControllerType::ValveIndex: + aControllerState.buttonPressed = + ShiftButtonBitForNewSlot(1, 0) | ShiftButtonBitForNewSlot(2, 1) | + ShiftButtonBitForNewSlot(0, 2) | ShiftButtonBitForNewSlot(5, 3) | + ShiftButtonBitForNewSlot(3, 4) | ShiftButtonBitForNewSlot(4, 5) | + ShiftButtonBitForNewSlot(6, 6) | ShiftButtonBitForNewSlot(7, 7) | + ShiftButtonBitForNewSlot(8, 8) | ShiftButtonBitForNewSlot(9, 9); + aControllerState.buttonTouched = ShiftButtonBitForNewSlot(1, 0, true) | + ShiftButtonBitForNewSlot(2, 1, true) | + ShiftButtonBitForNewSlot(0, 2, true) | + ShiftButtonBitForNewSlot(5, 3, true) | + ShiftButtonBitForNewSlot(3, 4, true) | + ShiftButtonBitForNewSlot(4, 5, true) | + ShiftButtonBitForNewSlot(6, 6, true) | + ShiftButtonBitForNewSlot(7, 7, true) | + ShiftButtonBitForNewSlot(8, 8, true) | + ShiftButtonBitForNewSlot(9, 9, true); + aControllerState.numButtons = 10; + aControllerState.numAxes = 4; + break; + case VRControllerType::PicoGaze: + aControllerState.buttonPressed = ShiftButtonBitForNewSlot(0, 0); + aControllerState.buttonTouched = ShiftButtonBitForNewSlot(0, 0, true); + aControllerState.numButtons = 1; + aControllerState.numAxes = 0; + break; + case VRControllerType::PicoG2: + aControllerState.buttonPressed = + ShiftButtonBitForNewSlot(0, 2) | ShiftButtonBitForNewSlot(1, 0); + aControllerState.buttonTouched = ShiftButtonBitForNewSlot(0, 2, true) | + ShiftButtonBitForNewSlot(1, 0, true); + aControllerState.numButtons = 2; + aControllerState.numAxes = 2; + break; + case VRControllerType::PicoNeo2: + aControllerState.buttonPressed = + ShiftButtonBitForNewSlot(0, 3) | ShiftButtonBitForNewSlot(1, 0) | + ShiftButtonBitForNewSlot(2, 1) | ShiftButtonBitForNewSlot(3, 4) | + ShiftButtonBitForNewSlot(4, 5) | ShiftButtonBitForNewSlot(5, 6); + aControllerState.buttonTouched = ShiftButtonBitForNewSlot(0, 3, true) | + ShiftButtonBitForNewSlot(1, 0, true) | + ShiftButtonBitForNewSlot(2, 1, true) | + ShiftButtonBitForNewSlot(3, 4, true) | + ShiftButtonBitForNewSlot(4, 5, true) | + ShiftButtonBitForNewSlot(5, 6, true); + aControllerState.axisValue[0] = aControllerState.axisValue[2]; + aControllerState.axisValue[1] = aControllerState.axisValue[3]; + aControllerState.numButtons = 6; + aControllerState.numAxes = 2; + break; + default: + // Undefined controller types, we will keep its the same order. + break; + } +} + +void VRDisplayClient::FireGamepadEvents() { + RefPtr<dom::GamepadManager> gamepadManager(dom::GamepadManager::GetService()); + if (!gamepadManager) { + return; + } + for (int stateIndex = 0; stateIndex < kVRControllerMaxCount; stateIndex++) { + VRControllerState state = {}, lastState = {}; + memcpy(&state, &mDisplayInfo.mControllerState[stateIndex], + sizeof(VRControllerState)); + memcpy(&lastState, &mLastEventControllerState[stateIndex], + sizeof(VRControllerState)); + GamepadMappingForWebVR(state); + GamepadMappingForWebVR(lastState); + + uint32_t gamepadHandleValue = + mDisplayInfo.mDisplayID * kVRControllerMaxCount + stateIndex; + + GamepadHandle gamepadHandle{gamepadHandleValue, GamepadHandleKind::VR}; + + bool bIsNew = false; + + // Send events to notify that controllers are removed + if (state.controllerName[0] == '\0') { + // Controller is not present + if (lastState.controllerName[0] != '\0') { + // Controller has been removed + dom::GamepadRemoved info; + dom::GamepadChangeEventBody body(info); + dom::GamepadChangeEvent event(gamepadHandle, body); + gamepadManager->Update(event); + } + // Do not process any further events for removed controllers + continue; + } + + // Send events to notify that new controllers are added + RefPtr<dom::Gamepad> existing = gamepadManager->GetGamepad(gamepadHandle); + // ControllerState in OpenVR action-based API gets delay to query btn and + // axis count. So, we need to check if they are more than zero. + if ((lastState.controllerName[0] == '\0' || !existing) && + (state.numButtons > 0 || state.numAxes > 0)) { + dom::GamepadAdded info(NS_ConvertUTF8toUTF16(state.controllerName), + dom::GamepadMappingType::_empty, state.hand, + mDisplayInfo.mDisplayID, state.numButtons, + state.numAxes, state.numHaptics, 0, 0); + dom::GamepadChangeEventBody body(info); + dom::GamepadChangeEvent event(gamepadHandle, body); + gamepadManager->Update(event); + bIsNew = true; + } + + // Send events for handedness changes + if (state.hand != lastState.hand) { + dom::GamepadHandInformation info(state.hand); + dom::GamepadChangeEventBody body(info); + dom::GamepadChangeEvent event(gamepadHandle, body); + gamepadManager->Update(event); + } + + // Send events for axis value changes + for (uint32_t axisIndex = 0; axisIndex < state.numAxes; axisIndex++) { + if (state.axisValue[axisIndex] != lastState.axisValue[axisIndex]) { + dom::GamepadAxisInformation info(axisIndex, state.axisValue[axisIndex]); + dom::GamepadChangeEventBody body(info); + dom::GamepadChangeEvent event(gamepadHandle, body); + gamepadManager->Update(event); + } + } + + // Send events for trigger, touch, and button value changes + if (!bIsNew) { + // When a new controller is added, we do not emit button events for + // the initial state of the inputs. + for (uint32_t buttonIndex = 0; buttonIndex < state.numButtons; + buttonIndex++) { + bool bPressed = (state.buttonPressed & (1ULL << buttonIndex)) != 0; + bool bTouched = (state.buttonTouched & (1ULL << buttonIndex)) != 0; + bool bLastPressed = + (lastState.buttonPressed & (1ULL << buttonIndex)) != 0; + bool bLastTouched = + (lastState.buttonTouched & (1ULL << buttonIndex)) != 0; + + if (state.triggerValue[buttonIndex] != + lastState.triggerValue[buttonIndex] || + bPressed != bLastPressed || bTouched != bLastTouched) { + dom::GamepadButtonInformation info( + buttonIndex, state.triggerValue[buttonIndex], bPressed, bTouched); + dom::GamepadChangeEventBody body(info); + dom::GamepadChangeEvent event(gamepadHandle, body); + gamepadManager->Update(event); + } + } + } + + // Send events for pose changes + // Note that VRPose is asserted to be a POD type so memcmp is safe + if (state.flags != lastState.flags || + state.isPositionValid != lastState.isPositionValid || + state.isOrientationValid != lastState.isOrientationValid || + memcmp(&state.pose, &lastState.pose, sizeof(VRPose)) != 0) { + // Convert pose to GamepadPoseState + dom::GamepadPoseState poseState; + poseState.Clear(); + poseState.flags = state.flags; + + // Orientation values + poseState.isOrientationValid = state.isOrientationValid; + poseState.orientation[0] = state.pose.orientation[0]; + poseState.orientation[1] = state.pose.orientation[1]; + poseState.orientation[2] = state.pose.orientation[2]; + poseState.orientation[3] = state.pose.orientation[3]; + poseState.angularVelocity[0] = state.pose.angularVelocity[0]; + poseState.angularVelocity[1] = state.pose.angularVelocity[1]; + poseState.angularVelocity[2] = state.pose.angularVelocity[2]; + poseState.angularAcceleration[0] = state.pose.angularAcceleration[0]; + poseState.angularAcceleration[1] = state.pose.angularAcceleration[1]; + poseState.angularAcceleration[2] = state.pose.angularAcceleration[2]; + + // Position values + poseState.isPositionValid = state.isPositionValid; + poseState.position[0] = state.pose.position[0]; + poseState.position[1] = state.pose.position[1]; + poseState.position[2] = state.pose.position[2]; + poseState.linearVelocity[0] = state.pose.linearVelocity[0]; + poseState.linearVelocity[1] = state.pose.linearVelocity[1]; + poseState.linearVelocity[2] = state.pose.linearVelocity[2]; + poseState.linearAcceleration[0] = state.pose.linearAcceleration[0]; + poseState.linearAcceleration[1] = state.pose.linearAcceleration[1]; + poseState.linearAcceleration[2] = state.pose.linearAcceleration[2]; + + // Send the event + dom::GamepadPoseInformation info(poseState); + dom::GamepadChangeEventBody body(info); + dom::GamepadChangeEvent event(gamepadHandle, body); + gamepadManager->Update(event); + } + } + + // Note that VRControllerState is asserted to be a POD type and memcpy is + // safe. + memcpy(mLastEventControllerState, mDisplayInfo.mControllerState, + sizeof(VRControllerState) * kVRControllerMaxCount); +} + +const VRHMDSensorState& VRDisplayClient::GetSensorState() const { + return mDisplayInfo.GetSensorState(); +} + +bool VRDisplayClient::GetIsConnected() const { + return mDisplayInfo.GetIsConnected(); +} + +bool VRDisplayClient::IsPresenting() { + return mDisplayInfo.mPresentingGroups != 0; +} + +void VRDisplayClient::NotifyDisconnected() { + mDisplayInfo.mDisplayState.isConnected = false; +} + +void VRDisplayClient::UpdateSubmitFrameResult( + const VRSubmitFrameResultInfo& aResult) { + mSubmitFrameResult = aResult; +} + +void VRDisplayClient::GetSubmitFrameResult(VRSubmitFrameResultInfo& aResult) { + aResult = mSubmitFrameResult; +} + +void VRDisplayClient::StartVRNavigation() { + /** + * A VR-to-VR site navigation has started, notify VRManager + * so we don't drop out of VR during the transition + */ + VRManagerChild* vm = VRManagerChild::Get(); + vm->SendStartVRNavigation(mDisplayInfo.mDisplayID); +} + +void VRDisplayClient::StopVRNavigation(const TimeDuration& aTimeout) { + /** + * A VR-to-VR site navigation has ended and the new site + * has received a vrdisplayactivate event. + * Don't actually consider the navigation transition over + * until aTimeout has elapsed. + * This may be called multiple times, in which case the timeout + * should be reset to aTimeout. + * When aTimeout is TimeDuration(0), we should consider the + * transition immediately ended. + */ + VRManagerChild* vm = VRManagerChild::Get(); + vm->SendStopVRNavigation(mDisplayInfo.mDisplayID, aTimeout); +} + +bool VRDisplayClient::IsReferenceSpaceTypeSupported( + dom::XRReferenceSpaceType aType) const { + /** + * https://immersive-web.github.io/webxr/#reference-space-is-supported + * + * We do not yet support local or local-floor for inline sessions. + * This could be expanded if we later support WebXR for inline-ar + * sessions on Firefox Fenix. + * + * We do not yet support unbounded reference spaces. + */ + switch (aType) { + case dom::XRReferenceSpaceType::Viewer: + // Viewer is always supported, for both inline and immersive sessions + return true; + case dom::XRReferenceSpaceType::Local: + case dom::XRReferenceSpaceType::Local_floor: + // Local and Local_Floor are always supported for immersive sessions + return bool(mDisplayInfo.GetCapabilities() & + (VRDisplayCapabilityFlags::Cap_ImmersiveVR | + VRDisplayCapabilityFlags::Cap_ImmersiveAR)); + case dom::XRReferenceSpaceType::Bounded_floor: + return bool(mDisplayInfo.GetCapabilities() & + VRDisplayCapabilityFlags::Cap_StageParameters); + default: + NS_WARNING( + "Unknown XRReferenceSpaceType passed to " + "VRDisplayClient::IsReferenceSpaceTypeSupported"); + return false; + } +} diff --git a/gfx/vr/VRDisplayClient.h b/gfx/vr/VRDisplayClient.h new file mode 100644 index 0000000000..19f8fdc413 --- /dev/null +++ b/gfx/vr/VRDisplayClient.h @@ -0,0 +1,103 @@ +/* -*- 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/. */ + +#ifndef GFX_VR_DISPLAY_CLIENT_H +#define GFX_VR_DISPLAY_CLIENT_H + +#include "nsCOMPtr.h" +#include "mozilla/Attributes.h" +#include "mozilla/RefPtr.h" +#include "mozilla/dom/VRDisplayBinding.h" + +#include "gfxVR.h" + +namespace mozilla { +namespace dom { +enum class XRReferenceSpaceType : uint8_t; +class XRSession; +} // namespace dom +namespace gfx { +class VRDisplayPresentation; +class VRManagerChild; + +class VRDisplayClient { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VRDisplayClient) + + explicit VRDisplayClient(const VRDisplayInfo& aDisplayInfo); + + MOZ_CAN_RUN_SCRIPT void UpdateDisplayInfo(const VRDisplayInfo& aDisplayInfo); + void UpdateSubmitFrameResult(const VRSubmitFrameResultInfo& aResult); + + const VRDisplayInfo& GetDisplayInfo() const { return mDisplayInfo; } + virtual const VRHMDSensorState& GetSensorState() const; + void GetSubmitFrameResult(VRSubmitFrameResultInfo& aResult); + + already_AddRefed<VRDisplayPresentation> BeginPresentation( + const nsTArray<dom::VRLayer>& aLayers, uint32_t aGroup); + void PresentationCreated(); + void PresentationDestroyed(); + + void SessionStarted(dom::XRSession* aSession); + void SessionEnded(dom::XRSession* aSession); + + bool GetIsConnected() const; + + void NotifyDisconnected(); + void SetGroupMask(uint32_t aGroupMask); + + bool IsPresentationGenerationCurrent() const; + void MakePresentationGenerationCurrent(); + + void StartVRNavigation(); + void StopVRNavigation(const TimeDuration& aTimeout); + + bool IsPresenting(); + bool IsReferenceSpaceTypeSupported(dom::XRReferenceSpaceType aType) const; + gfx::VRAPIMode GetXRAPIMode() const; + void SetXRAPIMode(gfx::VRAPIMode aMode); + + protected: + virtual ~VRDisplayClient(); + + MOZ_CAN_RUN_SCRIPT void FireEvents(); + void FireGamepadEvents(); + MOZ_CAN_RUN_SCRIPT void StartFrame(); + + VRDisplayInfo mDisplayInfo; + + bool bLastEventWasMounted; + bool bLastEventWasPresenting; + + int mPresentationCount; + uint64_t mLastEventFrameId; + uint32_t mLastPresentingGeneration; + + // Difference between mDisplayInfo.mControllerState and + // mLastEventControllerState determines what gamepad events to fire when + // updated. + VRControllerState mLastEventControllerState[kVRControllerMaxCount]; + + /** + * mSessions is cleared in VRDisplayClient::SessionEnded. + * SessionEnded is guaranteed to be called by every XRSession + * when it is shutdown explicitly with the WebXR XRSession.end + * call, when all JS references on the XRSession are released, or + * when the window is closed. + */ + nsTArray<RefPtr<dom::XRSession>> mSessions; + + private: + void GamepadMappingForWebVR(VRControllerState& aControllerState); + + VRSubmitFrameResultInfo mSubmitFrameResult; + gfx::VRAPIMode mAPIMode; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* GFX_VR_DISPLAY_CLIENT_H */ diff --git a/gfx/vr/VRDisplayPresentation.cpp b/gfx/vr/VRDisplayPresentation.cpp new file mode 100644 index 0000000000..127225105b --- /dev/null +++ b/gfx/vr/VRDisplayPresentation.cpp @@ -0,0 +1,155 @@ +/* -*- 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 "VRDisplayPresentation.h" +#include "mozilla/dom/DocGroup.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/XRWebGLLayer.h" +#include "mozilla/Unused.h" +#include "VRDisplayClient.h" +#include "VRLayerChild.h" + +using namespace mozilla; +using namespace mozilla::gfx; + +VRDisplayPresentation::VRDisplayPresentation( + VRDisplayClient* aDisplayClient, + const nsTArray<mozilla::dom::VRLayer>& aLayers, uint32_t aGroup) + : mDisplayClient(aDisplayClient), + mDOMLayers(aLayers.Clone()), + mGroup(aGroup) { + CreateLayers(); +} + +void VRDisplayPresentation::UpdateLayers( + const nsTArray<mozilla::dom::VRLayer>& aLayers) { + mDOMLayers = aLayers.Clone(); + CreateLayers(); +} + +void VRDisplayPresentation::UpdateXRWebGLLayer(dom::XRWebGLLayer* aLayer) { + VRManagerChild* manager = VRManagerChild::Get(); + if (!manager) { + // This should not happen, but let's log it and avoid a crash in case + // of regression. + NS_WARNING("VRManagerChild::Get returned null!"); + return; + } + + dom::HTMLCanvasElement* canvasElement = aLayer->GetCanvas(); + nsCOMPtr<nsISerialEventTarget> target = + canvasElement->OwnerDoc()->EventTargetFor(TaskCategory::Other); + + if (mLayers.Length() == 0) { + // WebXR uses a single layer for now. + RefPtr<VRLayerChild> vrLayer = + static_cast<VRLayerChild*>(manager->CreateVRLayer( + mDisplayClient->GetDisplayInfo().GetDisplayID(), target, mGroup)); + mLayers.AppendElement(vrLayer); + } + RefPtr<VRLayerChild> vrLayer = mLayers[0]; + + Rect leftBounds(0.0, 0.0, 0.5, 1.0); + Rect rightBounds(0.5, 0.0, 0.5, 1.0); + + vrLayer->Initialize(canvasElement, leftBounds, rightBounds); + vrLayer->SetXRFramebuffer(aLayer->GetFramebuffer()); +} + +uint32_t VRDisplayPresentation::GetGroup() const { return mGroup; } + +void VRDisplayPresentation::CreateLayers() { + VRManagerChild* manager = VRManagerChild::Get(); + if (!manager) { + // This should not happen, but let's log it and avoid a crash in case + // of regression. + NS_WARNING("VRManagerChild::Get returned null!"); + return; + } + + unsigned int iLayer = 0; + for (dom::VRLayer& layer : mDOMLayers) { + dom::HTMLCanvasElement* canvasElement = layer.mSource; + if (!canvasElement) { + /// XXX In the future we will support WebVR in WebWorkers here + continue; + } + + Rect leftBounds(0.0, 0.0, 0.5, 1.0); + if (layer.mLeftBounds.Length() == 4) { + leftBounds.SetRect(layer.mLeftBounds[0], layer.mLeftBounds[1], + layer.mLeftBounds[2], layer.mLeftBounds[3]); + } else if (layer.mLeftBounds.Length() != 0) { + /** + * We ignore layers with an incorrect number of values. + * In the future, VRDisplay.requestPresent may throw in + * this case. See https://github.com/w3c/webvr/issues/71 + */ + continue; + } + + Rect rightBounds(0.5, 0.0, 0.5, 1.0); + if (layer.mRightBounds.Length() == 4) { + rightBounds.SetRect(layer.mRightBounds[0], layer.mRightBounds[1], + layer.mRightBounds[2], layer.mRightBounds[3]); + } else if (layer.mRightBounds.Length() != 0) { + /** + * We ignore layers with an incorrect number of values. + * In the future, VRDisplay.requestPresent may throw in + * this case. See https://github.com/w3c/webvr/issues/71 + */ + continue; + } + + nsCOMPtr<nsISerialEventTarget> target = + canvasElement->OwnerDoc()->EventTargetFor(TaskCategory::Other); + + if (mLayers.Length() <= iLayer) { + // Not enough layers, let's add one + RefPtr<VRLayerChild> vrLayer = + static_cast<VRLayerChild*>(manager->CreateVRLayer( + mDisplayClient->GetDisplayInfo().GetDisplayID(), target, mGroup)); + if (!vrLayer) { + NS_WARNING("CreateVRLayer returned null!"); + continue; + } + vrLayer->Initialize(canvasElement, leftBounds, rightBounds); + mLayers.AppendElement(vrLayer); + } else { + // We already have a layer, let's update it + mLayers[iLayer]->Initialize(canvasElement, leftBounds, rightBounds); + } + iLayer++; + } + + // Truncate any excess layers that weren't included in the updated list + mLayers.SetLength(iLayer); +} + +void VRDisplayPresentation::DestroyLayers() { + for (VRLayerChild* layer : mLayers) { + if (layer->IsIPCOpen()) { + Unused << layer->SendDestroy(); + } + } + mLayers.Clear(); +} + +void VRDisplayPresentation::GetDOMLayers(nsTArray<dom::VRLayer>& result) { + result = mDOMLayers.Clone(); +} + +VRDisplayPresentation::~VRDisplayPresentation() { + DestroyLayers(); + mDisplayClient->PresentationDestroyed(); +} + +void VRDisplayPresentation::SubmitFrame() { + for (VRLayerChild* layer : mLayers) { + layer->SubmitFrame(mDisplayClient->GetDisplayInfo()); + break; // Currently only one layer supported, submit only the first + } +} diff --git a/gfx/vr/VRDisplayPresentation.h b/gfx/vr/VRDisplayPresentation.h new file mode 100644 index 0000000000..368048743f --- /dev/null +++ b/gfx/vr/VRDisplayPresentation.h @@ -0,0 +1,47 @@ +/* -*- 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/. */ + +#ifndef GFX_VR_DISPLAY_PRESENTATION_H +#define GFX_VR_DISPLAY_PRESENTATION_H + +#include "mozilla/RefPtr.h" +#include "mozilla/dom/VRDisplayBinding.h" + +namespace mozilla { +namespace dom { +class XRWebGLLayer; +} +namespace gfx { +class VRDisplayClient; +class VRLayerChild; + +class VRDisplayPresentation final { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VRDisplayPresentation) + + public: + VRDisplayPresentation(VRDisplayClient* aDisplayClient, + const nsTArray<dom::VRLayer>& aLayers, uint32_t aGroup); + void UpdateLayers(const nsTArray<mozilla::dom::VRLayer>& aLayers); + void UpdateXRWebGLLayer(dom::XRWebGLLayer* aLayer); + void SubmitFrame(); + void GetDOMLayers(nsTArray<dom::VRLayer>& result); + uint32_t GetGroup() const; + + private: + ~VRDisplayPresentation(); + void CreateLayers(); + void DestroyLayers(); + + RefPtr<VRDisplayClient> mDisplayClient; + nsTArray<dom::VRLayer> mDOMLayers; + nsTArray<RefPtr<VRLayerChild>> mLayers; + uint32_t mGroup; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* GFX_VR_DISPLAY_PRESENTAITON_H */ diff --git a/gfx/vr/VRManager.cpp b/gfx/vr/VRManager.cpp new file mode 100644 index 0000000000..72bc02b674 --- /dev/null +++ b/gfx/vr/VRManager.cpp @@ -0,0 +1,1542 @@ +/* -*- 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 "VRManager.h" + +#include "GeckoProfiler.h" +#include "VRManagerParent.h" +#include "VRShMem.h" +#include "VRThread.h" +#include "gfxVR.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/dom/VRDisplay.h" +#include "mozilla/dom/GamepadEventTypes.h" +#include "mozilla/layers/TextureHost.h" +#include "mozilla/layers/CompositorThread.h" +#include "mozilla/Preferences.h" +#include "mozilla/Services.h" +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/Telemetry.h" +#include "mozilla/Unused.h" +#include "nsIObserverService.h" + +#include "gfxVR.h" +#include <cstring> + +#include "ipc/VRLayerParent.h" +#if !defined(MOZ_WIDGET_ANDROID) +# include "VRServiceHost.h" +#endif + +#ifdef XP_WIN +# include "CompositorD3D11.h" +# include "TextureD3D11.h" +# include <d3d11.h> +# include "gfxWindowsPlatform.h" +# include "mozilla/gfx/DeviceManagerDx.h" +#elif defined(XP_MACOSX) +# include "mozilla/gfx/MacIOSurface.h" +# include <errno.h> +#elif defined(MOZ_WIDGET_ANDROID) +# include <string.h> +# include <pthread.h> +# include "GeckoVRManager.h" +# include "mozilla/java/GeckoSurfaceTextureWrappers.h" +# include "mozilla/layers/CompositorThread.h" +#endif // defined(MOZ_WIDGET_ANDROID) + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::layers; +using namespace mozilla::gl; + +using mozilla::dom::GamepadHandle; + +namespace mozilla::gfx { + +/** + * When VR content is active, we run the tasks at 1ms + * intervals, enabling multiple events to be processed + * per frame, such as haptic feedback pulses. + */ +const uint32_t kVRActiveTaskInterval = 1; // milliseconds + +/** + * When VR content is inactive, we run the tasks at 100ms + * intervals, enabling VR display enumeration and + * presentation startup to be relatively responsive + * while not consuming unnecessary resources. + */ +const uint32_t kVRIdleTaskInterval = 100; // milliseconds + +/** + * Max frame duration 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. + */ +const double kVRMaxFrameSubmitDuration = 4000.0f; // milliseconds + +static StaticRefPtr<VRManager> sVRManagerSingleton; + +static bool ValidVRManagerProcess() { + return XRE_IsParentProcess() || XRE_IsGPUProcess(); +} + +/* static */ +VRManager* VRManager::Get() { + MOZ_ASSERT(sVRManagerSingleton != nullptr); + MOZ_ASSERT(ValidVRManagerProcess()); + + return sVRManagerSingleton; +} + +/* static */ +VRManager* VRManager::MaybeGet() { + MOZ_ASSERT(ValidVRManagerProcess()); + + return sVRManagerSingleton; +} + +Atomic<uint32_t> VRManager::sDisplayBase(0); + +/* static */ +uint32_t VRManager::AllocateDisplayID() { return ++sDisplayBase; } + +/*static*/ +void VRManager::ManagerInit() { + MOZ_ASSERT(NS_IsMainThread()); + + if (!ValidVRManagerProcess()) { + return; + } + + // Enable gamepad extensions while VR is enabled. + // Preference only can be set at the Parent process. + if (StaticPrefs::dom_vr_enabled() && XRE_IsParentProcess()) { + Preferences::SetBool("dom.gamepad.extensions.enabled", true); + } + + if (sVRManagerSingleton == nullptr) { + sVRManagerSingleton = new VRManager(); + ClearOnShutdown(&sVRManagerSingleton); + } +} + +VRManager::VRManager() + : mState(VRManagerState::Disabled), + mAccumulator100ms(0.0f), + mRuntimeDetectionRequested(false), + mRuntimeDetectionCompleted(false), + mEnumerationRequested(false), + mEnumerationCompleted(false), + mVRDisplaysRequested(false), + mVRDisplaysRequestedNonFocus(false), + mVRControllersRequested(false), + mFrameStarted(false), + mTaskInterval(0), + mCurrentSubmitTaskMonitor("CurrentSubmitTaskMonitor"), + mCurrentSubmitTask(nullptr), + mLastSubmittedFrameId(0), + mLastStartedFrame(0), + mRuntimeSupportFlags(VRDisplayCapabilityFlags::Cap_None), + mAppPaused(false), + mShmem(nullptr), + mHapticPulseRemaining{}, + mDisplayInfo{}, + mLastUpdateDisplayInfo{}, + mBrowserState{}, + mLastSensorState{} { + MOZ_ASSERT(sVRManagerSingleton == nullptr); + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(ValidVRManagerProcess()); + +#if !defined(MOZ_WIDGET_ANDROID) + // XRE_IsGPUProcess() is helping us to check some platforms like + // Win 7 try which are not using GPU process but VR process is enabled. + mVRProcessEnabled = + StaticPrefs::dom_vr_process_enabled_AtStartup() && XRE_IsGPUProcess(); + VRServiceHost::Init(mVRProcessEnabled); + mServiceHost = VRServiceHost::Get(); + // We must shutdown before VRServiceHost, which is cleared + // on ShutdownPhase::ShutdownFinal, potentially before VRManager. + // We hold a reference to VRServiceHost to ensure it stays + // alive until we have shut down. +#else + // For Android, there is no VRProcess available and no VR service is + // created, so default to false. + mVRProcessEnabled = false; +#endif // !defined(MOZ_WIDGET_ANDROID) + + nsCOMPtr<nsIObserverService> service = services::GetObserverService(); + if (service) { + service->AddObserver(this, "application-background", false); + service->AddObserver(this, "application-foreground", false); + } +} + +void VRManager::OpenShmem() { + if (mShmem == nullptr) { + mShmem = new VRShMem(nullptr, true /*aRequiresMutex*/); + +#if !defined(MOZ_WIDGET_ANDROID) + mShmem->CreateShMem(mVRProcessEnabled /*aCreateOnSharedMemory*/); + // The VR Service accesses all hardware from a separate process + // and replaces the other VRManager when enabled. + // If the VR process is not enabled, create an in-process VRService. + if (!mVRProcessEnabled) { + // If the VR process is disabled, attempt to create a + // VR service within the current process + mServiceHost->CreateService(mShmem->GetExternalShmem()); + return; + } +#else + mShmem->CreateShMemForAndroid(); +#endif + } else { + mShmem->ClearShMem(); + } + + // Reset local information for new connection + mDisplayInfo.Clear(); + mLastUpdateDisplayInfo.Clear(); + mFrameStarted = false; + mBrowserState.Clear(); + mLastSensorState.Clear(); + mEnumerationCompleted = false; + mDisplayInfo.mGroupMask = kVRGroupContent; +} + +void VRManager::CloseShmem() { + if (mShmem != nullptr) { + mShmem->CloseShMem(); + delete mShmem; + mShmem = nullptr; + } +} + +VRManager::~VRManager() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mState == VRManagerState::Disabled); + + nsCOMPtr<nsIObserverService> service = services::GetObserverService(); + if (service) { + service->RemoveObserver(this, "application-background"); + service->RemoveObserver(this, "application-foreground"); + } + +#if !defined(MOZ_WIDGET_ANDROID) + mServiceHost->Shutdown(); +#endif + CloseShmem(); +} + +void VRManager::AddLayer(VRLayerParent* aLayer) { + mLayers.AppendElement(aLayer); + mDisplayInfo.mPresentingGroups |= aLayer->GetGroup(); + if (mLayers.Length() == 1) { + StartPresentation(); + } + + // Ensure that the content process receives the change immediately + if (mState != VRManagerState::Enumeration && + mState != VRManagerState::RuntimeDetection) { + DispatchVRDisplayInfoUpdate(); + } +} + +void VRManager::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 + if (mState != VRManagerState::Enumeration && + mState != VRManagerState::RuntimeDetection) { + DispatchVRDisplayInfoUpdate(); + } +} + +void VRManager::AddVRManagerParent(VRManagerParent* aVRManagerParent) { + mVRManagerParents.PutEntry(aVRManagerParent); +} + +void VRManager::RemoveVRManagerParent(VRManagerParent* aVRManagerParent) { + mVRManagerParents.RemoveEntry(aVRManagerParent); + if (mVRManagerParents.IsEmpty()) { + Destroy(); + } +} + +void VRManager::UpdateRequestedDevices() { + bool bHaveEventListener = false; + bool bHaveEventListenerNonFocus = false; + bool bHaveControllerListener = false; + + for (auto iter = mVRManagerParents.Iter(); !iter.Done(); iter.Next()) { + VRManagerParent* vmp = iter.Get()->GetKey(); + bHaveEventListener |= vmp->HaveEventListener() && vmp->GetVRActiveStatus(); + bHaveEventListenerNonFocus |= + vmp->HaveEventListener() && !vmp->GetVRActiveStatus(); + bHaveControllerListener |= vmp->HaveControllerListener(); + } + + mVRDisplaysRequested = bHaveEventListener; + mVRDisplaysRequestedNonFocus = bHaveEventListenerNonFocus; + // We only currently allow controllers to be used when + // also activating a VR display + mVRControllersRequested = mVRDisplaysRequested && bHaveControllerListener; +} + +/** + * VRManager::NotifyVsync must be called on every 2d vsync (usually at 60hz). + * This must be called even when no WebVR site is active. + * If we don't have a 2d display attached to the system, we can call this + * at the VR display's native refresh rate. + **/ +void VRManager::NotifyVsync(const TimeStamp& aVsyncTimestamp) { + if (mState != VRManagerState::Active) { + return; + } + /** + * If the display isn't presenting, refresh the sensors and trigger + * VRDisplay.requestAnimationFrame at the normal 2d display refresh rate. + */ + if (mDisplayInfo.mPresentingGroups == 0) { + StartFrame(); + } +} + +void VRManager::StartTasks() { + if (!mTaskTimer) { + mTaskInterval = GetOptimalTaskInterval(); + mTaskTimer = NS_NewTimer(); + mTaskTimer->SetTarget(CompositorThread()); + mTaskTimer->InitWithNamedFuncCallback( + TaskTimerCallback, this, mTaskInterval, + nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP, + "VRManager::TaskTimerCallback"); + } +} + +void VRManager::StopTasks() { + if (mTaskTimer) { + mTaskTimer->Cancel(); + mTaskTimer = nullptr; + } +} + +/*static*/ +void VRManager::TaskTimerCallback(nsITimer* aTimer, void* aClosure) { + /** + * It is safe to use the pointer passed in aClosure to reference the + * VRManager object as the timer is canceled in VRManager::Destroy. + * VRManager::Destroy set mState to VRManagerState::Disabled, which + * is asserted in the VRManager destructor, guaranteeing that this + * functions runs if and only if the VRManager object is valid. + */ + VRManager* self = static_cast<VRManager*>(aClosure); + self->RunTasks(); + + if (self->mAppPaused) { + // When the apps goes the background (e.g. Android) we should stop the + // tasks. + self->StopTasks(); + self->mState = VRManagerState::Idle; + } +} + +void VRManager::RunTasks() { + // Will be called once every 1ms when a VR presentation + // is active or once per vsync when a VR presentation is + // not active. + + if (mState == VRManagerState::Disabled) { + // We may have been destroyed but still have messages + // in the queue from mTaskTimer. Bail out to avoid + // running them. + return; + } + + TimeStamp now = TimeStamp::Now(); + double lastTickMs = mAccumulator100ms; + double deltaTime = 0.0f; + if (!mLastTickTime.IsNull()) { + deltaTime = (now - mLastTickTime).ToMilliseconds(); + } + mAccumulator100ms += deltaTime; + mLastTickTime = now; + + if (deltaTime > 0.0f && floor(mAccumulator100ms) != floor(lastTickMs)) { + // Even if more than 1 ms has passed, we will only + // execute Run1msTasks() once. + Run1msTasks(deltaTime); + } + + if (floor(mAccumulator100ms * 0.1f) != floor(lastTickMs * 0.1f)) { + // Even if more than 10 ms has passed, we will only + // execute Run10msTasks() once. + Run10msTasks(); + } + + if (mAccumulator100ms >= 100.0f) { + // Even if more than 100 ms has passed, we will only + // execute Run100msTasks() once. + Run100msTasks(); + mAccumulator100ms = fmod(mAccumulator100ms, 100.0f); + } + + uint32_t optimalTaskInterval = GetOptimalTaskInterval(); + if (mTaskTimer && optimalTaskInterval != mTaskInterval) { + mTaskTimer->SetDelay(optimalTaskInterval); + mTaskInterval = optimalTaskInterval; + } +} + +uint32_t VRManager::GetOptimalTaskInterval() { + /** + * When either VR content is detected or VR hardware + * has already been activated, we schedule tasks more + * frequently. + */ + bool wantGranularTasks = mVRDisplaysRequested || mVRControllersRequested || + mDisplayInfo.mDisplayID != 0; + if (wantGranularTasks) { + return kVRActiveTaskInterval; + } + + return kVRIdleTaskInterval; +} + +/** + * Run1msTasks() is guaranteed not to be + * called more than once within 1ms. + * When VR is not active, this will be + * called once per VSync if it wasn't + * called within the last 1ms. + */ +void VRManager::Run1msTasks(double aDeltaTime) { UpdateHaptics(aDeltaTime); } + +/** + * Run10msTasks() is guaranteed not to be + * called more than once within 10ms. + * When VR is not active, this will be + * called once per VSync if it wasn't + * called within the last 10ms. + */ +void VRManager::Run10msTasks() { + UpdateRequestedDevices(); + CheckWatchDog(); + ExpireNavigationTransition(); + PullState(); + PushState(); +} + +/** + * Run100msTasks() is guaranteed not to be + * called more than once within 100ms. + * When VR is not active, this will be + * called once per VSync if it wasn't + * called within the last 100ms. + */ +void VRManager::Run100msTasks() { + // We must continually refresh the VR display enumeration to check + // for events that we must fire such as Window.onvrdisplayconnect + // Note that enumeration itself may activate display hardware, such + // as Oculus, so we only do this when we know we are displaying content + // that is looking for VR displays. +#if !defined(MOZ_WIDGET_ANDROID) + mServiceHost->Refresh(); + CheckForPuppetCompletion(); +#endif + ProcessManagerState(); +} + +void VRManager::CheckForInactiveTimeout() { + // Shut down the VR devices when not in use + if (mVRDisplaysRequested || mVRDisplaysRequestedNonFocus || + mVRControllersRequested || mEnumerationRequested || + mRuntimeDetectionRequested || mState == VRManagerState::Enumeration || + mState == VRManagerState::RuntimeDetection) { + // We are using a VR device, keep it alive + mLastActiveTime = TimeStamp::Now(); + } else if (mLastActiveTime.IsNull()) { + Shutdown(); + } else { + TimeDuration duration = TimeStamp::Now() - mLastActiveTime; + if (duration.ToMilliseconds() > StaticPrefs::dom_vr_inactive_timeout()) { + Shutdown(); + // We must not throttle the next enumeration request + // after an idle timeout, as it may result in the + // user needing to refresh the browser to detect + // VR hardware when leaving and returning to a VR + // site. + mLastDisplayEnumerationTime = TimeStamp(); + } + } +} + +void VRManager::CheckForShutdown() { + // Check for remote end shutdown + if (mDisplayInfo.mDisplayState.shutdown) { + Shutdown(); + } +} + +#if !defined(MOZ_WIDGET_ANDROID) +void VRManager::CheckForPuppetCompletion() { + // Notify content process about completion of puppet test resets + if (mState != VRManagerState::Active) { + for (auto iter = mManagerParentsWaitingForPuppetReset.Iter(); !iter.Done(); + iter.Next()) { + Unused << iter.Get()->GetKey()->SendNotifyPuppetResetComplete(); + } + mManagerParentsWaitingForPuppetReset.Clear(); + } + // Notify content process about completion of puppet test scripts + if (mManagerParentRunningPuppet) { + mServiceHost->CheckForPuppetCompletion(); + } +} + +void VRManager::NotifyPuppetComplete() { + // Notify content process about completion of puppet test scripts + if (mManagerParentRunningPuppet) { + Unused << mManagerParentRunningPuppet + ->SendNotifyPuppetCommandBufferCompleted(true); + mManagerParentRunningPuppet = nullptr; + } +} + +#endif // !defined(MOZ_WIDGET_ANDROID) + +void VRManager::StartFrame() { + if (mState != VRManagerState::Active) { + return; + } + AUTO_PROFILER_TRACING_MARKER("VR", "GetSensorState", OTHER); + + /** + * Do not start more VR frames until the last submitted frame is already + * processed, or the last has stalled for more than + * kVRMaxFrameSubmitDuration milliseconds. + */ + TimeStamp now = TimeStamp::Now(); + const TimeStamp lastFrameStart = + mLastFrameStart[mDisplayInfo.mFrameId % kVRMaxLatencyFrames]; + const bool isPresenting = mLastUpdateDisplayInfo.GetPresentingGroups() != 0; + double duration = + lastFrameStart.IsNull() ? 0.0 : (now - lastFrameStart).ToMilliseconds(); + if (isPresenting && mLastStartedFrame > 0 && + mDisplayInfo.mDisplayState.lastSubmittedFrameId < mLastStartedFrame && + duration < kVRMaxFrameSubmitDuration) { + return; + } + + mDisplayInfo.mFrameId++; + size_t bufferIndex = mDisplayInfo.mFrameId % kVRMaxLatencyFrames; + mDisplayInfo.mLastSensorState[bufferIndex] = mLastSensorState; + mLastFrameStart[bufferIndex] = now; + mFrameStarted = true; + mLastStartedFrame = mDisplayInfo.mFrameId; + + DispatchVRDisplayInfoUpdate(); +} + +void VRManager::DetectRuntimes() { + if (mState == VRManagerState::RuntimeDetection) { + // Runtime detection has already been started. + // This additional request will also receive the + // result from the first request. + return; + } + + // Detect XR runtimes to determine if they are + // capable of supporting VR or AR sessions, while + // avoiding activating any XR devices or persistent + // background software. + if (mRuntimeDetectionCompleted) { + // We have already detected runtimes, so we can + // immediately respond with the same results. + // This will require the user to restart the browser + // after installing or removing an XR device + // runtime. + DispatchRuntimeCapabilitiesUpdate(); + return; + } + mRuntimeDetectionRequested = true; + ProcessManagerState(); +} + +void VRManager::EnumerateDevices() { + if (mState == VRManagerState::Enumeration || + (mRuntimeDetectionCompleted && + (mVRDisplaysRequested || mEnumerationRequested))) { + // Enumeration has already been started. + // This additional request will also receive the + // result from the first request. + return; + } + // Activate XR runtimes and enumerate XR devices. + mEnumerationRequested = true; + ProcessManagerState(); +} + +void VRManager::ProcessManagerState() { + switch (mState) { + case VRManagerState::Disabled: + ProcessManagerState_Disabled(); + break; + case VRManagerState::Idle: + ProcessManagerState_Idle(); + break; + case VRManagerState::RuntimeDetection: + ProcessManagerState_DetectRuntimes(); + break; + case VRManagerState::Enumeration: + ProcessManagerState_Enumeration(); + break; + case VRManagerState::Active: + ProcessManagerState_Active(); + break; + case VRManagerState::Stopping: + ProcessManagerState_Stopping(); + break; + } + CheckForInactiveTimeout(); + CheckForShutdown(); +} + +void VRManager::ProcessManagerState_Disabled() { + MOZ_ASSERT(mState == VRManagerState::Disabled); + + if (!StaticPrefs::dom_vr_enabled()) { + return; + } + + if (mRuntimeDetectionRequested || mEnumerationRequested || + mVRDisplaysRequested) { + StartTasks(); + mState = VRManagerState::Idle; + } +} + +void VRManager::ProcessManagerState_Stopping() { + MOZ_ASSERT(mState == VRManagerState::Stopping); + PullState(); + /** + * In the case of Desktop, the VRService shuts itself down. + * Before it's finished stopping, it sets a flag in the ShMem + * to let VRManager know that it's done. VRManager watches for + * this flag and transitions out of the VRManagerState::Stopping + * state to VRManagerState::Idle. + */ +#if defined(MOZ_WIDGET_ANDROID) + // On Android, the VR service never actually shuts + // down or requests VRManager to stop. + Shutdown(); +#endif // defined(MOZ_WIDGET_ANDROID) +} + +void VRManager::ProcessManagerState_Idle_StartEnumeration() { + MOZ_ASSERT(mState == VRManagerState::Idle); + + if (!mEarliestRestartTime.IsNull() && + mEarliestRestartTime > TimeStamp::Now()) { + // When the VR Service shuts down it informs us of how long we + // must wait until we can re-start it. + // We must wait until mEarliestRestartTime before attempting + // to enumerate again. + return; + } + + /** + * Throttle the rate of enumeration to the interval set in + * VRDisplayEnumerateInterval + */ + if (!mLastDisplayEnumerationTime.IsNull()) { + TimeDuration duration = TimeStamp::Now() - mLastDisplayEnumerationTime; + if (duration.ToMilliseconds() < + StaticPrefs::dom_vr_display_enumerate_interval()) { + return; + } + } + + /** + * If we get this far, don't try again until + * the VRDisplayEnumerateInterval elapses + */ + mLastDisplayEnumerationTime = TimeStamp::Now(); + + OpenShmem(); + + mEnumerationRequested = false; + // We must block until enumeration has completed in order + // to signal that the WebVR promise should be resolved at the + // right time. +#if defined(MOZ_WIDGET_ANDROID) + // In Android, we need to make sure calling + // GeckoVRManager::SetExternalContext() from an external VR service + // before doing enumeration. + if (!mShmem->GetExternalShmem()) { + mShmem->CreateShMemForAndroid(); + } + if (mShmem->GetExternalShmem()) { + mState = VRManagerState::Enumeration; + } else { + // Not connected to shmem, so no devices to enumerate. + mDisplayInfo.Clear(); + DispatchVRDisplayInfoUpdate(); + } +#else + + PushState(); + + /** + * We must start the VR Service thread + * and VR Process before enumeration. + * We don't want to start this until we will + * actualy enumerate, to avoid continuously + * re-launching the thread/process when + * no hardware is found or a VR software update + * is in progress + */ + mServiceHost->StartService(); + mState = VRManagerState::Enumeration; +#endif // MOZ_WIDGET_ANDROID +} + +void VRManager::ProcessManagerState_Idle_StartRuntimeDetection() { + MOZ_ASSERT(mState == VRManagerState::Idle); + + OpenShmem(); + mBrowserState.detectRuntimesOnly = true; + mRuntimeDetectionRequested = false; + + // We must block until enumeration has completed in order + // to signal that the WebVR promise should be resolved at the + // right time. +#if defined(MOZ_WIDGET_ANDROID) + // In Android, we need to make sure calling + // GeckoVRManager::SetExternalContext() from an external VR service + // before doing enumeration. + if (!mShmem->GetExternalShmem()) { + mShmem->CreateShMemForAndroid(); + } + if (mShmem->GetExternalShmem()) { + mState = VRManagerState::RuntimeDetection; + } else { + // Not connected to shmem, so no runtimes to detect. + mRuntimeSupportFlags = VRDisplayCapabilityFlags::Cap_None; + mRuntimeDetectionCompleted = true; + DispatchRuntimeCapabilitiesUpdate(); + } +#else + + PushState(); + + /** + * We must start the VR Service thread + * and VR Process before enumeration. + * We don't want to start this until we will + * actualy enumerate, to avoid continuously + * re-launching the thread/process when + * no hardware is found or a VR software update + * is in progress + */ + mServiceHost->StartService(); + mState = VRManagerState::RuntimeDetection; +#endif // MOZ_WIDGET_ANDROID +} + +void VRManager::ProcessManagerState_Idle() { + MOZ_ASSERT(mState == VRManagerState::Idle); + + if (!mRuntimeDetectionCompleted) { + // Check if we should start detecting runtimes + // We must alwasy detect runtimes before doing anything + // else with the VR process. + // This will happen only once per browser startup. + if (mRuntimeDetectionRequested || mEnumerationRequested) { + ProcessManagerState_Idle_StartRuntimeDetection(); + } + return; + } + + // Check if we should start activating enumerating XR hardware + if (mRuntimeDetectionCompleted && + (mVRDisplaysRequested || mEnumerationRequested)) { + ProcessManagerState_Idle_StartEnumeration(); + } +} + +void VRManager::ProcessManagerState_DetectRuntimes() { + MOZ_ASSERT(mState == VRManagerState::RuntimeDetection); + MOZ_ASSERT(mShmem != nullptr); + + PullState(); + if (mEnumerationCompleted) { + /** + * When mBrowserState.detectRuntimesOnly is set, the + * VRService and VR process will shut themselves down + * automatically after detecting runtimes. + * mEnumerationCompleted is also used in this case, + * but to mean "enumeration of runtimes" not + * "enumeration of VR devices". + * + * We set mState to `VRManagerState::Stopping` + * to make sure that we don't try to do anything + * else with the active VRService until it has stopped. + * We must start another one when an XR session will be + * requested. + * + * This logic is optimized for the WebXR design, but still + * works for WebVR so it can continue to function until + * deprecated and removed. + */ + mState = VRManagerState::Stopping; + mRuntimeSupportFlags = mDisplayInfo.mDisplayState.capabilityFlags & + (VRDisplayCapabilityFlags::Cap_ImmersiveVR | + VRDisplayCapabilityFlags::Cap_ImmersiveAR | + VRDisplayCapabilityFlags::Cap_Inline); + mRuntimeDetectionCompleted = true; + DispatchRuntimeCapabilitiesUpdate(); + } +} + +void VRManager::ProcessManagerState_Enumeration() { + MOZ_ASSERT(mState == VRManagerState::Enumeration); + MOZ_ASSERT(mShmem != nullptr); + + PullState(); + if (mEnumerationCompleted) { + if (mDisplayInfo.mDisplayState.isConnected) { + mDisplayInfo.mDisplayID = VRManager::AllocateDisplayID(); + mState = VRManagerState::Active; + } else { + mDisplayInfo.Clear(); + mState = VRManagerState::Stopping; + } + DispatchVRDisplayInfoUpdate(); + } +} + +void VRManager::ProcessManagerState_Active() { + MOZ_ASSERT(mState == VRManagerState::Active); + + if (mDisplayInfo != mLastUpdateDisplayInfo) { + // While the display is active, send continuous updates + DispatchVRDisplayInfoUpdate(); + } +} + +void VRManager::DispatchVRDisplayInfoUpdate() { + for (auto iter = mVRManagerParents.Iter(); !iter.Done(); iter.Next()) { + Unused << iter.Get()->GetKey()->SendUpdateDisplayInfo(mDisplayInfo); + } + mLastUpdateDisplayInfo = mDisplayInfo; +} + +void VRManager::DispatchRuntimeCapabilitiesUpdate() { + VRDisplayCapabilityFlags flags = mRuntimeSupportFlags; + if (StaticPrefs::dom_vr_always_support_vr()) { + flags |= VRDisplayCapabilityFlags::Cap_ImmersiveVR; + } + + if (StaticPrefs::dom_vr_always_support_ar()) { + flags |= VRDisplayCapabilityFlags::Cap_ImmersiveAR; + } + + for (auto iter = mVRManagerParents.Iter(); !iter.Done(); iter.Next()) { + Unused << iter.Get()->GetKey()->SendUpdateRuntimeCapabilities(flags); + } +} + +void VRManager::StopAllHaptics() { + for (size_t i = 0; i < mozilla::ArrayLength(mBrowserState.hapticState); i++) { + ClearHapticSlot(i); + } + PushState(); +} + +void VRManager::VibrateHaptic(GamepadHandle aGamepadHandle, + uint32_t aHapticIndex, double aIntensity, + double aDuration, + const VRManagerPromise& aPromise) + +{ + if (mState != VRManagerState::Active) { + return; + } + // VRDisplayClient::FireGamepadEvents() assigns a controller ID with + // ranges based on displayID. We must translate this to the indexes + // understood by VRDisplayExternal. + uint32_t controllerBaseIndex = + kVRControllerMaxCount * mDisplayInfo.mDisplayID; + uint32_t controllerIndex = aGamepadHandle.GetValue() - controllerBaseIndex; + + TimeStamp now = TimeStamp::Now(); + size_t bestSlotIndex = 0; + // Default to an empty slot, or the slot holding the oldest haptic pulse + for (size_t i = 0; i < mozilla::ArrayLength(mBrowserState.hapticState); i++) { + const VRHapticState& state = mBrowserState.hapticState[i]; + if (state.inputFrameID == 0) { + // Unused slot, use it + bestSlotIndex = i; + break; + } + if (mHapticPulseRemaining[i] < mHapticPulseRemaining[bestSlotIndex]) { + // If no empty slots are available, fall back to overriding + // the pulse which is ending soonest. + bestSlotIndex = i; + } + } + // Override the last pulse on the same actuator if present. + for (size_t i = 0; i < mozilla::ArrayLength(mBrowserState.hapticState); i++) { + const VRHapticState& state = mBrowserState.hapticState[i]; + if (state.inputFrameID == 0) { + // This is an empty slot -- no match + continue; + } + if (state.controllerIndex == controllerIndex && + state.hapticIndex == aHapticIndex) { + // Found pulse on same actuator -- let's override it. + bestSlotIndex = i; + } + } + ClearHapticSlot(bestSlotIndex); + + // Populate the selected slot with new haptic state + size_t bufferIndex = mDisplayInfo.mFrameId % kVRMaxLatencyFrames; + VRHapticState& bestSlot = mBrowserState.hapticState[bestSlotIndex]; + bestSlot.inputFrameID = + mDisplayInfo.mLastSensorState[bufferIndex].inputFrameID; + bestSlot.controllerIndex = controllerIndex; + bestSlot.hapticIndex = aHapticIndex; + bestSlot.pulseStart = (float)(now - mLastFrameStart[bufferIndex]).ToSeconds(); + bestSlot.pulseDuration = + (float)aDuration * 0.001f; // Convert from ms to seconds + bestSlot.pulseIntensity = (float)aIntensity; + + mHapticPulseRemaining[bestSlotIndex] = aDuration; + MOZ_ASSERT(bestSlotIndex <= mHapticPromises.Length()); + if (bestSlotIndex == mHapticPromises.Length()) { + mHapticPromises.AppendElement( + UniquePtr<VRManagerPromise>(new VRManagerPromise(aPromise))); + } else { + mHapticPromises[bestSlotIndex] = + UniquePtr<VRManagerPromise>(new VRManagerPromise(aPromise)); + } + PushState(); +} + +void VRManager::StopVibrateHaptic(GamepadHandle aGamepadHandle) { + if (mState != VRManagerState::Active) { + return; + } + // VRDisplayClient::FireGamepadEvents() assigns a controller ID with + // ranges based on displayID. We must translate this to the indexes + // understood by VRDisplayExternal. + uint32_t controllerBaseIndex = + kVRControllerMaxCount * mDisplayInfo.mDisplayID; + uint32_t controllerIndex = aGamepadHandle.GetValue() - controllerBaseIndex; + + for (size_t i = 0; i < mozilla::ArrayLength(mBrowserState.hapticState); i++) { + VRHapticState& state = mBrowserState.hapticState[i]; + if (state.controllerIndex == controllerIndex) { + memset(&state, 0, sizeof(VRHapticState)); + } + } + PushState(); +} + +void VRManager::NotifyVibrateHapticCompleted(const VRManagerPromise& aPromise) { + aPromise.mParent->SendReplyGamepadVibrateHaptic(aPromise.mPromiseID); +} + +void VRManager::StartVRNavigation(const uint32_t& aDisplayID) { + if (mState != VRManagerState::Active) { + return; + } + /** + * We only support a single VRSession with a single VR display at a + * time; however, due to the asynchronous nature of the API, it's possible + * that the previously used VR display was a different one than the one now + * allocated. We catch these cases to avoid automatically activating the new + * VR displays. This situation is expected to be very rare and possibly never + * seen. Perhaps further simplification could be made in the content process + * code which passes around displayID's that may no longer be needed. + **/ + if (mDisplayInfo.GetDisplayID() != aDisplayID) { + return; + } + mBrowserState.navigationTransitionActive = true; + mVRNavigationTransitionEnd = TimeStamp(); + PushState(); +} + +void VRManager::StopVRNavigation(const uint32_t& aDisplayID, + const TimeDuration& aTimeout) { + if (mState != VRManagerState::Active) { + return; + } + if (mDisplayInfo.GetDisplayID() != aDisplayID) { + return; + } + if (aTimeout.ToMilliseconds() <= 0) { + mBrowserState.navigationTransitionActive = false; + mVRNavigationTransitionEnd = TimeStamp(); + PushState(); + } + mVRNavigationTransitionEnd = TimeStamp::Now() + aTimeout; +} + +#if !defined(MOZ_WIDGET_ANDROID) + +bool VRManager::RunPuppet(const nsTArray<uint64_t>& aBuffer, + VRManagerParent* aManagerParent) { + if (!StaticPrefs::dom_vr_puppet_enabled()) { + // Sanity check to ensure that a compromised content process + // can't use this to escalate permissions. + return false; + } + if (mManagerParentRunningPuppet != nullptr) { + // Only one parent may run a puppet at a time + return false; + } + mManagerParentRunningPuppet = aManagerParent; + mServiceHost->PuppetSubmit(aBuffer); + return true; +} + +void VRManager::ResetPuppet(VRManagerParent* aManagerParent) { + mManagerParentsWaitingForPuppetReset.PutEntry(aManagerParent); + if (mManagerParentRunningPuppet != nullptr) { + Unused << mManagerParentRunningPuppet + ->SendNotifyPuppetCommandBufferCompleted(false); + mManagerParentRunningPuppet = nullptr; + } + mServiceHost->PuppetReset(); + // In the event that we are shut down, the task timer won't be running + // to trigger CheckForPuppetCompletion. + // In this case, CheckForPuppetCompletion() would immediately resolve + // the promises for mManagerParentsWaitingForPuppetReset. + // We can simply call it once here to handle that case. + CheckForPuppetCompletion(); +} + +#endif // !defined(MOZ_WIDGET_ANDROID) + +void VRManager::PullState( + const std::function<bool()>& aWaitCondition /* = nullptr */) { + if (mShmem != nullptr) { + mShmem->PullSystemState(mDisplayInfo.mDisplayState, mLastSensorState, + mDisplayInfo.mControllerState, + mEnumerationCompleted, aWaitCondition); + } +} + +void VRManager::PushState(bool aNotifyCond) { + if (mShmem != nullptr) { + mShmem->PushBrowserState(mBrowserState, aNotifyCond); + } +} + +void VRManager::Destroy() { + if (mState == VRManagerState::Disabled) { + return; + } + Shutdown(); + StopTasks(); + mState = VRManagerState::Disabled; +} + +void VRManager::Shutdown() { + if (mState == VRManagerState::Disabled || mState == VRManagerState::Idle) { + return; + } + + if (mDisplayInfo.mDisplayState.shutdown) { + // Shutdown was requested by VR Service, so we must throttle + // as requested by the VR Service + TimeStamp now = TimeStamp::Now(); + mEarliestRestartTime = + now + TimeDuration::FromMilliseconds( + (double)mDisplayInfo.mDisplayState.minRestartInterval); + } + + StopAllHaptics(); + StopPresentation(); + CancelCurrentSubmitTask(); + ShutdownSubmitThread(); + + mDisplayInfo.Clear(); + mEnumerationCompleted = false; + + if (mState == VRManagerState::RuntimeDetection) { + /** + * We have failed to detect runtimes before shutting down. + * Ensure that promises are resolved + * + * This call to DispatchRuntimeCapabilitiesUpdate will only + * happen when we have failed to detect runtimes. In that case, + * mRuntimeSupportFlags will be 0 and send the correct message + * to the content process. + * + * When we are successful, we store the result in mRuntimeSupportFlags + * and never try again unless the browser is restarted. mRuntimeSupportFlags + * is never reset back to 0 in that case but we will never re-enter the + * VRManagerState::RuntimeDetection state and hit this code path again. + */ + DispatchRuntimeCapabilitiesUpdate(); + } + + if (mState == VRManagerState::Enumeration) { + // We have failed to enumerate VR devices before shutting down. + // Ensure that promises are resolved + DispatchVRDisplayInfoUpdate(); + } + +#if !defined(MOZ_WIDGET_ANDROID) + mServiceHost->StopService(); +#endif + mState = VRManagerState::Idle; + + // We will close Shmem in the DTOR to avoid + // mSubmitThread is still running but its shmem + // has been released. +} + +void VRManager::ShutdownVRManagerParents() { + // Close removes the CanvasParent from the set so take a copy first. + VRManagerParentSet vrManagerParents; + for (auto iter = mVRManagerParents.Iter(); !iter.Done(); iter.Next()) { + vrManagerParents.PutEntry(iter.Get()->GetKey()); + } + + for (auto iter = vrManagerParents.Iter(); !iter.Done(); iter.Next()) { + iter.Get()->GetKey()->Close(); + iter.Remove(); + } + + MOZ_DIAGNOSTIC_ASSERT(mVRManagerParents.IsEmpty(), + "Closing should have cleared all entries."); +} + +void VRManager::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. + */ + if (mState != VRManagerState::Active) { + return; + } + 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::dom_vr_display_rafMaxDuration()) { + bShouldStartFrame = true; + } + } + + if (bShouldStartFrame) { + StartFrame(); + } +} + +void VRManager::ExpireNavigationTransition() { + if (mState != VRManagerState::Active) { + return; + } + if (!mVRNavigationTransitionEnd.IsNull() && + TimeStamp::Now() > mVRNavigationTransitionEnd) { + mBrowserState.navigationTransitionActive = false; + } +} + +void VRManager::UpdateHaptics(double aDeltaTime) { + if (mState != VRManagerState::Active) { + return; + } + bool bNeedPush = false; + // Check for any haptic pulses that have ended and clear them + for (size_t i = 0; i < mozilla::ArrayLength(mBrowserState.hapticState); i++) { + const VRHapticState& state = mBrowserState.hapticState[i]; + if (state.inputFrameID == 0) { + // Nothing in this slot + continue; + } + mHapticPulseRemaining[i] -= aDeltaTime; + if (mHapticPulseRemaining[i] <= 0.0f) { + // The pulse has finished + ClearHapticSlot(i); + bNeedPush = true; + } + } + if (bNeedPush) { + PushState(); + } +} + +void VRManager::ClearHapticSlot(size_t aSlot) { + MOZ_ASSERT(aSlot < mozilla::ArrayLength(mBrowserState.hapticState)); + memset(&mBrowserState.hapticState[aSlot], 0, sizeof(VRHapticState)); + mHapticPulseRemaining[aSlot] = 0.0f; + if (aSlot < mHapticPromises.Length() && mHapticPromises[aSlot]) { + NotifyVibrateHapticCompleted(*(mHapticPromises[aSlot])); + mHapticPromises[aSlot] = nullptr; + } +} + +void VRManager::ShutdownSubmitThread() { + if (mSubmitThread) { + mSubmitThread->Shutdown(); + mSubmitThread = nullptr; + } +} + +void VRManager::StartPresentation() { + if (mState != VRManagerState::Active) { + return; + } + if (mBrowserState.presentationActive) { + return; + } + mTelemetry.Clear(); + mTelemetry.mPresentationStart = TimeStamp::Now(); + + // Indicate that we are ready to start immersive mode + mBrowserState.presentationActive = true; + mBrowserState.layerState[0].type = VRLayerType::LayerType_Stereo_Immersive; + PushState(); + + mDisplayInfo.mDisplayState.lastSubmittedFrameId = 0; + if (mDisplayInfo.mDisplayState.reportsDroppedFrames) { + mTelemetry.mLastDroppedFrameCount = + mDisplayInfo.mDisplayState.droppedFrameCount; + } + + mLastSubmittedFrameId = 0; + mLastStartedFrame = 0; +} + +void VRManager::StopPresentation() { + if (mState != VRManagerState::Active) { + return; + } + if (!mBrowserState.presentationActive) { + return; + } + + // Indicate that we have stopped immersive mode + mBrowserState.presentationActive = false; + memset(mBrowserState.layerState, 0, + sizeof(VRLayerState) * mozilla::ArrayLength(mBrowserState.layerState)); + + PushState(true); + + Telemetry::HistogramID timeSpentID = Telemetry::HistogramCount; + Telemetry::HistogramID droppedFramesID = Telemetry::HistogramCount; + int viewIn = 0; + + if (mDisplayInfo.mDisplayState.eightCC == + GFX_VR_EIGHTCC('O', 'c', 'u', 'l', 'u', 's', ' ', 'D')) { + // Oculus Desktop API + timeSpentID = Telemetry::WEBVR_TIME_SPENT_VIEWING_IN_OCULUS; + droppedFramesID = Telemetry::WEBVR_DROPPED_FRAMES_IN_OCULUS; + viewIn = 1; + } else if (mDisplayInfo.mDisplayState.eightCC == + GFX_VR_EIGHTCC('O', 'p', 'e', 'n', 'V', 'R', ' ', ' ')) { + // OpenVR API + timeSpentID = Telemetry::WEBVR_TIME_SPENT_VIEWING_IN_OPENVR; + droppedFramesID = Telemetry::WEBVR_DROPPED_FRAMES_IN_OPENVR; + viewIn = 2; + } + + if (viewIn) { + const TimeDuration duration = + TimeStamp::Now() - mTelemetry.mPresentationStart; + Telemetry::Accumulate(Telemetry::WEBVR_USERS_VIEW_IN, viewIn); + Telemetry::Accumulate(timeSpentID, duration.ToMilliseconds()); + const uint32_t droppedFramesPerSec = + (uint32_t)((double)(mDisplayInfo.mDisplayState.droppedFrameCount - + mTelemetry.mLastDroppedFrameCount) / + duration.ToSeconds()); + Telemetry::Accumulate(droppedFramesID, droppedFramesPerSec); + } +} + +bool VRManager::IsPresenting() { + if (mShmem) { + return mDisplayInfo.mPresentingGroups != 0; + } + return false; +} + +void VRManager::SetGroupMask(uint32_t aGroupMask) { + if (mState != VRManagerState::Active) { + return; + } + mDisplayInfo.mGroupMask = aGroupMask; +} + +void VRManager::SubmitFrame(VRLayerParent* aLayer, + const layers::SurfaceDescriptor& aTexture, + uint64_t aFrameId, const gfx::Rect& aLeftEyeRect, + const gfx::Rect& aRightEyeRect) { + if (mState != VRManagerState::Active) { + return; + } + 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; + } + + /** + * 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; + + mFrameStarted = false; + + RefPtr<CancelableRunnable> task = NewCancelableRunnableMethod< + StoreCopyPassByConstLRef<layers::SurfaceDescriptor>, uint64_t, + StoreCopyPassByConstLRef<gfx::Rect>, StoreCopyPassByConstLRef<gfx::Rect>>( + "gfx::VRManager::SubmitFrameInternal", this, + &VRManager::SubmitFrameInternal, aTexture, aFrameId, aLeftEyeRect, + aRightEyeRect); + + if (!mCurrentSubmitTask) { + mCurrentSubmitTask = task; +#if !defined(MOZ_WIDGET_ANDROID) + if (!mSubmitThread) { + mSubmitThread = new VRThread("VR_SubmitFrame"_ns); + } + mSubmitThread->Start(); + mSubmitThread->PostTask(task.forget()); +#else + CompositorThread()->Dispatch(task.forget()); +#endif // defined(MOZ_WIDGET_ANDROID) + } +} + +bool VRManager::SubmitFrame(const layers::SurfaceDescriptor& aTexture, + uint64_t aFrameId, const gfx::Rect& aLeftEyeRect, + const gfx::Rect& aRightEyeRect) { + if (mState != VRManagerState::Active) { + return false; + } +#if defined(XP_WIN) || defined(XP_MACOSX) || defined(MOZ_WIDGET_ANDROID) + MOZ_ASSERT(mBrowserState.layerState[0].type == + VRLayerType::LayerType_Stereo_Immersive); + VRLayer_Stereo_Immersive& layer = + mBrowserState.layerState[0].layer_stereo_immersive; + + switch (aTexture.type()) { +# if defined(XP_WIN) + case SurfaceDescriptor::TSurfaceDescriptorD3D10: { + const SurfaceDescriptorD3D10& surf = + aTexture.get_SurfaceDescriptorD3D10(); + layer.textureType = + VRLayerTextureType::LayerTextureType_D3D10SurfaceDescriptor; + layer.textureHandle = (void*)surf.handle(); + layer.textureSize.width = surf.size().width; + layer.textureSize.height = surf.size().height; + } break; +# elif defined(XP_MACOSX) + case SurfaceDescriptor::TSurfaceDescriptorMacIOSurface: { + // MacIOSurface ptr can't be fetched or used at different threads. + // Both of fetching and using this MacIOSurface are at the VRService + // thread. + const auto& desc = aTexture.get_SurfaceDescriptorMacIOSurface(); + layer.textureType = VRLayerTextureType::LayerTextureType_MacIOSurface; + layer.textureHandle = desc.surfaceId(); + RefPtr<MacIOSurface> surf = + MacIOSurface::LookupSurface(desc.surfaceId(), desc.scaleFactor(), + !desc.isOpaque(), desc.yUVColorSpace()); + if (surf) { + layer.textureSize.width = surf->GetDevicePixelWidth(); + layer.textureSize.height = surf->GetDevicePixelHeight(); + } + } break; +# elif defined(MOZ_WIDGET_ANDROID) + case SurfaceDescriptor::TSurfaceTextureDescriptor: { + const SurfaceTextureDescriptor& desc = + aTexture.get_SurfaceTextureDescriptor(); + java::GeckoSurfaceTexture::LocalRef surfaceTexture = + java::GeckoSurfaceTexture::Lookup(desc.handle()); + if (!surfaceTexture) { + NS_WARNING("VRManager::SubmitFrame failed to get a SurfaceTexture"); + return false; + } + layer.textureType = + VRLayerTextureType::LayerTextureType_GeckoSurfaceTexture; + layer.textureHandle = desc.handle(); + layer.textureSize.width = desc.size().width; + layer.textureSize.height = desc.size().height; + } break; +# endif + default: { + MOZ_ASSERT(false); + return false; + } + } + + layer.frameId = aFrameId; + layer.inputFrameId = + mDisplayInfo.mLastSensorState[mDisplayInfo.mFrameId % kVRMaxLatencyFrames] + .inputFrameID; + + layer.leftEyeRect.x = aLeftEyeRect.x; + layer.leftEyeRect.y = aLeftEyeRect.y; + layer.leftEyeRect.width = aLeftEyeRect.width; + layer.leftEyeRect.height = aLeftEyeRect.height; + layer.rightEyeRect.x = aRightEyeRect.x; + layer.rightEyeRect.y = aRightEyeRect.y; + layer.rightEyeRect.width = aRightEyeRect.width; + layer.rightEyeRect.height = aRightEyeRect.height; + + PushState(true); + + PullState([&]() { + return (mDisplayInfo.mDisplayState.lastSubmittedFrameId >= aFrameId) || + mDisplayInfo.mDisplayState.suppressFrames || + !mDisplayInfo.mDisplayState.isConnected; + }); + + if (mDisplayInfo.mDisplayState.suppressFrames || + !mDisplayInfo.mDisplayState.isConnected) { + // External implementation wants to supress frames, service has shut + // down or hardware has been disconnected. + return false; + } + + return mDisplayInfo.mDisplayState.lastSubmittedFrameSuccessful; +#else + MOZ_ASSERT(false); // Not implmented for this platform + return false; +#endif +} + +void VRManager::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_MARKER("VR", "SubmitFrameAtVRDisplayExternal", OTHER); + + { // scope lock + MonitorAutoLock lock(mCurrentSubmitTaskMonitor); + + if (!SubmitFrame(aTexture, aFrameId, aLeftEyeRect, aRightEyeRect)) { + mCurrentSubmitTask = nullptr; + return; + } + mCurrentSubmitTask = nullptr; + } + +#if defined(XP_WIN) || defined(XP_MACOSX) + + /** + * 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 + * StartFrame immediately, as it will run unbounded. + * If StartFrame is not called here due to SubmitFrame failing, the + * fallback "watchdog" code in VRManager::NotifyVSync() will cause + * frames to continue at a lower refresh rate until frame submission + * succeeds again. + */ + CompositorThread()->Dispatch(NewRunnableMethod("gfx::VRManager::StartFrame", + this, &VRManager::StartFrame)); +#elif defined(MOZ_WIDGET_ANDROID) + // We are already in the CompositorThreadHolder event loop on Android. + StartFrame(); +#endif +} + +void VRManager::CancelCurrentSubmitTask() { + MonitorAutoLock lock(mCurrentSubmitTaskMonitor); + if (mCurrentSubmitTask) { + mCurrentSubmitTask->Cancel(); + mCurrentSubmitTask = nullptr; + } +} + +//----------------------------------------------------------------------------- +// VRManager::nsIObserver +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +VRManager::Observe(nsISupports* subject, const char* topic, + const char16_t* data) { + if (!strcmp(topic, "application-background")) { + // StopTasks() is called later in the timer thread based on this flag to + // avoid threading issues. + mAppPaused = true; + } else if (!strcmp(topic, "application-foreground") && mAppPaused) { + mAppPaused = false; + // When the apps goes the foreground (e.g. Android) we should restart the + // tasks. + StartTasks(); + } + return NS_OK; +} + +NS_IMPL_ISUPPORTS(VRManager, nsIObserver) + +} // namespace mozilla::gfx diff --git a/gfx/vr/VRManager.h b/gfx/vr/VRManager.h new file mode 100644 index 0000000000..8abbad76da --- /dev/null +++ b/gfx/vr/VRManager.h @@ -0,0 +1,194 @@ +/* -*- 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/. */ + +#ifndef GFX_VR_MANAGER_H +#define GFX_VR_MANAGER_H + +#include "nsHashKeys.h" +#include "nsIObserver.h" +#include "nsTArray.h" +#include "nsThreadUtils.h" +#include "mozilla/Atomics.h" +#include "mozilla/dom/GamepadHandle.h" +#include "mozilla/layers/LayersSurfaces.h" // for SurfaceDescriptor +#include "mozilla/Monitor.h" +#include "mozilla/TimeStamp.h" +#include "gfxVR.h" + +class nsITimer; +namespace mozilla { +namespace gfx { +class VRLayerParent; +class VRManagerParent; +class VRServiceHost; +class VRThread; +class VRShMem; + +enum class VRManagerState : uint32_t { + Disabled, // All VRManager activity is stopped + Idle, // No VR hardware has been activated, but background tasks are running + RuntimeDetection, // Waiting for detection of runtimes without starting up VR + // hardware + Enumeration, // Waiting for enumeration and startup of VR hardware + Active, // VR hardware is active + Stopping, // Waiting for the VRService to stop +}; + +class VRManager : nsIObserver { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIOBSERVER + + static void ManagerInit(); + static VRManager* Get(); + static VRManager* MaybeGet(); + + void AddVRManagerParent(VRManagerParent* aVRManagerParent); + void RemoveVRManagerParent(VRManagerParent* aVRManagerParent); + + void NotifyVsync(const TimeStamp& aVsyncTimestamp); + + void DetectRuntimes(); + void EnumerateDevices(); + void StopAllHaptics(); + + void VibrateHaptic(mozilla::dom::GamepadHandle aGamepadHandle, + uint32_t aHapticIndex, double aIntensity, double aDuration, + const VRManagerPromise& aPromise); + void StopVibrateHaptic(mozilla::dom::GamepadHandle aGamepadHandle); + void NotifyVibrateHapticCompleted(const VRManagerPromise& aPromise); + void StartVRNavigation(const uint32_t& aDisplayID); + void StopVRNavigation(const uint32_t& aDisplayID, + const TimeDuration& aTimeout); + void Shutdown(); + void ShutdownVRManagerParents(); +#if !defined(MOZ_WIDGET_ANDROID) + bool RunPuppet(const nsTArray<uint64_t>& aBuffer, + VRManagerParent* aManagerParent); + void ResetPuppet(VRManagerParent* aManagerParent); + void NotifyPuppetComplete(); +#endif + void AddLayer(VRLayerParent* aLayer); + void RemoveLayer(VRLayerParent* aLayer); + void SetGroupMask(uint32_t aGroupMask); + void SubmitFrame(VRLayerParent* aLayer, + const layers::SurfaceDescriptor& aTexture, uint64_t aFrameId, + const gfx::Rect& aLeftEyeRect, + const gfx::Rect& aRightEyeRect); + bool IsPresenting(); + + private: + VRManager(); + virtual ~VRManager(); + void Destroy(); + void StartTasks(); + void StopTasks(); + static void TaskTimerCallback(nsITimer* aTimer, void* aClosure); + void RunTasks(); + void Run1msTasks(double aDeltaTime); + void Run10msTasks(); + void Run100msTasks(); + uint32_t GetOptimalTaskInterval(); + void ProcessManagerState(); + void ProcessManagerState_Disabled(); + void ProcessManagerState_Idle(); + void ProcessManagerState_Idle_StartRuntimeDetection(); + void ProcessManagerState_Idle_StartEnumeration(); + void ProcessManagerState_DetectRuntimes(); + void ProcessManagerState_Enumeration(); + void ProcessManagerState_Active(); + void ProcessManagerState_Stopping(); + void PullState(const std::function<bool()>& aWaitCondition = nullptr); + void PushState(const bool aNotifyCond = false); + static uint32_t AllocateDisplayID(); + void DispatchVRDisplayInfoUpdate(); + void DispatchRuntimeCapabilitiesUpdate(); + void UpdateRequestedDevices(); + void CheckForInactiveTimeout(); +#if !defined(MOZ_WIDGET_ANDROID) + void CheckForPuppetCompletion(); +#endif + void CheckForShutdown(); + void CheckWatchDog(); + void ExpireNavigationTransition(); + void OpenShmem(); + void CloseShmem(); + void UpdateHaptics(double aDeltaTime); + void ClearHapticSlot(size_t aSlot); + void StartFrame(); + void ShutdownSubmitThread(); + void StartPresentation(); + void StopPresentation(); + void CancelCurrentSubmitTask(); + + void SubmitFrameInternal(const layers::SurfaceDescriptor& aTexture, + uint64_t aFrameId, const gfx::Rect& aLeftEyeRect, + const gfx::Rect& aRightEyeRect); + bool SubmitFrame(const layers::SurfaceDescriptor& aTexture, uint64_t aFrameId, + const gfx::Rect& aLeftEyeRect, + const gfx::Rect& aRightEyeRect); + + Atomic<VRManagerState> mState; + typedef nsTHashtable<nsRefPtrHashKey<VRManagerParent>> VRManagerParentSet; + VRManagerParentSet mVRManagerParents; +#if !defined(MOZ_WIDGET_ANDROID) + VRManagerParentSet mManagerParentsWaitingForPuppetReset; + RefPtr<VRManagerParent> mManagerParentRunningPuppet; +#endif + // Weak reference to mLayers entries are cleared in + // VRLayerParent destructor + nsTArray<VRLayerParent*> mLayers; + + TimeStamp mLastDisplayEnumerationTime; + TimeStamp mLastActiveTime; + TimeStamp mLastTickTime; + TimeStamp mEarliestRestartTime; + TimeStamp mVRNavigationTransitionEnd; + TimeStamp mLastFrameStart[kVRMaxLatencyFrames]; + double mAccumulator100ms; + bool mRuntimeDetectionRequested; + bool mRuntimeDetectionCompleted; + bool mEnumerationRequested; + bool mEnumerationCompleted; + bool mVRDisplaysRequested; + bool mVRDisplaysRequestedNonFocus; + bool mVRControllersRequested; + bool mFrameStarted; + uint32_t mTaskInterval; + RefPtr<nsITimer> mTaskTimer; + mozilla::Monitor mCurrentSubmitTaskMonitor; + RefPtr<CancelableRunnable> mCurrentSubmitTask; + uint64_t mLastSubmittedFrameId; + uint64_t mLastStartedFrame; + VRDisplayCapabilityFlags mRuntimeSupportFlags; + bool mAppPaused; + + // Note: mShmem doesn't support RefPtr; thus, do not share this private + // pointer so that its lifetime can still be controlled by VRManager + VRShMem* mShmem; + bool mVRProcessEnabled; + +#if !defined(MOZ_WIDGET_ANDROID) + RefPtr<VRServiceHost> mServiceHost; +#endif + + static Atomic<uint32_t> sDisplayBase; + RefPtr<VRThread> mSubmitThread; + VRTelemetry mTelemetry; + nsTArray<UniquePtr<VRManagerPromise>> mHapticPromises; + // Duration of haptic pulse time remaining (milliseconds) + double mHapticPulseRemaining[kVRHapticsMaxCount]; + + VRDisplayInfo mDisplayInfo; + VRDisplayInfo mLastUpdateDisplayInfo; + VRBrowserState mBrowserState; + VRHMDSensorState mLastSensorState; +}; + +} // namespace gfx +} // namespace mozilla + +#endif // GFX_VR_MANAGER_H diff --git a/gfx/vr/VRPuppetCommandBuffer.cpp b/gfx/vr/VRPuppetCommandBuffer.cpp new file mode 100644 index 0000000000..310a233b88 --- /dev/null +++ b/gfx/vr/VRPuppetCommandBuffer.cpp @@ -0,0 +1,515 @@ +/* -*- 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 "VRPuppetCommandBuffer.h" +#include "prthread.h" +#include "mozilla/ClearOnShutdown.h" + +namespace mozilla::gfx { + +static StaticRefPtr<VRPuppetCommandBuffer> sVRPuppetCommandBufferSingleton; + +/* static */ +VRPuppetCommandBuffer& VRPuppetCommandBuffer::Get() { + if (sVRPuppetCommandBufferSingleton == nullptr) { + sVRPuppetCommandBufferSingleton = new VRPuppetCommandBuffer(); + ClearOnShutdown(&sVRPuppetCommandBufferSingleton); + } + return *sVRPuppetCommandBufferSingleton; +} + +/* static */ +bool VRPuppetCommandBuffer::IsCreated() { + return sVRPuppetCommandBufferSingleton != nullptr; +} + +VRPuppetCommandBuffer::VRPuppetCommandBuffer() + : mMutex("VRPuppetCommandBuffer::mMutex") { + MOZ_COUNT_CTOR(VRPuppetCommandBuffer); + MOZ_ASSERT(sVRPuppetCommandBufferSingleton == nullptr); + Reset(); +} + +VRPuppetCommandBuffer::~VRPuppetCommandBuffer() { + MOZ_COUNT_DTOR(VRPuppetCommandBuffer); +} + +void VRPuppetCommandBuffer::Submit(const nsTArray<uint64_t>& aBuffer) { + MutexAutoLock lock(mMutex); + mBuffer.AppendElements(aBuffer); + mEnded = false; + mEndedWithTimeout = false; +} + +bool VRPuppetCommandBuffer::HasEnded() { + MutexAutoLock lock(mMutex); + return mEnded; +} + +void VRPuppetCommandBuffer::Reset() { + MutexAutoLock lock(mMutex); + memset(&mPendingState, 0, sizeof(VRSystemState)); + memset(&mCommittedState, 0, sizeof(VRSystemState)); + for (int iControllerIdx = 0; iControllerIdx < kVRControllerMaxCount; + iControllerIdx++) { + for (int iHaptic = 0; iHaptic < kNumPuppetHaptics; iHaptic++) { + mHapticPulseRemaining[iControllerIdx][iHaptic] = 0.0f; + mHapticPulseIntensity[iControllerIdx][iHaptic] = 0.0f; + } + } + mDataOffset = 0; + mPresentationRequested = false; + mFrameSubmitted = false; + mFrameAccepted = false; + mTimeoutDuration = 10.0f; + mWaitRemaining = 0.0f; + mBlockedTime = 0.0f; + mTimerElapsed = 0.0f; + mEnded = true; + mEndedWithTimeout = false; + mLastRunTimestamp = TimeStamp(); + mTimerSamples.Clear(); + mBuffer.Clear(); +} + +bool VRPuppetCommandBuffer::RunCommand(uint64_t aCommand, double aDeltaTime) { + /** + * Run a single command. If the command is blocking on a state change and + * can't be executed, return false. + * + * VRPuppetCommandBuffer::RunCommand is only called by + *VRPuppetCommandBuffer::Run(), which is already holding the mutex. + * + * Note that VRPuppetCommandBuffer::RunCommand may potentially be called >1000 + *times per frame. It might not hurt to add an assert here, but we should + *avoid adding code which may potentially malloc (eg string handling) even for + *debug builds here. This function will need to be reasonably fast, even in + *debug builds which will be using it during tests. + **/ + switch ((VRPuppet_Command)(aCommand & 0xff00000000000000)) { + case VRPuppet_Command::VRPuppet_End: + CompleteTest(false); + break; + case VRPuppet_Command::VRPuppet_ClearAll: + memset(&mPendingState, 0, sizeof(VRSystemState)); + memset(&mCommittedState, 0, sizeof(VRSystemState)); + mPresentationRequested = false; + mFrameSubmitted = false; + mFrameAccepted = false; + break; + case VRPuppet_Command::VRPuppet_ClearController: { + uint8_t controllerIdx = aCommand & 0x00000000000000ff; + if (controllerIdx < kVRControllerMaxCount) { + mPendingState.controllerState[controllerIdx].Clear(); + } + } break; + case VRPuppet_Command::VRPuppet_Timeout: + mTimeoutDuration = (double)(aCommand & 0x00000000ffffffff) / 1000.0f; + break; + case VRPuppet_Command::VRPuppet_Wait: + if (mWaitRemaining == 0.0f) { + mWaitRemaining = (double)(aCommand & 0x00000000ffffffff) / 1000.0f; + // Wait timer started, block + return false; + } + mWaitRemaining -= aDeltaTime; + if (mWaitRemaining > 0.0f) { + // Wait timer still running, block + return false; + } + // Wait timer has elapsed, unblock + mWaitRemaining = 0.0f; + break; + case VRPuppet_Command::VRPuppet_WaitSubmit: + if (!mFrameSubmitted) { + return false; + } + break; + case VRPuppet_Command::VRPuppet_CaptureFrame: + // TODO - Capture the frame and record the output (Bug 1555180) + break; + case VRPuppet_Command::VRPuppet_AcknowledgeFrame: + mFrameSubmitted = false; + mFrameAccepted = true; + break; + case VRPuppet_Command::VRPuppet_RejectFrame: + mFrameSubmitted = false; + mFrameAccepted = false; + break; + case VRPuppet_Command::VRPuppet_WaitPresentationStart: + if (!mPresentationRequested) { + return false; + } + break; + case VRPuppet_Command::VRPuppet_WaitPresentationEnd: + if (mPresentationRequested) { + return false; + } + break; + case VRPuppet_Command::VRPuppet_WaitHapticIntensity: { + // 0x0800cchhvvvvvvvv - VRPuppet_WaitHapticIntensity(c, h, v) + uint8_t iControllerIdx = (aCommand & 0x0000ff0000000000) >> 40; + if (iControllerIdx >= kVRControllerMaxCount) { + // Puppet test is broken, ensure it fails + return false; + } + uint8_t iHapticIdx = (aCommand & 0x000000ff00000000) >> 32; + if (iHapticIdx >= kNumPuppetHaptics) { + // Puppet test is broken, ensure it fails + return false; + } + uint32_t iHapticIntensity = + aCommand & 0x00000000ffffffff; // interpreted as 16.16 fixed point + + SimulateHaptics(aDeltaTime); + uint64_t iCurrentIntensity = + round(mHapticPulseIntensity[iControllerIdx][iHapticIdx] * + (1 << 16)); // convert to 16.16 fixed point + if (iCurrentIntensity > 0xffffffff) { + iCurrentIntensity = 0xffffffff; + } + if (iCurrentIntensity != iHapticIntensity) { + return false; + } + } break; + + case VRPuppet_Command::VRPuppet_StartTimer: + mTimerElapsed = 0.0f; + break; + case VRPuppet_Command::VRPuppet_StopTimer: + mTimerSamples.AppendElements(mTimerElapsed); + // TODO - Return the timer samples to Javascript once the command buffer + // is complete (Bug 1555182) + break; + + case VRPuppet_Command::VRPuppet_UpdateDisplay: + mDataOffset = (uint8_t*)&mPendingState.displayState - + (uint8_t*)&mPendingState + (aCommand & 0x00000000ffffffff); + break; + case VRPuppet_Command::VRPuppet_UpdateSensor: + mDataOffset = (uint8_t*)&mPendingState.sensorState - + (uint8_t*)&mPendingState + (aCommand & 0x00000000ffffffff); + break; + case VRPuppet_Command::VRPuppet_UpdateControllers: + mDataOffset = (uint8_t*)&mPendingState.controllerState - + (uint8_t*)&mPendingState + (aCommand & 0x00000000ffffffff); + break; + case VRPuppet_Command::VRPuppet_Commit: + memcpy(&mCommittedState, &mPendingState, sizeof(VRSystemState)); + break; + + case VRPuppet_Command::VRPuppet_Data7: + WriteData((aCommand & 0x00ff000000000000) >> 48); + [[fallthrough]]; + // Purposefully, no break + case VRPuppet_Command::VRPuppet_Data6: + WriteData((aCommand & 0x0000ff0000000000) >> 40); + [[fallthrough]]; + // Purposefully, no break + case VRPuppet_Command::VRPuppet_Data5: + WriteData((aCommand & 0x000000ff00000000) >> 32); + [[fallthrough]]; + // Purposefully, no break + case VRPuppet_Command::VRPuppet_Data4: + WriteData((aCommand & 0x00000000ff000000) >> 24); + [[fallthrough]]; + // Purposefully, no break + case VRPuppet_Command::VRPuppet_Data3: + WriteData((aCommand & 0x0000000000ff0000) >> 16); + [[fallthrough]]; + // Purposefully, no break + case VRPuppet_Command::VRPuppet_Data2: + WriteData((aCommand & 0x000000000000ff00) >> 8); + [[fallthrough]]; + // Purposefully, no break + case VRPuppet_Command::VRPuppet_Data1: + WriteData(aCommand & 0x00000000000000ff); + break; + } + return true; +} + +void VRPuppetCommandBuffer::WriteData(uint8_t aData) { + if (mDataOffset && mDataOffset < sizeof(VRSystemState)) { + ((uint8_t*)&mPendingState)[mDataOffset++] = aData; + } +} + +void VRPuppetCommandBuffer::Run() { + MutexAutoLock lock(mMutex); + TimeStamp now = TimeStamp::Now(); + double deltaTime = 0.0f; + if (!mLastRunTimestamp.IsNull()) { + deltaTime = (now - mLastRunTimestamp).ToSeconds(); + } + mLastRunTimestamp = now; + mTimerElapsed += deltaTime; + size_t transactionLength = 0; + while (transactionLength < mBuffer.Length() && !mEnded) { + if (RunCommand(mBuffer[transactionLength], deltaTime)) { + mBlockedTime = 0.0f; + transactionLength++; + } else { + mBlockedTime += deltaTime; + if (mBlockedTime > mTimeoutDuration) { + CompleteTest(true); + } + // If a command is blocked, we don't increment transactionLength, + // allowing the command to be retried on the next cycle + break; + } + } + mBuffer.RemoveElementsAt(0, transactionLength); +} + +void VRPuppetCommandBuffer::Run(VRSystemState& aState) { + Run(); + // We don't want to stomp over some members + bool bEnumerationCompleted = aState.enumerationCompleted; + bool bShutdown = aState.displayState.shutdown; + uint32_t minRestartInterval = aState.displayState.minRestartInterval; + + // Overwrite it all + memcpy(&aState, &mCommittedState, sizeof(VRSystemState)); + + // Restore the members + aState.enumerationCompleted = bEnumerationCompleted; + aState.displayState.shutdown = bShutdown; + aState.displayState.minRestartInterval = minRestartInterval; +} + +void VRPuppetCommandBuffer::StartPresentation() { + mPresentationRequested = true; + Run(); +} + +void VRPuppetCommandBuffer::StopPresentation() { + mPresentationRequested = false; + Run(); +} + +bool VRPuppetCommandBuffer::SubmitFrame() { + // Emulate blocking behavior of various XR API's as + // described by puppet script + mFrameSubmitted = true; + mFrameAccepted = false; + while (true) { + Run(); + if (!mFrameSubmitted || mEnded) { + break; + } + PR_Sleep(PR_INTERVAL_NO_WAIT); // Yield + } + + return mFrameAccepted; +} + +void VRPuppetCommandBuffer::VibrateHaptic(uint32_t aControllerIdx, + uint32_t aHapticIndex, + float aIntensity, float aDuration) { + if (aHapticIndex >= kNumPuppetHaptics || + aControllerIdx >= kVRControllerMaxCount) { + return; + } + + // We must Run() before and after updating haptic state to avoid script + // deadlocks + // The deadlocks would be caused by scripts that include two + // VRPuppet_WaitHapticIntensity commands. If + // VRPuppetCommandBuffer::VibrateHaptic() is called twice without advancing + // through the command buffer with VRPuppetCommandBuffer::Run() in between, + // the first VRPuppet_WaitHapticInensity may not see the transient value that + // it is waiting for, thus blocking forever and deadlocking the script. + Run(); + mHapticPulseRemaining[aControllerIdx][aHapticIndex] = aDuration; + mHapticPulseIntensity[aControllerIdx][aHapticIndex] = aIntensity; + Run(); +} + +void VRPuppetCommandBuffer::StopVibrateHaptic(uint32_t aControllerIdx) { + if (aControllerIdx >= kVRControllerMaxCount) { + return; + } + // We must Run() before and after updating haptic state to avoid script + // deadlocks + Run(); + for (int iHaptic = 0; iHaptic < kNumPuppetHaptics; iHaptic++) { + mHapticPulseRemaining[aControllerIdx][iHaptic] = 0.0f; + mHapticPulseIntensity[aControllerIdx][iHaptic] = 0.0f; + } + Run(); +} + +void VRPuppetCommandBuffer::StopAllHaptics() { + // We must Run() before and after updating haptic state to avoid script + // deadlocks + Run(); + for (int iControllerIdx = 0; iControllerIdx < kVRControllerMaxCount; + iControllerIdx++) { + for (int iHaptic = 0; iHaptic < kNumPuppetHaptics; iHaptic++) { + mHapticPulseRemaining[iControllerIdx][iHaptic] = 0.0f; + mHapticPulseIntensity[iControllerIdx][iHaptic] = 0.0f; + } + } + Run(); +} + +void VRPuppetCommandBuffer::SimulateHaptics(double aDeltaTime) { + for (int iControllerIdx = 0; iControllerIdx < kVRControllerMaxCount; + iControllerIdx++) { + for (int iHaptic = 0; iHaptic < kNumPuppetHaptics; iHaptic++) { + if (mHapticPulseIntensity[iControllerIdx][iHaptic] > 0.0f) { + mHapticPulseRemaining[iControllerIdx][iHaptic] -= aDeltaTime; + if (mHapticPulseRemaining[iControllerIdx][iHaptic] <= 0.0f) { + mHapticPulseRemaining[iControllerIdx][iHaptic] = 0.0f; + mHapticPulseIntensity[iControllerIdx][iHaptic] = 0.0f; + } + } + } + } +} + +void VRPuppetCommandBuffer::CompleteTest(bool aTimedOut) { + mEndedWithTimeout = aTimedOut; + mEnded = true; +} + +/** + * Generates a sequence of VRPuppet_Data# commands, as described + * in VRPuppetCommandBuffer.h, to encode the changes to be made to + * a "destination" structure to match the "source" structure. + * As the commands are encoded, the destination structure is updated + * to match the source. + * + * @param aBuffer + * The buffer in which the commands will be appended. + * @param aSrcStart + * Byte pointer to the start of the structure that + * will be copied from. + * @param aDstStart + * Byte pointer to the start of the structure that + * will be copied to. + * @param aLength + * Length of the structure that will be copied. + * @param aUpdateCommand + * VRPuppet_... command indicating which structure is being + * copied: + * VRPuppet_Command::VRPuppet_UpdateDisplay: + * A single VRDisplayState struct + * VRPuppet_Command::VRPuppet_UpdateSensor: + * A single VRHMDSensorState struct + * VRPuppet_Command::VRPuppet_UpdateControllers: + * An array of VRControllerState structs with a + * count of kVRControllerMaxCount + */ +void VRPuppetCommandBuffer::EncodeStruct(nsTArray<uint64_t>& aBuffer, + uint8_t* aSrcStart, uint8_t* aDstStart, + size_t aLength, + VRPuppet_Command aUpdateCommand) { + // Naive implementation, but will not be executed in realtime, so will not + // affect test timer results. Could be improved to avoid unaligned reads and + // to use SSE. + + // Pointer to source byte being compared+copied + uint8_t* src = aSrcStart; + + // Pointer to destination byte being compared+copied + uint8_t* dst = aDstStart; + + // Number of bytes packed into bufData + uint8_t bufLen = 0; + + // 64-bits to be interpreted as up to 7 separate bytes + // This will form the lower 56 bits of the command + uint64_t bufData = 0; + + // purgebuffer takes the bytes stored in bufData and generates a VRPuppet + // command representing those bytes as "VRPuppet Data". + // VRPUppet_Data1 encodes 1 byte + // VRPuppet_Data2 encodes 2 bytes + // and so on, until.. + // VRPuppet_Data7 encodes 7 bytes + // This command is appended to aBuffer, then bufLen and bufData are reset + auto purgeBuffer = [&]() { + // Only emit a command if there are data bytes in bufData + if (bufLen > 0) { + MOZ_ASSERT(bufLen <= 7); + uint64_t command = (uint64_t)VRPuppet_Command::VRPuppet_Data1; + command += ((uint64_t)VRPuppet_Command::VRPuppet_Data2 - + (uint64_t)VRPuppet_Command::VRPuppet_Data1) * + (bufLen - 1); + command |= bufData; + aBuffer.AppendElement(command); + bufLen = 0; + bufData = 0; + } + }; + + // Loop through the bytes of the structs. + // While copying the struct at aSrcStart to aDstStart, + // the differences are encoded as VRPuppet commands and + // appended to aBuffer. + for (size_t i = 0; i < aLength; i++) { + if (*src != *dst) { + // This byte is different + + // Copy the byte to the destination + *dst = *src; + + if (bufLen == 0) { + // This is the start of a new span of changed bytes + + // Output a command to specify the offset of the + // span. + aBuffer.AppendElement((uint64_t)aUpdateCommand + i); + + // Store this first byte in bufData. + // We will batch up to 7 bytes in one VRPuppet_DataXX + // command, so we won't emit it yet. + bufLen = 1; + bufData = *src; + } else if (bufLen <= 6) { + // This is the continuation of a span of changed bytes. + // There is room to add more bytes to bufData. + + // Store the next byte in bufData. + // We will batch up to 7 bytes in one VRPuppet_DataXX + // command, so we won't emit it yet. + bufData = (bufData << 8) | *src; + bufLen++; + } else { + MOZ_ASSERT(bufLen == 7); + // This is the continuation of a span of changed bytes. + // There are already 7 bytes in bufData, so we must emit + // the VRPuppet_Data7 command for the prior bytes before + // starting a new command. + aBuffer.AppendElement((uint64_t)VRPuppet_Command::VRPuppet_Data7 + + bufData); + + // Store this byte to be included in the next VRPuppet_DataXX + // command. + bufLen = 1; + bufData = *src; + } + } else { + // This byte is the same. + // If there are bytes in bufData, the span has now ended and we must + // emit a VRPuppet_DataXX command for the accumulated bytes. + // purgeBuffer will not emit any commands if there are no bytes + // accumulated. + purgeBuffer(); + } + // Advance to the next source and destination byte. + ++src; + ++dst; + } + // In the event that the very last byte of the structs differ, we must + // ensure that the accumulated bytes are emitted as a VRPuppet_DataXX + // command. + purgeBuffer(); +} + +} // namespace mozilla::gfx diff --git a/gfx/vr/VRPuppetCommandBuffer.h b/gfx/vr/VRPuppetCommandBuffer.h new file mode 100644 index 0000000000..6acbcbe80d --- /dev/null +++ b/gfx/vr/VRPuppetCommandBuffer.h @@ -0,0 +1,236 @@ +/* -*- 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/. */ + +#ifndef GFX_VR_SERVICE_VRPUPPETCOMMANDBUFFER_H +#define GFX_VR_SERVICE_VRPUPPETCOMMANDBUFFER_H + +#include <inttypes.h> +#include "mozilla/Mutex.h" +#include "nsTArray.h" +#include "moz_external_vr.h" +#include "mozilla/TimeStamp.h" + +namespace mozilla { +namespace gfx { + +/** + * A Puppet Device command buffer consists of a stream of 64-bit + * commands. + * The first 8 bits identifies the command and informs format of + * the remaining 56 bits. + * + * These commands will be streamed into a buffer until + * VRPuppet_End (0x0000000000000000) has been received. + * + * When VRPuppet_End is received, this command buffer will + * be executed asynchronously, collecting the timer results + * requested. + * + * In addition to the effects seen through the WebXR/VR API, tests + * can get further feedback from the Puppet device. + * Command buffers can be constructed such that when expected states + * are not reached, the timeout timer will expire. + * Data recorded with timer commands is returned when the command + * buffer is completed, to validate against accepted ranges and to + * quantify performance regressions. + * Images submitted to the Puppet display are rendered with the + * 2d browser output in order to enable reftests to validate + * output against reference images. + * + * The state of the virtual puppet device is expressed to the VR service + * in the same manner as physical devices -- using the VRDisplayState, + * VRHMDSensorState and VRControllerState structures. + * + * By enabling partial updates of these structures, the command buffer + * size is reduced to the values that change each frame. This enables + * realtime capture of a session, with physical hardware, for + * replay in automated tests and benchmarks. + * + * All updates to the state structures are atomically updated in the + * VR session thread, triggered by VRPuppet_Commit (0x1500000000000000). + * + * Command buffers are expected to be serialized to a human readable, + * ascii format if stored on disk. The binary representation is not + * guaranteed to be consistent between versions or target platforms. + * They should be re-constructed with the VRServiceTest DOM api at + * runtime. + * + * The command set: + * + * 0x0000000000000000 - VRPuppet_End() + * - End of stream, resolve promise returned by VRServiceTest::Play() + * + * 0x0100000000000000 - VRPuppet_ClearAll() + * - Clear all structs + * + * 0x02000000000000nn - VRPuppet_ClearController(n) + * - Clear a single controller struct + * + * 0x03000000nnnnnnnn - VRPuppet_Timeout(n) + * - Reset the timeout timer to n milliseconds + * - Initially the timeout timer starts at 10 seconds + * + * 0x04000000nnnnnnnn - VRPuppet_Wait(n) + * - Wait n milliseconds before advancing to next command + * + * 0x0500000000000000 - VRPuppet_WaitSubmit() + * - Wait until a frame has been submitted before advancing to the next command + * + * 0x0600000000000000 - VRPuppet_WaitPresentationStart() + * - Wait until a presentation becomes active + * + * 0x0700000000000000 - VRPuppet_WaitPresentationEnd() + * - Wait until a presentation ends + * + * 0x0800cchhvvvvvvvv - VRPuppet_WaitHapticIntensity(c, h, v) + * - Wait until controller at index c's haptic actuator at index h reaches value + * v. + * - v is a 16.16 fixed point value, with 1.0f being the highest intensity and + * 0.0f indicating that the haptic actuator is not running + * + * 0x0900000000000000 - VRPuppet_CaptureFrame() + * - Captures the submitted frame. Must later call + * VRPuppet_AcknowledgeFrame or VRPuppet_RejectFrame + * to unblock + * + * 0x0900000000000000 - VRPuppet_AcknowledgeFrame() + * - Acknowledge the submitted frame, unblocking the Submit call. + * + * 0x0a00000000000000 - VRPuppet_RejectFrame() + * - Reject the submitted frame, unblocking the Submit call. + * + * 0x0b00000000000000 - VRPuppet_StartTimer() + * - Starts the timer + * + * 0x0c00000000000000 - VRPuppet_StopTimer() + * - Stops the timer, the elapsed duration is recorded for access after the end + * of stream + * + * 0x0d000000aaaaaaaa - VRPuppet_UpdateDisplay(a) + * - Start writing data to the VRDisplayState struct, at offset a + * + * 0x0e000000aaaaaaaa - VRPuppet_UpdateSensor(a) + * - Start writing data to the VRHMDSensorState struct, at offset a + * + * 0x0f000000aaaaaaaa - VRPuppet_UpdateControllers(a) + * - Start writing data to the VRControllerState array, at offset a + * + * 0x100000000000000 - VRPuppet_Commit + * - Atomically commit the VRPuppet_Data updates to VRDisplayState, + * VRHMDSensorState and VRControllerState. + * + * 0xf0000000000000dd - VRPuppet_Data(d) + * - 1 byte of data + * + * 0xf10000000000dddd - VRPuppet_Data(d) + * - 2 bytes of data + * + * 0xf200000000dddddd - VRPuppet_Data(d) + * - 3 bytes of data + * + * 0xf3000000dddddddd - VRPuppet_Data(d) + * - 4 bytes of data + * + * 0xf40000dddddddddd - VRPuppet_Data(d) + * - 5 bytes of data + * + * 0xf500dddddddddddd - VRPuppet_Data(d) + * - 6 bytes of data + * + * 0xf6dddddddddddddd - VRPuppet_Data(d) + * - 7 bytes of data + * + */ +enum class VRPuppet_Command : uint64_t { + VRPuppet_End = 0x0000000000000000, + VRPuppet_ClearAll = 0x0100000000000000, + VRPuppet_ClearController = 0x0200000000000000, + VRPuppet_Timeout = 0x0300000000000000, + VRPuppet_Wait = 0x0400000000000000, + VRPuppet_WaitSubmit = 0x0500000000000000, + VRPuppet_WaitPresentationStart = 0x0600000000000000, + VRPuppet_WaitPresentationEnd = 0x0700000000000000, + VRPuppet_WaitHapticIntensity = 0x0800000000000000, + VRPuppet_CaptureFrame = 0x0900000000000000, + VRPuppet_AcknowledgeFrame = 0x0a00000000000000, + VRPuppet_RejectFrame = 0x0b00000000000000, + VRPuppet_StartTimer = 0x0c00000000000000, + VRPuppet_StopTimer = 0x0d00000000000000, + VRPuppet_UpdateDisplay = 0x0e00000000000000, + VRPuppet_UpdateSensor = 0x0f00000000000000, + VRPuppet_UpdateControllers = 0x1000000000000000, + VRPuppet_Commit = 0x1100000000000000, + VRPuppet_Data1 = 0xf000000000000000, + VRPuppet_Data2 = 0xf100000000000000, + VRPuppet_Data3 = 0xf200000000000000, + VRPuppet_Data4 = 0xf300000000000000, + VRPuppet_Data5 = 0xf400000000000000, + VRPuppet_Data6 = 0xf500000000000000, + VRPuppet_Data7 = 0xf600000000000000, +}; + +static const int kNumPuppetHaptics = 8; + +class VRPuppetCommandBuffer { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::gfx::VRPuppetCommandBuffer) + static VRPuppetCommandBuffer& Get(); + static bool IsCreated(); + + // Interface to VRTestSystem + void Submit(const nsTArray<uint64_t>& aBuffer); + void Reset(); + bool HasEnded(); + + // Interface to PuppetSession + void Run(VRSystemState& aState); + void StartPresentation(); + void StopPresentation(); + bool SubmitFrame(); + void VibrateHaptic(uint32_t aControllerIdx, uint32_t aHapticIndex, + float aIntensity, float aDuration); + void StopVibrateHaptic(uint32_t aControllerIdx); + void StopAllHaptics(); + + static void EncodeStruct(nsTArray<uint64_t>& aBuffer, uint8_t* aSrcStart, + uint8_t* aDstStart, size_t aLength, + gfx::VRPuppet_Command aUpdateCommand); + + private: + VRPuppetCommandBuffer(); + ~VRPuppetCommandBuffer(); + void Run(); + bool RunCommand(uint64_t aCommand, double aDeltaTime); + void WriteData(uint8_t aData); + void SimulateHaptics(double aDeltaTime); + void CompleteTest(bool aTimedOut); + nsTArray<uint64_t> mBuffer; + mozilla::Mutex mMutex; + VRSystemState mPendingState; + VRSystemState mCommittedState; + double mHapticPulseRemaining[kVRControllerMaxCount][kNumPuppetHaptics]; + float mHapticPulseIntensity[kVRControllerMaxCount][kNumPuppetHaptics]; + + size_t mDataOffset; + bool mPresentationRequested; + bool mFrameSubmitted; + bool mFrameAccepted; + double mTimeoutDuration; // Seconds + double mWaitRemaining; // Seconds + double mBlockedTime; // Seconds + double mTimerElapsed; // Seconds + TimeStamp mLastRunTimestamp; + + // Test Results: + bool mEnded; + bool mEndedWithTimeout; + nsTArray<double> mTimerSamples; +}; + +} // namespace gfx +} // namespace mozilla + +#endif // GFX_VR_SERVICE_VRPUPPETCOMMANDBUFFER_H diff --git a/gfx/vr/VRServiceHost.cpp b/gfx/vr/VRServiceHost.cpp new file mode 100644 index 0000000000..7250bfe74d --- /dev/null +++ b/gfx/vr/VRServiceHost.cpp @@ -0,0 +1,327 @@ +/* -*- 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 "VRServiceHost.h" +#include "VRGPUChild.h" +#include "VRPuppetCommandBuffer.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/gfx/GPUParent.h" +#include "service/VRService.h" +#include "VRManager.h" + +namespace mozilla { +namespace gfx { + +static StaticRefPtr<VRServiceHost> sVRServiceHostSingleton; + +/* static */ +void VRServiceHost::Init(bool aEnableVRProcess) { + MOZ_ASSERT(NS_IsMainThread()); + + if (sVRServiceHostSingleton == nullptr) { + sVRServiceHostSingleton = new VRServiceHost(aEnableVRProcess); + ClearOnShutdown(&sVRServiceHostSingleton); + } +} + +/* static */ +VRServiceHost* VRServiceHost::Get() { + MOZ_ASSERT(sVRServiceHostSingleton != nullptr); + return sVRServiceHostSingleton; +} + +VRServiceHost::VRServiceHost(bool aEnableVRProcess) + : mVRService(nullptr), + mVRProcessEnabled(aEnableVRProcess), + mVRProcessStarted(false), + mVRServiceReadyInVRProcess(false), + mVRServiceRequested(false) + +{ + MOZ_COUNT_CTOR(VRServiceHost); +} + +VRServiceHost::~VRServiceHost() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_COUNT_DTOR(VRServiceHost); +} + +void VRServiceHost::StartService() { + mVRServiceRequested = true; + if (mVRProcessEnabled) { + // VRService in the VR process + RefreshVRProcess(); + } else if (mVRService) { + // VRService in the GPU process if enabled, or + // the parent process if the GPU process is not enabled. + mVRService->Start(); + } +} + +void VRServiceHost::StopService() { + mVRServiceRequested = false; + if (mVRProcessEnabled) { + // VRService in the VR process + RefreshVRProcess(); + } else if (mVRService) { + // VRService in the GPU process if enabled, or + // the parent process if the GPU process is not enabled. + mVRService->Stop(); + } +} + +void VRServiceHost::Shutdown() { + PuppetReset(); + StopService(); + mVRService = nullptr; +} + +void VRServiceHost::Refresh() { + if (mVRService) { + mVRService->Refresh(); + } +} + +void VRServiceHost::CreateService(volatile VRExternalShmem* aShmem) { + MOZ_ASSERT(!mVRProcessEnabled); + mVRService = VRService::Create(aShmem); +} + +bool VRServiceHost::NeedVRProcess() { + if (!mVRProcessEnabled) { + return false; + } + return mVRServiceRequested; +} + +void VRServiceHost::RefreshVRProcess() { + // Start or stop the VR process if needed + if (NeedVRProcess()) { + if (!mVRProcessStarted) { + CreateVRProcess(); + } + } else { + if (mVRProcessStarted) { + ShutdownVRProcess(); + } + } +} + +void VRServiceHost::CreateVRProcess() { + // This is only allowed to run in the main thread of the GPU process + if (!XRE_IsGPUProcess()) { + return; + } + // Forward this to the main thread if not already there + if (!NS_IsMainThread()) { + RefPtr<Runnable> task = NS_NewRunnableFunction( + "VRServiceHost::CreateVRProcess", + []() -> void { VRServiceHost::Get()->CreateVRProcess(); }); + NS_DispatchToMainThread(task.forget()); + return; + } + if (mVRProcessStarted) { + return; + } + + mVRProcessStarted = true; + // Using PGPU channel to tell the main process + // to create the VR process. + gfx::GPUParent* gpu = GPUParent::GetSingleton(); + MOZ_ASSERT(gpu); + Unused << gpu->SendCreateVRProcess(); +} + +void VRServiceHost::NotifyVRProcessStarted() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mVRProcessEnabled); + if (!mVRProcessStarted) { + // We have received this after the VR process + // has been stopped; the VR service is no + // longer running in the VR process. + return; + } + + if (!VRGPUChild::IsCreated()) { + return; + } + VRGPUChild* vrGPUChild = VRGPUChild::Get(); + + // The VR service has started in the VR process + // If there were pending puppet commands, we + // can send them now. + // This must occur before the VRService + // is started so the buffer can be seen + // by VRPuppetSession::Initialize(). + if (!mPuppetPendingCommands.IsEmpty()) { + vrGPUChild->SendPuppetSubmit(mPuppetPendingCommands); + mPuppetPendingCommands.Clear(); + } + + vrGPUChild->SendStartVRService(); + mVRServiceReadyInVRProcess = true; +} + +void VRServiceHost::ShutdownVRProcess() { + // This is only allowed to run in the main thread of the GPU process + if (!XRE_IsGPUProcess()) { + return; + } + // Forward this to the main thread if not already there + if (!NS_IsMainThread()) { + RefPtr<Runnable> task = NS_NewRunnableFunction( + "VRServiceHost::ShutdownVRProcess", + []() -> void { VRServiceHost::Get()->ShutdownVRProcess(); }); + NS_DispatchToMainThread(task.forget()); + return; + } + if (VRGPUChild::IsCreated()) { + VRGPUChild* vrGPUChild = VRGPUChild::Get(); + vrGPUChild->SendStopVRService(); + if (!vrGPUChild->IsClosed()) { + vrGPUChild->Close(); + } + VRGPUChild::Shutdown(); + } + if (!mVRProcessStarted) { + return; + } + // Using PGPU channel to tell the main process + // to shutdown VR process. + gfx::GPUParent* gpu = GPUParent::GetSingleton(); + MOZ_ASSERT(gpu); + Unused << gpu->SendShutdownVRProcess(); + mVRProcessStarted = false; + mVRServiceReadyInVRProcess = false; +} + +void VRServiceHost::PuppetSubmit(const nsTArray<uint64_t>& aBuffer) { + if (!mVRProcessEnabled) { + // Puppet is running in this process, submit commands directly + VRPuppetCommandBuffer::Get().Submit(aBuffer); + return; + } + + // We need to send the buffer to the VR process + SendPuppetSubmitToVRProcess(aBuffer); +} + +void VRServiceHost::SendPuppetSubmitToVRProcess( + const nsTArray<uint64_t>& aBuffer) { + // This is only allowed to run in the main thread of the GPU process + if (!XRE_IsGPUProcess()) { + return; + } + // Forward this to the main thread if not already there + if (!NS_IsMainThread()) { + RefPtr<Runnable> task = NS_NewRunnableFunction( + "VRServiceHost::SendPuppetSubmitToVRProcess", + [buffer{aBuffer.Clone()}]() -> void { + VRServiceHost::Get()->SendPuppetSubmitToVRProcess(buffer); + }); + NS_DispatchToMainThread(task.forget()); + return; + } + if (!mVRServiceReadyInVRProcess) { + // Queue the commands to be sent to the VR process once it is started + mPuppetPendingCommands.AppendElements(aBuffer); + return; + } + if (VRGPUChild::IsCreated()) { + VRGPUChild* vrGPUChild = VRGPUChild::Get(); + vrGPUChild->SendPuppetSubmit(aBuffer); + } +} + +void VRServiceHost::PuppetReset() { + // If we're already into ShutdownFinal, the VRPuppetCommandBuffer instance + // will have been cleared, so don't try to access it after that point. + if (!mVRProcessEnabled && + !(NS_IsMainThread() && PastShutdownPhase(ShutdownPhase::ShutdownFinal))) { + // Puppet is running in this process, tell it to reset directly. + VRPuppetCommandBuffer::Get().Reset(); + } + + mPuppetPendingCommands.Clear(); + if (!mVRProcessStarted) { + // Process is stopped, so puppet state is already clear + return; + } + + // We need to tell the VR process to reset the puppet + SendPuppetResetToVRProcess(); +} + +void VRServiceHost::SendPuppetResetToVRProcess() { + // This is only allowed to run in the main thread of the GPU process + if (!XRE_IsGPUProcess()) { + return; + } + // Forward this to the main thread if not already there + if (!NS_IsMainThread()) { + RefPtr<Runnable> task = NS_NewRunnableFunction( + "VRServiceHost::SendPuppetResetToVRProcess", + []() -> void { VRServiceHost::Get()->SendPuppetResetToVRProcess(); }); + NS_DispatchToMainThread(task.forget()); + return; + } + if (VRGPUChild::IsCreated()) { + VRGPUChild* vrGPUChild = VRGPUChild::Get(); + vrGPUChild->SendPuppetReset(); + } +} + +void VRServiceHost::CheckForPuppetCompletion() { + if (!mVRProcessEnabled) { + // Puppet is running in this process, ask it directly + if (VRPuppetCommandBuffer::Get().HasEnded()) { + VRManager::Get()->NotifyPuppetComplete(); + } + } + if (!mPuppetPendingCommands.IsEmpty()) { + // There are puppet commands pending to be sent to the + // VR process once its started, thus it has not ended. + return; + } + if (!mVRProcessStarted) { + // The VR process will be kept alive as long + // as there is a queue in the puppet command + // buffer. If the process is stopped, we can + // infer that the queue has been cleared and + // puppet state is reset. + VRManager::Get()->NotifyPuppetComplete(); + } + + // We need to ask the VR process if the puppet has ended + SendPuppetCheckForCompletionToVRProcess(); + + // VRGPUChild::RecvNotifyPuppetComplete will call + // VRManager::NotifyPuppetComplete if the puppet has completed + // in the VR Process. +} + +void VRServiceHost::SendPuppetCheckForCompletionToVRProcess() { + // This is only allowed to run in the main thread of the GPU process + if (!XRE_IsGPUProcess()) { + return; + } + // Forward this to the main thread if not already there + if (!NS_IsMainThread()) { + RefPtr<Runnable> task = NS_NewRunnableFunction( + "VRServiceHost::SendPuppetCheckForCompletionToVRProcess", []() -> void { + VRServiceHost::Get()->SendPuppetCheckForCompletionToVRProcess(); + }); + NS_DispatchToMainThread(task.forget()); + return; + } + if (VRGPUChild::IsCreated()) { + VRGPUChild* vrGPUChild = VRGPUChild::Get(); + vrGPUChild->SendPuppetCheckForCompletion(); + } +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/vr/VRServiceHost.h b/gfx/vr/VRServiceHost.h new file mode 100644 index 0000000000..0c1a6193cc --- /dev/null +++ b/gfx/vr/VRServiceHost.h @@ -0,0 +1,93 @@ +/* -*- 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/. */ + +#ifndef GFX_VR_SERVICE_HOST_H +#define GFX_VR_SERVICE_HOST_H + +#include "mozilla/RefPtr.h" +#include "nsISupportsImpl.h" +#include "nsTArray.h" + +#include <cstdint> + +namespace mozilla { +namespace gfx { + +struct VRExternalShmem; +class VRService; + +/** + * VRServiceHost is allocated as a singleton in the GPU process. + * It is responsible for allocating VRService either within the GPU process + * or in the VR process. + * When the VR process is enabled, it maintains the state of the VR process, + * starting and stopping it as needed. + * VRServiceHost provides an interface that enables communication of the + * VRService in the same way regardless of it running within the GPU process + * or the VR process. + */ + +class VRServiceHost { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::gfx::VRServiceHost) + public: + static void Init(bool aEnableVRProcess); + static VRServiceHost* Get(); + + void Refresh(); + void StartService(); + void StopService(); + void Shutdown(); + void CreateService(volatile VRExternalShmem* aShmem); + void NotifyVRProcessStarted(); + void CheckForPuppetCompletion(); + + void PuppetSubmit(const nsTArray<uint64_t>& aBuffer); + void PuppetReset(); + + protected: + private: + explicit VRServiceHost(bool aEnableVRProcess); + ~VRServiceHost(); + + void RefreshVRProcess(); + bool NeedVRProcess(); + void CreateVRProcess(); + void ShutdownVRProcess(); + void SendPuppetResetToVRProcess(); + void SendPuppetCheckForCompletionToVRProcess(); + void SendPuppetSubmitToVRProcess(const nsTArray<uint64_t>& aBuffer); + + // Commands pending to be sent to the puppet device + // once the VR service is started. + nsTArray<uint64_t> mPuppetPendingCommands; + + RefPtr<VRService> mVRService; + // mVRProcessEnabled indicates that a separate, VR Process, should be used. + // This may be false if the VR process is disabled with the + // dom.vr.process.enabled preference or when the GPU process is disabled. + // mVRProcessEnabled will not change once the browser is started and does not + // reflect the started / stopped state of the VR Process. + bool mVRProcessEnabled; + // mVRProcessStarted is true when the VR Process is running. + bool mVRProcessStarted; + // mVRServiceReadyInVRProcess is true when the VR Process is running and the + // VRService in the VR Process is ready to accept commands. + bool mVRServiceReadyInVRProcess; + // mVRServiceRequested is true when the VRService is needed. This can be due + // to Web API activity (WebXR, WebVR), browser activity (eg, VR Video + // Playback), or a request to simulate a VR device with the VRServiceTest / + // puppet API. mVRServiceRequested indicates the intended state of the VR + // Service and is not an indication that the VR Service is ready to accept + // requests or that the VR Process is enabled or running. Toggling the + // mVRServiceRequested flag will result in the VR Service and/or the VR + // Process either starting or stopping as needed. + bool mVRServiceRequested; +}; + +} // namespace gfx +} // namespace mozilla + +#endif // GFX_VR_SERVICE_HOST_H diff --git a/gfx/vr/VRShMem.cpp b/gfx/vr/VRShMem.cpp new file mode 100644 index 0000000000..778a27e54c --- /dev/null +++ b/gfx/vr/VRShMem.cpp @@ -0,0 +1,736 @@ +/* -*- 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 "VRShMem.h" + +#ifdef MOZILLA_INTERNAL_API +# include "nsString.h" +# include "nsXULAppAPI.h" +#endif + +#include "gfxVRMutex.h" + +#if defined(XP_MACOSX) +# include <sys/mman.h> +# include <sys/stat.h> /* For mode constants */ +# include <fcntl.h> /* For O_* constants */ +#elif defined(MOZ_WIDGET_ANDROID) +# include "GeckoVRManager.h" +#endif + +#if !defined(XP_WIN) +# include <unistd.h> // for ::sleep +#endif + +using namespace mozilla::gfx; + +#ifdef XP_WIN +static const char* kShmemName = "moz.gecko.vr_ext." SHMEM_VERSION; +static LPCTSTR kMutexName = TEXT("mozilla::vr::ShmemMutex" SHMEM_VERSION); +#elif defined(XP_MACOSX) +static const char* kShmemName = "/moz.gecko.vr_ext." SHMEM_VERSION; +#endif // XP_WIN + +#if !defined(MOZ_WIDGET_ANDROID) +namespace { +void YieldThread() { +# if defined(XP_WIN) + ::Sleep(0); +# else + ::sleep(0); +# endif +} +} // anonymous namespace +#endif // !defined(MOZ_WIDGET_ANDROID) + +VRShMem::VRShMem(volatile VRExternalShmem* aShmem, bool aRequiresMutex) + : mExternalShmem(aShmem), + mIsSharedExternalShmem(aShmem != nullptr) +#if defined(XP_WIN) + , + mRequiresMutex(aRequiresMutex) +#endif +#if defined(XP_MACOSX) + , + mShmemFD(0) +#elif defined(XP_WIN) + , + mShmemFile(nullptr), + mMutex(nullptr) +#endif +{ + // Regarding input parameters, + // - aShmem is null for VRManager or for VRService in multi-proc + // - aShmem is !null for VRService in-proc (i.e., no VR proc) +} + +// Note: This function should only be called for in-proc scenarios, where the +// shared memory is only shared within the same proc (rather than across +// processes). Also, this local heap memory's lifetime is tied to the class. +// Callers to this must ensure that its reference doesn't outlive the owning +// VRShMem instance. +volatile VRExternalShmem* VRShMem::GetExternalShmem() const { +#if defined(XP_MACOSX) + MOZ_ASSERT(mShmemFD == 0); +#elif defined(XP_WIN) + MOZ_ASSERT(mShmemFile == nullptr); +#endif + return mExternalShmem; +} + +bool VRShMem::IsDisplayStateShutdown() const { + // adapted from VRService::Refresh + // Does this need the mutex for getting .shutdown? + return mExternalShmem != nullptr && + mExternalShmem->state.displayState.shutdown; +} + +// This method returns true when there is a Shmem struct allocated and +// when there is a shmem handle from the OS. This implies that the struct +// is mapped to shared memory rather than being allocated on the heap by +// this process. +bool VRShMem::IsCreatedOnSharedMemory() const { +#if defined(XP_MACOSX) + return HasExternalShmem() && (mShmemFD != 0); +#elif defined(XP_WIN) + return HasExternalShmem() && (mShmemFile != nullptr); +#else + // VRShMem does not support system shared memory on remaining platformss + return false; +#endif +} + +// CreateShMem allocates system shared memory for mExternalShmem and +// synchronization primitives to protect it. +// Callers/Processes to CreateShMem should followup with CloseShMem +void VRShMem::CreateShMem(bool aCreateOnSharedMemory) { + if (HasExternalShmem()) { + MOZ_ASSERT(mIsSharedExternalShmem && !IsCreatedOnSharedMemory()); + return; + } +#if defined(XP_WIN) + if (mMutex == nullptr) { + mMutex = CreateMutex(nullptr, // default security descriptor + false, // mutex not owned + kMutexName); // object name + if (mMutex == nullptr) { +# ifdef MOZILLA_INTERNAL_API + nsAutoCString msg; + msg.AppendPrintf("VRManager CreateMutex error \"%lu\".", GetLastError()); + NS_WARNING(msg.get()); +# endif + MOZ_ASSERT(false); + return; + } + // At xpcshell extension tests, it creates multiple VRManager + // instances in plug-contrainer.exe. It causes GetLastError() return + // `ERROR_ALREADY_EXISTS`. However, even though `ERROR_ALREADY_EXISTS`, it + // still returns the same mutex handle. + // + // https://docs.microsoft.com/en-us/windows/desktop/api/synchapi/nf-synchapi-createmutexa + MOZ_ASSERT(GetLastError() == 0 || GetLastError() == ERROR_ALREADY_EXISTS); + } +#endif // XP_WIN +#if !defined(MOZ_WIDGET_ANDROID) + // The VR Service accesses all hardware from a separate process + // and replaces the other VRManager when enabled. + // If the VR process is not enabled, create an in-process VRService. + if (!aCreateOnSharedMemory) { + MOZ_ASSERT(mExternalShmem == nullptr); + // If the VR process is disabled, attempt to create a + // VR service within the current process on the heap + mExternalShmem = new VRExternalShmem(); + ClearShMem(); + return; + } +#endif + + MOZ_ASSERT(aCreateOnSharedMemory); + +#if defined(XP_MACOSX) + if (mShmemFD == 0) { + mShmemFD = + shm_open(kShmemName, O_RDWR, S_IRUSR | S_IWUSR | S_IROTH | S_IWOTH); + } + if (mShmemFD <= 0) { + mShmemFD = 0; + return; + } + + struct stat sb; + fstat(mShmemFD, &sb); + off_t length = sb.st_size; + if (length < (off_t)sizeof(VRExternalShmem)) { + // TODO - Implement logging (Bug 1558912) + CloseShMem(); + return; + } + + mExternalShmem = (VRExternalShmem*)mmap(NULL, length, PROT_READ | PROT_WRITE, + MAP_SHARED, mShmemFD, 0); + if (mExternalShmem == MAP_FAILED) { + // TODO - Implement logging (Bug 1558912) + mExternalShmem = NULL; + CloseShMem(); + return; + } + +#elif defined(XP_WIN) + if (mShmemFile == nullptr) { + mShmemFile = + CreateFileMappingA(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, 0, + sizeof(VRExternalShmem), kShmemName); + MOZ_ASSERT(GetLastError() == 0 || GetLastError() == ERROR_ALREADY_EXISTS); + MOZ_ASSERT(mShmemFile); + if (mShmemFile == nullptr) { + // TODO - Implement logging (Bug 1558912) + CloseShMem(); + return; + } + } + + LARGE_INTEGER length; + length.QuadPart = sizeof(VRExternalShmem); + mExternalShmem = (VRExternalShmem*)MapViewOfFile( + mShmemFile, // handle to map object + FILE_MAP_ALL_ACCESS, // read/write permission + 0, 0, length.QuadPart); + + if (mExternalShmem == nullptr) { + // TODO - Implement logging (Bug 1558912) + CloseShMem(); + return; + } +#elif defined(MOZ_WIDGET_ANDROID) + MOZ_ASSERT(false, + "CreateShMem should not be called for Android. Use " + "CreateShMemForAndroid instead"); +#endif +} + +// This function sets mExternalShmem in the Android/GeckoView +// scenarios where the host creates it in-memory and VRShMem +// accesses it via GeckVRManager. +void VRShMem::CreateShMemForAndroid() { +#if defined(MOZ_WIDGET_ANDROID) && defined(MOZILLA_INTERNAL_API) + mExternalShmem = + (VRExternalShmem*)mozilla::GeckoVRManager::GetExternalContext(); + if (!mExternalShmem) { + return; + } else { + mIsSharedExternalShmem = true; + } + + int32_t version = -1; + int32_t size = 0; + if (pthread_mutex_lock((pthread_mutex_t*)&(mExternalShmem->systemMutex)) == + 0) { + version = mExternalShmem->version; + size = mExternalShmem->size; + pthread_mutex_unlock((pthread_mutex_t*)&(mExternalShmem->systemMutex)); + } else { + return; + } + if (version != kVRExternalVersion) { + mExternalShmem = nullptr; + return; + } + if (size != sizeof(VRExternalShmem)) { + mExternalShmem = nullptr; + return; + } +#endif +} + +void VRShMem::ClearShMem() { + if (mExternalShmem != nullptr) { +#ifdef MOZILLA_INTERNAL_API + // VRExternalShmem is asserted to be POD + mExternalShmem->Clear(); +#else + memset((void*)mExternalShmem, 0, sizeof(VRExternalShmem)); +#endif + } +} + +// The cleanup corresponding to CreateShMem +void VRShMem::CloseShMem() { +#if !defined(MOZ_WIDGET_ANDROID) + if (!IsCreatedOnSharedMemory()) { + MOZ_ASSERT(!mIsSharedExternalShmem); + if (mExternalShmem) { + delete mExternalShmem; + mExternalShmem = nullptr; + } + return; + } +#endif +#if defined(XP_MACOSX) + if (mExternalShmem) { + munmap((void*)mExternalShmem, sizeof(VRExternalShmem)); + mExternalShmem = NULL; + } + if (mShmemFD) { + close(mShmemFD); + mShmemFD = 0; + } +#elif defined(XP_WIN) + if (mExternalShmem) { + UnmapViewOfFile((void*)mExternalShmem); + mExternalShmem = nullptr; + } + if (mShmemFile) { + CloseHandle(mShmemFile); + mShmemFile = nullptr; + } +#elif defined(MOZ_WIDGET_ANDROID) + mExternalShmem = NULL; +#endif + +#if defined(XP_WIN) + if (mMutex) { + MOZ_ASSERT(mRequiresMutex); + CloseHandle(mMutex); + mMutex = nullptr; + } +#endif +} + +// Called to use an existing shmem instance created by another process +// Callers to JoinShMem should call LeaveShMem for cleanup +bool VRShMem::JoinShMem() { +#if defined(XP_WIN) + if (!mMutex && mRequiresMutex) { + // Check that there are no errors before making system calls + MOZ_ASSERT(GetLastError() == 0); + + mMutex = OpenMutex(MUTEX_ALL_ACCESS, // request full access + false, // handle not inheritable + kMutexName); // object name + + if (mMutex == nullptr) { +# ifdef MOZILLA_INTERNAL_API + nsAutoCString msg; + msg.AppendPrintf("VRService OpenMutex error \"%lu\".", GetLastError()); + NS_WARNING(msg.get()); +# endif + return false; + } + MOZ_ASSERT(GetLastError() == 0); + } +#endif + + if (HasExternalShmem()) { + // An ExternalShmem is already set. No need to override and rejoin + return true; + } + +#if defined(XP_WIN) + // Opening a file-mapping object by name + base::ProcessHandle targetHandle = + OpenFileMappingA(FILE_MAP_ALL_ACCESS, // read/write access + FALSE, // do not inherit the name + kShmemName); // name of mapping object + + MOZ_ASSERT(GetLastError() == 0); + + LARGE_INTEGER length; + length.QuadPart = sizeof(VRExternalShmem); + mExternalShmem = (VRExternalShmem*)MapViewOfFile( + reinterpret_cast<base::ProcessHandle>( + targetHandle), // handle to map object + FILE_MAP_ALL_ACCESS, // read/write permission + 0, 0, length.QuadPart); + MOZ_ASSERT(GetLastError() == 0); + + // TODO - Implement logging (Bug 1558912) + mShmemFile = targetHandle; + if (!mExternalShmem) { + MOZ_ASSERT(mExternalShmem); + return false; + } +#else + // TODO: Implement shmem for other platforms. + // + // TODO: ** Does this mean that ShMem only works in Windows for now? If so, + // let's delete the code from other platforms (Bug 1563234) + MOZ_ASSERT(false, "JoinShMem not implemented"); +#endif + return true; +} + +// The cleanup corresponding to JoinShMem +void VRShMem::LeaveShMem() { +#if defined(XP_WIN) + // Check that there are no errors before making system calls + MOZ_ASSERT(GetLastError() == 0); + + if (mShmemFile) { + ::CloseHandle(mShmemFile); + mShmemFile = nullptr; + } +#endif + + if (mExternalShmem != nullptr) { +#if defined(XP_WIN) + if (IsCreatedOnSharedMemory()) { + UnmapViewOfFile((void*)mExternalShmem); + MOZ_ASSERT(GetLastError() == 0); + } + // Otherwise, if not created on shared memory, simply null the shared + // reference to the heap object. The call to CloseShMem will appropriately + // free the allocation. +#endif + mExternalShmem = nullptr; + } +#if defined(XP_WIN) + if (mMutex) { + MOZ_ASSERT(mRequiresMutex); + CloseHandle(mMutex); + mMutex = nullptr; + } +#endif +} + +void VRShMem::PushBrowserState(VRBrowserState& aBrowserState, + bool aNotifyCond) { + if (!mExternalShmem) { + return; + } +#if defined(MOZ_WIDGET_ANDROID) + if (pthread_mutex_lock((pthread_mutex_t*)&(mExternalShmem->geckoMutex)) == + 0) { + memcpy((void*)&(mExternalShmem->geckoState), (void*)&aBrowserState, + sizeof(VRBrowserState)); + if (aNotifyCond) { + pthread_cond_signal((pthread_cond_t*)&(mExternalShmem->geckoCond)); + } + pthread_mutex_unlock((pthread_mutex_t*)&(mExternalShmem->geckoMutex)); + } +#else + bool status = true; +# if defined(XP_WIN) + WaitForMutex lock(mMutex); + status = lock.GetStatus(); +# endif // defined(XP_WIN) + if (status) { + mExternalShmem->geckoGenerationA++; + memcpy((void*)&(mExternalShmem->geckoState), (void*)&aBrowserState, + sizeof(VRBrowserState)); + mExternalShmem->geckoGenerationB++; + } +#endif // defined(MOZ_WIDGET_ANDROID) +} + +void VRShMem::PullBrowserState(mozilla::gfx::VRBrowserState& aState) { + if (!mExternalShmem) { + return; + } + // Copying the browser state from the shmem is non-blocking + // on x86/x64 architectures. Arm requires a mutex that is + // locked for the duration of the memcpy to and from shmem on + // both sides. + // On x86/x64 It is fallable -- If a dirty copy is detected by + // a mismatch of geckoGenerationA and geckoGenerationB, + // the copy is discarded and will not replace the last known + // browser state. + +#if defined(MOZ_WIDGET_ANDROID) + // TODO: This code is out-of-date and fails to compile, as + // VRService isn't compiled for Android (Bug 1563234) + /* + if (pthread_mutex_lock((pthread_mutex_t*)&(mExternalShmem->geckoMutex)) == + 0) { + memcpy(&aState, &tmp.geckoState, sizeof(VRBrowserState)); + pthread_mutex_unlock((pthread_mutex_t*)&(mExternalShmem->geckoMutex)); + } + */ + MOZ_ASSERT(false, "PullBrowserState not implemented"); +#else + bool status = true; +# if defined(XP_WIN) + if (mRequiresMutex) { + // TODO: Is this scoped lock okay? Seems like it should allow some + // race condition (Bug 1563234) + WaitForMutex lock(mMutex); + status = lock.GetStatus(); + } +# endif // defined(XP_WIN) + if (status) { + VRExternalShmem tmp; + if (mExternalShmem->geckoGenerationA != mBrowserGeneration) { + // TODO - (void *) cast removes volatile semantics. + // The memcpy is not likely to be optimized out, but is theoretically + // possible. Suggest refactoring to either explicitly enforce memory + // order or to use locks. + memcpy(&tmp, (void*)mExternalShmem, sizeof(VRExternalShmem)); + if (tmp.geckoGenerationA == tmp.geckoGenerationB && + tmp.geckoGenerationA != 0) { + memcpy(&aState, &tmp.geckoState, sizeof(VRBrowserState)); + mBrowserGeneration = tmp.geckoGenerationA; + } + } + } +#endif // defined(MOZ_WIDGET_ANDROID) +} + +void VRShMem::PushSystemState(const mozilla::gfx::VRSystemState& aState) { + if (!mExternalShmem) { + return; + } + // Copying the VR service state to the shmem is atomic, infallable, + // and non-blocking on x86/x64 architectures. Arm requires a mutex + // that is locked for the duration of the memcpy to and from shmem on + // both sides. + +#if defined(MOZ_WIDGET_ANDROID) + // TODO: This code is out-of-date and fails to compile, as + // VRService isn't compiled for Android (Bug 1563234) + MOZ_ASSERT(false, "JoinShMem not implemented"); + /* + if (pthread_mutex_lock((pthread_mutex_t*)&(mExternalShmem->systemMutex)) == + 0) { + // We are casting away the volatile keyword, which is not accepted by + // memcpy. It is possible (although very unlikely) that the compiler + // may optimize out the memcpy here as memcpy isn't explicitly safe for + // volatile memory in the C++ standard. + memcpy((void*)&mExternalShmem->state, &aState, sizeof(VRSystemState)); + pthread_mutex_unlock((pthread_mutex_t*)&(mExternalShmem->systemMutex)); + } + */ +#else + bool lockState = true; +# if defined(XP_WIN) + if (mRequiresMutex) { + // TODO: Is this scoped lock okay? Seems like it should allow some + // race condition (Bug 1563234) + WaitForMutex lock(mMutex); + lockState = lock.GetStatus(); + } +# endif // defined(XP_WIN) + if (lockState) { + mExternalShmem->generationA++; + memcpy((void*)&mExternalShmem->state, &aState, sizeof(VRSystemState)); + mExternalShmem->generationB++; + } +#endif // defined(MOZ_WIDGET_ANDROID) +} + +#if defined(MOZ_WIDGET_ANDROID) +void VRShMem::PullSystemState( + VRDisplayState& aDisplayState, VRHMDSensorState& aSensorState, + VRControllerState (&aControllerState)[kVRControllerMaxCount], + bool& aEnumerationCompleted, + const std::function<bool()>& aWaitCondition /* = nullptr */) { + if (!mExternalShmem) { + return; + } + bool done = false; + while (!done) { + if (pthread_mutex_lock((pthread_mutex_t*)&(mExternalShmem->systemMutex)) == + 0) { + while (true) { + memcpy(&aDisplayState, (void*)&(mExternalShmem->state.displayState), + sizeof(VRDisplayState)); + memcpy(&aSensorState, (void*)&(mExternalShmem->state.sensorState), + sizeof(VRHMDSensorState)); + memcpy(aControllerState, + (void*)&(mExternalShmem->state.controllerState), + sizeof(VRControllerState) * kVRControllerMaxCount); + aEnumerationCompleted = mExternalShmem->state.enumerationCompleted; + if (!aWaitCondition || aWaitCondition()) { + done = true; + break; + } + // Block current thead using the condition variable until data + // changes + pthread_cond_wait((pthread_cond_t*)&mExternalShmem->systemCond, + (pthread_mutex_t*)&mExternalShmem->systemMutex); + } // while (true) + pthread_mutex_unlock((pthread_mutex_t*)&(mExternalShmem->systemMutex)); + } else if (!aWaitCondition) { + // pthread_mutex_lock failed and we are not waiting for a condition to + // exit from PullState call. + return; + } + } // while (!done) { +} +#else +void VRShMem::PullSystemState( + VRDisplayState& aDisplayState, VRHMDSensorState& aSensorState, + VRControllerState (&aControllerState)[kVRControllerMaxCount], + bool& aEnumerationCompleted, + const std::function<bool()>& aWaitCondition /* = nullptr */) { + MOZ_ASSERT(mExternalShmem); + if (!mExternalShmem) { + return; + } + while (true) { + { // Scope for WaitForMutex +# if defined(XP_WIN) + bool status = true; + WaitForMutex lock(mMutex); + status = lock.GetStatus(); + if (status) { +# endif // defined(XP_WIN) + VRExternalShmem tmp; + memcpy(&tmp, (void*)mExternalShmem, sizeof(VRExternalShmem)); + bool isCleanCopy = + tmp.generationA == tmp.generationB && tmp.generationA != 0; + if (isCleanCopy) { + memcpy(&aDisplayState, &tmp.state.displayState, + sizeof(VRDisplayState)); + memcpy(&aSensorState, &tmp.state.sensorState, + sizeof(VRHMDSensorState)); + memcpy(aControllerState, + (void*)&(mExternalShmem->state.controllerState), + sizeof(VRControllerState) * kVRControllerMaxCount); + aEnumerationCompleted = mExternalShmem->state.enumerationCompleted; + // Check for wait condition + if (!aWaitCondition || aWaitCondition()) { + return; + } + } else if (!aWaitCondition) { + // We did not get a clean copy, and we are not waiting for a condition + // to exit from PullState call. + return; + } + // Yield the thread while polling + YieldThread(); +# if defined(XP_WIN) + } else if (!aWaitCondition) { + // WaitForMutex failed and we are not waiting for a condition to + // exit from PullState call. + return; + } +# endif // defined(XP_WIN) + } // End: Scope for WaitForMutex + // Yield the thread while polling + YieldThread(); + } // while (!true) +} +#endif // defined(MOZ_WIDGET_ANDROID) + +void VRShMem::PushWindowState(VRWindowState& aState) { +#if defined(XP_WIN) + if (!mExternalShmem) { + return; + } + + bool status = true; + WaitForMutex lock(mMutex); + status = lock.GetStatus(); + if (status) { + memcpy((void*)&(mExternalShmem->windowState), (void*)&aState, + sizeof(VRWindowState)); + } +#endif // defined(XP_WIN) +} + +void VRShMem::PullWindowState(VRWindowState& aState) { +#if defined(XP_WIN) + if (!mExternalShmem) { + return; + } + + bool status = true; + WaitForMutex lock(mMutex); + status = lock.GetStatus(); + if (status) { + memcpy((void*)&aState, (void*)&(mExternalShmem->windowState), + sizeof(VRWindowState)); + } +#endif // defined(XP_WIN) +} + +void VRShMem::PushTelemetryState(VRTelemetryState& aState) { +#if defined(XP_WIN) + if (!mExternalShmem) { + return; + } + + bool status = true; + WaitForMutex lock(mMutex); + status = lock.GetStatus(); + if (status) { + memcpy((void*)&(mExternalShmem->telemetryState), (void*)&aState, + sizeof(VRTelemetryState)); + } +#endif // defined(XP_WIN) +} +void VRShMem::PullTelemetryState(VRTelemetryState& aState) { +#if defined(XP_WIN) + if (!mExternalShmem) { + return; + } + + bool status = true; + WaitForMutex lock(mMutex); + status = lock.GetStatus(); + if (status) { + memcpy((void*)&aState, (void*)&(mExternalShmem->telemetryState), + sizeof(VRTelemetryState)); + } +#endif // defined(XP_WIN) +} + +void VRShMem::SendEvent(uint64_t aWindowID, + mozilla::gfx::VRFxEventType aEventType, + mozilla::gfx::VRFxEventState aEventState) { + MOZ_ASSERT(!HasExternalShmem()); + if (JoinShMem()) { + mozilla::gfx::VRWindowState windowState = {0}; + PullWindowState(windowState); + windowState.windowID = aWindowID; + windowState.eventType = aEventType; + windowState.eventState = aEventState; + PushWindowState(windowState); + LeaveShMem(); + +#if defined(XP_WIN) + // Notify the waiting host process that the data is now available + HANDLE hSignal = ::OpenEventA(EVENT_ALL_ACCESS, // dwDesiredAccess + FALSE, // bInheritHandle + windowState.signalName // lpName + ); + ::SetEvent(hSignal); + ::CloseHandle(hSignal); +#endif // defined(XP_WIN) + } +} + +void VRShMem::SendIMEState(uint64_t aWindowID, + mozilla::gfx::VRFxEventState aEventState) { + SendEvent(aWindowID, mozilla::gfx::VRFxEventType::IME, aEventState); +} + +void VRShMem::SendFullscreenState(uint64_t aWindowID, bool aFullscreen) { + SendEvent(aWindowID, mozilla::gfx::VRFxEventType::FULLSCREEN, + aFullscreen ? mozilla::gfx::VRFxEventState::FULLSCREEN_ENTER + : mozilla::gfx::VRFxEventState::FULLSCREEN_EXIT); +} + +// Note: this should be called from the VRShMem instance that created +// the external shmem rather than joined it. +void VRShMem::SendShutdowmState(uint64_t aWindowID) { + MOZ_ASSERT(HasExternalShmem()); + + mozilla::gfx::VRWindowState windowState = {0}; + PullWindowState(windowState); + windowState.windowID = aWindowID; + windowState.eventType = mozilla::gfx::VRFxEventType::SHUTDOWN; + PushWindowState(windowState); + +#if defined(XP_WIN) + // Notify the waiting host process that the data is now available + HANDLE hSignal = ::OpenEventA(EVENT_ALL_ACCESS, // dwDesiredAccess + FALSE, // bInheritHandle + windowState.signalName // lpName + ); + ::SetEvent(hSignal); + ::CloseHandle(hSignal); +#endif // defined(XP_WIN) +} diff --git a/gfx/vr/VRShMem.h b/gfx/vr/VRShMem.h new file mode 100644 index 0000000000..a10b8189a8 --- /dev/null +++ b/gfx/vr/VRShMem.h @@ -0,0 +1,105 @@ +/* -*- 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/. */ + +#ifndef GFX_VR_VRSHMEM_H +#define GFX_VR_VRSHMEM_H + +// This file declares VRShMem, which is used for cross-platform, cross-process +// communication between VR components. +// A shared memory handle is opened for the struct of type VRExternalShmem, and +// different processes write or read members of it to share data. Note that no +// process should be reading or writing to the same members, except for the +// versioning members used for synchronization. + +#include "moz_external_vr.h" +#include "base/process.h" // for base::ProcessHandle +#include <functional> + +namespace mozilla { +namespace gfx { +class VRShMem final { + public: + VRShMem(volatile VRExternalShmem* aShmem, bool aRequiresMutex); + ~VRShMem() = default; + + void CreateShMem(bool aCreateOnSharedMemory); + void CreateShMemForAndroid(); + void ClearShMem(); + void CloseShMem(); + + bool JoinShMem(); + void LeaveShMem(); + + void PushBrowserState(VRBrowserState& aBrowserState, bool aNotifyCond); + void PullBrowserState(mozilla::gfx::VRBrowserState& aState); + + void PushSystemState(const mozilla::gfx::VRSystemState& aState); + void PullSystemState( + VRDisplayState& aDisplayState, VRHMDSensorState& aSensorState, + VRControllerState (&aControllerState)[kVRControllerMaxCount], + bool& aEnumerationCompleted, + const std::function<bool()>& aWaitCondition = nullptr); + + void PushWindowState(VRWindowState& aState); + void PullWindowState(VRWindowState& aState); + + void PushTelemetryState(VRTelemetryState& aState); + void PullTelemetryState(VRTelemetryState& aState); + + void SendIMEState(uint64_t aWindowID, mozilla::gfx::VRFxEventState aImeState); + void SendFullscreenState(uint64_t aWindowID, bool aFullscreen); + void SendShutdowmState(uint64_t aWindowID); + + bool HasExternalShmem() const { return mExternalShmem != nullptr; } + bool IsSharedExternalShmem() const { return mIsSharedExternalShmem; } + volatile VRExternalShmem* GetExternalShmem() const; + bool IsDisplayStateShutdown() const; + + private: + bool IsCreatedOnSharedMemory() const; + void SendEvent(uint64_t aWindowID, mozilla::gfx::VRFxEventType aEventType, + mozilla::gfx::VRFxEventState aEventState); + + // mExternalShmem can have one of three sources: + // - Allocated outside of this class on the heap and passed in via + // constructor, or acquired via GeckoVRManager (for GeckoView scenario). + // This is usually for scenarios where there is no VR process for cross- + // proc communication, and VRService is receiving the object. + // --> mIsSharedExternalShmem == true, IsCreatedOnSharedMemory() == false + // --> [Windows 7, Mac, Android, Linux] + // - Allocated inside this class on the heap. This is usually for scenarios + // where there is no VR process, and VRManager is creating the object. + // --> mIsSharedExternalShmem == false, IsCreatedOnSharedMemory() == false + // --> [Windows 7, Mac, Linux] + // - Allocated inside this class on shared memory. This is usually for + // scenarios where there is a VR process and cross-process communication + // is necessary + // --> mIsSharedExternalShmem == false, IsCreatedOnSharedMemory() == true + // --> [Windows 10 with VR process enabled] + volatile VRExternalShmem* mExternalShmem = nullptr; + // This member is true when mExternalShmem was allocated externally, via + // being passed into the constructor or accessed via GeckoVRManager + bool mIsSharedExternalShmem; + +#if defined(XP_WIN) + bool mRequiresMutex; +#endif + +#if defined(XP_MACOSX) + int mShmemFD; +#elif defined(XP_WIN) + base::ProcessHandle mShmemFile; + base::ProcessHandle mMutex; +#endif + +#if !defined(MOZ_WIDGET_ANDROID) + int64_t mBrowserGeneration = 0; +#endif +}; +} // namespace gfx +} // namespace mozilla + +#endif diff --git a/gfx/vr/VRThread.cpp b/gfx/vr/VRThread.cpp new file mode 100644 index 0000000000..818a16dbfd --- /dev/null +++ b/gfx/vr/VRThread.cpp @@ -0,0 +1,117 @@ +/* -*- 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 "VRThread.h" +#include "nsDebug.h" +#include "nsThreadManager.h" +#include "nsThread.h" +#include "nsThreadUtils.h" + +namespace mozilla { + +namespace gfx { + +static const uint32_t kDefaultThreadLifeTime = 60; // in 60 seconds. +static const uint32_t kDelayPostTaskTime = 20000; // in 20000 ms. + +VRThread::VRThread(const nsCString& aName) + : mThread(nullptr), mLifeTime(kDefaultThreadLifeTime), mStarted(false) { + mName = aName; +} + +VRThread::~VRThread() { Shutdown(); } + +void VRThread::Start() { + if (!mThread) { + nsresult rv = NS_NewNamedThread(mName, getter_AddRefs(mThread)); + MOZ_ASSERT(mThread); + + if (NS_FAILED(rv)) { + MOZ_ASSERT(false, "Failed to create a vr thread."); + } + RefPtr<Runnable> runnable = + NewRunnableMethod<TimeStamp>("gfx::VRThread::CheckLife", this, + &VRThread::CheckLife, TimeStamp::Now()); + // Post it to the main thread for tracking the lifetime. + nsCOMPtr<nsIThread> mainThread; + rv = NS_GetMainThread(getter_AddRefs(mainThread)); + if (NS_FAILED(rv)) { + NS_WARNING("VRThread::Start() could not get Main thread"); + return; + } + mainThread->DelayedDispatch(runnable.forget(), kDelayPostTaskTime); + } + mStarted = true; + mLastActiveTime = TimeStamp::Now(); +} + +void VRThread::Shutdown() { + if (mThread) { + if (nsThreadManager::get().IsNSThread()) { + mThread->Shutdown(); + } else { + NS_WARNING( + "VRThread::Shutdown() may only be called from an XPCOM thread"); + NS_DispatchToMainThread(NS_NewRunnableFunction( + "VRThread::Shutdown", [thread = mThread]() { thread->Shutdown(); })); + } + mThread = nullptr; + } + mStarted = false; +} + +const nsCOMPtr<nsIThread> VRThread::GetThread() const { return mThread; } + +void VRThread::PostTask(already_AddRefed<Runnable> aTask) { + PostDelayedTask(std::move(aTask), 0); +} + +void VRThread::PostDelayedTask(already_AddRefed<Runnable> aTask, + uint32_t aTime) { + MOZ_ASSERT(mStarted, "Must call Start() before posting tasks."); + MOZ_ASSERT(mThread); + mLastActiveTime = TimeStamp::Now(); + + if (!aTime) { + mThread->Dispatch(std::move(aTask), NS_DISPATCH_NORMAL); + } else { + mThread->DelayedDispatch(std::move(aTask), aTime); + } +} + +void VRThread::CheckLife(TimeStamp aCheckTimestamp) { + // VR system is going to shutdown. + if (!mStarted) { + Shutdown(); + return; + } + + const TimeDuration timeout = TimeDuration::FromSeconds(mLifeTime); + if ((aCheckTimestamp - mLastActiveTime) > timeout) { + Shutdown(); + } else { + RefPtr<Runnable> runnable = + NewRunnableMethod<TimeStamp>("gfx::VRThread::CheckLife", this, + &VRThread::CheckLife, TimeStamp::Now()); + // Post it to the main thread for tracking the lifetime. + nsCOMPtr<nsIThread> mainThread; + nsresult rv = NS_GetMainThread(getter_AddRefs(mainThread)); + if (NS_FAILED(rv)) { + NS_WARNING("VRThread::CheckLife() could not get Main thread"); + return; + } + mainThread->DelayedDispatch(runnable.forget(), kDelayPostTaskTime); + } +} + +void VRThread::SetLifeTime(uint32_t aLifeTime) { mLifeTime = aLifeTime; } + +uint32_t VRThread::GetLifeTime() { return mLifeTime; } + +bool VRThread::IsActive() { return !!mThread; } + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/vr/VRThread.h b/gfx/vr/VRThread.h new file mode 100644 index 0000000000..85d9ea4b84 --- /dev/null +++ b/gfx/vr/VRThread.h @@ -0,0 +1,46 @@ +/* -*- 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/. */ + +#ifndef GFX_VR_THREAD_H +#define GFX_VR_THREAD_H + +#include "nsISupportsImpl.h" +#include "base/thread.h" // for Thread + +namespace mozilla { +namespace gfx { + +class VRThread final { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VRThread) + + public: + explicit VRThread(const nsCString& aName); + + void Start(); + void Shutdown(); + void SetLifeTime(uint32_t aLifeTime); + uint32_t GetLifeTime(); + void CheckLife(TimeStamp aCheckTimestamp); + void PostTask(already_AddRefed<Runnable> aTask); + void PostDelayedTask(already_AddRefed<Runnable> aTask, uint32_t aTime); + const nsCOMPtr<nsIThread> GetThread() const; + bool IsActive(); + + protected: + ~VRThread(); + + private: + nsCOMPtr<nsIThread> mThread; + TimeStamp mLastActiveTime; + nsCString mName; + uint32_t mLifeTime; + Atomic<bool> mStarted; +}; + +} // namespace gfx +} // namespace mozilla + +#endif // GFX_VR_THREAD_H diff --git a/gfx/vr/components.conf b/gfx/vr/components.conf new file mode 100644 index 0000000000..169357c40c --- /dev/null +++ b/gfx/vr/components.conf @@ -0,0 +1,15 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +Classes = [ + { + 'cid': '{5baca10a-4d53-4335-b24d-c69696640a9a}', + 'contract_ids': ['@mozilla.org/fxr/clh;1'], + 'type': 'nsFxrCommandLineHandler', + 'headers': ['/gfx/vr/nsFxrCommandLineHandler.h'], + 'categories': {'command-line-handler': 'm-vrbrowser'}, + }, +] diff --git a/gfx/vr/external_api/moz_external_vr.h b/gfx/vr/external_api/moz_external_vr.h new file mode 100644 index 0000000000..1727e1e540 --- /dev/null +++ b/gfx/vr/external_api/moz_external_vr.h @@ -0,0 +1,670 @@ +/* -*- 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/. */ + +#ifndef GFX_VR_EXTERNAL_API_H +#define GFX_VR_EXTERNAL_API_H + +#define GFX_VR_EIGHTCC(c1, c2, c3, c4, c5, c6, c7, c8) \ + ((uint64_t)(c1) << 56 | (uint64_t)(c2) << 48 | (uint64_t)(c3) << 40 | \ + (uint64_t)(c4) << 32 | (uint64_t)(c5) << 24 | (uint64_t)(c6) << 16 | \ + (uint64_t)(c7) << 8 | (uint64_t)(c8)) + +#ifdef MOZILLA_INTERNAL_API +# define __STDC_WANT_LIB_EXT1__ 1 +// __STDC_WANT_LIB_EXT1__ required for memcpy_s +# include <stdlib.h> +# include <string.h> +# include "mozilla/TypedEnumBits.h" +# include "mozilla/gfx/2D.h" +# include <stddef.h> +# include <stdint.h> +# include <type_traits> +#endif // MOZILLA_INTERNAL_API + +#if defined(__ANDROID__) +# include <pthread.h> +#endif // defined(__ANDROID__) + +#include <cstdint> +#include <type_traits> + +namespace mozilla { +#ifdef MOZILLA_INTERNAL_API +namespace dom { +enum class GamepadHand : uint8_t; +enum class GamepadCapabilityFlags : uint16_t; +} // namespace dom +#endif // MOZILLA_INTERNAL_API +namespace gfx { + +// If there is any change of "SHMEM_VERSION" or "kVRExternalVersion", +// we need to change both of them at the same time. + +// TODO: we might need to use different names for the mutexes +// and mapped files if we have both release and nightlies +// running at the same time? Or...what if we have multiple +// release builds running on same machine? (Bug 1563232) +#define SHMEM_VERSION "0.0.11" +static const int32_t kVRExternalVersion = 18; + +// We assign VR presentations to groups with a bitmask. +// Currently, we will only display either content or chrome. +// Later, we will have more groups to support VR home spaces and +// multitasking environments. +// These values are not exposed to regular content and only affect +// chrome-only API's. They may be changed at any time. +static const uint32_t kVRGroupNone = 0; +static const uint32_t kVRGroupContent = 1 << 0; +static const uint32_t kVRGroupChrome = 1 << 1; +static const uint32_t kVRGroupAll = 0xffffffff; + +static const int kVRDisplayNameMaxLen = 256; +static const int kVRControllerNameMaxLen = 256; +static const int kVRControllerMaxCount = 16; +static const int kVRControllerMaxButtons = 64; +static const int kVRControllerMaxAxis = 16; +static const int kVRLayerMaxCount = 8; +static const int kVRHapticsMaxCount = 32; + +#if defined(__ANDROID__) +typedef uint64_t VRLayerTextureHandle; +#elif defined(XP_MACOSX) +typedef uint32_t VRLayerTextureHandle; +#else +typedef void* VRLayerTextureHandle; +#endif + +struct Point3D_POD { + float x; + float y; + float z; +}; + +struct IntSize_POD { + int32_t width; + int32_t height; +}; + +struct FloatSize_POD { + float width; + float height; +}; + +#ifndef MOZILLA_INTERNAL_API + +enum class ControllerHand : uint8_t { _empty, Left, Right, EndGuard_ }; + +enum class ControllerCapabilityFlags : uint16_t { + Cap_None = 0, + /** + * Cap_Position is set if the Gamepad is capable of tracking its position. + */ + Cap_Position = 1 << 1, + /** + * Cap_Orientation is set if the Gamepad is capable of tracking its + * orientation. + */ + Cap_Orientation = 1 << 2, + /** + * Cap_AngularAcceleration is set if the Gamepad is capable of tracking its + * angular acceleration. + */ + Cap_AngularAcceleration = 1 << 3, + /** + * Cap_LinearAcceleration is set if the Gamepad is capable of tracking its + * linear acceleration. + */ + Cap_LinearAcceleration = 1 << 4, + /** + * Cap_TargetRaySpacePosition is set if the Gamepad has a grip space position. + */ + Cap_GripSpacePosition = 1 << 5, + /** + * Cap_PositionEmulated is set if the XRInputSoruce is capable of setting a + * emulated position (e.g. neck model) even if still doesn't support 6DOF + * tracking. + */ + Cap_PositionEmulated = 1 << 6, + /** + * Cap_All used for validity checking during IPC serialization + */ + Cap_All = (1 << 7) - 1 +}; + +#endif // ifndef MOZILLA_INTERNAL_API + +enum class VRControllerType : uint8_t { + _empty, + HTCVive, + HTCViveCosmos, + HTCViveFocus, + HTCViveFocusPlus, + MSMR, + ValveIndex, + OculusGo, + OculusTouch, + OculusTouch2, + PicoGaze, + PicoG2, + PicoNeo2, + _end +}; + +enum class TargetRayMode : uint8_t { Gaze, TrackedPointer, Screen }; + +enum class GamepadMappingType : uint8_t { _empty, Standard, XRStandard }; + +enum class VRDisplayBlendMode : uint8_t { Opaque, Additive, AlphaBlend }; + +enum class VRDisplayCapabilityFlags : uint16_t { + Cap_None = 0, + /** + * Cap_Position is set if the VRDisplay is capable of tracking its position. + */ + Cap_Position = 1 << 1, + /** + * Cap_Orientation is set if the VRDisplay is capable of tracking its + * orientation. + */ + Cap_Orientation = 1 << 2, + /** + * Cap_Present is set if the VRDisplay is capable of presenting content to an + * HMD or similar device. Can be used to indicate "magic window" devices that + * are capable of 6DoF tracking but for which requestPresent is not + * meaningful. If false then calls to requestPresent should always fail, and + * getEyeParameters should return null. + */ + Cap_Present = 1 << 3, + /** + * Cap_External is set if the VRDisplay is separate from the device's + * primary display. If presenting VR content will obscure + * other content on the device, this should be un-set. When + * un-set, the application should not attempt to mirror VR content + * or update non-VR UI because that content will not be visible. + */ + Cap_External = 1 << 4, + /** + * Cap_AngularAcceleration is set if the VRDisplay is capable of tracking its + * angular acceleration. + */ + Cap_AngularAcceleration = 1 << 5, + /** + * Cap_LinearAcceleration is set if the VRDisplay is capable of tracking its + * linear acceleration. + */ + Cap_LinearAcceleration = 1 << 6, + /** + * Cap_StageParameters is set if the VRDisplay is capable of room scale VR + * and can report the StageParameters to describe the space. + */ + Cap_StageParameters = 1 << 7, + /** + * Cap_MountDetection is set if the VRDisplay is capable of sensing when the + * user is wearing the device. + */ + Cap_MountDetection = 1 << 8, + /** + * Cap_PositionEmulated is set if the VRDisplay is capable of setting a + * emulated position (e.g. neck model) even if still doesn't support 6DOF + * tracking. + */ + Cap_PositionEmulated = 1 << 9, + /** + * Cap_Inline is set if the device can be used for WebXR inline sessions + * where the content is displayed within an element on the page. + */ + Cap_Inline = 1 << 10, + /** + * Cap_ImmersiveVR is set if the device can give exclusive access to the + * XR device display and that content is not intended to be integrated + * with the user's environment + */ + Cap_ImmersiveVR = 1 << 11, + /** + * Cap_ImmersiveAR is set if the device can give exclusive access to the + * XR device display and that content is intended to be integrated with + * the user's environment. + */ + Cap_ImmersiveAR = 1 << 12, + /** + * Cap_UseDepthValues is set if the device will use the depth values of the + * submitted frames if provided. How the depth values are used is determined + * by the VR runtime. Often the depth is used for occlusion of system UI + * or to enable more effective asynchronous reprojection of frames. + */ + Cap_UseDepthValues = 1 << 13, + /** + * Cap_All used for validity checking during IPC serialization + */ + Cap_All = (1 << 14) - 1 +}; + +#ifdef MOZILLA_INTERNAL_API +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(VRDisplayCapabilityFlags) +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(VRDisplayBlendMode) +#endif // MOZILLA_INTERNAL_API + +struct VRPose { + float orientation[4]; + float position[3]; + float angularVelocity[3]; + float angularAcceleration[3]; + float linearVelocity[3]; + float linearAcceleration[3]; +}; + +struct VRHMDSensorState { + uint64_t inputFrameID; + double timestamp; + VRDisplayCapabilityFlags flags; + + // These members will only change with inputFrameID: + VRPose pose; + float leftViewMatrix[16]; + float rightViewMatrix[16]; + +#ifdef MOZILLA_INTERNAL_API + + void Clear() { memset(this, 0, sizeof(VRHMDSensorState)); } + + bool operator==(const VRHMDSensorState& other) const { + return inputFrameID == other.inputFrameID && timestamp == other.timestamp; + } + + bool operator!=(const VRHMDSensorState& other) const { + return !(*this == other); + } + + void CalcViewMatrices(const gfx::Matrix4x4* aHeadToEyeTransforms); + +#endif // MOZILLA_INTERNAL_API +}; + +struct VRFieldOfView { + double upDegrees; + double rightDegrees; + double downDegrees; + double leftDegrees; + +#ifdef MOZILLA_INTERNAL_API + + VRFieldOfView() = default; + VRFieldOfView(double up, double right, double down, double left) + : upDegrees(up), + rightDegrees(right), + downDegrees(down), + leftDegrees(left) {} + + void SetFromTanRadians(double up, double right, double down, double left) { + upDegrees = atan(up) * 180.0 / M_PI; + rightDegrees = atan(right) * 180.0 / M_PI; + downDegrees = atan(down) * 180.0 / M_PI; + leftDegrees = atan(left) * 180.0 / M_PI; + } + + bool operator==(const VRFieldOfView& other) const { + return other.upDegrees == upDegrees && other.downDegrees == downDegrees && + other.rightDegrees == rightDegrees && + other.leftDegrees == leftDegrees; + } + + bool operator!=(const VRFieldOfView& other) const { + return !(*this == other); + } + + bool IsZero() const { + return upDegrees == 0.0 || rightDegrees == 0.0 || downDegrees == 0.0 || + leftDegrees == 0.0; + } + + Matrix4x4 ConstructProjectionMatrix(float zNear, float zFar, + bool rightHanded) const; + +#endif // MOZILLA_INTERNAL_API +}; + +struct VRDisplayState { + enum Eye { Eye_Left, Eye_Right, NumEyes }; + + // When true, indicates that the VR service has shut down + bool shutdown; + // Minimum number of milliseconds to wait before attempting + // to start the VR service again + uint32_t minRestartInterval; + char displayName[kVRDisplayNameMaxLen]; + // eight byte character code identifier + // LSB first, so "ABCDEFGH" -> ('H'<<56) + ('G'<<48) + ('F'<<40) + + // ('E'<<32) + ('D'<<24) + ('C'<<16) + + // ('B'<<8) + 'A'). + uint64_t eightCC; + VRDisplayCapabilityFlags capabilityFlags; + VRDisplayBlendMode blendMode; + VRFieldOfView eyeFOV[VRDisplayState::NumEyes]; + Point3D_POD eyeTranslation[VRDisplayState::NumEyes]; + IntSize_POD eyeResolution; + float nativeFramebufferScaleFactor; + bool suppressFrames; + bool isConnected; + bool isMounted; + FloatSize_POD stageSize; + // We can't use a Matrix4x4 here unless we ensure it's a POD type + float sittingToStandingTransform[16]; + uint64_t lastSubmittedFrameId; + bool lastSubmittedFrameSuccessful; + uint32_t presentingGeneration; + // Telemetry + bool reportsDroppedFrames; + uint64_t droppedFrameCount; + +#ifdef MOZILLA_INTERNAL_API + void Clear() { memset(this, 0, sizeof(VRDisplayState)); } +#endif +}; + +struct VRControllerState { + char controllerName[kVRControllerNameMaxLen]; +#ifdef MOZILLA_INTERNAL_API + dom::GamepadHand hand; +#else + ControllerHand hand; +#endif + // For WebXR->WebVR mapping conversion, once we remove WebVR, + // we can remove this item. + VRControllerType type; + // https://immersive-web.github.io/webxr/#enumdef-xrtargetraymode + TargetRayMode targetRayMode; + + // https://immersive-web.github.io/webxr-gamepads-module/#enumdef-gamepadmappingtype + GamepadMappingType mappingType; + + // Start frame ID of the most recent primary select + // action, or 0 if the select action has never occurred. + uint64_t selectActionStartFrameId; + // End frame Id of the most recent primary select + // action, or 0 if action never occurred. + // If selectActionStopFrameId is less than + // selectActionStartFrameId, then the select + // action has not ended yet. + uint64_t selectActionStopFrameId; + + // start frame Id of the most recent primary squeeze + // action, or 0 if the squeeze action has never occurred. + uint64_t squeezeActionStartFrameId; + // End frame Id of the most recent primary squeeze + // action, or 0 if action never occurred. + // If squeezeActionStopFrameId is less than + // squeezeActionStartFrameId, then the squeeze + // action has not ended yet. + uint64_t squeezeActionStopFrameId; + + uint32_t numButtons; + uint32_t numAxes; + uint32_t numHaptics; + // The current button pressed bit of button mask. + uint64_t buttonPressed; + // The current button touched bit of button mask. + uint64_t buttonTouched; + float triggerValue[kVRControllerMaxButtons]; + float axisValue[kVRControllerMaxAxis]; + +#ifdef MOZILLA_INTERNAL_API + dom::GamepadCapabilityFlags flags; +#else + ControllerCapabilityFlags flags; +#endif + + // When Cap_Position is set in flags, pose corresponds + // to the controllers' pose in grip space: + // https://immersive-web.github.io/webxr/#dom-xrinputsource-gripspace + VRPose pose; + + // When Cap_TargetRaySpacePosition is set in flags, targetRayPose corresponds + // to the controllers' pose in target ray space: + // https://immersive-web.github.io/webxr/#dom-xrinputsource-targetrayspace + VRPose targetRayPose; + + bool isPositionValid; + bool isOrientationValid; + +#ifdef MOZILLA_INTERNAL_API + void Clear() { memset(this, 0, sizeof(VRControllerState)); } +#endif +}; + +struct VRLayerEyeRect { + float x; + float y; + float width; + float height; +}; + +enum class VRLayerType : uint16_t { + LayerType_None = 0, + LayerType_2D_Content = 1, + LayerType_Stereo_Immersive = 2 +}; + +enum class VRLayerTextureType : uint16_t { + LayerTextureType_None = 0, + LayerTextureType_D3D10SurfaceDescriptor = 1, + LayerTextureType_MacIOSurface = 2, + LayerTextureType_GeckoSurfaceTexture = 3 +}; + +struct VRLayer_2D_Content { + VRLayerTextureHandle textureHandle; + VRLayerTextureType textureType; + uint64_t frameId; +}; + +struct VRLayer_Stereo_Immersive { + VRLayerTextureHandle textureHandle; + VRLayerTextureType textureType; + uint64_t frameId; + uint64_t inputFrameId; + VRLayerEyeRect leftEyeRect; + VRLayerEyeRect rightEyeRect; + IntSize_POD textureSize; +}; + +struct VRLayerState { + VRLayerType type; + union { + VRLayer_2D_Content layer_2d_content; + VRLayer_Stereo_Immersive layer_stereo_immersive; + }; +}; + +struct VRHapticState { + // Reference frame for timing. + // When 0, this does not represent an active haptic pulse. + uint64_t inputFrameID; + // Index within VRSystemState.controllerState identifying the controller + // to emit the haptic pulse + uint32_t controllerIndex; + // 0-based index indicating which haptic actuator within the controller + uint32_t hapticIndex; + // Start time of the haptic feedback pulse, relative to the start of + // inputFrameID, in seconds + float pulseStart; + // Duration of the haptic feedback pulse, in seconds + float pulseDuration; + // Intensity of the haptic feedback pulse, from 0.0f to 1.0f + float pulseIntensity; +}; + +struct VRBrowserState { +#if defined(__ANDROID__) + bool shutdown; +#endif // defined(__ANDROID__) + /** + * In order to support WebXR's navigator.xr.IsSessionSupported call without + * displaying any permission dialogue, it is necessary to have a safe way to + * detect the capability of running a VR or AR session without activating XR + * runtimes or powering on hardware. + * + * API's such as OpenVR make no guarantee that hardware and software won't be + * left activated after enumerating devices, so each backend in gfx/vr/service + * must allow for more granular detection of capabilities. + * + * When detectRuntimesOnly is true, the initialization exits early after + * reporting the presence of XR runtime software. + * + * The result of the runtime detection is reported with the Cap_ImmersiveVR + * and Cap_ImmersiveAR bits in VRDisplayState.flags. + */ + bool detectRuntimesOnly; + bool presentationActive; + bool navigationTransitionActive; + VRLayerState layerState[kVRLayerMaxCount]; + VRHapticState hapticState[kVRHapticsMaxCount]; + +#ifdef MOZILLA_INTERNAL_API + void Clear() { memset(this, 0, sizeof(VRBrowserState)); } +#endif +}; + +struct VRSystemState { + bool enumerationCompleted; + VRDisplayState displayState; + VRHMDSensorState sensorState; + VRControllerState controllerState[kVRControllerMaxCount]; +}; + +enum class VRFxEventType : uint8_t { + NONE = 0, + IME, + SHUTDOWN, + FULLSCREEN, + TOTAL +}; + +enum class VRFxEventState : uint8_t { + NONE = 0, + BLUR, + FOCUS, + FULLSCREEN_ENTER, + FULLSCREEN_EXIT, + TOTAL +}; + +// Data shared via shmem for running Firefox in a VR windowed environment +struct VRWindowState { + // State from Firefox + uint64_t hwndFx; + uint32_t widthFx; + uint32_t heightFx; + VRLayerTextureHandle textureFx; + uint32_t windowID; + VRFxEventType eventType; + VRFxEventState eventState; + + // State from VRHost + uint32_t dxgiAdapterHost; + uint32_t widthHost; + uint32_t heightHost; + + // Name of synchronization primitive to signal change to this struct + char signalName[32]; +}; + +enum class VRTelemetryId : uint8_t { + NONE = 0, + INSTALLED_FROM = 1, + ENTRY_METHOD = 2, + FIRST_RUN = 3, + TOTAL = 4, +}; + +enum class VRTelemetryInstallFrom : uint8_t { + User = 0, + FxR = 1, + HTC = 2, + Valve = 3, + TOTAL = 4, +}; + +enum class VRTelemetryEntryMethod : uint8_t { + SystemBtn = 0, + Library = 1, + Gaze = 2, + TOTAL = 3, +}; + +struct VRTelemetryState { + uint32_t uid; + + bool installedFrom : 1; + bool entryMethod : 1; + bool firstRun : 1; + + uint8_t installedFromValue : 3; + uint8_t entryMethodValue : 3; + bool firstRunValue : 1; +}; + +struct VRExternalShmem { + int32_t version; + int32_t size; +#if defined(__ANDROID__) + pthread_mutex_t systemMutex; + pthread_mutex_t geckoMutex; + pthread_mutex_t servoMutex; + pthread_cond_t systemCond; + pthread_cond_t geckoCond; + pthread_cond_t servoCond; +#else + int64_t generationA; +#endif // defined(__ANDROID__) + VRSystemState state; +#if !defined(__ANDROID__) + int64_t generationB; + int64_t geckoGenerationA; + int64_t servoGenerationA; +#endif // !defined(__ANDROID__) + VRBrowserState geckoState; + VRBrowserState servoState; +#if !defined(__ANDROID__) + int64_t geckoGenerationB; + int64_t servoGenerationB; +#endif // !defined(__ANDROID__) +#if defined(XP_WIN) + VRWindowState windowState; + VRTelemetryState telemetryState; +#endif +#ifdef MOZILLA_INTERNAL_API + void Clear() volatile { +/** + * When possible we do a memset_s, which is explicitly safe for + * the volatile, POD struct. A memset may be optimized out by + * the compiler and will fail to compile due to volatile keyword + * propagation. + * + * A loop-based fallback is provided in case the toolchain does + * not include STDC_LIB_EXT1 for memset_s. + */ +# ifdef __STDC_LIB_EXT1__ + memset_s((void*)this, sizeof(VRExternalShmem), 0, sizeof(VRExternalShmem)); +# else + size_t remaining = sizeof(VRExternalShmem); + volatile unsigned char* d = (volatile unsigned char*)this; + while (remaining--) { + *d++ = 0; + } +# endif + } +#endif +}; + +// As we are memcpy'ing VRExternalShmem and its members around, it must be a POD +// type +static_assert(std::is_pod<VRExternalShmem>::value, + "VRExternalShmem must be a POD type."); + +} // namespace gfx +} // namespace mozilla + +#endif /* GFX_VR_EXTERNAL_API_H */ diff --git a/gfx/vr/gfxVR.cpp b/gfx/vr/gfxVR.cpp new file mode 100644 index 0000000000..ed1c6d32d6 --- /dev/null +++ b/gfx/vr/gfxVR.cpp @@ -0,0 +1,91 @@ +/* -*- 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 <math.h> + +#include "gfxVR.h" + +#ifndef M_PI +# define M_PI 3.14159265358979323846 +#endif + +using namespace mozilla; +using namespace mozilla::gfx; + +Matrix4x4 VRFieldOfView::ConstructProjectionMatrix(float zNear, float zFar, + bool rightHanded) const { + float upTan = tan(upDegrees * M_PI / 180.0); + float downTan = tan(downDegrees * M_PI / 180.0); + float leftTan = tan(leftDegrees * M_PI / 180.0); + float rightTan = tan(rightDegrees * M_PI / 180.0); + + float handednessScale = rightHanded ? -1.0 : 1.0; + + float pxscale = 2.0f / (leftTan + rightTan); + float pxoffset = (leftTan - rightTan) * pxscale * 0.5; + float pyscale = 2.0f / (upTan + downTan); + float pyoffset = (upTan - downTan) * pyscale * 0.5; + + Matrix4x4 mobj; + float* m = &mobj._11; + + m[0 * 4 + 0] = pxscale; + m[2 * 4 + 0] = pxoffset * handednessScale; + + m[1 * 4 + 1] = pyscale; + m[2 * 4 + 1] = -pyoffset * handednessScale; + + m[2 * 4 + 2] = zFar / (zNear - zFar) * -handednessScale; + m[3 * 4 + 2] = (zFar * zNear) / (zNear - zFar); + + m[2 * 4 + 3] = handednessScale; + m[3 * 4 + 3] = 0.0f; + + return mobj; +} + +void VRHMDSensorState::CalcViewMatrices( + const gfx::Matrix4x4* aHeadToEyeTransforms) { + gfx::Matrix4x4 matHead; + if (flags & VRDisplayCapabilityFlags::Cap_Orientation) { + matHead.SetRotationFromQuaternion( + gfx::Quaternion(-pose.orientation[0], -pose.orientation[1], + -pose.orientation[2], pose.orientation[3])); + } + matHead.PreTranslate(-pose.position[0], -pose.position[1], -pose.position[2]); + + gfx::Matrix4x4 matView = + matHead * aHeadToEyeTransforms[VRDisplayState::Eye_Left]; + matView.Normalize(); + memcpy(leftViewMatrix, matView.components, sizeof(matView.components)); + matView = matHead * aHeadToEyeTransforms[VRDisplayState::Eye_Right]; + matView.Normalize(); + memcpy(rightViewMatrix, matView.components, sizeof(matView.components)); +} + +const IntSize VRDisplayInfo::SuggestedEyeResolution() const { + return IntSize(mDisplayState.eyeResolution.width, + mDisplayState.eyeResolution.height); +} + +const Point3D VRDisplayInfo::GetEyeTranslation(uint32_t whichEye) const { + return Point3D(mDisplayState.eyeTranslation[whichEye].x, + mDisplayState.eyeTranslation[whichEye].y, + mDisplayState.eyeTranslation[whichEye].z); +} + +const Size VRDisplayInfo::GetStageSize() const { + return Size(mDisplayState.stageSize.width, mDisplayState.stageSize.height); +} + +const Matrix4x4 VRDisplayInfo::GetSittingToStandingTransform() const { + Matrix4x4 m; + // If we could replace Matrix4x4 with a pod type, we could + // use it directly from the VRDisplayInfo struct. + memcpy(m.components, mDisplayState.sittingToStandingTransform, + sizeof(float) * 16); + return m; +} diff --git a/gfx/vr/gfxVR.h b/gfx/vr/gfxVR.h new file mode 100644 index 0000000000..d4d772b6a2 --- /dev/null +++ b/gfx/vr/gfxVR.h @@ -0,0 +1,162 @@ +/* -*- 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/. */ + +#ifndef GFX_VR_H +#define GFX_VR_H + +#include "moz_external_vr.h" +#include "nsTArray.h" +#include "nsString.h" +#include "nsCOMPtr.h" +#include "mozilla/RefPtr.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/Atomics.h" +#include "mozilla/EnumeratedArray.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/TypedEnumBits.h" +#include <type_traits> + +namespace mozilla { +namespace layers { +class PTextureParent; +} +namespace dom { +enum class GamepadMappingType : uint8_t; +enum class GamepadHand : uint8_t; +} // namespace dom +namespace gfx { +enum class VRAPIMode : uint8_t { WebXR, WebVR, NumVRAPIModes }; + +class VRLayerParent; +class VRDisplayHost; +class VRManagerPromise; + +// The maximum number of frames of latency that we would expect before we +// should give up applying pose prediction. +// If latency is greater than one second, then the experience is not likely +// to be corrected by pose prediction. Setting this value too +// high may result in unnecessary memory allocation. +// As the current fastest refresh rate is 90hz, 100 is selected as a +// conservative value. +static const int kVRMaxLatencyFrames = 100; + +struct VRDisplayInfo { + uint32_t mDisplayID; + uint32_t mPresentingGroups; + uint32_t mGroupMask; + uint64_t mFrameId; + VRDisplayState mDisplayState; + VRControllerState mControllerState[kVRControllerMaxCount]; + + VRHMDSensorState mLastSensorState[kVRMaxLatencyFrames]; + void Clear() { memset(this, 0, sizeof(VRDisplayInfo)); } + const VRHMDSensorState& GetSensorState() const { + return mLastSensorState[mFrameId % kVRMaxLatencyFrames]; + } + + uint32_t GetDisplayID() const { return mDisplayID; } + const char* GetDisplayName() const { return mDisplayState.displayName; } + VRDisplayCapabilityFlags GetCapabilities() const { + return mDisplayState.capabilityFlags; + } + + const IntSize SuggestedEyeResolution() const; + const Point3D GetEyeTranslation(uint32_t whichEye) const; + const VRFieldOfView& GetEyeFOV(uint32_t whichEye) const { + return mDisplayState.eyeFOV[whichEye]; + } + bool GetIsConnected() const { return mDisplayState.isConnected; } + bool GetIsMounted() const { return mDisplayState.isMounted; } + uint32_t GetPresentingGroups() const { return mPresentingGroups; } + uint32_t GetGroupMask() const { return mGroupMask; } + const Size GetStageSize() const; + const Matrix4x4 GetSittingToStandingTransform() const; + uint64_t GetFrameId() const { return mFrameId; } + + bool operator==(const VRDisplayInfo& other) const { + for (size_t i = 0; i < kVRMaxLatencyFrames; i++) { + if (mLastSensorState[i] != other.mLastSensorState[i]) { + return false; + } + } + // Note that mDisplayState and mControllerState are asserted to be POD + // types, so memcmp is safe + return mDisplayID == other.mDisplayID && + memcmp(&mDisplayState, &other.mDisplayState, + sizeof(VRDisplayState)) == 0 && + memcmp(mControllerState, other.mControllerState, + sizeof(VRControllerState) * kVRControllerMaxCount) == 0 && + mPresentingGroups == other.mPresentingGroups && + mGroupMask == other.mGroupMask && mFrameId == other.mFrameId; + } + + bool operator!=(const VRDisplayInfo& other) const { + return !(*this == other); + } +}; + +static_assert(std::is_pod<VRDisplayInfo>::value, + "VRDisplayInfo must be a POD type."); + +struct VRSubmitFrameResultInfo { + VRSubmitFrameResultInfo() + : mFormat(SurfaceFormat::UNKNOWN), mFrameNum(0), mWidth(0), mHeight(0) {} + + nsCString mBase64Image; + SurfaceFormat mFormat; + uint64_t mFrameNum; + uint32_t mWidth; + uint32_t mHeight; +}; + +struct VRControllerInfo { + uint32_t GetControllerID() const { return mControllerID; } + const char* GetControllerName() const { + return mControllerState.controllerName; + } + dom::GamepadMappingType GetMappingType() const { return mMappingType; } + uint32_t GetDisplayID() const { return mDisplayID; } + dom::GamepadHand GetHand() const { return mControllerState.hand; } + uint32_t GetNumButtons() const { return mControllerState.numButtons; } + uint32_t GetNumAxes() const { return mControllerState.numAxes; } + uint32_t GetNumHaptics() const { return mControllerState.numHaptics; } + + uint32_t mControllerID; + dom::GamepadMappingType mMappingType; + uint32_t mDisplayID; + VRControllerState mControllerState; + bool operator==(const VRControllerInfo& other) const { + // Note that mControllerState is asserted to be a POD type, so memcmp is + // safe + return mControllerID == other.mControllerID && + memcmp(&mControllerState, &other.mControllerState, + sizeof(VRControllerState)) == 0 && + mMappingType == other.mMappingType && mDisplayID == other.mDisplayID; + } + + bool operator!=(const VRControllerInfo& other) const { + return !(*this == other); + } +}; + +struct VRTelemetry { + VRTelemetry() : mLastDroppedFrameCount(-1) {} + + void Clear() { + mPresentationStart = TimeStamp(); + mLastDroppedFrameCount = -1; + } + + bool IsLastDroppedFrameValid() { return (mLastDroppedFrameCount != -1); } + + TimeStamp mPresentationStart; + int32_t mLastDroppedFrameCount; +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* GFX_VR_H */ diff --git a/gfx/vr/gfxVRMutex.h b/gfx/vr/gfxVRMutex.h new file mode 100644 index 0000000000..a54b8dc8b0 --- /dev/null +++ b/gfx/vr/gfxVRMutex.h @@ -0,0 +1,63 @@ +/* -*- 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/. */ + +#ifndef GFX_VR_MUTEX_H +#define GFX_VR_MUTEX_H + +namespace mozilla { +namespace gfx { + +#if defined(XP_WIN) +class WaitForMutex { + public: + explicit WaitForMutex(HANDLE handle) : mHandle(handle), mStatus(false) { + MOZ_ASSERT(mHandle); + + DWORD dwWaitResult; + dwWaitResult = WaitForSingleObject(mHandle, // handle to mutex + INFINITE); // no time-out interval + + switch (dwWaitResult) { + // The thread got ownership of the mutex + case WAIT_OBJECT_0: + mStatus = true; + break; + + // The thread got ownership of an abandoned mutex + // The shmem is in an indeterminate state + case WAIT_ABANDONED: + mStatus = false; + break; + default: + mStatus = false; + break; + } + } + + ~WaitForMutex() { + if (mHandle && !ReleaseMutex(mHandle)) { +# ifdef MOZILLA_INTERNAL_API + nsAutoCString msg; + msg.AppendPrintf("WaitForMutex %d ReleaseMutex error \"%lu\".", mHandle, + GetLastError()); + NS_WARNING(msg.get()); +# endif + MOZ_ASSERT(false, "Failed to release mutex."); + } + } + + bool GetStatus() { return mStatus; } + + private: + HANDLE mHandle; + bool mStatus; +}; +#endif + +} // namespace gfx +} // namespace mozilla + +#endif /* GFX_VR_MUTEX_H */ diff --git a/gfx/vr/ipc/PVR.ipdl b/gfx/vr/ipc/PVR.ipdl new file mode 100644 index 0000000000..6633ae957b --- /dev/null +++ b/gfx/vr/ipc/PVR.ipdl @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +using mozilla::TimeStamp from "mozilla/TimeStamp.h"; +using mozilla::gfx::VRControllerType from "moz_external_vr.h"; +using mozilla::dom::NativeThreadId from "mozilla/dom/NativeThreadId.h"; +include "VRMessageUtils.h"; + +include GraphicsMessages; +include MemoryReportTypes; +include PrefsTypes; +include protocol PVRGPU; + +namespace mozilla { +namespace gfx { + +async protocol PVR +{ +parent: + async NewGPUVRManager(Endpoint<PVRGPUParent> endpoint); + async Init(GfxVarUpdate[] vars, DevicePrefs devicePrefs); + + async UpdateVar(GfxVarUpdate var); + async PreferenceUpdate(Pref pref); + async OpenVRControllerActionPathToVR(nsCString aPath); + async OpenVRControllerManifestPathToVR(VRControllerType aType, nsCString aPath); + async RequestMemoryReport(uint32_t generation, + bool anonymize, + bool minimizeMemoryUsage, + FileDescriptor? DMDFile) + returns (uint32_t aGeneration); + +child: + // Sent when the GPU process has initialized devices. This occurs once, after + // Init(). + async InitComplete(); + async OpenVRControllerActionPathToParent(nsCString aPath); + async OpenVRControllerManifestPathToParent(VRControllerType aType, nsCString aPath); + async InitCrashReporter(NativeThreadId threadId); + async AddMemoryReport(MemoryReport aReport); +}; + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/vr/ipc/PVRGPU.ipdl b/gfx/vr/ipc/PVRGPU.ipdl new file mode 100644 index 0000000000..1e0f2d9fab --- /dev/null +++ b/gfx/vr/ipc/PVRGPU.ipdl @@ -0,0 +1,24 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +namespace mozilla { +namespace gfx { + +// The parent process is the VR process. +// The child process is the GPU process. +async protocol PVRGPU +{ +parent: + async StartVRService(); + async StopVRService(); + async PuppetSubmit(uint64_t[] aBuffer); + async PuppetReset(); + async PuppetCheckForCompletion(); +child: + async NotifyPuppetComplete(); +}; + +} // gfx +} // mozilla
\ No newline at end of file diff --git a/gfx/vr/ipc/PVRLayer.ipdl b/gfx/vr/ipc/PVRLayer.ipdl new file mode 100644 index 0000000000..5c73ccff2c --- /dev/null +++ b/gfx/vr/ipc/PVRLayer.ipdl @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=8 et : + */ +/* 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 LayersSurfaces; +include protocol PVRManager; + +using mozilla::gfx::Rect from "mozilla/gfx/Rect.h"; + +namespace mozilla { +namespace gfx { + +async protocol PVRLayer +{ + manager PVRManager; + +parent: + async SubmitFrame(SurfaceDescriptor aTexture, uint64_t aFrameId, + Rect aLeftEyeRect, Rect aRightEyeRect); + + async Destroy(); + +child: + async __delete__(); +}; + +} // gfx +} // mozilla diff --git a/gfx/vr/ipc/PVRManager.ipdl b/gfx/vr/ipc/PVRManager.ipdl new file mode 100644 index 0000000000..1b97c38170 --- /dev/null +++ b/gfx/vr/ipc/PVRManager.ipdl @@ -0,0 +1,84 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=8 et : + */ +/* 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 LayersSurfaces; +include protocol PVRLayer; +include LayersMessages; +include GamepadEventTypes; + +include "VRMessageUtils.h"; + +using struct mozilla::gfx::VRFieldOfView from "gfxVR.h"; +using struct mozilla::gfx::VRDisplayInfo from "gfxVR.h"; +using struct mozilla::gfx::VRSensorUpdate from "gfxVR.h"; +using struct mozilla::gfx::VRHMDSensorState from "gfxVR.h"; +using struct mozilla::gfx::VRControllerInfo from "gfxVR.h"; +using struct mozilla::gfx::VRSubmitFrameResultInfo from "gfxVR.h"; +using mozilla::gfx::VRDisplayCapabilityFlags from "moz_external_vr.h"; +using mozilla::layers::LayersBackend from "mozilla/layers/LayersTypes.h"; +using mozilla::layers::TextureFlags from "mozilla/layers/CompositorTypes.h"; +using mozilla::dom::GamepadHandle from "mozilla/dom/GamepadHandle.h"; + + +namespace mozilla { +namespace gfx { + +/** + * The PVRManager protocol is used to enable communication of VR display + * enumeration and sensor state between the compositor thread and + * content threads/processes. + */ +sync protocol PVRManager +{ + manages PVRLayer; + +parent: + async PVRLayer(uint32_t aDisplayID, uint32_t aGroup); + + // Detect runtime capabilities. This will return the presense of VR and/or AR + // runtime software, without enumerating or activating any hardware devices. + async DetectRuntimes(); + + // (Re)Enumerate VR Displays. An updated list of VR displays will be returned + // asynchronously to children via UpdateDisplayInfo. + async RefreshDisplays(); + + async SetGroupMask(uint32_t aDisplayID, uint32_t aGroupMask); + async SetHaveEventListener(bool aHaveEventListener); + + async ControllerListenerAdded(); + async ControllerListenerRemoved(); + async VibrateHaptic(GamepadHandle aGamepadHandle, uint32_t aHapticIndex, + double aIntensity, double aDuration, uint32_t aPromiseID); + async StopVibrateHaptic(GamepadHandle aGamepadHandle); + async StartVRNavigation(uint32_t aDeviceID); + async StopVRNavigation(uint32_t aDeviceID, TimeDuration aDuration); + async StartActivity(); + async StopActivity(); + + async RunPuppet(uint64_t[] buffer); + async ResetPuppet(); + +child: + // Notify children of updated VR display enumeration and details. This will + // be sent to all children when the parent receives RefreshDisplays, even + // if no changes have been detected. This ensures that Promises exposed + // through DOM calls are always resolved. + async UpdateDisplayInfo(VRDisplayInfo aDisplayInfo); + + async UpdateRuntimeCapabilities(VRDisplayCapabilityFlags aCapabilities); + + async ReplyGamepadVibrateHaptic(uint32_t aPromiseID); + async NotifyPuppetCommandBufferCompleted(bool aSuccess); + async NotifyPuppetResetComplete(); + + async __delete__(); + +}; + +} // gfx +} // mozilla diff --git a/gfx/vr/ipc/VRChild.cpp b/gfx/vr/ipc/VRChild.cpp new file mode 100644 index 0000000000..d9e21ddc47 --- /dev/null +++ b/gfx/vr/ipc/VRChild.cpp @@ -0,0 +1,214 @@ +/* -*- 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 "VRChild.h" +#include "VRProcessManager.h" +#include "VRProcessParent.h" +#include "gfxConfig.h" + +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/Telemetry.h" +#include "mozilla/VsyncDispatcher.h" +#include "mozilla/dom/MemoryReportRequest.h" + +namespace mozilla { +namespace gfx { + +class OpenVRControllerManifestManager { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(OpenVRControllerManifestManager) + public: + explicit OpenVRControllerManifestManager() = default; + + void SetOpenVRControllerActionPath(const nsCString& aPath) { + mAction = aPath; + } + + void SetOpenVRControllerManifestPath(VRControllerType aType, + const nsCString& aPath) { + mManifest.Put(static_cast<uint32_t>(aType), aPath); + } + + bool GetActionPath(nsCString* aPath) { + if (!mAction.IsEmpty()) { + *aPath = mAction; + return true; + } + return false; + } + + bool GetManifestPath(VRControllerType aType, nsCString* aPath) { + return mManifest.Get(static_cast<uint32_t>(aType), aPath); + } + + private: + ~OpenVRControllerManifestManager() { + if (!mAction.IsEmpty() && remove(mAction.BeginReading()) != 0) { + MOZ_ASSERT(false, "Delete controller action file failed."); + } + mAction = ""; + + for (auto iter = mManifest.Iter(); !iter.Done(); iter.Next()) { + nsCString path(iter.Data()); + if (!path.IsEmpty() && remove(path.BeginReading()) != 0) { + MOZ_ASSERT(false, "Delete controller manifest file failed."); + } + } + mManifest.Clear(); + } + + nsCString mAction; + nsDataHashtable<nsUint32HashKey, nsCString> mManifest; + OpenVRControllerManifestManager(const OpenVRControllerManifestManager&) = + delete; + + const OpenVRControllerManifestManager& operator=( + const OpenVRControllerManifestManager&) = delete; +}; + +StaticRefPtr<OpenVRControllerManifestManager> sOpenVRControllerManifestManager; + +VRChild::VRChild(VRProcessParent* aHost) : mHost(aHost), mVRReady(false) { + MOZ_ASSERT(XRE_IsParentProcess()); +} + +mozilla::ipc::IPCResult VRChild::RecvAddMemoryReport( + const MemoryReport& aReport) { + if (mMemoryReportRequest) { + mMemoryReportRequest->RecvReport(aReport); + } + return IPC_OK(); +} + +void VRChild::ActorDestroy(ActorDestroyReason aWhy) { + if (aWhy == AbnormalShutdown) { + GenerateCrashReport(OtherPid()); + + Telemetry::Accumulate( + Telemetry::SUBPROCESS_ABNORMAL_ABORT, + nsDependentCString(XRE_GeckoProcessTypeToString(GeckoProcessType_VR)), + 1); + } + gfxVars::RemoveReceiver(this); + mHost->OnChannelClosed(); +} + +void VRChild::Init() { + nsTArray<GfxVarUpdate> updates = gfxVars::FetchNonDefaultVars(); + + DevicePrefs devicePrefs; + devicePrefs.hwCompositing() = gfxConfig::GetValue(Feature::HW_COMPOSITING); + devicePrefs.d3d11Compositing() = + gfxConfig::GetValue(Feature::D3D11_COMPOSITING); + devicePrefs.oglCompositing() = + gfxConfig::GetValue(Feature::OPENGL_COMPOSITING); + devicePrefs.advancedLayers() = gfxConfig::GetValue(Feature::ADVANCED_LAYERS); + devicePrefs.useD2D1() = gfxConfig::GetValue(Feature::DIRECT2D); + + SendInit(updates, devicePrefs); + + if (!sOpenVRControllerManifestManager) { + sOpenVRControllerManifestManager = new OpenVRControllerManifestManager(); + NS_DispatchToMainThread(NS_NewRunnableFunction( + "ClearOnShutdown OpenVRControllerManifestManager", + []() { ClearOnShutdown(&sOpenVRControllerManifestManager); })); + } + + nsCString output; + if (sOpenVRControllerManifestManager->GetActionPath(&output)) { + SendOpenVRControllerActionPathToVR(output); + } + if (sOpenVRControllerManifestManager->GetManifestPath( + VRControllerType::HTCVive, &output)) { + SendOpenVRControllerManifestPathToVR(VRControllerType::HTCVive, output); + } + if (sOpenVRControllerManifestManager->GetManifestPath(VRControllerType::MSMR, + &output)) { + SendOpenVRControllerManifestPathToVR(VRControllerType::MSMR, output); + } + if (sOpenVRControllerManifestManager->GetManifestPath( + VRControllerType::ValveIndex, &output)) { + SendOpenVRControllerManifestPathToVR(VRControllerType::ValveIndex, output); + } + gfxVars::AddReceiver(this); +} + +bool VRChild::EnsureVRReady() { + if (!mVRReady) { + return false; + } + + return true; +} + +mozilla::ipc::IPCResult VRChild::RecvOpenVRControllerActionPathToParent( + const nsCString& aPath) { + sOpenVRControllerManifestManager->SetOpenVRControllerActionPath(aPath); + return IPC_OK(); +} + +mozilla::ipc::IPCResult VRChild::RecvOpenVRControllerManifestPathToParent( + const VRControllerType& aType, const nsCString& aPath) { + sOpenVRControllerManifestManager->SetOpenVRControllerManifestPath(aType, + aPath); + return IPC_OK(); +} + +mozilla::ipc::IPCResult VRChild::RecvInitComplete() { + // We synchronously requested VR parameters before this arrived. + mVRReady = true; + return IPC_OK(); +} + +bool VRChild::SendRequestMemoryReport(const uint32_t& aGeneration, + const bool& aAnonymize, + const bool& aMinimizeMemoryUsage, + const Maybe<FileDescriptor>& aDMDFile) { + mMemoryReportRequest = MakeUnique<MemoryReportRequestHost>(aGeneration); + + PVRChild::SendRequestMemoryReport( + aGeneration, aAnonymize, aMinimizeMemoryUsage, aDMDFile, + [&](const uint32_t& aGeneration2) { + if (VRProcessManager* vpm = VRProcessManager::Get()) { + if (VRChild* child = vpm->GetVRChild()) { + if (child->mMemoryReportRequest) { + child->mMemoryReportRequest->Finish(aGeneration2); + child->mMemoryReportRequest = nullptr; + } + } + } + }, + [&](mozilla::ipc::ResponseRejectReason) { + if (VRProcessManager* vpm = VRProcessManager::Get()) { + if (VRChild* child = vpm->GetVRChild()) { + child->mMemoryReportRequest = nullptr; + } + } + }); + + return true; +} + +void VRChild::OnVarChanged(const GfxVarUpdate& aVar) { SendUpdateVar(aVar); } + +class DeferredDeleteVRChild : public Runnable { + public: + explicit DeferredDeleteVRChild(UniquePtr<VRChild>&& aChild) + : Runnable("gfx::DeferredDeleteVRChild"), mChild(std::move(aChild)) {} + + NS_IMETHODIMP Run() override { return NS_OK; } + + private: + UniquePtr<VRChild> mChild; +}; + +/* static */ +void VRChild::Destroy(UniquePtr<VRChild>&& aChild) { + NS_DispatchToMainThread(new DeferredDeleteVRChild(std::move(aChild))); +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/vr/ipc/VRChild.h b/gfx/vr/ipc/VRChild.h new file mode 100644 index 0000000000..04d9501fe8 --- /dev/null +++ b/gfx/vr/ipc/VRChild.h @@ -0,0 +1,63 @@ +/* -*- 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/. */ + +#ifndef GFX_VR_CHILD_H +#define GFX_VR_CHILD_H + +#include "mozilla/gfx/PVRChild.h" +#include "mozilla/gfx/gfxVarReceiver.h" +#include "mozilla/ipc/CrashReporterHelper.h" +#include "mozilla/VsyncDispatcher.h" +#include "moz_external_vr.h" + +namespace mozilla { +namespace dom { +class MemoryReportRequestHost; +} // namespace dom +namespace gfx { + +class VRProcessParent; +class VRChild; + +class VRChild final : public PVRChild, + public ipc::CrashReporterHelper<GeckoProcessType_VR>, + public gfxVarReceiver { + typedef mozilla::dom::MemoryReportRequestHost MemoryReportRequestHost; + friend class PVRChild; + + public: + explicit VRChild(VRProcessParent* aHost); + ~VRChild() = default; + + static void Destroy(UniquePtr<VRChild>&& aChild); + void Init(); + bool EnsureVRReady(); + virtual void OnVarChanged(const GfxVarUpdate& aVar) override; + bool SendRequestMemoryReport(const uint32_t& aGeneration, + const bool& aAnonymize, + const bool& aMinimizeMemoryUsage, + const Maybe<ipc::FileDescriptor>& aDMDFile); + + protected: + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + mozilla::ipc::IPCResult RecvOpenVRControllerActionPathToParent( + const nsCString& aPath); + mozilla::ipc::IPCResult RecvOpenVRControllerManifestPathToParent( + const VRControllerType& aType, const nsCString& aPath); + mozilla::ipc::IPCResult RecvInitComplete(); + + mozilla::ipc::IPCResult RecvAddMemoryReport(const MemoryReport& aReport); + + private: + VRProcessParent* mHost; + UniquePtr<MemoryReportRequestHost> mMemoryReportRequest; + bool mVRReady; +}; + +} // namespace gfx +} // namespace mozilla + +#endif // GFX_VR_CHILD_H diff --git a/gfx/vr/ipc/VRGPUChild.cpp b/gfx/vr/ipc/VRGPUChild.cpp new file mode 100644 index 0000000000..f8b90a5b44 --- /dev/null +++ b/gfx/vr/ipc/VRGPUChild.cpp @@ -0,0 +1,81 @@ +/* -*- 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 "VRGPUChild.h" +#include "VRServiceHost.h" + +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/layers/CompositorThread.h" +#include "VRManager.h" + +namespace mozilla { +namespace gfx { + +static StaticRefPtr<VRGPUChild> sVRGPUChildSingleton; + +/* static */ +bool VRGPUChild::InitForGPUProcess(Endpoint<PVRGPUChild>&& aEndpoint) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!sVRGPUChildSingleton); + + RefPtr<VRGPUChild> child(new VRGPUChild()); + if (!aEndpoint.Bind(child)) { + return false; + } + sVRGPUChildSingleton = child; + +#if !defined(MOZ_WIDGET_ANDROID) + RefPtr<Runnable> task = NS_NewRunnableFunction( + "VRServiceHost::NotifyVRProcessStarted", []() -> void { + VRServiceHost* host = VRServiceHost::Get(); + host->NotifyVRProcessStarted(); + }); + + NS_DispatchToMainThread(task.forget()); +#endif + + return true; +} + +/* static */ +bool VRGPUChild::IsCreated() { return !!sVRGPUChildSingleton; } + +/* static */ +VRGPUChild* VRGPUChild::Get() { + MOZ_ASSERT(IsCreated(), "VRGPUChild haven't initialized yet."); + return sVRGPUChildSingleton; +} + +/*static*/ +void VRGPUChild::Shutdown() { + MOZ_ASSERT(NS_IsMainThread()); + if (sVRGPUChildSingleton && !sVRGPUChildSingleton->IsClosed()) { + sVRGPUChildSingleton->Close(); + } + sVRGPUChildSingleton = nullptr; +} + +void VRGPUChild::ActorDestroy(ActorDestroyReason aWhy) { + VRManager* vm = VRManager::Get(); + mozilla::layers::CompositorThread()->Dispatch( + NewRunnableMethod("VRGPUChild::ActorDestroy", vm, &VRManager::Shutdown)); + + mClosed = true; +} + +mozilla::ipc::IPCResult VRGPUChild::RecvNotifyPuppetComplete() { +#if !defined(MOZ_WIDGET_ANDROID) + VRManager* vm = VRManager::Get(); + mozilla::layers::CompositorThread()->Dispatch(NewRunnableMethod( + "VRManager::NotifyPuppetComplete", vm, &VRManager::NotifyPuppetComplete)); +#endif + return IPC_OK(); +} + +bool VRGPUChild::IsClosed() { return mClosed; } + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/vr/ipc/VRGPUChild.h b/gfx/vr/ipc/VRGPUChild.h new file mode 100644 index 0000000000..3c13ecfc76 --- /dev/null +++ b/gfx/vr/ipc/VRGPUChild.h @@ -0,0 +1,43 @@ +/* -*- 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/. */ + +#ifndef GFX_VR_GPU_CHILD_H +#define GFX_VR_GPU_CHILD_H + +#include "mozilla/gfx/PVRGPUChild.h" + +namespace mozilla { +namespace gfx { + +class VRGPUChild final : public PVRGPUChild { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VRGPUChild); + + static VRGPUChild* Get(); + static bool InitForGPUProcess(Endpoint<PVRGPUChild>&& aEndpoint); + static bool IsCreated(); + static void Shutdown(); + + mozilla::ipc::IPCResult RecvNotifyPuppetComplete(); + mozilla::ipc::IPCResult RecvNotifyServiceStarted(); + + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + bool IsClosed(); + + protected: + explicit VRGPUChild() : mClosed(false) {} + ~VRGPUChild() = default; + + private: + DISALLOW_COPY_AND_ASSIGN(VRGPUChild); + + bool mClosed; +}; + +} // namespace gfx +} // namespace mozilla + +#endif // GFX_VR_GPU_CHILD_H diff --git a/gfx/vr/ipc/VRGPUParent.cpp b/gfx/vr/ipc/VRGPUParent.cpp new file mode 100644 index 0000000000..ce1eef77fa --- /dev/null +++ b/gfx/vr/ipc/VRGPUParent.cpp @@ -0,0 +1,112 @@ +/* -*- 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 "VRGPUParent.h" +#include "VRPuppetCommandBuffer.h" + +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/ipc/ProcessChild.h" + +namespace mozilla { +namespace gfx { + +using namespace ipc; + +VRGPUParent::VRGPUParent(ProcessId aChildProcessId) : mClosed(false) { + MOZ_COUNT_CTOR(VRGPUParent); + MOZ_ASSERT(NS_IsMainThread()); + + SetOtherProcessId(aChildProcessId); +} + +VRGPUParent::~VRGPUParent() { MOZ_COUNT_DTOR(VRGPUParent); } + +void VRGPUParent::ActorDestroy(ActorDestroyReason aWhy) { +#if !defined(MOZ_WIDGET_ANDROID) + if (mVRService) { + mVRService->Stop(); + mVRService = nullptr; + } +#endif + + mClosed = true; + GetCurrentSerialEventTarget()->Dispatch( + NewRunnableMethod("gfx::VRGPUParent::DeferredDestroy", this, + &VRGPUParent::DeferredDestroy)); +} + +void VRGPUParent::DeferredDestroy() { mSelfRef = nullptr; } + +/* static */ +RefPtr<VRGPUParent> VRGPUParent::CreateForGPU( + Endpoint<PVRGPUParent>&& aEndpoint) { + RefPtr<VRGPUParent> vcp = new VRGPUParent(aEndpoint.OtherPid()); + GetCurrentSerialEventTarget()->Dispatch( + NewRunnableMethod<Endpoint<PVRGPUParent>&&>("gfx::VRGPUParent::Bind", vcp, + &VRGPUParent::Bind, + std::move(aEndpoint))); + + return vcp; +} + +void VRGPUParent::Bind(Endpoint<PVRGPUParent>&& aEndpoint) { + if (!aEndpoint.Bind(this)) { + return; + } + + mSelfRef = this; +} + +mozilla::ipc::IPCResult VRGPUParent::RecvStartVRService() { +#if !defined(MOZ_WIDGET_ANDROID) + mVRService = VRService::Create(); + MOZ_ASSERT(mVRService); + + mVRService->Start(); +#endif + + return IPC_OK(); +} + +mozilla::ipc::IPCResult VRGPUParent::RecvStopVRService() { +#if !defined(MOZ_WIDGET_ANDROID) + if (mVRService) { + mVRService->Stop(); + mVRService = nullptr; + } +#endif + + return IPC_OK(); +} + +mozilla::ipc::IPCResult VRGPUParent::RecvPuppetReset() { +#if !defined(MOZ_WIDGET_ANDROID) + VRPuppetCommandBuffer::Get().Reset(); +#endif + return IPC_OK(); +} + +mozilla::ipc::IPCResult VRGPUParent::RecvPuppetSubmit( + const nsTArray<uint64_t>& aBuffer) { +#if !defined(MOZ_WIDGET_ANDROID) + VRPuppetCommandBuffer::Get().Submit(aBuffer); +#endif + return IPC_OK(); +} + +mozilla::ipc::IPCResult VRGPUParent::RecvPuppetCheckForCompletion() { +#if !defined(MOZ_WIDGET_ANDROID) + if (VRPuppetCommandBuffer::Get().HasEnded()) { + Unused << SendNotifyPuppetComplete(); + } +#endif + return IPC_OK(); +} + +bool VRGPUParent::IsClosed() { return mClosed; } + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/vr/ipc/VRGPUParent.h b/gfx/vr/ipc/VRGPUParent.h new file mode 100644 index 0000000000..0a5b5ca9b0 --- /dev/null +++ b/gfx/vr/ipc/VRGPUParent.h @@ -0,0 +1,52 @@ +/* -*- 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/. */ + +#ifndef GFX_VR_GPU_PARENT_H +#define GFX_VR_GPU_PARENT_H + +#include "mozilla/gfx/PVRGPUParent.h" +#include "VRService.h" + +namespace mozilla { +namespace gfx { + +class VRGPUParent final : public PVRGPUParent { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VRGPUParent) + + friend class PVRGPUParent; + + public: + static RefPtr<VRGPUParent> CreateForGPU(Endpoint<PVRGPUParent>&& aEndpoint); + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + bool IsClosed(); + + protected: + void Bind(Endpoint<PVRGPUParent>&& aEndpoint); + mozilla::ipc::IPCResult RecvStartVRService(); + mozilla::ipc::IPCResult RecvStopVRService(); + mozilla::ipc::IPCResult RecvPuppetReset(); + mozilla::ipc::IPCResult RecvPuppetSubmit(const nsTArray<uint64_t>& aBuffer); + mozilla::ipc::IPCResult RecvPuppetCheckForCompletion(); + + private: + explicit VRGPUParent(ProcessId aChildProcessId); + ~VRGPUParent(); + + void DeferredDestroy(); + + RefPtr<VRGPUParent> mSelfRef; +#if !defined(MOZ_WIDGET_ANDROID) + RefPtr<VRService> mVRService; +#endif + bool mClosed; + + DISALLOW_COPY_AND_ASSIGN(VRGPUParent); +}; + +} // namespace gfx +} // namespace mozilla + +#endif // GFX_VR_CONTENT_PARENT_H diff --git a/gfx/vr/ipc/VRLayerChild.cpp b/gfx/vr/ipc/VRLayerChild.cpp new file mode 100644 index 0000000000..fd9dd19988 --- /dev/null +++ b/gfx/vr/ipc/VRLayerChild.cpp @@ -0,0 +1,146 @@ +/* -*- 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 "VRLayerChild.h" + +#include "mozilla/dom/HTMLCanvasElement.h" +#include "mozilla/layers/ImageBridgeChild.h" +#include "mozilla/layers/LayersMessages.h" // for TimedTexture +#include "mozilla/layers/SyncObject.h" // for SyncObjectClient +#include "mozilla/StaticPrefs_webgl.h" + +#include "ClientWebGLContext.h" +#include "gfxPlatform.h" +#include "GLContext.h" +#include "GLScreenBuffer.h" +#include "SharedSurface.h" // for SharedSurface +#include "SharedSurfaceGL.h" // for SharedSurface + +namespace mozilla::gfx { + +VRLayerChild::VRLayerChild() { MOZ_COUNT_CTOR(VRLayerChild); } + +VRLayerChild::~VRLayerChild() { + ClearSurfaces(); + + MOZ_COUNT_DTOR(VRLayerChild); +} + +void VRLayerChild::Initialize(dom::HTMLCanvasElement* aCanvasElement, + const gfx::Rect& aLeftEyeRect, + const gfx::Rect& aRightEyeRect) { + MOZ_ASSERT(aCanvasElement); + mLeftEyeRect = aLeftEyeRect; + mRightEyeRect = aRightEyeRect; + mCanvasElement = aCanvasElement; +} + +void VRLayerChild::SetXRFramebuffer(WebGLFramebufferJS* fb) { + mFramebuffer = fb; +} + +static constexpr bool kIsAndroid = +#if defined(MOZ_WIDGET_ANDROID) + true; +#else + false; +#endif + +void VRLayerChild::SubmitFrame(const VRDisplayInfo& aDisplayInfo) { + uint64_t frameId = aDisplayInfo.GetFrameId(); + + // aFrameId will not increment unless the previuosly submitted + // frame was received by the VR thread and submitted to the VR + // compositor. We early-exit here in the event that SubmitFrame + // was called twice for the same aFrameId. + if (!mCanvasElement || frameId == mLastSubmittedFrameId) { + return; + } + + const auto& webgl = mCanvasElement->GetWebGLContext(); + if (!webgl) return; + + // Keep the SharedSurfaceTextureClient alive long enough for + // 1 extra frame, accomodating overlapped asynchronous rendering. + mLastFrameTextureDesc = mThisFrameTextureDesc; + + bool getNewFrame = true; + if (kIsAndroid) { + /** + * Do not blit WebGL to a SurfaceTexture until the last submitted frame is + * already processed and the new frame poses are ready. SurfaceTextures need + * to be released in the VR render thread in order to allow to be used again + * in the WebGLContext GLScreenBuffer producer. Not doing so causes some + * freezes, crashes or other undefined behaviour. + */ + getNewFrame = (!mThisFrameTextureDesc || + aDisplayInfo.mDisplayState.lastSubmittedFrameId == + mLastSubmittedFrameId); + } + if (getNewFrame) { + const RefPtr<layers::ImageBridgeChild> imageBridge = + layers::ImageBridgeChild::GetSingleton(); + + auto texType = layers::TextureType::Unknown; + if (imageBridge) { + texType = layers::PreferredCanvasTextureType(imageBridge); + } + if (kIsAndroid && StaticPrefs::webgl_enable_surface_texture()) { + texType = layers::TextureType::AndroidNativeWindow; + } + + webgl->Present(mFramebuffer, texType, true); + mThisFrameTextureDesc = webgl->GetFrontBuffer(mFramebuffer, true); + } + + mLastSubmittedFrameId = frameId; + + if (!mThisFrameTextureDesc) { + gfxCriticalError() << "ToSurfaceDescriptor failed in " + "VRLayerChild::SubmitFrame"; + return; + } + + SendSubmitFrame(*mThisFrameTextureDesc, frameId, mLeftEyeRect, mRightEyeRect); +} + +bool VRLayerChild::IsIPCOpen() { return mIPCOpen; } + +void VRLayerChild::ClearSurfaces() { + mThisFrameTextureDesc = Nothing(); + mLastFrameTextureDesc = Nothing(); + const auto& webgl = mCanvasElement->GetWebGLContext(); + if (!mFramebuffer && webgl) { + webgl->ClearVRSwapChain(); + } +} + +void VRLayerChild::ActorDestroy(ActorDestroyReason aWhy) { mIPCOpen = false; } + +// static +PVRLayerChild* VRLayerChild::CreateIPDLActor() { + VRLayerChild* c = new VRLayerChild(); + c->AddIPDLReference(); + return c; +} + +// static +bool VRLayerChild::DestroyIPDLActor(PVRLayerChild* actor) { + static_cast<VRLayerChild*>(actor)->ReleaseIPDLReference(); + return true; +} + +void VRLayerChild::AddIPDLReference() { + MOZ_ASSERT(mIPCOpen == false); + mIPCOpen = true; + AddRef(); +} +void VRLayerChild::ReleaseIPDLReference() { + MOZ_ASSERT(mIPCOpen == false); + Release(); +} + +} // namespace mozilla::gfx diff --git a/gfx/vr/ipc/VRLayerChild.h b/gfx/vr/ipc/VRLayerChild.h new file mode 100644 index 0000000000..de91a46b87 --- /dev/null +++ b/gfx/vr/ipc/VRLayerChild.h @@ -0,0 +1,76 @@ +/* -*- 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/. */ + +#ifndef GFX_VR_LAYERCHILD_H +#define GFX_VR_LAYERCHILD_H + +#include "VRManagerChild.h" + +#include "mozilla/RefPtr.h" +#include "mozilla/gfx/PVRLayerChild.h" +#include "gfxVR.h" + +class nsICanvasRenderingContextInternal; + +namespace mozilla { +class WebGLContext; +class WebGLFramebufferJS; +namespace dom { +class HTMLCanvasElement; +} +namespace layers { +class SharedSurfaceTextureClient; +} +namespace gl { +class SurfaceFactory; +} +namespace gfx { + +class VRLayerChild : public PVRLayerChild { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VRLayerChild) + + public: + static PVRLayerChild* CreateIPDLActor(); + static bool DestroyIPDLActor(PVRLayerChild* actor); + + void Initialize(dom::HTMLCanvasElement* aCanvasElement, + const gfx::Rect& aLeftEyeRect, + const gfx::Rect& aRightEyeRect); + void SetXRFramebuffer(WebGLFramebufferJS*); + void SubmitFrame(const VRDisplayInfo& aDisplayInfo); + bool IsIPCOpen(); + + private: + VRLayerChild(); + virtual ~VRLayerChild(); + void ClearSurfaces(); + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + + RefPtr<dom::HTMLCanvasElement> mCanvasElement; + bool mIPCOpen = false; + + // AddIPDLReference and ReleaseIPDLReference are only to be called by + // CreateIPDLActor and DestroyIPDLActor, respectively. We intentionally make + // them private to prevent misuse. The purpose of these methods is to be aware + // of when the IPC system around this actor goes down: mIPCOpen is then set to + // false. + void AddIPDLReference(); + void ReleaseIPDLReference(); + + gfx::Rect mLeftEyeRect; + gfx::Rect mRightEyeRect; + RefPtr<WebGLFramebufferJS> mFramebuffer; + + Maybe<layers::SurfaceDescriptor> mThisFrameTextureDesc; + Maybe<layers::SurfaceDescriptor> mLastFrameTextureDesc; + + uint64_t mLastSubmittedFrameId = 0; +}; + +} // namespace gfx +} // namespace mozilla + +#endif diff --git a/gfx/vr/ipc/VRLayerParent.cpp b/gfx/vr/ipc/VRLayerParent.cpp new file mode 100644 index 0000000000..e7a8b4bf41 --- /dev/null +++ b/gfx/vr/ipc/VRLayerParent.cpp @@ -0,0 +1,54 @@ +/* -*- 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 "VRLayerParent.h" +#include "VRManager.h" +#include "mozilla/Unused.h" +#include "mozilla/layers/CompositorThread.h" + +namespace mozilla { +using namespace layers; +namespace gfx { + +VRLayerParent::VRLayerParent(uint32_t aVRDisplayID, const uint32_t aGroup) + : mIPCOpen(true), mVRDisplayID(aVRDisplayID), mGroup(aGroup) {} + +VRLayerParent::~VRLayerParent() { MOZ_COUNT_DTOR(VRLayerParent); } + +mozilla::ipc::IPCResult VRLayerParent::RecvDestroy() { + Destroy(); + return IPC_OK(); +} + +void VRLayerParent::ActorDestroy(ActorDestroyReason aWhy) { mIPCOpen = false; } + +void VRLayerParent::Destroy() { + if (mVRDisplayID) { + VRManager* vm = VRManager::Get(); + vm->RemoveLayer(this); + // 0 will never be a valid VRDisplayID; we can use it to indicate that + // we are destroyed and no longer associated with a display. + mVRDisplayID = 0; + } + + if (mIPCOpen) { + Unused << PVRLayerParent::Send__delete__(this); + } +} + +mozilla::ipc::IPCResult VRLayerParent::RecvSubmitFrame( + const layers::SurfaceDescriptor& aTexture, const uint64_t& aFrameId, + const gfx::Rect& aLeftEyeRect, const gfx::Rect& aRightEyeRect) { + if (mVRDisplayID) { + VRManager* vm = VRManager::Get(); + vm->SubmitFrame(this, aTexture, aFrameId, aLeftEyeRect, aRightEyeRect); + } + + return IPC_OK(); +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/vr/ipc/VRLayerParent.h b/gfx/vr/ipc/VRLayerParent.h new file mode 100644 index 0000000000..9fc2f32c0d --- /dev/null +++ b/gfx/vr/ipc/VRLayerParent.h @@ -0,0 +1,46 @@ +/* -*- 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/. */ + +#ifndef GFX_VR_LAYERPARENT_H +#define GFX_VR_LAYERPARENT_H + +#include "mozilla/RefPtr.h" +#include "mozilla/gfx/PVRLayerParent.h" +#include "gfxVR.h" + +namespace mozilla { +namespace gfx { + +class VRLayerParent : public PVRLayerParent { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VRLayerParent) + + public: + VRLayerParent(uint32_t aVRDisplayID, const uint32_t aGroup); + virtual mozilla::ipc::IPCResult RecvSubmitFrame( + const layers::SurfaceDescriptor& aTexture, const uint64_t& aFrameId, + const gfx::Rect& aLeftEyeRect, const gfx::Rect& aRightEyeRect) override; + virtual mozilla::ipc::IPCResult RecvDestroy() override; + uint32_t GetDisplayID() const { return mVRDisplayID; } + uint32_t GetGroup() const { return mGroup; } + + protected: + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + + virtual ~VRLayerParent(); + void Destroy(); + + bool mIPCOpen; + + uint32_t mVRDisplayID; + gfx::Rect mLeftEyeRect; + gfx::Rect mRightEyeRect; + uint32_t mGroup; +}; + +} // namespace gfx +} // namespace mozilla + +#endif diff --git a/gfx/vr/ipc/VRManagerChild.cpp b/gfx/vr/ipc/VRManagerChild.cpp new file mode 100644 index 0000000000..beb8b5c988 --- /dev/null +++ b/gfx/vr/ipc/VRManagerChild.cpp @@ -0,0 +1,637 @@ +/* -*- 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 "VRManagerChild.h" + +#include "VRLayerChild.h" +#include "VRManagerParent.h" +#include "VRThread.h" +#include "VRDisplayClient.h" +#include "nsGlobalWindow.h" +#include "mozilla/ProfilerMarkers.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/layers/CompositorThread.h" // for CompositorThread +#include "mozilla/dom/Navigator.h" +#include "mozilla/dom/VREventObserver.h" +#include "mozilla/dom/WebXRBinding.h" +#include "mozilla/dom/WindowBinding.h" // for FrameRequestCallback +#include "mozilla/dom/XRSystem.h" +#include "mozilla/dom/XRFrame.h" +#include "mozilla/dom/ContentChild.h" +#include "nsContentUtils.h" +#include "mozilla/dom/GamepadManager.h" +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/layers/SyncObject.h" +#include "mozilla/layers/TextureForwarder.h" + +using namespace mozilla::dom; + +namespace { +const nsTArray<RefPtr<mozilla::gfx::VRManagerEventObserver>>::index_type + kNoIndex = nsTArray<RefPtr<mozilla::gfx::VRManagerEventObserver>>::NoIndex; +} // namespace + +namespace mozilla { +namespace gfx { + +static StaticRefPtr<VRManagerChild> sVRManagerChildSingleton; +static StaticRefPtr<VRManagerParent> sVRManagerParentSingleton; + +static TimeStamp sMostRecentFrameEnd; +static TimeDuration sAverageFrameInterval; + +void ReleaseVRManagerParentSingleton() { sVRManagerParentSingleton = nullptr; } + +VRManagerChild::VRManagerChild() + : mRuntimeCapabilities(VRDisplayCapabilityFlags::Cap_None), + mFrameRequestCallbackCounter(0), + mWaitingForEnumeration(false), + mBackend(layers::LayersBackend::LAYERS_NONE) { + MOZ_ASSERT(NS_IsMainThread()); + + mStartTimeStamp = TimeStamp::Now(); + AddRef(); +} + +VRManagerChild::~VRManagerChild() { MOZ_ASSERT(NS_IsMainThread()); } + +/*static*/ +void VRManagerChild::IdentifyTextureHost( + const TextureFactoryIdentifier& aIdentifier) { + if (sVRManagerChildSingleton) { + sVRManagerChildSingleton->mBackend = aIdentifier.mParentBackend; + } +} + +layers::LayersBackend VRManagerChild::GetBackendType() const { + return mBackend; +} + +/*static*/ +VRManagerChild* VRManagerChild::Get() { + MOZ_ASSERT(sVRManagerChildSingleton); + return sVRManagerChildSingleton; +} + +/* static */ +bool VRManagerChild::IsCreated() { return !!sVRManagerChildSingleton; } + +/* static */ +bool VRManagerChild::IsPresenting() { + if (!VRManagerChild::IsCreated()) { + return false; + } + + nsTArray<RefPtr<VRDisplayClient>> displays; + sVRManagerChildSingleton->GetVRDisplays(displays); + + bool result = false; + for (auto& display : displays) { + result |= display->IsPresenting(); + } + return result; +} + +TimeStamp VRManagerChild::GetIdleDeadlineHint(TimeStamp aDefault) { + MOZ_ASSERT(NS_IsMainThread()); + if (!VRManagerChild::IsCreated() || sMostRecentFrameEnd.IsNull()) { + return aDefault; + } + + TimeStamp idleEnd = sMostRecentFrameEnd + sAverageFrameInterval; + return idleEnd < aDefault ? idleEnd : aDefault; +} + +/* static */ +bool VRManagerChild::InitForContent(Endpoint<PVRManagerChild>&& aEndpoint) { + MOZ_ASSERT(NS_IsMainThread()); + + RefPtr<VRManagerChild> child(new VRManagerChild()); + if (!aEndpoint.Bind(child)) { + return false; + } + sVRManagerChildSingleton = child; + return true; +} + +/*static*/ +void VRManagerChild::InitSameProcess() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!sVRManagerChildSingleton); + + sVRManagerChildSingleton = new VRManagerChild(); + sVRManagerParentSingleton = VRManagerParent::CreateSameProcess(); + sVRManagerChildSingleton->Open(sVRManagerParentSingleton->GetIPCChannel(), + CompositorThread(), mozilla::ipc::ChildSide); +} + +/* static */ +void VRManagerChild::InitWithGPUProcess(Endpoint<PVRManagerChild>&& aEndpoint) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!sVRManagerChildSingleton); + + sVRManagerChildSingleton = new VRManagerChild(); + if (!aEndpoint.Bind(sVRManagerChildSingleton)) { + MOZ_CRASH("Couldn't Open() Compositor channel."); + } +} + +/*static*/ +void VRManagerChild::ShutDown() { + MOZ_ASSERT(NS_IsMainThread()); + if (!sVRManagerChildSingleton) { + return; + } + sVRManagerChildSingleton->Close(); + sVRManagerChildSingleton = nullptr; +} + +void VRManagerChild::ActorDealloc() { Release(); } + +void VRManagerChild::ActorDestroy(ActorDestroyReason aReason) { + if (sVRManagerChildSingleton == this) { + sVRManagerChildSingleton = nullptr; + } +} + +PVRLayerChild* VRManagerChild::AllocPVRLayerChild(const uint32_t& aDisplayID, + const uint32_t& aGroup) { + return VRLayerChild::CreateIPDLActor(); +} + +bool VRManagerChild::DeallocPVRLayerChild(PVRLayerChild* actor) { + return VRLayerChild::DestroyIPDLActor(actor); +} + +void VRManagerChild::UpdateDisplayInfo(const VRDisplayInfo& aDisplayInfo) { + nsTArray<uint32_t> disconnectedDisplays; + nsTArray<uint32_t> connectedDisplays; + + const nsTArray<RefPtr<VRDisplayClient>> prevDisplays(mDisplays.Clone()); + + // Check if any displays have been disconnected + for (auto& display : prevDisplays) { + bool found = false; + if (aDisplayInfo.GetDisplayID() != 0) { + if (display->GetDisplayInfo().GetDisplayID() == + aDisplayInfo.GetDisplayID()) { + found = true; + break; + } + } + if (!found) { + // In order to make the current VRDisplay can continue to apply for the + // newest VRDisplayInfo, we need to exit presentionation before + // disconnecting. + if (display->IsPresentationGenerationCurrent()) { + NotifyPresentationGenerationChangedInternal( + display->GetDisplayInfo().GetDisplayID()); + + RefPtr<VRManagerChild> vm = VRManagerChild::Get(); + vm->FireDOMVRDisplayPresentChangeEvent( + display->GetDisplayInfo().GetDisplayID()); + } + display->NotifyDisconnected(); + disconnectedDisplays.AppendElement( + display->GetDisplayInfo().GetDisplayID()); + } + } + + // mDisplays could be a hashed container for more scalability, but not worth + // it now as we expect < 10 entries. + nsTArray<RefPtr<VRDisplayClient>> displays; + if (aDisplayInfo.GetDisplayID() != 0) { + bool isNewDisplay = true; + for (auto& display : prevDisplays) { + const VRDisplayInfo& prevInfo = display->GetDisplayInfo(); + if (prevInfo.GetDisplayID() == aDisplayInfo.GetDisplayID()) { + if (aDisplayInfo.GetIsConnected() && !prevInfo.GetIsConnected()) { + connectedDisplays.AppendElement(aDisplayInfo.GetDisplayID()); + } + if (!aDisplayInfo.GetIsConnected() && prevInfo.GetIsConnected()) { + disconnectedDisplays.AppendElement(aDisplayInfo.GetDisplayID()); + } + // MOZ_KnownLive because 'prevDisplays' is guaranteed to keep it alive. + // + // This can go away once + // https://bugzilla.mozilla.org/show_bug.cgi?id=1620312 is fixed. + MOZ_KnownLive(display)->UpdateDisplayInfo(aDisplayInfo); + displays.AppendElement(display); + isNewDisplay = false; + break; + } + } + if (isNewDisplay) { + displays.AppendElement(new VRDisplayClient(aDisplayInfo)); + connectedDisplays.AppendElement(aDisplayInfo.GetDisplayID()); + } + } + + mDisplays = std::move(displays); + + // We wish to fire the events only after mDisplays is updated + for (uint32_t displayID : disconnectedDisplays) { + FireDOMVRDisplayDisconnectEvent(displayID); + } + + for (uint32_t displayID : connectedDisplays) { + FireDOMVRDisplayConnectEvent(displayID); + } +} + +bool VRManagerChild::RuntimeSupportsVR() const { + return bool(mRuntimeCapabilities & VRDisplayCapabilityFlags::Cap_ImmersiveVR); +} +bool VRManagerChild::RuntimeSupportsAR() const { + return bool(mRuntimeCapabilities & VRDisplayCapabilityFlags::Cap_ImmersiveAR); +} +bool VRManagerChild::RuntimeSupportsInline() const { + return bool(mRuntimeCapabilities & VRDisplayCapabilityFlags::Cap_Inline); +} + +mozilla::ipc::IPCResult VRManagerChild::RecvUpdateRuntimeCapabilities( + const VRDisplayCapabilityFlags& aCapabilities) { + mRuntimeCapabilities = aCapabilities; + nsContentUtils::AddScriptRunner(NewRunnableMethod<>( + "gfx::VRManagerChild::NotifyRuntimeCapabilitiesUpdatedInternal", this, + &VRManagerChild::NotifyRuntimeCapabilitiesUpdatedInternal)); + return IPC_OK(); +} + +void VRManagerChild::NotifyRuntimeCapabilitiesUpdatedInternal() { + const nsTArray<RefPtr<VRManagerEventObserver>> listeners = mListeners.Clone(); + for (auto& listener : listeners) { + listener->NotifyDetectRuntimesCompleted(); + } +} + +mozilla::ipc::IPCResult VRManagerChild::RecvUpdateDisplayInfo( + const VRDisplayInfo& aDisplayInfo) { + UpdateDisplayInfo(aDisplayInfo); + for (auto& windowId : mNavigatorCallbacks) { + /** We must call NotifyVRDisplaysUpdated for every + * window's Navigator in mNavigatorCallbacks to ensure that + * the promise returned by Navigator.GetVRDevices + * can resolve. This must happen even if no changes + * to VRDisplays have been detected here. + */ + nsGlobalWindowInner* window = + nsGlobalWindowInner::GetInnerWindowWithId(windowId); + if (!window) { + continue; + } + dom::Navigator* nav = window->Navigator(); + if (!nav) { + continue; + } + nav->NotifyVRDisplaysUpdated(); + } + mNavigatorCallbacks.Clear(); + if (mWaitingForEnumeration) { + nsContentUtils::AddScriptRunner(NewRunnableMethod<>( + "gfx::VRManagerChild::NotifyEnumerationCompletedInternal", this, + &VRManagerChild::NotifyEnumerationCompletedInternal)); + mWaitingForEnumeration = false; + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult VRManagerChild::RecvNotifyPuppetCommandBufferCompleted( + bool aSuccess) { + RefPtr<dom::Promise> promise = mRunPuppetPromise; + mRunPuppetPromise = nullptr; + if (aSuccess) { + promise->MaybeResolve(JS::UndefinedHandleValue); + } else { + promise->MaybeRejectWithUndefined(); + } + return IPC_OK(); +} + +mozilla::ipc::IPCResult VRManagerChild::RecvNotifyPuppetResetComplete() { + nsTArray<RefPtr<dom::Promise>> promises; + promises.AppendElements(mResetPuppetPromises); + mResetPuppetPromises.Clear(); + for (const auto& promise : promises) { + promise->MaybeResolve(JS::UndefinedHandleValue); + } + return IPC_OK(); +} + +void VRManagerChild::RunPuppet(const nsTArray<uint64_t>& aBuffer, + dom::Promise* aPromise, ErrorResult& aRv) { + if (mRunPuppetPromise) { + // We only allow one puppet script to run simultaneously. + // The prior promise must be resolved before running a new + // script. + aRv.Throw(NS_ERROR_INVALID_ARG); + return; + } + if (!SendRunPuppet(aBuffer)) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + mRunPuppetPromise = aPromise; +} + +void VRManagerChild::ResetPuppet(dom::Promise* aPromise, ErrorResult& aRv) { + if (!SendResetPuppet()) { + aRv.Throw(NS_ERROR_FAILURE); + return; + } + mResetPuppetPromises.AppendElement(aPromise); +} + +void VRManagerChild::GetVRDisplays( + nsTArray<RefPtr<VRDisplayClient>>& aDisplays) { + aDisplays = mDisplays.Clone(); +} + +bool VRManagerChild::RefreshVRDisplaysWithCallback(uint64_t aWindowId) { + bool success = SendRefreshDisplays(); + if (success) { + mNavigatorCallbacks.AppendElement(aWindowId); + } + return success; +} + +bool VRManagerChild::EnumerateVRDisplays() { + bool success = SendRefreshDisplays(); + if (success) { + mWaitingForEnumeration = true; + } + return success; +} + +void VRManagerChild::DetectRuntimes() { Unused << SendDetectRuntimes(); } + +PVRLayerChild* VRManagerChild::CreateVRLayer(uint32_t aDisplayID, + nsISerialEventTarget* aTarget, + uint32_t aGroup) { + PVRLayerChild* vrLayerChild = AllocPVRLayerChild(aDisplayID, aGroup); + // Do the DOM labeling. + if (aTarget) { + SetEventTargetForActor(vrLayerChild, aTarget); + MOZ_ASSERT(vrLayerChild->GetActorEventTarget()); + } + return SendPVRLayerConstructor(vrLayerChild, aDisplayID, aGroup); +} + +void VRManagerChild::XRFrameRequest::Call( + const DOMHighResTimeStamp& aTimeStamp) { + if (mCallback) { + RefPtr<mozilla::dom::FrameRequestCallback> callback = mCallback; + callback->Call(aTimeStamp); + } else { + RefPtr<mozilla::dom::XRFrameRequestCallback> callback = mXRCallback; + RefPtr<mozilla::dom::XRFrame> frame = mXRFrame; + callback->Call(aTimeStamp, *frame); + } +} + +nsresult VRManagerChild::ScheduleFrameRequestCallback( + mozilla::dom::FrameRequestCallback& aCallback, int32_t* aHandle) { + if (mFrameRequestCallbackCounter == INT32_MAX) { + // Can't increment without overflowing; bail out + return NS_ERROR_NOT_AVAILABLE; + } + int32_t newHandle = ++mFrameRequestCallbackCounter; + + mFrameRequestCallbacks.AppendElement(XRFrameRequest(aCallback, newHandle)); + + *aHandle = newHandle; + return NS_OK; +} + +void VRManagerChild::CancelFrameRequestCallback(int32_t aHandle) { + // mFrameRequestCallbacks is stored sorted by handle + mFrameRequestCallbacks.RemoveElementSorted(aHandle); +} + +void VRManagerChild::RunFrameRequestCallbacks() { + AUTO_PROFILER_TRACING_MARKER("VR", "RunFrameRequestCallbacks", GRAPHICS); + + TimeStamp nowTime = TimeStamp::Now(); + mozilla::TimeDuration duration = nowTime - mStartTimeStamp; + DOMHighResTimeStamp timeStamp = duration.ToMilliseconds(); + + if (!sMostRecentFrameEnd.IsNull()) { + TimeDuration frameInterval = nowTime - sMostRecentFrameEnd; + if (sAverageFrameInterval.IsZero()) { + sAverageFrameInterval = frameInterval; + } else { + // Calculate the average interval between frame end and next frame start. + // Apply some smoothing to make it more stable. + const double smooth = 0.9; + sAverageFrameInterval = sAverageFrameInterval.MultDouble(smooth) + + frameInterval.MultDouble(1.0 - smooth); + } + } + + nsTArray<XRFrameRequest> callbacks; + callbacks.AppendElements(mFrameRequestCallbacks); + mFrameRequestCallbacks.Clear(); + for (auto& callback : callbacks) { + // The FrameRequest copied into the on-stack array holds a strong ref to its + // mCallback and there's nothing that can drop that ref until we return. + MOZ_KnownLive(callback.mCallback)->Call(timeStamp); + } + + if (IsPresenting()) { + sMostRecentFrameEnd = TimeStamp::Now(); + } +} + +void VRManagerChild::NotifyPresentationGenerationChanged(uint32_t aDisplayID) { + nsContentUtils::AddScriptRunner(NewRunnableMethod<uint32_t>( + "gfx::VRManagerChild::NotifyPresentationGenerationChangedInternal", this, + &VRManagerChild::NotifyPresentationGenerationChangedInternal, + aDisplayID)); +} + +void VRManagerChild::FireDOMVRDisplayMountedEvent(uint32_t aDisplayID) { + nsContentUtils::AddScriptRunner(NewRunnableMethod<uint32_t>( + "gfx::VRManagerChild::FireDOMVRDisplayMountedEventInternal", this, + &VRManagerChild::FireDOMVRDisplayMountedEventInternal, aDisplayID)); +} + +void VRManagerChild::FireDOMVRDisplayUnmountedEvent(uint32_t aDisplayID) { + nsContentUtils::AddScriptRunner(NewRunnableMethod<uint32_t>( + "gfx::VRManagerChild::FireDOMVRDisplayUnmountedEventInternal", this, + &VRManagerChild::FireDOMVRDisplayUnmountedEventInternal, aDisplayID)); +} + +void VRManagerChild::FireDOMVRDisplayConnectEvent(uint32_t aDisplayID) { + nsContentUtils::AddScriptRunner(NewRunnableMethod<uint32_t>( + "gfx::VRManagerChild::FireDOMVRDisplayConnectEventInternal", this, + &VRManagerChild::FireDOMVRDisplayConnectEventInternal, aDisplayID)); +} + +void VRManagerChild::FireDOMVRDisplayDisconnectEvent(uint32_t aDisplayID) { + nsContentUtils::AddScriptRunner(NewRunnableMethod<uint32_t>( + "gfx::VRManagerChild::FireDOMVRDisplayDisconnectEventInternal", this, + &VRManagerChild::FireDOMVRDisplayDisconnectEventInternal, aDisplayID)); +} + +void VRManagerChild::FireDOMVRDisplayPresentChangeEvent(uint32_t aDisplayID) { + nsContentUtils::AddScriptRunner(NewRunnableMethod<uint32_t>( + "gfx::VRManagerChild::FireDOMVRDisplayPresentChangeEventInternal", this, + &VRManagerChild::FireDOMVRDisplayPresentChangeEventInternal, aDisplayID)); + + if (!IsPresenting()) { + sMostRecentFrameEnd = TimeStamp(); + sAverageFrameInterval = 0; + } +} + +void VRManagerChild::FireDOMVRDisplayMountedEventInternal(uint32_t aDisplayID) { + // Iterate over a copy of mListeners, as dispatched events may modify it. + for (auto& listener : mListeners.Clone()) { + listener->NotifyVRDisplayMounted(aDisplayID); + } +} + +void VRManagerChild::FireDOMVRDisplayUnmountedEventInternal( + uint32_t aDisplayID) { + // Iterate over a copy of mListeners, as dispatched events may modify it. + for (auto& listener : mListeners.Clone()) { + listener->NotifyVRDisplayUnmounted(aDisplayID); + } +} + +void VRManagerChild::FireDOMVRDisplayConnectEventInternal(uint32_t aDisplayID) { + // Iterate over a copy of mListeners, as dispatched events may modify it. + for (auto& listener : mListeners.Clone()) { + listener->NotifyVRDisplayConnect(aDisplayID); + } +} + +void VRManagerChild::FireDOMVRDisplayDisconnectEventInternal( + uint32_t aDisplayID) { + // Iterate over a copy of mListeners, as dispatched events may modify it. + for (auto& listener : mListeners.Clone()) { + listener->NotifyVRDisplayDisconnect(aDisplayID); + } +} + +void VRManagerChild::FireDOMVRDisplayPresentChangeEventInternal( + uint32_t aDisplayID) { + // Iterate over a copy of mListeners, as dispatched events may modify it. + for (auto& listener : mListeners.Clone()) { + // MOZ_KnownLive because 'listeners' is guaranteed to keep it alive. + // + // This can go away once + // https://bugzilla.mozilla.org/show_bug.cgi?id=1620312 is fixed. + MOZ_KnownLive(listener)->NotifyVRDisplayPresentChange(aDisplayID); + } +} + +void VRManagerChild::FireDOMVRDisplayConnectEventsForLoadInternal( + uint32_t aDisplayID, VRManagerEventObserver* aObserver) { + aObserver->NotifyVRDisplayConnect(aDisplayID); +} + +void VRManagerChild::NotifyPresentationGenerationChangedInternal( + uint32_t aDisplayID) { + for (auto& listener : mListeners.Clone()) { + listener->NotifyPresentationGenerationChanged(aDisplayID); + } +} + +void VRManagerChild::NotifyEnumerationCompletedInternal() { + for (auto& listener : mListeners.Clone()) { + listener->NotifyEnumerationCompleted(); + } +} + +void VRManagerChild::FireDOMVRDisplayConnectEventsForLoad( + VRManagerEventObserver* aObserver) { + // We need to fire the VRDisplayConnect event when a page is loaded + // for each VR Display that has already been enumerated + for (const auto& display : mDisplays.Clone()) { + const VRDisplayInfo& info = display->GetDisplayInfo(); + if (info.GetIsConnected()) { + nsContentUtils::AddScriptRunner(NewRunnableMethod< + uint32_t, RefPtr<VRManagerEventObserver>>( + "gfx::VRManagerChild::FireDOMVRDisplayConnectEventsForLoadInternal", + this, &VRManagerChild::FireDOMVRDisplayConnectEventsForLoadInternal, + info.GetDisplayID(), aObserver)); + } + } +} + +void VRManagerChild::AddListener(VRManagerEventObserver* aObserver) { + MOZ_ASSERT(aObserver); + + if (mListeners.IndexOf(aObserver) != kNoIndex) { + return; // already exists + } + + mListeners.AppendElement(aObserver); + if (mListeners.Length() == 1) { + Unused << SendSetHaveEventListener(true); + } +} + +void VRManagerChild::RemoveListener(VRManagerEventObserver* aObserver) { + MOZ_ASSERT(aObserver); + + mListeners.RemoveElement(aObserver); + if (mListeners.IsEmpty()) { + Unused << SendSetHaveEventListener(false); + } +} + +void VRManagerChild::StartActivity() { Unused << SendStartActivity(); } + +void VRManagerChild::StopActivity() { + for (auto& listener : mListeners) { + if (!listener->GetStopActivityStatus()) { + // We are still showing VR in the active window. + return; + } + } + + Unused << SendStopActivity(); +} + +void VRManagerChild::HandleFatalError(const char* aMsg) const { + dom::ContentChild::FatalErrorIfNotUsingGPUProcess(aMsg, OtherPid()); +} + +void VRManagerChild::AddPromise(const uint32_t& aID, dom::Promise* aPromise) { + MOZ_ASSERT(!mGamepadPromiseList.Get(aID, nullptr)); + mGamepadPromiseList.Put(aID, RefPtr{aPromise}); +} + +gfx::VRAPIMode VRManagerChild::GetVRAPIMode(uint32_t aDisplayID) const { + for (auto& display : mDisplays) { + if (display->GetDisplayInfo().GetDisplayID() == aDisplayID) { + return display->GetXRAPIMode(); + } + } + return VRAPIMode::WebXR; +} + +mozilla::ipc::IPCResult VRManagerChild::RecvReplyGamepadVibrateHaptic( + const uint32_t& aPromiseID) { + // VRManagerChild could be at other processes, but GamepadManager + // only exists at the content process or the same process + // in non-e10s mode. + MOZ_ASSERT(XRE_IsContentProcess() || IsSameProcess()); + + RefPtr<dom::Promise> p; + if (!mGamepadPromiseList.Get(aPromiseID, getter_AddRefs(p))) { + MOZ_CRASH("We should always have a promise."); + } + + p->MaybeResolve(true); + mGamepadPromiseList.Remove(aPromiseID); + return IPC_OK(); +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/vr/ipc/VRManagerChild.h b/gfx/vr/ipc/VRManagerChild.h new file mode 100644 index 0000000000..b650d80cec --- /dev/null +++ b/gfx/vr/ipc/VRManagerChild.h @@ -0,0 +1,200 @@ +/* -*- 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/. */ + +#ifndef MOZILLA_GFX_VR_VRMANAGERCHILD_H +#define MOZILLA_GFX_VR_VRMANAGERCHILD_H + +#include "nsISupportsImpl.h" +#include "mozilla/Attributes.h" +#include "mozilla/dom/WindowBinding.h" // For FrameRequestCallback +#include "mozilla/dom/WebXRBinding.h" +#include "mozilla/dom/XRFrame.h" +#include "mozilla/gfx/PVRManagerChild.h" +#include "mozilla/ipc/SharedMemory.h" // for SharedMemory, etc +#include "mozilla/layers/ISurfaceAllocator.h" // for ISurfaceAllocator +#include "mozilla/layers/LayersTypes.h" // for LayersBackend + +namespace mozilla { +namespace dom { +class Promise; +class GamepadManager; +class Navigator; +class VRDisplay; +class FrameRequestCallback; +} // namespace dom +namespace gfx { +class VRLayerChild; +class VRDisplayClient; + +class VRManagerEventObserver { + public: + NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING + virtual void NotifyVRDisplayMounted(uint32_t aDisplayID) = 0; + virtual void NotifyVRDisplayUnmounted(uint32_t aDisplayID) = 0; + virtual void NotifyVRDisplayConnect(uint32_t aDisplayID) = 0; + virtual void NotifyVRDisplayDisconnect(uint32_t aDisplayID) = 0; + virtual void NotifyVRDisplayPresentChange(uint32_t aDisplayID) = 0; + virtual void NotifyPresentationGenerationChanged(uint32_t aDisplayID) = 0; + virtual bool GetStopActivityStatus() const = 0; + virtual void NotifyEnumerationCompleted() = 0; + virtual void NotifyDetectRuntimesCompleted() = 0; + + protected: + virtual ~VRManagerEventObserver() = default; +}; + +class VRManagerChild : public PVRManagerChild { + friend class PVRManagerChild; + + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VRManagerChild); + + static VRManagerChild* Get(); + + // Indicate that an observer wants to receive VR events. + void AddListener(VRManagerEventObserver* aObserver); + // Indicate that an observer should no longer receive VR events. + void RemoveListener(VRManagerEventObserver* aObserver); + void StartActivity(); + void StopActivity(); + bool RuntimeSupportsVR() const; + bool RuntimeSupportsAR() const; + bool RuntimeSupportsInline() const; + + void GetVRDisplays(nsTArray<RefPtr<VRDisplayClient>>& aDisplays); + bool RefreshVRDisplaysWithCallback(uint64_t aWindowId); + bool EnumerateVRDisplays(); + void DetectRuntimes(); + void AddPromise(const uint32_t& aID, dom::Promise* aPromise); + gfx::VRAPIMode GetVRAPIMode(uint32_t aDisplayID) const; + + static void InitSameProcess(); + static void InitWithGPUProcess(Endpoint<PVRManagerChild>&& aEndpoint); + static bool InitForContent(Endpoint<PVRManagerChild>&& aEndpoint); + static void ShutDown(); + + static bool IsCreated(); + static bool IsPresenting(); + static TimeStamp GetIdleDeadlineHint(TimeStamp aDefault); + + PVRLayerChild* CreateVRLayer(uint32_t aDisplayID, + nsISerialEventTarget* aTarget, uint32_t aGroup); + + static void IdentifyTextureHost( + const layers::TextureFactoryIdentifier& aIdentifier); + layers::LayersBackend GetBackendType() const; + + nsresult ScheduleFrameRequestCallback(dom::FrameRequestCallback& aCallback, + int32_t* aHandle); + void CancelFrameRequestCallback(int32_t aHandle); + MOZ_CAN_RUN_SCRIPT + void RunFrameRequestCallbacks(); + void NotifyPresentationGenerationChanged(uint32_t aDisplayID); + + MOZ_CAN_RUN_SCRIPT + void UpdateDisplayInfo(const VRDisplayInfo& aDisplayInfo); + void FireDOMVRDisplayMountedEvent(uint32_t aDisplayID); + void FireDOMVRDisplayUnmountedEvent(uint32_t aDisplayID); + void FireDOMVRDisplayConnectEvent(uint32_t aDisplayID); + void FireDOMVRDisplayDisconnectEvent(uint32_t aDisplayID); + void FireDOMVRDisplayPresentChangeEvent(uint32_t aDisplayID); + void FireDOMVRDisplayConnectEventsForLoad(VRManagerEventObserver* aObserver); + + void HandleFatalError(const char* aMsg) const override; + void ActorDestroy(ActorDestroyReason aReason) override; + + void RunPuppet(const nsTArray<uint64_t>& aBuffer, dom::Promise* aPromise, + ErrorResult& aRv); + void ResetPuppet(dom::Promise* aPromise, ErrorResult& aRv); + + protected: + explicit VRManagerChild(); + ~VRManagerChild(); + + PVRLayerChild* AllocPVRLayerChild(const uint32_t& aDisplayID, + const uint32_t& aGroup); + bool DeallocPVRLayerChild(PVRLayerChild* actor); + + void ActorDealloc() override; + + // MOZ_CAN_RUN_SCRIPT_BOUNDARY until we can mark ipdl-generated things as + // MOZ_CAN_RUN_SCRIPT. + MOZ_CAN_RUN_SCRIPT_BOUNDARY + mozilla::ipc::IPCResult RecvUpdateDisplayInfo( + const VRDisplayInfo& aDisplayInfo); + mozilla::ipc::IPCResult RecvUpdateRuntimeCapabilities( + const VRDisplayCapabilityFlags& aCapabilities); + mozilla::ipc::IPCResult RecvReplyGamepadVibrateHaptic( + const uint32_t& aPromiseID); + + MOZ_CAN_RUN_SCRIPT_BOUNDARY + mozilla::ipc::IPCResult RecvNotifyPuppetCommandBufferCompleted(bool aSuccess); + MOZ_CAN_RUN_SCRIPT_BOUNDARY + mozilla::ipc::IPCResult RecvNotifyPuppetResetComplete(); + + bool IsSameProcess() const { return OtherPid() == base::GetCurrentProcId(); } + + private: + void FireDOMVRDisplayMountedEventInternal(uint32_t aDisplayID); + void FireDOMVRDisplayUnmountedEventInternal(uint32_t aDisplayID); + void FireDOMVRDisplayConnectEventInternal(uint32_t aDisplayID); + void FireDOMVRDisplayDisconnectEventInternal(uint32_t aDisplayID); + void FireDOMVRDisplayPresentChangeEventInternal(uint32_t aDisplayID); + void FireDOMVRDisplayConnectEventsForLoadInternal( + uint32_t aDisplayID, VRManagerEventObserver* aObserver); + void NotifyPresentationGenerationChangedInternal(uint32_t aDisplayID); + void NotifyEnumerationCompletedInternal(); + void NotifyRuntimeCapabilitiesUpdatedInternal(); + + nsTArray<RefPtr<VRDisplayClient>> mDisplays; + VRDisplayCapabilityFlags mRuntimeCapabilities; + bool mDisplaysInitialized; + nsTArray<uint64_t> mNavigatorCallbacks; + + struct XRFrameRequest { + XRFrameRequest(mozilla::dom::FrameRequestCallback& aCallback, + int32_t aHandle) + : mCallback(&aCallback), mHandle(aHandle) {} + + XRFrameRequest(mozilla::dom::XRFrameRequestCallback& aCallback, + mozilla::dom::XRFrame& aFrame, int32_t aHandle) + : mXRCallback(&aCallback), mXRFrame(&aFrame), mHandle(aHandle) {} + MOZ_CAN_RUN_SCRIPT + void Call(const DOMHighResTimeStamp& aTimeStamp); + + // Comparator operators to allow RemoveElementSorted with an + // integer argument on arrays of XRFrameRequest + bool operator==(int32_t aHandle) const { return mHandle == aHandle; } + bool operator<(int32_t aHandle) const { return mHandle < aHandle; } + + RefPtr<mozilla::dom::FrameRequestCallback> mCallback; + RefPtr<mozilla::dom::XRFrameRequestCallback> mXRCallback; + RefPtr<mozilla::dom::XRFrame> mXRFrame; + int32_t mHandle; + }; + + nsTArray<XRFrameRequest> mFrameRequestCallbacks; + /** + * The current frame request callback handle + */ + int32_t mFrameRequestCallbackCounter; + mozilla::TimeStamp mStartTimeStamp; + + nsTArray<RefPtr<VRManagerEventObserver>> mListeners; + bool mWaitingForEnumeration; + + layers::LayersBackend mBackend; + nsRefPtrHashtable<nsUint32HashKey, dom::Promise> mGamepadPromiseList; + RefPtr<dom::Promise> mRunPuppetPromise; + nsTArray<RefPtr<dom::Promise>> mResetPuppetPromises; + + DISALLOW_COPY_AND_ASSIGN(VRManagerChild); +}; + +} // namespace gfx +} // namespace mozilla + +#endif // MOZILLA_GFX_VR_VRMANAGERCHILD_H diff --git a/gfx/vr/ipc/VRManagerParent.cpp b/gfx/vr/ipc/VRManagerParent.cpp new file mode 100644 index 0000000000..3f0140596f --- /dev/null +++ b/gfx/vr/ipc/VRManagerParent.cpp @@ -0,0 +1,299 @@ +/* -*- 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 "VRManagerParent.h" + +#include "ipc/VRLayerParent.h" +#include "mozilla/gfx/PVRManagerParent.h" +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/ipc/ProtocolTypes.h" +#include "mozilla/ipc/ProtocolUtils.h" // for IToplevelProtocol +#include "mozilla/TimeStamp.h" // for TimeStamp +#include "mozilla/Unused.h" +#include "VRManager.h" +#include "VRThread.h" + +using mozilla::dom::GamepadHandle; + +namespace mozilla { +using namespace layers; +namespace gfx { + +// See VRManagerChild.cpp +void ReleaseVRManagerParentSingleton(); + +VRManagerParent::VRManagerParent(ProcessId aChildProcessId, + bool aIsContentChild) + : mHaveEventListener(false), + mHaveControllerListener(false), + mIsContentChild(aIsContentChild), + mVRActiveStatus(false) { + MOZ_COUNT_CTOR(VRManagerParent); + MOZ_ASSERT(NS_IsMainThread()); + + SetOtherProcessId(aChildProcessId); +} + +VRManagerParent::~VRManagerParent() { + MOZ_ASSERT(!mVRManagerHolder); + + MOZ_COUNT_DTOR(VRManagerParent); +} + +PVRLayerParent* VRManagerParent::AllocPVRLayerParent(const uint32_t& aDisplayID, + const uint32_t& aGroup) { + RefPtr<VRLayerParent> layer; + layer = new VRLayerParent(aDisplayID, aGroup); + VRManager* vm = VRManager::Get(); + vm->AddLayer(layer); + return layer.forget().take(); +} + +bool VRManagerParent::DeallocPVRLayerParent(PVRLayerParent* actor) { + delete actor; + return true; +} + +bool VRManagerParent::IsSameProcess() const { + return OtherPid() == base::GetCurrentProcId(); +} + +void VRManagerParent::RegisterWithManager() { + VRManager* vm = VRManager::Get(); + vm->AddVRManagerParent(this); + mVRManagerHolder = vm; +} + +void VRManagerParent::UnregisterFromManager() { + VRManager* vm = VRManager::Get(); + vm->RemoveVRManagerParent(this); + mVRManagerHolder = nullptr; +} + +/* static */ +bool VRManagerParent::CreateForContent(Endpoint<PVRManagerParent>&& aEndpoint) { + if (!CompositorThread()) { + return false; + } + + RefPtr<VRManagerParent> vmp = new VRManagerParent(aEndpoint.OtherPid(), true); + CompositorThread()->Dispatch(NewRunnableMethod<Endpoint<PVRManagerParent>&&>( + "gfx::VRManagerParent::Bind", vmp, &VRManagerParent::Bind, + std::move(aEndpoint))); + + return true; +} + +void VRManagerParent::Bind(Endpoint<PVRManagerParent>&& aEndpoint) { + if (!aEndpoint.Bind(this)) { + return; + } + mSelfRef = this; + + RegisterWithManager(); +} + +/*static*/ +void VRManagerParent::RegisterVRManagerInCompositorThread( + VRManagerParent* aVRManager) { + aVRManager->RegisterWithManager(); +} + +/*static*/ +VRManagerParent* VRManagerParent::CreateSameProcess() { + RefPtr<VRManagerParent> vmp = + new VRManagerParent(base::GetCurrentProcId(), false); + vmp->mCompositorThreadHolder = CompositorThreadHolder::GetSingleton(); + vmp->mSelfRef = vmp; + CompositorThread()->Dispatch( + NewRunnableFunction("RegisterVRManagerIncompositorThreadRunnable", + RegisterVRManagerInCompositorThread, vmp.get())); + return vmp.get(); +} + +bool VRManagerParent::CreateForGPUProcess( + Endpoint<PVRManagerParent>&& aEndpoint) { + RefPtr<VRManagerParent> vmp = + new VRManagerParent(aEndpoint.OtherPid(), false); + vmp->mCompositorThreadHolder = CompositorThreadHolder::GetSingleton(); + vmp->mSelfRef = vmp; + CompositorThread()->Dispatch(NewRunnableMethod<Endpoint<PVRManagerParent>&&>( + "gfx::VRManagerParent::Bind", vmp, &VRManagerParent::Bind, + std::move(aEndpoint))); + return true; +} + +/*static*/ +void VRManagerParent::Shutdown() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT( + CompositorThread(), + "Shutdown() must gets called before the compositor thread is shutdown"); + ReleaseVRManagerParentSingleton(); + CompositorThread()->Dispatch(NS_NewRunnableFunction( + "VRManagerParent::Shutdown", + [vm = RefPtr<VRManager>(VRManager::MaybeGet())]() -> void { + if (!vm) { + return; + } + vm->ShutdownVRManagerParents(); + })); +} + +void VRManagerParent::ActorDestroy(ActorDestroyReason why) {} + +void VRManagerParent::ActorDealloc() { + UnregisterFromManager(); + mCompositorThreadHolder = nullptr; + mSelfRef = nullptr; +} + +void VRManagerParent::OnChannelConnected(int32_t aPid) { + mCompositorThreadHolder = CompositorThreadHolder::GetSingleton(); +} + +mozilla::ipc::IPCResult VRManagerParent::RecvDetectRuntimes() { + // Detect runtime capabilities. This will return the presense of VR and/or AR + // runtime software, without enumerating or activating any hardware devices. + // UpdateDisplayInfo will be sent to VRManagerChild with the results of the + // detection. + VRManager* vm = VRManager::Get(); + vm->DetectRuntimes(); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult VRManagerParent::RecvRefreshDisplays() { + // This is called to activate the VR runtimes, detecting the + // presence and capabilities of XR hardware. + // UpdateDisplayInfo will be sent to VRManagerChild with the results of the + // enumerated hardware. + VRManager* vm = VRManager::Get(); + vm->EnumerateDevices(); + + return IPC_OK(); +} + +mozilla::ipc::IPCResult VRManagerParent::RecvSetGroupMask( + const uint32_t& aDisplayID, const uint32_t& aGroupMask) { + VRManager* vm = VRManager::Get(); + vm->SetGroupMask(aGroupMask); + return IPC_OK(); +} + +bool VRManagerParent::HaveEventListener() { return mHaveEventListener; } + +bool VRManagerParent::HaveControllerListener() { + return mHaveControllerListener; +} + +bool VRManagerParent::GetVRActiveStatus() { return mVRActiveStatus; } + +mozilla::ipc::IPCResult VRManagerParent::RecvSetHaveEventListener( + const bool& aHaveEventListener) { + mHaveEventListener = aHaveEventListener; + return IPC_OK(); +} + +mozilla::ipc::IPCResult VRManagerParent::RecvControllerListenerAdded() { + // Force update the available controllers for GamepadManager, + VRManager* vm = VRManager::Get(); + vm->StopAllHaptics(); + mHaveControllerListener = true; + return IPC_OK(); +} + +mozilla::ipc::IPCResult VRManagerParent::RecvControllerListenerRemoved() { + mHaveControllerListener = false; + VRManager* vm = VRManager::Get(); + vm->StopAllHaptics(); + return IPC_OK(); +} + +mozilla::ipc::IPCResult VRManagerParent::RecvRunPuppet( + const nsTArray<uint64_t>& aBuffer) { +#if defined(MOZ_WIDGET_ANDROID) + // Not yet implemented for Android / GeckoView + // See Bug 1555192 + Unused << SendNotifyPuppetCommandBufferCompleted(false); +#else + VRManager* vm = VRManager::Get(); + if (!vm->RunPuppet(aBuffer, this)) { + // We have immediately failed, need to resolve the + // promise right away + Unused << SendNotifyPuppetCommandBufferCompleted(false); + } +#endif // defined(MOZ_WIDGET_ANDROID) + return IPC_OK(); +} + +mozilla::ipc::IPCResult VRManagerParent::RecvResetPuppet() { +#if defined(MOZ_WIDGET_ANDROID) + // Not yet implemented for Android / GeckoView + // See Bug 1555192 +#else + VRManager* vm = VRManager::Get(); + vm->ResetPuppet(this); +#endif // defined(MOZ_WIDGET_ANDROID) + return IPC_OK(); +} + +mozilla::ipc::IPCResult VRManagerParent::RecvVibrateHaptic( + const mozilla::dom::GamepadHandle& aGamepadHandle, + const uint32_t& aHapticIndex, const double& aIntensity, + const double& aDuration, const uint32_t& aPromiseID) { + VRManager* vm = VRManager::Get(); + VRManagerPromise promise(this, aPromiseID); + + vm->VibrateHaptic(aGamepadHandle, aHapticIndex, aIntensity, aDuration, + promise); + return IPC_OK(); +} + +mozilla::ipc::IPCResult VRManagerParent::RecvStopVibrateHaptic( + const mozilla::dom::GamepadHandle& aGamepadHandle) { + VRManager* vm = VRManager::Get(); + vm->StopVibrateHaptic(aGamepadHandle); + return IPC_OK(); +} + +bool VRManagerParent::SendReplyGamepadVibrateHaptic( + const uint32_t& aPromiseID) { + // GamepadManager only exists at the content process + // or the same process in non-e10s mode. + if (mHaveControllerListener && (mIsContentChild || IsSameProcess())) { + return PVRManagerParent::SendReplyGamepadVibrateHaptic(aPromiseID); + } + + return true; +} + +mozilla::ipc::IPCResult VRManagerParent::RecvStartVRNavigation( + const uint32_t& aDeviceID) { + VRManager* vm = VRManager::Get(); + vm->StartVRNavigation(aDeviceID); + return IPC_OK(); +} + +mozilla::ipc::IPCResult VRManagerParent::RecvStopVRNavigation( + const uint32_t& aDeviceID, const TimeDuration& aTimeout) { + VRManager* vm = VRManager::Get(); + vm->StopVRNavigation(aDeviceID, aTimeout); + return IPC_OK(); +} + +mozilla::ipc::IPCResult VRManagerParent::RecvStartActivity() { + mVRActiveStatus = true; + return IPC_OK(); +} + +mozilla::ipc::IPCResult VRManagerParent::RecvStopActivity() { + mVRActiveStatus = false; + return IPC_OK(); +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/vr/ipc/VRManagerParent.h b/gfx/vr/ipc/VRManagerParent.h new file mode 100644 index 0000000000..fee9569b78 --- /dev/null +++ b/gfx/vr/ipc/VRManagerParent.h @@ -0,0 +1,122 @@ +/* -*- 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/. */ + +#ifndef MOZILLA_GFX_VR_VRMANAGERPARENT_H +#define MOZILLA_GFX_VR_VRMANAGERPARENT_H + +#include "mozilla/layers/CompositorThread.h" // for CompositorThreadHolder +#include "mozilla/layers/CompositableTransactionParent.h" // need? +#include "mozilla/gfx/PVRManagerParent.h" // for PVRManagerParent +#include "mozilla/gfx/PVRLayerParent.h" // for PVRLayerParent +#include "mozilla/ipc/ProtocolUtils.h" // for IToplevelProtocol +#include "mozilla/TimeStamp.h" // for TimeStamp +#include "gfxVR.h" // for VRFieldOfView + +namespace mozilla { +using namespace layers; +namespace gfx { + +class VRManager; + +class VRManagerParent final : public PVRManagerParent { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VRManagerParent); + + friend class PVRManagerParent; + + public: + explicit VRManagerParent(ProcessId aChildProcessId, bool aIsContentChild); + + static VRManagerParent* CreateSameProcess(); + static bool CreateForGPUProcess(Endpoint<PVRManagerParent>&& aEndpoint); + static bool CreateForContent(Endpoint<PVRManagerParent>&& aEndpoint); + static void Shutdown(); + + bool IsSameProcess() const; + bool HaveEventListener(); + bool HaveControllerListener(); + bool GetVRActiveStatus(); + bool SendReplyGamepadVibrateHaptic(const uint32_t& aPromiseID); + + protected: + ~VRManagerParent(); + + PVRLayerParent* AllocPVRLayerParent(const uint32_t& aDisplayID, + const uint32_t& aGroup); + bool DeallocPVRLayerParent(PVRLayerParent* actor); + + virtual void ActorDestroy(ActorDestroyReason why) override; + void OnChannelConnected(int32_t pid) override; + + mozilla::ipc::IPCResult RecvDetectRuntimes(); + mozilla::ipc::IPCResult RecvRefreshDisplays(); + mozilla::ipc::IPCResult RecvSetGroupMask(const uint32_t& aDisplayID, + const uint32_t& aGroupMask); + mozilla::ipc::IPCResult RecvSetHaveEventListener( + const bool& aHaveEventListener); + mozilla::ipc::IPCResult RecvControllerListenerAdded(); + mozilla::ipc::IPCResult RecvControllerListenerRemoved(); + mozilla::ipc::IPCResult RecvVibrateHaptic( + const mozilla::dom::GamepadHandle& aGamepadHandle, + const uint32_t& aHapticIndex, const double& aIntensity, + const double& aDuration, const uint32_t& aPromiseID); + mozilla::ipc::IPCResult RecvStopVibrateHaptic( + const mozilla::dom::GamepadHandle& aGamepadHandle); + mozilla::ipc::IPCResult RecvStartVRNavigation(const uint32_t& aDeviceID); + mozilla::ipc::IPCResult RecvStopVRNavigation(const uint32_t& aDeviceID, + const TimeDuration& aTimeout); + mozilla::ipc::IPCResult RecvStartActivity(); + mozilla::ipc::IPCResult RecvStopActivity(); + + mozilla::ipc::IPCResult RecvRunPuppet(const nsTArray<uint64_t>& aBuffer); + mozilla::ipc::IPCResult RecvResetPuppet(); + + private: + void ActorDealloc() override; + void RegisterWithManager(); + void UnregisterFromManager(); + + void Bind(Endpoint<PVRManagerParent>&& aEndpoint); + + static void RegisterVRManagerInCompositorThread(VRManagerParent* aVRManager); + + // This keeps us alive until ActorDestroy(), at which point we do a + // deferred destruction of ourselves. + RefPtr<VRManagerParent> mSelfRef; + // Keep the compositor thread alive, until we have destroyed ourselves. + RefPtr<CompositorThreadHolder> mCompositorThreadHolder; + + // Keep the VRManager alive, until we have destroyed ourselves. + RefPtr<VRManager> mVRManagerHolder; + bool mHaveEventListener; + bool mHaveControllerListener; + bool mIsContentChild; + + // When VR tabs are switched the background, we won't need to + // initialize its session in VRService thread. + bool mVRActiveStatus; +}; + +class VRManagerPromise final { + friend class VRManager; + + public: + explicit VRManagerPromise(RefPtr<VRManagerParent> aParent, + uint32_t aPromiseID) + : mParent(aParent), mPromiseID(aPromiseID) {} + ~VRManagerPromise() { mParent = nullptr; } + bool operator==(const VRManagerPromise& aOther) const { + return mParent == aOther.mParent && mPromiseID == aOther.mPromiseID; + } + + private: + RefPtr<VRManagerParent> mParent; + uint32_t mPromiseID; +}; + +} // namespace gfx +} // namespace mozilla + +#endif // MOZILLA_GFX_VR_VRMANAGERPARENT_H diff --git a/gfx/vr/ipc/VRMessageUtils.h b/gfx/vr/ipc/VRMessageUtils.h new file mode 100644 index 0000000000..df18456b51 --- /dev/null +++ b/gfx/vr/ipc/VRMessageUtils.h @@ -0,0 +1,74 @@ +/* -*- 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/. */ + +#ifndef mozilla_gfx_vr_VRMessageUtils_h +#define mozilla_gfx_vr_VRMessageUtils_h + +#include "ipc/EnumSerializer.h" +#include "ipc/IPCMessageUtils.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/GfxMessageUtils.h" +#include "mozilla/dom/GamepadMessageUtils.h" + +#include "gfxVR.h" + +namespace IPC { + +template <> +struct ParamTraits<mozilla::gfx::VRControllerType> + : public ContiguousEnumSerializer<mozilla::gfx::VRControllerType, + mozilla::gfx::VRControllerType::_empty, + mozilla::gfx::VRControllerType::_end> {}; + +// VRHMDSensorState is POD, we can use PlainOldDataSerializer +static_assert(std::is_pod<mozilla::gfx::VRHMDSensorState>::value, + "mozilla::gfx::VRHMDSensorState must be a POD type."); +template <> +struct ParamTraits<mozilla::gfx::VRHMDSensorState> + : public PlainOldDataSerializer<mozilla::gfx::VRHMDSensorState> {}; + +// VRDisplayInfo is POD, we can use PlainOldDataSerializer +static_assert(std::is_pod<mozilla::gfx::VRDisplayInfo>::value, + "mozilla::gfx::VRDisplayInfo must be a POD type."); +template <> +struct ParamTraits<mozilla::gfx::VRDisplayInfo> + : public PlainOldDataSerializer<mozilla::gfx::VRDisplayInfo> {}; + +template <> +struct ParamTraits<mozilla::gfx::VRSubmitFrameResultInfo> { + typedef mozilla::gfx::VRSubmitFrameResultInfo paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mBase64Image); + WriteParam(aMsg, aParam.mFormat); + WriteParam(aMsg, aParam.mWidth); + WriteParam(aMsg, aParam.mHeight); + WriteParam(aMsg, aParam.mFrameNum); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + if (!ReadParam(aMsg, aIter, &(aResult->mBase64Image)) || + !ReadParam(aMsg, aIter, &(aResult->mFormat)) || + !ReadParam(aMsg, aIter, &(aResult->mWidth)) || + !ReadParam(aMsg, aIter, &(aResult->mHeight)) || + !ReadParam(aMsg, aIter, &(aResult->mFrameNum))) { + return false; + } + + return true; + } +}; + +template <> +struct ParamTraits<mozilla::gfx::VRDisplayCapabilityFlags> + : public BitFlagsEnumSerializer< + mozilla::gfx::VRDisplayCapabilityFlags, + mozilla::gfx::VRDisplayCapabilityFlags::Cap_All> {}; + +} // namespace IPC + +#endif // mozilla_gfx_vr_VRMessageUtils_h diff --git a/gfx/vr/ipc/VRParent.cpp b/gfx/vr/ipc/VRParent.cpp new file mode 100644 index 0000000000..21c1cde5de --- /dev/null +++ b/gfx/vr/ipc/VRParent.cpp @@ -0,0 +1,191 @@ +/* -*- 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 "VRParent.h" +#include "VRGPUParent.h" +#include "gfxConfig.h" +#include "nsDebugImpl.h" +#include "nsThreadManager.h" +#include "ProcessUtils.h" + +#include "mozilla/dom/MemoryReportRequest.h" +#include "mozilla/gfx/gfxVars.h" +#include "mozilla/ipc/CrashReporterClient.h" +#include "mozilla/ipc/ProcessChild.h" +#include "mozilla/Preferences.h" + +#if defined(XP_WIN) +# include <process.h> +# include "mozilla/gfx/DeviceManagerDx.h" +#else +# include <unistd.h> +#endif + +namespace mozilla { +namespace gfx { + +using mozilla::ipc::IPCResult; + +VRParent::VRParent() : mVRGPUParent(nullptr) {} + +IPCResult VRParent::RecvNewGPUVRManager(Endpoint<PVRGPUParent>&& aEndpoint) { + RefPtr<VRGPUParent> vrGPUParent = + VRGPUParent::CreateForGPU(std::move(aEndpoint)); + if (!vrGPUParent) { + return IPC_FAIL_NO_REASON(this); + } + + mVRGPUParent = std::move(vrGPUParent); + return IPC_OK(); +} + +IPCResult VRParent::RecvInit(nsTArray<GfxVarUpdate>&& vars, + const DevicePrefs& devicePrefs) { + Unused << SendInitComplete(); + + for (const auto& var : vars) { + gfxVars::ApplyUpdate(var); + } + + // Inherit device preferences. + gfxConfig::Inherit(Feature::HW_COMPOSITING, devicePrefs.hwCompositing()); + gfxConfig::Inherit(Feature::D3D11_COMPOSITING, + devicePrefs.d3d11Compositing()); + gfxConfig::Inherit(Feature::OPENGL_COMPOSITING, devicePrefs.oglCompositing()); + gfxConfig::Inherit(Feature::ADVANCED_LAYERS, devicePrefs.advancedLayers()); + gfxConfig::Inherit(Feature::DIRECT2D, devicePrefs.useD2D1()); + +#if defined(XP_WIN) + if (gfxConfig::IsEnabled(Feature::D3D11_COMPOSITING)) { + DeviceManagerDx::Get()->CreateCompositorDevices(); + } +#endif + return IPC_OK(); +} + +IPCResult VRParent::RecvUpdateVar(const GfxVarUpdate& aUpdate) { + gfxVars::ApplyUpdate(aUpdate); + return IPC_OK(); +} + +mozilla::ipc::IPCResult VRParent::RecvPreferenceUpdate(const Pref& aPref) { + Preferences::SetPreference(aPref); + return IPC_OK(); +} + +mozilla::ipc::IPCResult VRParent::RecvOpenVRControllerActionPathToVR( + const nsCString& aPath) { + mOpenVRControllerAction = aPath; + return IPC_OK(); +} + +mozilla::ipc::IPCResult VRParent::RecvOpenVRControllerManifestPathToVR( + const VRControllerType& aType, const nsCString& aPath) { + mOpenVRControllerManifest.Put(static_cast<uint32_t>(aType), aPath); + return IPC_OK(); +} + +mozilla::ipc::IPCResult VRParent::RecvRequestMemoryReport( + const uint32_t& aGeneration, const bool& aAnonymize, + const bool& aMinimizeMemoryUsage, const Maybe<FileDescriptor>& aDMDFile, + const RequestMemoryReportResolver& aResolver) { + MOZ_ASSERT(XRE_IsVRProcess()); + nsPrintfCString processName("VR (pid %u)", (unsigned)getpid()); + + mozilla::dom::MemoryReportRequestClient::Start( + aGeneration, aAnonymize, aMinimizeMemoryUsage, aDMDFile, processName, + [&](const MemoryReport& aReport) { + Unused << SendAddMemoryReport(aReport); + }, + aResolver); + return IPC_OK(); +} + +void VRParent::ActorDestroy(ActorDestroyReason aWhy) { + if (AbnormalShutdown == aWhy) { + NS_WARNING("Shutting down VR process early due to a crash!"); + ipc::ProcessChild::QuickExit(); + } + if (mVRGPUParent && !mVRGPUParent->IsClosed()) { + mVRGPUParent->Close(); + } + mVRGPUParent = nullptr; + +#ifndef NS_FREE_PERMANENT_DATA + // No point in going through XPCOM shutdown because we don't keep persistent + // state. + ProcessChild::QuickExit(); +#endif + +#if defined(XP_WIN) + DeviceManagerDx::Shutdown(); +#endif + gfxVars::Shutdown(); + gfxConfig::Shutdown(); + ipc::CrashReporterClient::DestroySingleton(); + // Only calling XRE_ShutdownChildProcess() at the child process + // instead of the main process. Otherwise, it will close all child processes + // that are spawned from the main process. + XRE_ShutdownChildProcess(); +} + +bool VRParent::Init(base::ProcessId aParentPid, const char* aParentBuildID, + MessageLoop* aIOLoop, UniquePtr<IPC::Channel> aChannel) { + // Initialize the thread manager before starting IPC. Otherwise, messages + // may be posted to the main thread and we won't be able to process them. + if (NS_WARN_IF(NS_FAILED(nsThreadManager::get().Init()))) { + return false; + } + + // Now it's safe to start IPC. + if (NS_WARN_IF(!Open(std::move(aChannel), aParentPid, aIOLoop))) { + return false; + } + + nsDebugImpl::SetMultiprocessMode("VR"); + + // This must be checked before any IPDL message, which may hit sentinel + // errors due to parent and content processes having different + // versions. + MessageChannel* channel = GetIPCChannel(); + if (channel && !channel->SendBuildIDsMatchMessage(aParentBuildID)) { + // We need to quit this process if the buildID doesn't match the parent's. + // This can occur when an update occurred in the background. + ipc::ProcessChild::QuickExit(); + } + + // Init crash reporter support. + ipc::CrashReporterClient::InitSingleton(this); + + gfxConfig::Init(); + gfxVars::Initialize(); +#if defined(XP_WIN) + DeviceManagerDx::Init(); +#endif + if (NS_FAILED(NS_InitMinimalXPCOM())) { + return false; + } + + mozilla::ipc::SetThisProcessName("VR Process"); + return true; +} + +bool VRParent::GetOpenVRControllerActionPath(nsCString* aPath) { + if (!mOpenVRControllerAction.IsEmpty()) { + *aPath = mOpenVRControllerAction; + return true; + } + + return false; +} + +bool VRParent::GetOpenVRControllerManifestPath(VRControllerType aType, + nsCString* aPath) { + return mOpenVRControllerManifest.Get(static_cast<uint32_t>(aType), aPath); +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/vr/ipc/VRParent.h b/gfx/vr/ipc/VRParent.h new file mode 100644 index 0000000000..ab44ce0072 --- /dev/null +++ b/gfx/vr/ipc/VRParent.h @@ -0,0 +1,64 @@ +/* -*- 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/. */ + +#ifndef GFX_VR_PARENT_H +#define GFX_VR_PARENT_H + +#include "mozilla/gfx/PVRParent.h" +#include "VRGPUParent.h" + +namespace mozilla { +namespace gfx { + +class VRService; +class VRSystemManagerExternal; + +class VRParent final : public PVRParent { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VRParent); + + friend class PVRParent; + + public: + explicit VRParent(); + + bool Init(base::ProcessId aParentPid, const char* aParentBuildID, + MessageLoop* aIOLoop, UniquePtr<IPC::Channel> aChannel); + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + bool GetOpenVRControllerActionPath(nsCString* aPath); + bool GetOpenVRControllerManifestPath(VRControllerType aType, + nsCString* aPath); + + protected: + ~VRParent() = default; + + mozilla::ipc::IPCResult RecvNewGPUVRManager( + Endpoint<PVRGPUParent>&& aEndpoint); + mozilla::ipc::IPCResult RecvInit(nsTArray<GfxVarUpdate>&& vars, + const DevicePrefs& devicePrefs); + mozilla::ipc::IPCResult RecvNotifyVsync(const TimeStamp& vsyncTimestamp); + mozilla::ipc::IPCResult RecvUpdateVar(const GfxVarUpdate& pref); + mozilla::ipc::IPCResult RecvPreferenceUpdate(const Pref& pref); + mozilla::ipc::IPCResult RecvOpenVRControllerActionPathToVR( + const nsCString& aPath); + mozilla::ipc::IPCResult RecvOpenVRControllerManifestPathToVR( + const VRControllerType& aType, const nsCString& aPath); + mozilla::ipc::IPCResult RecvRequestMemoryReport( + const uint32_t& generation, const bool& anonymize, + const bool& minimizeMemoryUsage, + const Maybe<ipc::FileDescriptor>& DMDFile, + const RequestMemoryReportResolver& aResolver); + + private: + nsCString mOpenVRControllerAction; + nsDataHashtable<nsUint32HashKey, nsCString> mOpenVRControllerManifest; + RefPtr<VRGPUParent> mVRGPUParent; + DISALLOW_COPY_AND_ASSIGN(VRParent); +}; + +} // namespace gfx +} // namespace mozilla + +#endif // GFX_VR_PARENT_H diff --git a/gfx/vr/ipc/VRProcessChild.cpp b/gfx/vr/ipc/VRProcessChild.cpp new file mode 100644 index 0000000000..da1a7c3a87 --- /dev/null +++ b/gfx/vr/ipc/VRProcessChild.cpp @@ -0,0 +1,84 @@ +/* -*- 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 "VRProcessChild.h" + +#include "mozilla/BackgroundHangMonitor.h" +#include "mozilla/ipc/IOThreadChild.h" +#include "ProcessUtils.h" + +using namespace mozilla; +using namespace mozilla::gfx; +using mozilla::ipc::IOThreadChild; + +StaticRefPtr<VRParent> sVRParent; + +VRProcessChild::VRProcessChild(ProcessId aParentPid) + : ProcessChild(aParentPid) {} + +VRProcessChild::~VRProcessChild() { sVRParent = nullptr; } + +/*static*/ +VRParent* VRProcessChild::GetVRParent() { + MOZ_ASSERT(sVRParent); + return sVRParent; +} + +bool VRProcessChild::Init(int aArgc, char* aArgv[]) { + char* parentBuildID = nullptr; + char* prefsHandle = nullptr; + char* prefMapHandle = nullptr; + char* prefsLen = nullptr; + char* prefMapSize = nullptr; + for (int i = 1; i < aArgc; i++) { + if (!aArgv[i]) { + continue; + } + if (strcmp(aArgv[i], "-parentBuildID") == 0) { + parentBuildID = aArgv[i + 1]; + +#ifdef XP_WIN + } else if (strcmp(aArgv[i], "-prefsHandle") == 0) { + if (++i == aArgc) { + return false; + } + prefsHandle = aArgv[i]; + } else if (strcmp(aArgv[i], "-prefMapHandle") == 0) { + if (++i == aArgc) { + return false; + } + prefMapHandle = aArgv[i]; +#endif + } else if (strcmp(aArgv[i], "-prefsLen") == 0) { + if (++i == aArgc) { + return false; + } + prefsLen = aArgv[i]; + } else if (strcmp(aArgv[i], "-prefMapSize") == 0) { + if (++i == aArgc) { + return false; + } + prefMapSize = aArgv[i]; + } + } + + ipc::SharedPreferenceDeserializer deserializer; + if (!deserializer.DeserializeFromSharedMemory(prefsHandle, prefMapHandle, + prefsLen, prefMapSize)) { + return false; + } + + sVRParent = new VRParent(); + sVRParent->Init(ParentPid(), parentBuildID, IOThreadChild::message_loop(), + IOThreadChild::TakeChannel()); + + return true; +} + +void VRProcessChild::CleanUp() { + sVRParent = nullptr; + NS_ShutdownXPCOM(nullptr); +} diff --git a/gfx/vr/ipc/VRProcessChild.h b/gfx/vr/ipc/VRProcessChild.h new file mode 100644 index 0000000000..69d13a37e5 --- /dev/null +++ b/gfx/vr/ipc/VRProcessChild.h @@ -0,0 +1,42 @@ +/* -*- 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/. */ + +#ifndef GFX_VR_PROCESS_CHILD_H +#define GFX_VR_PROCESS_CHILD_H + +#include "mozilla/ipc/ProcessChild.h" +#include "VRParent.h" + +namespace mozilla { +namespace gfx { + +/** + * Contains the VRChild object that facilitates IPC communication to/from + * the instance of the VR library that is run in this process. + */ +class VRProcessChild final : public mozilla::ipc::ProcessChild { + protected: + typedef mozilla::ipc::ProcessChild ProcessChild; + + public: + explicit VRProcessChild(ProcessId aParentPid); + ~VRProcessChild(); + + // IPC channel for VR process talk to the parent process. + static VRParent* GetVRParent(); + + // ProcessChild functions. + virtual bool Init(int aArgc, char* aArgv[]) override; + virtual void CleanUp() override; + + private: + DISALLOW_COPY_AND_ASSIGN(VRProcessChild); +}; + +} // namespace gfx +} // namespace mozilla + +#endif /* GFX_VR_PROCESS_CHILD_H */ diff --git a/gfx/vr/ipc/VRProcessManager.cpp b/gfx/vr/ipc/VRProcessManager.cpp new file mode 100644 index 0000000000..082c8643fd --- /dev/null +++ b/gfx/vr/ipc/VRProcessManager.cpp @@ -0,0 +1,289 @@ +/* -*- 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 "VRProcessManager.h" + +#include "VRProcessParent.h" +#include "VRChild.h" +#include "VRGPUChild.h" +#include "VRGPUParent.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/gfx/Logging.h" +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/MemoryReportingProcess.h" +#include "mozilla/Preferences.h" + +namespace mozilla { +namespace gfx { + +static StaticAutoPtr<VRProcessManager> sSingleton; + +/* static */ +VRProcessManager* VRProcessManager::Get() { return sSingleton; } + +/* static */ +void VRProcessManager::Initialize() { + MOZ_ASSERT(XRE_IsParentProcess()); + if (sSingleton == nullptr) { + sSingleton = new VRProcessManager(); + } +} + +/* static */ +void VRProcessManager::Shutdown() { sSingleton = nullptr; } + +VRProcessManager::VRProcessManager() : mProcess(nullptr), mVRChild(nullptr) { + MOZ_COUNT_CTOR(VRProcessManager); + + mObserver = new Observer(this); + nsContentUtils::RegisterShutdownObserver(mObserver); + Preferences::AddStrongObserver(mObserver, ""); +} + +VRProcessManager::~VRProcessManager() { + MOZ_COUNT_DTOR(VRProcessManager); + + if (mObserver) { + nsContentUtils::UnregisterShutdownObserver(mObserver); + Preferences::RemoveObserver(mObserver, ""); + mObserver = nullptr; + } + + DestroyProcess(); + // The VR process should have already been shut down. + MOZ_ASSERT(!mProcess); +} + +void VRProcessManager::LaunchVRProcess() { + if (mProcess) { + return; + } + + // The subprocess is launched asynchronously, so we wait for a callback to + // acquire the IPDL actor. + mProcess = new VRProcessParent(this); + if (!mProcess->Launch()) { + DisableVRProcess("Failed to launch VR process"); + } +} + +void VRProcessManager::DisableVRProcess(const char* aMessage) { + if (!StaticPrefs::dom_vr_process_enabled_AtStartup()) { + return; + } + + DestroyProcess(); +} + +void VRProcessManager::DestroyProcess() { + if (!mProcess) { + return; + } + + mProcess->Shutdown(); + mProcess = nullptr; + mVRChild = nullptr; + + CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::VRProcessStatus, + "Destroyed"_ns); +} + +bool VRProcessManager::EnsureVRReady() { + if (mProcess && !mProcess->IsConnected()) { + if (!mProcess->WaitForLaunch()) { + // If this fails, we should have fired OnProcessLaunchComplete and + // removed the process. + MOZ_ASSERT(!mProcess && !mVRChild); + return false; + } + } + + if (mVRChild) { + if (mVRChild->EnsureVRReady()) { + return true; + } + + // If the initialization above fails, we likely have a GPU process teardown + // waiting in our message queue (or will soon). We need to ensure we don't + // restart it later because if we fail here, our callers assume they should + // fall back to a combined UI/GPU process. This also ensures our internal + // state is consistent (e.g. process token is reset). + DisableVRProcess("Failed to initialize VR process"); + } + + return false; +} + +void VRProcessManager::OnProcessLaunchComplete(VRProcessParent* aParent) { + MOZ_ASSERT(mProcess && mProcess == aParent); + + mVRChild = mProcess->GetActor(); + + if (!mProcess->IsConnected()) { + DestroyProcess(); + return; + } + + // Flush any pref updates that happened during launch and weren't + // included in the blobs set up in LaunchGPUProcess. + for (const mozilla::dom::Pref& pref : mQueuedPrefs) { + Unused << NS_WARN_IF(!mVRChild->SendPreferenceUpdate(pref)); + } + mQueuedPrefs.Clear(); + + CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::VRProcessStatus, + "Running"_ns); +} + +void VRProcessManager::OnProcessUnexpectedShutdown(VRProcessParent* aParent) { + MOZ_ASSERT(mProcess && mProcess == aParent); + + DestroyProcess(); +} + +bool VRProcessManager::CreateGPUBridges( + base::ProcessId aOtherProcess, + mozilla::ipc::Endpoint<PVRGPUChild>* aOutVRBridge) { + if (!CreateGPUVRManager(aOtherProcess, aOutVRBridge)) { + return false; + } + return true; +} + +bool VRProcessManager::CreateGPUVRManager( + base::ProcessId aOtherProcess, + mozilla::ipc::Endpoint<PVRGPUChild>* aOutEndpoint) { + if (mProcess && !mProcess->IsConnected()) { + NS_WARNING("VR process haven't connected with the parent process yet"); + return false; + } + + base::ProcessId vrparentPid = mProcess + ? mProcess->OtherPid() // VR process id. + : base::GetCurrentProcId(); + + ipc::Endpoint<PVRGPUParent> vrparentPipe; + ipc::Endpoint<PVRGPUChild> vrchildPipe; + nsresult rv = PVRGPU::CreateEndpoints(vrparentPid, // vr process id + aOtherProcess, // gpu process id + &vrparentPipe, &vrchildPipe); + + if (NS_FAILED(rv)) { + gfxCriticalNote << "Could not create gpu-vr bridge: " << hexa(int(rv)); + return false; + } + + // Bind vr-gpu pipe to VRParent and make a PVRGPU connection. + VRChild* vrChild = mProcess->GetActor(); + vrChild->SendNewGPUVRManager(std::move(vrparentPipe)); + + *aOutEndpoint = std::move(vrchildPipe); + return true; +} + +NS_IMPL_ISUPPORTS(VRProcessManager::Observer, nsIObserver); + +VRProcessManager::Observer::Observer(VRProcessManager* aManager) + : mManager(aManager) {} + +NS_IMETHODIMP +VRProcessManager::Observer::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) { + if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { + mManager->OnXPCOMShutdown(); + } else if (!strcmp(aTopic, "nsPref:changed")) { + mManager->OnPreferenceChange(aData); + } + return NS_OK; +} + +void VRProcessManager::CleanShutdown() { DestroyProcess(); } + +void VRProcessManager::OnXPCOMShutdown() { + if (mObserver) { + nsContentUtils::UnregisterShutdownObserver(mObserver); + Preferences::RemoveObserver(mObserver, ""); + mObserver = nullptr; + } + + CleanShutdown(); +} + +void VRProcessManager::OnPreferenceChange(const char16_t* aData) { + // A pref changed. If it is useful to do so, inform child processes. + if (!dom::ContentParent::ShouldSyncPreference(aData)) { + return; + } + + // We know prefs are ASCII here. + NS_LossyConvertUTF16toASCII strData(aData); + + mozilla::dom::Pref pref(strData, /* isLocked */ false, Nothing(), Nothing()); + Preferences::GetPreference(&pref); + if (!!mVRChild) { + MOZ_ASSERT(mQueuedPrefs.IsEmpty()); + mVRChild->SendPreferenceUpdate(pref); + } else { + mQueuedPrefs.AppendElement(pref); + } +} + +VRChild* VRProcessManager::GetVRChild() { return mProcess->GetActor(); } + +class VRMemoryReporter : public MemoryReportingProcess { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VRMemoryReporter, override) + + bool IsAlive() const override { + if (VRProcessManager* vpm = VRProcessManager::Get()) { + return !!vpm->GetVRChild(); + } + return false; + } + + bool SendRequestMemoryReport( + const uint32_t& aGeneration, const bool& aAnonymize, + const bool& aMinimizeMemoryUsage, + const Maybe<ipc::FileDescriptor>& aDMDFile) override { + VRChild* child = GetChild(); + if (!child) { + return false; + } + + return child->SendRequestMemoryReport(aGeneration, aAnonymize, + aMinimizeMemoryUsage, aDMDFile); + } + + int32_t Pid() const override { + if (VRChild* child = GetChild()) { + return (int32_t)child->OtherPid(); + } + return 0; + } + + private: + VRChild* GetChild() const { + if (VRProcessManager* vpm = VRProcessManager::Get()) { + if (VRChild* child = vpm->GetVRChild()) { + return child; + } + } + return nullptr; + } + + protected: + ~VRMemoryReporter() = default; +}; + +RefPtr<MemoryReportingProcess> VRProcessManager::GetProcessMemoryReporter() { + if (!EnsureVRReady()) { + return nullptr; + } + return new VRMemoryReporter(); +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/vr/ipc/VRProcessManager.h b/gfx/vr/ipc/VRProcessManager.h new file mode 100644 index 0000000000..030045d300 --- /dev/null +++ b/gfx/vr/ipc/VRProcessManager.h @@ -0,0 +1,95 @@ +/* -*- 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/. */ +#ifndef GFX_VR_PROCESS_MANAGER_H +#define GFX_VR_PROCESS_MANAGER_H + +#include "VRProcessParent.h" + +#include "mozilla/ipc/ProtocolUtils.h" +#include "nsIObserver.h" + +namespace mozilla { +class MemoryReportingProcess; +namespace ipc { +template <typename T> +class Endpoint; +} +namespace gfx { + +class VRManagerChild; +class PVRGPUChild; +class VRChild; + +// The VRProcessManager is a singleton responsible for creating VR-bound +// objects that may live in another process. +class VRProcessManager final : public VRProcessParent::Listener { + public: + static VRProcessManager* Get(); + static void Initialize(); + static void Shutdown(); + + ~VRProcessManager(); + + // If not using a VR process, launch a new VR process asynchronously. + void LaunchVRProcess(); + + // Ensure that VR-bound methods can be used. If no VR process is being + // used, or one is launched and ready, this function returns immediately. + // Otherwise it blocks until the VR process has finished launching. + bool EnsureVRReady(); + + bool CreateGPUBridges(base::ProcessId aOtherProcess, + mozilla::ipc::Endpoint<PVRGPUChild>* aOutVRBridge); + + VRChild* GetVRChild(); + // If a VR process is present, create a MemoryReportingProcess object. + // Otherwise, return null. + RefPtr<MemoryReportingProcess> GetProcessMemoryReporter(); + + virtual void OnProcessLaunchComplete(VRProcessParent* aParent) override; + virtual void OnProcessUnexpectedShutdown(VRProcessParent* aParent) override; + + private: + VRProcessManager(); + + DISALLOW_COPY_AND_ASSIGN(VRProcessManager); + + bool CreateGPUVRManager(base::ProcessId aOtherProcess, + mozilla::ipc::Endpoint<PVRGPUChild>* aOutEndpoint); + void OnXPCOMShutdown(); + void OnPreferenceChange(const char16_t* aData); + void CleanShutdown(); + void DestroyProcess(); + + // Permanently disable the VR process and record a message why. + void DisableVRProcess(const char* aMessage); + + class Observer final : public nsIObserver { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + explicit Observer(VRProcessManager* aManager); + + protected: + ~Observer() = default; + + VRProcessManager* mManager; + }; + friend class Observer; + + RefPtr<Observer> mObserver; + VRProcessParent* mProcess; + VRChild* mVRChild; + // Collects any pref changes that occur during process launch (after + // the initial map is passed in command-line arguments) to be sent + // when the process can receive IPC messages. + nsTArray<mozilla::dom::Pref> mQueuedPrefs; +}; + +} // namespace gfx +} // namespace mozilla + +#endif // GFX_VR_PROCESS_MANAGER_H diff --git a/gfx/vr/ipc/VRProcessParent.cpp b/gfx/vr/ipc/VRProcessParent.cpp new file mode 100644 index 0000000000..46c7bd37ab --- /dev/null +++ b/gfx/vr/ipc/VRProcessParent.cpp @@ -0,0 +1,243 @@ +/* -*- 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 "VRProcessParent.h" +#include "VRGPUChild.h" +#include "VRProcessManager.h" +#include "mozilla/dom/MemoryReportRequest.h" +#include "mozilla/gfx/GPUProcessManager.h" +#include "mozilla/gfx/GPUChild.h" +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/ipc/ProtocolTypes.h" +#include "mozilla/ipc/ProtocolUtils.h" // for IToplevelProtocol +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/TimeStamp.h" // for TimeStamp +#include "mozilla/Unused.h" +#include "ProcessUtils.h" +#include "VRChild.h" +#include "VRThread.h" + +#include "nsAppRunner.h" // for IToplevelProtocol +#include "mozilla/ipc/ProtocolUtils.h" + +using std::string; +using std::vector; + +using namespace mozilla::ipc; + +namespace mozilla { +namespace gfx { + +VRProcessParent::VRProcessParent(Listener* aListener) + : GeckoChildProcessHost(GeckoProcessType_VR), + mTaskFactory(this), + mListener(aListener), + mLaunchPhase(LaunchPhase::Unlaunched), + mChannelClosed(false), + mShutdownRequested(false) { + MOZ_COUNT_CTOR(VRProcessParent); +} + +VRProcessParent::~VRProcessParent() { + // Cancel all tasks. We don't want anything triggering after our caller + // expects this to go away. + { + MonitorAutoLock lock(mMonitor); + mTaskFactory.RevokeAll(); + } + MOZ_COUNT_DTOR(VRProcessParent); +} + +bool VRProcessParent::Launch() { + MOZ_ASSERT(mLaunchPhase == LaunchPhase::Unlaunched); + MOZ_ASSERT(!mVRChild); + mLaunchThread = NS_GetCurrentThread(); + + mLaunchPhase = LaunchPhase::Waiting; + + std::vector<std::string> extraArgs; + nsCString parentBuildID(mozilla::PlatformBuildID()); + extraArgs.push_back("-parentBuildID"); + extraArgs.push_back(parentBuildID.get()); + + mPrefSerializer = MakeUnique<ipc::SharedPreferenceSerializer>(); + if (!mPrefSerializer->SerializeToSharedMemory()) { + return false; + } + mPrefSerializer->AddSharedPrefCmdLineArgs(*this, extraArgs); + + if (!GeckoChildProcessHost::AsyncLaunch(extraArgs)) { + mLaunchPhase = LaunchPhase::Complete; + mPrefSerializer = nullptr; + return false; + } + return true; +} + +bool VRProcessParent::WaitForLaunch() { + if (mLaunchPhase == LaunchPhase::Complete) { + return !!mVRChild; + } + + int32_t timeoutMs = + StaticPrefs::dom_vr_process_startup_timeout_ms_AtStartup(); + + // If one of the following environment variables are set we can effectively + // ignore the timeout - as we can guarantee the compositor process will be + // terminated + if (PR_GetEnv("MOZ_DEBUG_CHILD_PROCESS") || + PR_GetEnv("MOZ_DEBUG_CHILD_PAUSE")) { + timeoutMs = 0; + } + + // Our caller expects the connection to be finished after we return, so we + // immediately set up the IPDL actor and fire callbacks. The IO thread will + // still dispatch a notification to the main thread - we'll just ignore it. + bool result = GeckoChildProcessHost::WaitUntilConnected(timeoutMs); + result &= InitAfterConnect(result); + return result; +} + +void VRProcessParent::Shutdown() { + MOZ_ASSERT(!mShutdownRequested); + mListener = nullptr; + + if (mVRChild) { + // The channel might already be closed if we got here unexpectedly. + if (!mChannelClosed) { + mVRChild->Close(); + } + // OnChannelClosed uses this to check if the shutdown was expected or + // unexpected. + mShutdownRequested = true; + +#ifndef NS_FREE_PERMANENT_DATA + // No need to communicate shutdown, the VR process doesn't need to + // communicate anything back. + KillHard("NormalShutdown"); +#endif + + // If we're shutting down unexpectedly, we're in the middle of handling an + // ActorDestroy for PVRChild, which is still on the stack. We'll return + // back to OnChannelClosed. + // + // Otherwise, we'll wait for OnChannelClose to be called whenever PVRChild + // acknowledges shutdown. + return; + } + + DestroyProcess(); +} + +void VRProcessParent::DestroyProcess() { + if (mLaunchThread) { + mLaunchThread->Dispatch(NS_NewRunnableFunction("DestroyProcessRunnable", + [this] { Destroy(); })); + } +} + +bool VRProcessParent::InitAfterConnect(bool aSucceeded) { + MOZ_ASSERT(mLaunchPhase == LaunchPhase::Waiting); + MOZ_ASSERT(!mVRChild); + + mLaunchPhase = LaunchPhase::Complete; + mPrefSerializer = nullptr; + + if (aSucceeded) { + GPUChild* gpuChild = GPUProcessManager::Get()->GetGPUChild(); + if (!gpuChild) { + NS_WARNING( + "GPU process haven't connected with the parent process yet" + "when creating VR process."); + return false; + } + + mVRChild = MakeUnique<VRChild>(this); + + DebugOnly<bool> rv = + mVRChild->Open(TakeChannel(), base::GetProcId(GetChildProcessHandle())); + MOZ_ASSERT(rv); + + mVRChild->Init(); + + if (mListener) { + mListener->OnProcessLaunchComplete(this); + } + + // Make vr-gpu process connection + Endpoint<PVRGPUChild> vrGPUBridge; + VRProcessManager* vpm = VRProcessManager::Get(); + DebugOnly<bool> opened = + vpm->CreateGPUBridges(gpuChild->OtherPid(), &vrGPUBridge); + MOZ_ASSERT(opened); + + Unused << gpuChild->SendInitVR(std::move(vrGPUBridge)); + } + + return true; +} + +void VRProcessParent::KillHard(const char* aReason) { + ProcessHandle handle = GetChildProcessHandle(); + if (!base::KillProcess(handle, base::PROCESS_END_KILLED_BY_USER, false)) { + NS_WARNING("failed to kill subprocess!"); + } + + SetAlreadyDead(); +} + +void VRProcessParent::OnChannelError() { + MOZ_ASSERT(false, "VR process channel error."); +} + +void VRProcessParent::OnChannelConnected(int32_t peer_pid) { + MOZ_ASSERT(!NS_IsMainThread()); + + GeckoChildProcessHost::OnChannelConnected(peer_pid); + + // Post a task to the main thread. Take the lock because mTaskFactory is not + // thread-safe. + RefPtr<Runnable> runnable; + { + MonitorAutoLock lock(mMonitor); + runnable = mTaskFactory.NewRunnableMethod( + &VRProcessParent::OnChannelConnectedTask); + } + NS_DispatchToMainThread(runnable); +} + +void VRProcessParent::OnChannelConnectedTask() { + if (mLaunchPhase == LaunchPhase::Waiting) { + InitAfterConnect(true); + } +} + +void VRProcessParent::OnChannelErrorTask() { + if (mLaunchPhase == LaunchPhase::Waiting) { + InitAfterConnect(false); + } +} + +void VRProcessParent::OnChannelClosed() { + mChannelClosed = true; + if (!mShutdownRequested && mListener) { + // This is an unclean shutdown. Notify we're going away. + mListener->OnProcessUnexpectedShutdown(this); + } else { + DestroyProcess(); + } + + // Release the actor. + VRChild::Destroy(std::move(mVRChild)); + MOZ_ASSERT(!mVRChild); +} + +base::ProcessId VRProcessParent::OtherPid() { return mVRChild->OtherPid(); } + +bool VRProcessParent::IsConnected() const { return !!mVRChild; } + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/vr/ipc/VRProcessParent.h b/gfx/vr/ipc/VRProcessParent.h new file mode 100644 index 0000000000..efe1708af0 --- /dev/null +++ b/gfx/vr/ipc/VRProcessParent.h @@ -0,0 +1,87 @@ +/* -*- 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/. */ + +#ifndef GFX_VR_PROCESS_PARENT_H +#define GFX_VR_PROCESS_PARENT_H + +#include "mozilla/UniquePtr.h" + +#include "mozilla/ipc/GeckoChildProcessHost.h" +#include "mozilla/ipc/TaskFactory.h" + +namespace mozilla { +namespace ipc { +class SharedPreferenceSerializer; +} +namespace gfx { + +class VRChild; + +class VRProcessParent final : public mozilla::ipc::GeckoChildProcessHost { + public: + class Listener { + public: + virtual void OnProcessLaunchComplete(VRProcessParent* aParent) {} + + // Follow GPU and RDD process manager, adding this to avoid + // unexpectedly shutdown or had its connection severed. + // This is not called if an error occurs after calling Shutdown(). + virtual void OnProcessUnexpectedShutdown(VRProcessParent* aParent) {} + }; + + explicit VRProcessParent(Listener* aListener); + + // Launch the subprocess asynchronously. On failure, false is returned. + // Otherwise, true is returned, and the OnProcessLaunchComplete listener + // callback will be invoked either when a connection has been established, or + // if a connection could not be established due to an asynchronous error. + bool Launch(); + // If the process is being launched, block until it has launched and + // connected. If a launch task is pending, it will fire immediately. + // + // Returns true if the process is successfully connected; false otherwise. + bool WaitForLaunch(); + void Shutdown(); + void DestroyProcess(); + bool CanShutdown() override { return true; } + + void OnChannelError() override; + void OnChannelConnected(int32_t peer_pid) override; + void OnChannelConnectedTask(); + void OnChannelErrorTask(); + void OnChannelClosed(); + bool IsConnected() const; + + base::ProcessId OtherPid(); + VRChild* GetActor() const { return mVRChild.get(); } + // Return a unique id for this process, guaranteed not to be shared with any + // past or future instance of VRProcessParent. + uint64_t GetProcessToken() const; + + private: + ~VRProcessParent(); + + DISALLOW_COPY_AND_ASSIGN(VRProcessParent); + + bool InitAfterConnect(bool aSucceeded); + void KillHard(const char* aReason); + + UniquePtr<VRChild> mVRChild; + mozilla::ipc::TaskFactory<VRProcessParent> mTaskFactory; + nsCOMPtr<nsIThread> mLaunchThread; + Listener* mListener; + + enum class LaunchPhase { Unlaunched, Waiting, Complete }; + LaunchPhase mLaunchPhase; + bool mChannelClosed; + bool mShutdownRequested; + UniquePtr<mozilla::ipc::SharedPreferenceSerializer> mPrefSerializer; +}; + +} // namespace gfx +} // namespace mozilla + +#endif // ifndef GFX_VR_PROCESS_PARENT_H diff --git a/gfx/vr/moz.build b/gfx/vr/moz.build new file mode 100644 index 0000000000..6f24beb9ba --- /dev/null +++ b/gfx/vr/moz.build @@ -0,0 +1,112 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +EXPORTS += [ + "external_api/moz_external_vr.h", + "FxROutputHandler.h", + "FxRWindowManager.h", + "gfxVR.h", + "ipc/VRChild.h", + "ipc/VRGPUChild.h", + "ipc/VRGPUParent.h", + "ipc/VRLayerChild.h", + "ipc/VRManagerChild.h", + "ipc/VRManagerParent.h", + "ipc/VRMessageUtils.h", + "ipc/VRParent.h", + "ipc/VRProcessChild.h", + "ipc/VRProcessManager.h", + "ipc/VRProcessParent.h", + "service/VRService.h", + "VRDisplayClient.h", + "VRDisplayPresentation.h", + "VRManager.h", + "VRPuppetCommandBuffer.h", + "VRShMem.h", + "VRThread.h", +] + +LOCAL_INCLUDES += [ + "/dom/base", + "/dom/canvas", + "/gfx/layers/d3d11", + "/gfx/thebes", +] + +UNIFIED_SOURCES += [ + "gfxVR.cpp", + "ipc/VRChild.cpp", + "ipc/VRGPUChild.cpp", + "ipc/VRGPUParent.cpp", + "ipc/VRManagerChild.cpp", + "ipc/VRManagerParent.cpp", + "ipc/VRParent.cpp", + "ipc/VRProcessChild.cpp", + "ipc/VRProcessManager.cpp", + "ipc/VRProcessParent.cpp", + "VRDisplayClient.cpp", + "VRDisplayPresentation.cpp", + "VRThread.cpp", +] + +SOURCES += [ + "ipc/VRLayerChild.cpp", + "ipc/VRLayerParent.cpp", + "VRManager.cpp", + "VRPuppetCommandBuffer.cpp", + "VRShMem.cpp", +] + +if CONFIG["OS_TARGET"] == "Android": + LOCAL_INCLUDES += ["/widget/android"] +else: + DIRS += [ + "service", + ] + UNIFIED_SOURCES += [ + "VRServiceHost.cpp", + ] + +# Only target x64 for vrhost since WebVR is only supported on 64bit. +# Also, only use MSVC compiler for Windows-specific linking +if ( + CONFIG["OS_ARCH"] == "WINNT" + and CONFIG["HAVE_64BIT_BUILD"] + and CONFIG["CC_TYPE"] not in ("clang", "gcc") +): + DIRS += ["vrhost"] + +IPDL_SOURCES = [ + "ipc/PVR.ipdl", + "ipc/PVRGPU.ipdl", + "ipc/PVRLayer.ipdl", + "ipc/PVRManager.ipdl", +] + +# For now, only enable FxR CLH for Windows Nightly builds (BUG 1565349) +if CONFIG["OS_ARCH"] == "WINNT" and CONFIG["NIGHTLY_BUILD"]: + XPCOM_MANIFESTS += [ + "components.conf", + ] + SOURCES += [ + "nsFxrCommandLineHandler.cpp", + ] + +if CONFIG["OS_ARCH"] == "WINNT": + LOCAL_INCLUDES += ["/layout/generic", "/widget", "/widget/windows"] + SOURCES += ["FxROutputHandler.cpp", "FxRWindowManager.cpp"] + +CXXFLAGS += CONFIG["MOZ_CAIRO_CFLAGS"] +CXXFLAGS += CONFIG["TK_CFLAGS"] +CFLAGS += CONFIG["MOZ_CAIRO_CFLAGS"] +CFLAGS += CONFIG["TK_CFLAGS"] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" + +with Files("**"): + BUG_COMPONENT = ("Core", "WebVR") diff --git a/gfx/vr/nsFxrCommandLineHandler.cpp b/gfx/vr/nsFxrCommandLineHandler.cpp new file mode 100644 index 0000000000..4c7b930e5c --- /dev/null +++ b/gfx/vr/nsFxrCommandLineHandler.cpp @@ -0,0 +1,139 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +// vim:cindent:tabstop=4:expandtab:shiftwidth=4: +/* 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/. */ + +#ifndef XP_WIN +# error "nsFxrCommandLineHandler currently only supported on Windows" +#endif + +#include "nsFxrCommandLineHandler.h" +#include "FxRWindowManager.h" + +#include "nsICommandLine.h" +#include "nsIWindowWatcher.h" +#include "mozIDOMWindow.h" +#include "nsPIDOMWindow.h" +#include "mozilla/WidgetUtils.h" +#include "nsIWidget.h" +#include "nsServiceManagerUtils.h" +#include "nsString.h" +#include "nsArray.h" +#include "nsCOMPtr.h" +#include "mozilla/StaticPrefs_extensions.h" + +#include "windows.h" +#include "WinUtils.h" + +#include "VRShMem.h" + +NS_IMPL_ISUPPORTS(nsFxrCommandLineHandler, nsICommandLineHandler) + +// nsFxrCommandLineHandler acts in the middle of bootstrapping Firefox +// Reality with desktop Firefox. Details of the processes involved are +// described below: +// +// Host +// (vrhost!CreateVRWindow) Fx Main Fx GPU +// | + + +// VRShMem creates shared + + +// memory in OS + + +// | + + +// Launch firefox.exe + + +// with --fxr + + +// | | + +// Wait for Signal... nsFxrCLH handles param + +// | joins VRShMem + +// | creates new window | +// | sets .hwndFx in VRShMem | +// | | | +// | | After compositor and +// | | swapchain created, +// | | share texture handle to +// | | VRShMem and set signal +// CreateVRWindow returns | | +// to host with relevant | | +// return data from VRShMem | | +// | Fx continues to run | +// | | Fx continues to render +// | | | +// ... ... ... + +NS_IMETHODIMP +nsFxrCommandLineHandler::Handle(nsICommandLine* aCmdLine) { + bool handleFlagRetVal = false; + nsresult result = aCmdLine->HandleFlag(u"fxr"_ns, false, &handleFlagRetVal); + if (result == NS_OK && handleFlagRetVal) { + if (XRE_IsParentProcess() && !XRE_IsE10sParentProcess()) { + MOZ_CRASH("--fxr not supported without e10s"); + } + + MOZ_ASSERT(mozilla::StaticPrefs::extensions_webextensions_remote(), + "Remote extensions are the only supported configuration on " + "desktop platforms"); + + aCmdLine->SetPreventDefault(true); + + nsCOMPtr<nsIWindowWatcher> wwatch = + do_GetService(NS_WINDOWWATCHER_CONTRACTID); + NS_ENSURE_TRUE(wwatch, NS_ERROR_FAILURE); + + nsCOMPtr<mozIDOMWindowProxy> newWindow; + result = wwatch->OpenWindow( + nullptr, // aParent + "chrome://fxr/content/fxrui.html"_ns, // aUrl + "_blank"_ns, // aName + "chrome,dialog=no,all,private,alwaysontop"_ns, // aFeatures + nullptr, // aArguments + getter_AddRefs(newWindow)); + + MOZ_ASSERT(result == NS_OK); + + nsPIDOMWindowOuter* newWindowOuter = nsPIDOMWindowOuter::From(newWindow); + FxRWindowManager::GetInstance()->AddWindow(newWindowOuter); + + // Set ForceFullScreenInWidget so that full-screen (in an FxR window) + // fills only the window and thus the same texture that will already be + // shared with the host. Also, this is set here per-window because + // changing the related pref would impact all browser window instances. + newWindowOuter->ForceFullScreenInWidget(); + + // Send the window's HWND to vrhost through VRShMem + mozilla::gfx::VRShMem shmem(nullptr, true /*aRequiresMutex*/); + if (shmem.JoinShMem()) { + mozilla::gfx::VRWindowState windowState = {0}; + shmem.PullWindowState(windowState); + + nsCOMPtr<nsIWidget> newWidget = + mozilla::widget::WidgetUtils::DOMWindowToWidget(newWindowOuter); + HWND hwndWidget = (HWND)newWidget->GetNativeData(NS_NATIVE_WINDOW); + + // The CLH should populate these members first + MOZ_ASSERT(windowState.hwndFx == 0); + MOZ_ASSERT(windowState.textureFx == nullptr); + windowState.hwndFx = (uint64_t)hwndWidget; + + shmem.PushWindowState(windowState); + shmem.LeaveShMem(); + + // The GPU process will notify the host that window creation is complete + // after output data is set in VRShMem + newWidget->RequestFxrOutput(); + } else { +#ifndef NIGHTLY_BUILD + MOZ_CRASH("failed to start with --fxr"); +#endif + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsFxrCommandLineHandler::GetHelpInfo(nsACString& aResult) { + aResult.AssignLiteral( + " --fxr Creates a new window for Firefox Reality on Desktop when " + "available\n"); + return NS_OK; +} diff --git a/gfx/vr/nsFxrCommandLineHandler.h b/gfx/vr/nsFxrCommandLineHandler.h new file mode 100644 index 0000000000..e9a2a2b489 --- /dev/null +++ b/gfx/vr/nsFxrCommandLineHandler.h @@ -0,0 +1,32 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +// vim:cindent:tabstop=4:expandtab:shiftwidth=4: +/* 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/. */ + +#ifndef GFX_VR_nsFxrCommandLineHandler_h_ +#define GFX_VR_nsFxrCommandLineHandler_h_ + +#include "nsICommandLineHandler.h" + +// 5baca10a-4d53-4335-b24d-c69696640a9a +#define NS_FXRCOMMANDLINEHANDLER_CID \ + { \ + 0x5baca10a, 0x4d53, 0x4335, { \ + 0xb2, 0x4d, 0xc6, 0x96, 0x96, 0x64, 0x0a, 0x9a \ + } \ + } + +// nsFxrCommandLineHandler is responsible for handling parameters used to +// bootstrap Firefox Reality running on desktop-class machines. +class nsFxrCommandLineHandler : public nsICommandLineHandler { + public: + nsFxrCommandLineHandler() = default; + NS_DECL_ISUPPORTS + NS_DECL_NSICOMMANDLINEHANDLER + + protected: + virtual ~nsFxrCommandLineHandler() = default; +}; + +#endif /* !defined(GFX_VR_nsFxrCommandLineHandler_h_) */ diff --git a/gfx/vr/service/OSVRSession.cpp b/gfx/vr/service/OSVRSession.cpp new file mode 100644 index 0000000000..486cb03286 --- /dev/null +++ b/gfx/vr/service/OSVRSession.cpp @@ -0,0 +1,511 @@ +/* -*- 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 "OSVRSession.h" +#include "prenv.h" +#include "nsString.h" +#include "mozilla/Preferences.h" +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/SharedLibrary.h" +#include "mozilla/gfx/Quaternion.h" + +#if defined(XP_WIN) +# include <d3d11.h> +# include "mozilla/gfx/DeviceManagerDx.h" +#endif // defined(XP_WIN) + +#ifndef M_PI +# define M_PI 3.14159265358979323846 +#endif + +using namespace mozilla; +using namespace mozilla::gfx; + +namespace { +// need to typedef functions that will be used in the code below +extern "C" { +typedef OSVR_ClientContext (*pfn_osvrClientInit)( + const char applicationIdentifier[], uint32_t flags); +typedef OSVR_ReturnCode (*pfn_osvrClientShutdown)(OSVR_ClientContext ctx); +typedef OSVR_ReturnCode (*pfn_osvrClientUpdate)(OSVR_ClientContext ctx); +typedef OSVR_ReturnCode (*pfn_osvrClientCheckStatus)(OSVR_ClientContext ctx); +typedef OSVR_ReturnCode (*pfn_osvrClientGetInterface)( + OSVR_ClientContext ctx, const char path[], OSVR_ClientInterface* iface); +typedef OSVR_ReturnCode (*pfn_osvrClientFreeInterface)( + OSVR_ClientContext ctx, OSVR_ClientInterface iface); +typedef OSVR_ReturnCode (*pfn_osvrGetOrientationState)( + OSVR_ClientInterface iface, OSVR_TimeValue* timestamp, + OSVR_OrientationState* state); +typedef OSVR_ReturnCode (*pfn_osvrGetPositionState)(OSVR_ClientInterface iface, + OSVR_TimeValue* timestamp, + OSVR_PositionState* state); +typedef OSVR_ReturnCode (*pfn_osvrClientGetDisplay)(OSVR_ClientContext ctx, + OSVR_DisplayConfig* disp); +typedef OSVR_ReturnCode (*pfn_osvrClientFreeDisplay)(OSVR_DisplayConfig disp); +typedef OSVR_ReturnCode (*pfn_osvrClientGetNumEyesForViewer)( + OSVR_DisplayConfig disp, OSVR_ViewerCount viewer, OSVR_EyeCount* eyes); +typedef OSVR_ReturnCode (*pfn_osvrClientGetViewerEyePose)( + OSVR_DisplayConfig disp, OSVR_ViewerCount viewer, OSVR_EyeCount eye, + OSVR_Pose3* pose); +typedef OSVR_ReturnCode (*pfn_osvrClientGetDisplayDimensions)( + OSVR_DisplayConfig disp, OSVR_DisplayInputCount displayInputIndex, + OSVR_DisplayDimension* width, OSVR_DisplayDimension* height); +typedef OSVR_ReturnCode ( + *pfn_osvrClientGetViewerEyeSurfaceProjectionClippingPlanes)( + OSVR_DisplayConfig disp, OSVR_ViewerCount viewer, OSVR_EyeCount eye, + OSVR_SurfaceCount surface, double* left, double* right, double* bottom, + double* top); +typedef OSVR_ReturnCode (*pfn_osvrClientGetRelativeViewportForViewerEyeSurface)( + OSVR_DisplayConfig disp, OSVR_ViewerCount viewer, OSVR_EyeCount eye, + OSVR_SurfaceCount surface, OSVR_ViewportDimension* left, + OSVR_ViewportDimension* bottom, OSVR_ViewportDimension* width, + OSVR_ViewportDimension* height); +typedef OSVR_ReturnCode (*pfn_osvrClientGetViewerEyeSurfaceProjectionMatrixf)( + OSVR_DisplayConfig disp, OSVR_ViewerCount viewer, OSVR_EyeCount eye, + OSVR_SurfaceCount surface, float near, float far, + OSVR_MatrixConventions flags, float* matrix); +typedef OSVR_ReturnCode (*pfn_osvrClientCheckDisplayStartup)( + OSVR_DisplayConfig disp); +typedef OSVR_ReturnCode (*pfn_osvrClientSetRoomRotationUsingHead)( + OSVR_ClientContext ctx); +} + +static pfn_osvrClientInit osvr_ClientInit = nullptr; +static pfn_osvrClientShutdown osvr_ClientShutdown = nullptr; +static pfn_osvrClientUpdate osvr_ClientUpdate = nullptr; +static pfn_osvrClientCheckStatus osvr_ClientCheckStatus = nullptr; +static pfn_osvrClientGetInterface osvr_ClientGetInterface = nullptr; +static pfn_osvrClientFreeInterface osvr_ClientFreeInterface = nullptr; +static pfn_osvrGetOrientationState osvr_GetOrientationState = nullptr; +static pfn_osvrGetPositionState osvr_GetPositionState = nullptr; +static pfn_osvrClientGetDisplay osvr_ClientGetDisplay = nullptr; +static pfn_osvrClientFreeDisplay osvr_ClientFreeDisplay = nullptr; +static pfn_osvrClientGetNumEyesForViewer osvr_ClientGetNumEyesForViewer = + nullptr; +static pfn_osvrClientGetViewerEyePose osvr_ClientGetViewerEyePose = nullptr; +static pfn_osvrClientGetDisplayDimensions osvr_ClientGetDisplayDimensions = + nullptr; +static pfn_osvrClientGetViewerEyeSurfaceProjectionClippingPlanes + osvr_ClientGetViewerEyeSurfaceProjectionClippingPlanes = nullptr; +static pfn_osvrClientGetRelativeViewportForViewerEyeSurface + osvr_ClientGetRelativeViewportForViewerEyeSurface = nullptr; +static pfn_osvrClientGetViewerEyeSurfaceProjectionMatrixf + osvr_ClientGetViewerEyeSurfaceProjectionMatrixf = nullptr; +static pfn_osvrClientCheckDisplayStartup osvr_ClientCheckDisplayStartup = + nullptr; +static pfn_osvrClientSetRoomRotationUsingHead + osvr_ClientSetRoomRotationUsingHead = nullptr; + +bool LoadOSVRRuntime() { + static PRLibrary* osvrUtilLib = nullptr; + static PRLibrary* osvrCommonLib = nullptr; + static PRLibrary* osvrClientLib = nullptr; + static PRLibrary* osvrClientKitLib = nullptr; + // this looks up the path in the about:config setting, from greprefs.js or + // modules\libpref\init\all.js we need all the libs to be valid +#ifdef XP_WIN + constexpr static auto* pfnGetPathStringPref = mozilla::Preferences::GetString; + nsAutoString osvrUtilPath, osvrCommonPath, osvrClientPath, osvrClientKitPath; +#else + constexpr static auto* pfnGetPathStringPref = + mozilla::Preferences::GetCString; + nsAutoCString osvrUtilPath, osvrCommonPath, osvrClientPath, osvrClientKitPath; +#endif + if (NS_FAILED(pfnGetPathStringPref("gfx.vr.osvr.utilLibPath", osvrUtilPath, + PrefValueKind::User)) || + NS_FAILED(pfnGetPathStringPref("gfx.vr.osvr.commonLibPath", + osvrCommonPath, PrefValueKind::User)) || + NS_FAILED(pfnGetPathStringPref("gfx.vr.osvr.clientLibPath", + osvrClientPath, PrefValueKind::User)) || + NS_FAILED(pfnGetPathStringPref("gfx.vr.osvr.clientKitLibPath", + osvrClientKitPath, PrefValueKind::User))) { + return false; + } + + osvrUtilLib = LoadLibraryWithFlags(osvrUtilPath.get()); + osvrCommonLib = LoadLibraryWithFlags(osvrCommonPath.get()); + osvrClientLib = LoadLibraryWithFlags(osvrClientPath.get()); + osvrClientKitLib = LoadLibraryWithFlags(osvrClientKitPath.get()); + + if (!osvrUtilLib) { + printf_stderr("[OSVR] Failed to load OSVR Util library!\n"); + return false; + } + if (!osvrCommonLib) { + printf_stderr("[OSVR] Failed to load OSVR Common library!\n"); + return false; + } + if (!osvrClientLib) { + printf_stderr("[OSVR] Failed to load OSVR Client library!\n"); + return false; + } + if (!osvrClientKitLib) { + printf_stderr("[OSVR] Failed to load OSVR ClientKit library!\n"); + return false; + } + +// make sure all functions that we'll be using are available +#define REQUIRE_FUNCTION(_x) \ + do { \ + *(void**)&osvr_##_x = (void*)PR_FindSymbol(osvrClientKitLib, "osvr" #_x); \ + if (!osvr_##_x) { \ + printf_stderr("osvr" #_x " symbol missing\n"); \ + goto fail; \ + } \ + } while (0) + + REQUIRE_FUNCTION(ClientInit); + REQUIRE_FUNCTION(ClientShutdown); + REQUIRE_FUNCTION(ClientUpdate); + REQUIRE_FUNCTION(ClientCheckStatus); + REQUIRE_FUNCTION(ClientGetInterface); + REQUIRE_FUNCTION(ClientFreeInterface); + REQUIRE_FUNCTION(GetOrientationState); + REQUIRE_FUNCTION(GetPositionState); + REQUIRE_FUNCTION(ClientGetDisplay); + REQUIRE_FUNCTION(ClientFreeDisplay); + REQUIRE_FUNCTION(ClientGetNumEyesForViewer); + REQUIRE_FUNCTION(ClientGetViewerEyePose); + REQUIRE_FUNCTION(ClientGetDisplayDimensions); + REQUIRE_FUNCTION(ClientGetViewerEyeSurfaceProjectionClippingPlanes); + REQUIRE_FUNCTION(ClientGetRelativeViewportForViewerEyeSurface); + REQUIRE_FUNCTION(ClientGetViewerEyeSurfaceProjectionMatrixf); + REQUIRE_FUNCTION(ClientCheckDisplayStartup); + REQUIRE_FUNCTION(ClientSetRoomRotationUsingHead); + +#undef REQUIRE_FUNCTION + + return true; + +fail: + return false; +} + +} // namespace + +mozilla::gfx::VRFieldOfView SetFromTanRadians(double left, double right, + double bottom, double top) { + mozilla::gfx::VRFieldOfView fovInfo; + fovInfo.leftDegrees = atan(left) * 180.0 / M_PI; + fovInfo.rightDegrees = atan(right) * 180.0 / M_PI; + fovInfo.upDegrees = atan(top) * 180.0 / M_PI; + fovInfo.downDegrees = atan(bottom) * 180.0 / M_PI; + return fovInfo; +} + +OSVRSession::OSVRSession() + : VRSession(), + mRuntimeLoaded(false), + mOSVRInitialized(false), + mClientContextInitialized(false), + mDisplayConfigInitialized(false), + mInterfaceInitialized(false), + m_ctx(nullptr), + m_iface(nullptr), + m_display(nullptr) {} +OSVRSession::~OSVRSession() { Shutdown(); } + +bool OSVRSession::Initialize(mozilla::gfx::VRSystemState& aSystemState, + bool aDetectRuntimesOnly) { + if (StaticPrefs::dom_vr_puppet_enabled()) { + // Ensure that tests using the VR Puppet do not find real hardware + return false; + } + if (!StaticPrefs::dom_vr_enabled() || !StaticPrefs::dom_vr_osvr_enabled()) { + return false; + } + if (mOSVRInitialized) { + return true; + } + if (!LoadOSVRRuntime()) { + return false; + } + mRuntimeLoaded = true; + + if (aDetectRuntimesOnly) { + aSystemState.displayState.capabilityFlags |= + VRDisplayCapabilityFlags::Cap_ImmersiveVR; + return false; + } + + // initialize client context + InitializeClientContext(); + // try to initialize interface + InitializeInterface(); + // try to initialize display object + InitializeDisplay(); + // verify all components are initialized + CheckOSVRStatus(); + + if (!mOSVRInitialized) { + return false; + } + + if (!InitState(aSystemState)) { + return false; + } + + return true; +} + +void OSVRSession::CheckOSVRStatus() { + if (mOSVRInitialized) { + return; + } + + // client context must be initialized first + InitializeClientContext(); + + // update client context + osvr_ClientUpdate(m_ctx); + + // initialize interface and display if they are not initialized yet + InitializeInterface(); + InitializeDisplay(); + + // OSVR is fully initialized now + if (mClientContextInitialized && mDisplayConfigInitialized && + mInterfaceInitialized) { + mOSVRInitialized = true; + } +} + +void OSVRSession::InitializeClientContext() { + // already initialized + if (mClientContextInitialized) { + return; + } + + // first time creating + if (!m_ctx) { + // get client context + m_ctx = osvr_ClientInit("com.osvr.webvr", 0); + // update context + osvr_ClientUpdate(m_ctx); + // verify we are connected + if (OSVR_RETURN_SUCCESS == osvr_ClientCheckStatus(m_ctx)) { + mClientContextInitialized = true; + } + } + // client context exists but not up and running yet + else { + // update context + osvr_ClientUpdate(m_ctx); + if (OSVR_RETURN_SUCCESS == osvr_ClientCheckStatus(m_ctx)) { + mClientContextInitialized = true; + } + } +} + +void OSVRSession::InitializeInterface() { + // already initialized + if (mInterfaceInitialized) { + return; + } + // Client context must be initialized before getting interface + if (mClientContextInitialized) { + // m_iface will remain nullptr if no interface is returned + if (OSVR_RETURN_SUCCESS == + osvr_ClientGetInterface(m_ctx, "/me/head", &m_iface)) { + mInterfaceInitialized = true; + } + } +} + +void OSVRSession::InitializeDisplay() { + // display is fully configured + if (mDisplayConfigInitialized) { + return; + } + + // Client context must be initialized before getting interface + if (mClientContextInitialized) { + // first time creating display object + if (m_display == nullptr) { + OSVR_ReturnCode ret = osvr_ClientGetDisplay(m_ctx, &m_display); + + if (ret == OSVR_RETURN_SUCCESS) { + osvr_ClientUpdate(m_ctx); + // display object may have been created but not fully startup + if (OSVR_RETURN_SUCCESS == osvr_ClientCheckDisplayStartup(m_display)) { + mDisplayConfigInitialized = true; + } + } + + // Typically once we get Display object, pose data is available after + // clientUpdate but sometimes it takes ~ 200 ms to get + // a succesfull connection, so we might have to run a few update cycles + } else { + if (OSVR_RETURN_SUCCESS == osvr_ClientCheckDisplayStartup(m_display)) { + mDisplayConfigInitialized = true; + } + } + } +} + +bool OSVRSession::InitState(mozilla::gfx::VRSystemState& aSystemState) { + VRDisplayState& state = aSystemState.displayState; + strncpy(state.displayName, "OSVR HMD", kVRDisplayNameMaxLen); + state.eightCC = GFX_VR_EIGHTCC('O', 'S', 'V', 'R', ' ', ' ', ' ', ' '); + state.isConnected = true; + state.isMounted = false; + state.capabilityFlags = (VRDisplayCapabilityFlags)( + (int)VRDisplayCapabilityFlags::Cap_None | + (int)VRDisplayCapabilityFlags::Cap_Orientation | + (int)VRDisplayCapabilityFlags::Cap_Position | + (int)VRDisplayCapabilityFlags::Cap_External | + (int)VRDisplayCapabilityFlags::Cap_Present | + (int)VRDisplayCapabilityFlags::Cap_ImmersiveVR); + state.blendMode = VRDisplayBlendMode::Opaque; + state.reportsDroppedFrames = false; + + // XXX OSVR display topology allows for more than one viewer + // will assume only one viewer for now (most likely stay that way) + + OSVR_EyeCount numEyes; + osvr_ClientGetNumEyesForViewer(m_display, 0, &numEyes); + + for (uint8_t eye = 0; eye < numEyes; eye++) { + double left, right, bottom, top; + // XXX for now there is only one surface per eye + osvr_ClientGetViewerEyeSurfaceProjectionClippingPlanes( + m_display, 0, eye, 0, &left, &right, &bottom, &top); + state.eyeFOV[eye] = SetFromTanRadians(-left, right, -bottom, top); + } + + // XXX Assuming there is only one display input for now + // however, it's possible to have more than one (dSight with 2 HDMI inputs) + OSVR_DisplayDimension width, height; + osvr_ClientGetDisplayDimensions(m_display, 0, &width, &height); + + for (uint8_t eye = 0; eye < numEyes; eye++) { + OSVR_ViewportDimension l, b, w, h; + osvr_ClientGetRelativeViewportForViewerEyeSurface(m_display, 0, eye, 0, &l, + &b, &w, &h); + state.eyeResolution.width = w; + state.eyeResolution.height = h; + OSVR_Pose3 eyePose; + // Viewer eye pose may not be immediately available, update client context + // until we get it + OSVR_ReturnCode ret = + osvr_ClientGetViewerEyePose(m_display, 0, eye, &eyePose); + while (ret != OSVR_RETURN_SUCCESS) { + osvr_ClientUpdate(m_ctx); + ret = osvr_ClientGetViewerEyePose(m_display, 0, eye, &eyePose); + } + state.eyeTranslation[eye].x = eyePose.translation.data[0]; + state.eyeTranslation[eye].y = eyePose.translation.data[1]; + state.eyeTranslation[eye].z = eyePose.translation.data[2]; + + Matrix4x4 pose; + pose.SetRotationFromQuaternion(gfx::Quaternion( + osvrQuatGetX(&eyePose.rotation), osvrQuatGetY(&eyePose.rotation), + osvrQuatGetZ(&eyePose.rotation), osvrQuatGetW(&eyePose.rotation))); + pose.PreTranslate(eyePose.translation.data[0], eyePose.translation.data[1], + eyePose.translation.data[2]); + pose.Invert(); + mHeadToEye[eye] = pose; + } + + // default to an identity quaternion + VRHMDSensorState& sensorState = aSystemState.sensorState; + sensorState.flags = (VRDisplayCapabilityFlags)( + (int)VRDisplayCapabilityFlags::Cap_Orientation | + (int)VRDisplayCapabilityFlags::Cap_Position); + sensorState.pose.orientation[3] = 1.0f; // Default to an identity quaternion + + return true; +} + +void OSVRSession::Shutdown() { + if (!mRuntimeLoaded) { + return; + } + mOSVRInitialized = false; + // client context may not have been initialized + if (m_ctx) { + osvr_ClientFreeDisplay(m_display); + } + // osvr checks that m_ctx or m_iface are not null + osvr_ClientFreeInterface(m_ctx, m_iface); + osvr_ClientShutdown(m_ctx); +} + +void OSVRSession::ProcessEvents(mozilla::gfx::VRSystemState& aSystemState) {} + +void OSVRSession::StartFrame(mozilla::gfx::VRSystemState& aSystemState) { + UpdateHeadsetPose(aSystemState); +} + +void OSVRSession::UpdateHeadsetPose(mozilla::gfx::VRSystemState& aState) { + // Update client context before anything + // this usually goes into app's mainloop + osvr_ClientUpdate(m_ctx); + + VRHMDSensorState result{}; + OSVR_TimeValue timestamp; + + OSVR_OrientationState orientation; + + OSVR_ReturnCode ret = + osvr_GetOrientationState(m_iface, ×tamp, &orientation); + + aState.sensorState.timestamp = timestamp.seconds; + + if (ret == OSVR_RETURN_SUCCESS) { + result.flags |= VRDisplayCapabilityFlags::Cap_Orientation; + result.pose.orientation[0] = orientation.data[1]; + result.pose.orientation[1] = orientation.data[2]; + result.pose.orientation[2] = orientation.data[3]; + result.pose.orientation[3] = orientation.data[0]; + } else { + // default to an identity quaternion + result.pose.orientation[3] = 1.0f; + } + + OSVR_PositionState position; + ret = osvr_GetPositionState(m_iface, ×tamp, &position); + if (ret == OSVR_RETURN_SUCCESS) { + result.flags |= VRDisplayCapabilityFlags::Cap_Position; + result.pose.position[0] = position.data[0]; + result.pose.position[1] = position.data[1]; + result.pose.position[2] = position.data[2]; + } + + result.CalcViewMatrices(mHeadToEye); +} + +bool OSVRSession::StartPresentation() { + return false; + // TODO Implement +} + +void OSVRSession::StopPresentation() { + // TODO Implement +} + +#if defined(XP_WIN) +bool OSVRSession::SubmitFrame( + const mozilla::gfx::VRLayer_Stereo_Immersive& aLayer, + ID3D11Texture2D* aTexture) { + return false; + // TODO Implement +} +#elif defined(XP_MACOSX) +bool OSVRSession::SubmitFrame( + const mozilla::gfx::VRLayer_Stereo_Immersive& aLayer, + const VRLayerTextureHandle& aTexture) { + return false; + // TODO Implement +} +#endif + +void OSVRSession::VibrateHaptic(uint32_t aControllerIdx, uint32_t aHapticIndex, + float aIntensity, float aDuration) {} + +void OSVRSession::StopVibrateHaptic(uint32_t aControllerIdx) {} + +void OSVRSession::StopAllHaptics() {} diff --git a/gfx/vr/service/OSVRSession.h b/gfx/vr/service/OSVRSession.h new file mode 100644 index 0000000000..d98553ff33 --- /dev/null +++ b/gfx/vr/service/OSVRSession.h @@ -0,0 +1,76 @@ +/* -*- 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/. */ + +#ifndef GFX_VR_SERVICE_OSVRSESSION_H +#define GFX_VR_SERVICE_OSVRSESSION_H + +#include "VRSession.h" + +#include "mozilla/gfx/2D.h" +#include "mozilla/TimeStamp.h" +#include "moz_external_vr.h" + +#include <osvr/ClientKit/ClientKitC.h> +#include <osvr/ClientKit/DisplayC.h> + +#if defined(XP_WIN) +# include <d3d11_1.h> +#endif + +namespace mozilla { +namespace gfx { + +class OSVRSession : public VRSession { + public: + OSVRSession(); + virtual ~OSVRSession(); + + bool Initialize(mozilla::gfx::VRSystemState& aSystemState, + bool aDetectRuntimesOnly) override; + void Shutdown() override; + void ProcessEvents(mozilla::gfx::VRSystemState& aSystemState) override; + void StartFrame(mozilla::gfx::VRSystemState& aSystemState) override; + bool StartPresentation() override; + void StopPresentation() override; + void VibrateHaptic(uint32_t aControllerIdx, uint32_t aHapticIndex, + float aIntensity, float aDuration) override; + void StopVibrateHaptic(uint32_t aControllerIdx) override; + void StopAllHaptics() override; + + protected: +#if defined(XP_WIN) + bool SubmitFrame(const mozilla::gfx::VRLayer_Stereo_Immersive& aLayer, + ID3D11Texture2D* aTexture) override; +#elif defined(XP_MACOSX) + bool SubmitFrame(const mozilla::gfx::VRLayer_Stereo_Immersive& aLayer, + const VRLayerTextureHandle& aTexture) override; +#endif + + private: + bool InitState(mozilla::gfx::VRSystemState& aSystemState); + void UpdateHeadsetPose(mozilla::gfx::VRSystemState& aState); + bool mRuntimeLoaded; + bool mOSVRInitialized; + bool mClientContextInitialized; + bool mDisplayConfigInitialized; + bool mInterfaceInitialized; + OSVR_ClientContext m_ctx; + OSVR_ClientInterface m_iface; + OSVR_DisplayConfig m_display; + gfx::Matrix4x4 mHeadToEye[2]; + + // check if all components are initialized + // and if not, it will try to initialize them + void CheckOSVRStatus(); + void InitializeClientContext(); + void InitializeDisplay(); + void InitializeInterface(); +}; + +} // namespace gfx +} // namespace mozilla + +#endif // GFX_VR_SERVICE_OSVRSESSION_H diff --git a/gfx/vr/service/OculusSession.cpp b/gfx/vr/service/OculusSession.cpp new file mode 100644 index 0000000000..741798e15d --- /dev/null +++ b/gfx/vr/service/OculusSession.cpp @@ -0,0 +1,1526 @@ +/* -*- 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/. */ + +#ifndef XP_WIN +# error "Oculus support only available for Windows" +#endif + +#include <math.h> +#include <d3d11.h> + +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/dom/GamepadEventTypes.h" +#include "mozilla/dom/GamepadBinding.h" +#include "mozilla/gfx/DeviceManagerDx.h" +#include "mozilla/gfx/Logging.h" +#include "mozilla/SharedLibrary.h" +#include "OculusSession.h" + +/** XXX The DX11 objects and quad blitting could be encapsulated + * into a separate object if either Oculus starts supporting + * non-Windows platforms or the blit is needed by other HMD\ + * drivers. + * Alternately, we could remove the extra blit for + * Oculus as well with some more refactoring. + */ + +// See CompositorD3D11Shaders.h +namespace mozilla { +namespace layers { +struct ShaderBytes { + const void* mData; + size_t mLength; +}; +extern ShaderBytes sRGBShader; +extern ShaderBytes sLayerQuadVS; +} // namespace layers +} // namespace mozilla + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::layers; + +namespace { + +static pfn_ovr_Initialize ovr_Initialize = nullptr; +static pfn_ovr_Shutdown ovr_Shutdown = nullptr; +static pfn_ovr_GetLastErrorInfo ovr_GetLastErrorInfo = nullptr; +static pfn_ovr_GetVersionString ovr_GetVersionString = nullptr; +static pfn_ovr_TraceMessage ovr_TraceMessage = nullptr; +static pfn_ovr_IdentifyClient ovr_IdentifyClient = nullptr; +static pfn_ovr_GetHmdDesc ovr_GetHmdDesc = nullptr; +static pfn_ovr_GetTrackerCount ovr_GetTrackerCount = nullptr; +static pfn_ovr_GetTrackerDesc ovr_GetTrackerDesc = nullptr; +static pfn_ovr_Create ovr_Create = nullptr; +static pfn_ovr_Destroy ovr_Destroy = nullptr; +static pfn_ovr_GetSessionStatus ovr_GetSessionStatus = nullptr; +static pfn_ovr_IsExtensionSupported ovr_IsExtensionSupported = nullptr; +static pfn_ovr_EnableExtension ovr_EnableExtension = nullptr; +static pfn_ovr_SetTrackingOriginType ovr_SetTrackingOriginType = nullptr; +static pfn_ovr_GetTrackingOriginType ovr_GetTrackingOriginType = nullptr; +static pfn_ovr_RecenterTrackingOrigin ovr_RecenterTrackingOrigin = nullptr; +static pfn_ovr_SpecifyTrackingOrigin ovr_SpecifyTrackingOrigin = nullptr; +static pfn_ovr_ClearShouldRecenterFlag ovr_ClearShouldRecenterFlag = nullptr; +static pfn_ovr_GetTrackingState ovr_GetTrackingState = nullptr; +static pfn_ovr_GetDevicePoses ovr_GetDevicePoses = nullptr; +static pfn_ovr_GetTrackerPose ovr_GetTrackerPose = nullptr; +static pfn_ovr_GetInputState ovr_GetInputState = nullptr; +static pfn_ovr_GetConnectedControllerTypes ovr_GetConnectedControllerTypes = + nullptr; +static pfn_ovr_GetTouchHapticsDesc ovr_GetTouchHapticsDesc = nullptr; +static pfn_ovr_SetControllerVibration ovr_SetControllerVibration = nullptr; +static pfn_ovr_SubmitControllerVibration ovr_SubmitControllerVibration = + nullptr; +static pfn_ovr_GetControllerVibrationState ovr_GetControllerVibrationState = + nullptr; +static pfn_ovr_TestBoundary ovr_TestBoundary = nullptr; +static pfn_ovr_TestBoundaryPoint ovr_TestBoundaryPoint = nullptr; +static pfn_ovr_SetBoundaryLookAndFeel ovr_SetBoundaryLookAndFeel = nullptr; +static pfn_ovr_ResetBoundaryLookAndFeel ovr_ResetBoundaryLookAndFeel = nullptr; +static pfn_ovr_GetBoundaryGeometry ovr_GetBoundaryGeometry = nullptr; +static pfn_ovr_GetBoundaryDimensions ovr_GetBoundaryDimensions = nullptr; +static pfn_ovr_GetBoundaryVisible ovr_GetBoundaryVisible = nullptr; +static pfn_ovr_RequestBoundaryVisible ovr_RequestBoundaryVisible = nullptr; +static pfn_ovr_GetTextureSwapChainLength ovr_GetTextureSwapChainLength = + nullptr; +static pfn_ovr_GetTextureSwapChainCurrentIndex + ovr_GetTextureSwapChainCurrentIndex = nullptr; +static pfn_ovr_GetTextureSwapChainDesc ovr_GetTextureSwapChainDesc = nullptr; +static pfn_ovr_CommitTextureSwapChain ovr_CommitTextureSwapChain = nullptr; +static pfn_ovr_DestroyTextureSwapChain ovr_DestroyTextureSwapChain = nullptr; +static pfn_ovr_DestroyMirrorTexture ovr_DestroyMirrorTexture = nullptr; +static pfn_ovr_GetFovTextureSize ovr_GetFovTextureSize = nullptr; +static pfn_ovr_GetRenderDesc2 ovr_GetRenderDesc2 = nullptr; +static pfn_ovr_WaitToBeginFrame ovr_WaitToBeginFrame = nullptr; +static pfn_ovr_BeginFrame ovr_BeginFrame = nullptr; +static pfn_ovr_EndFrame ovr_EndFrame = nullptr; +static pfn_ovr_SubmitFrame ovr_SubmitFrame = nullptr; +static pfn_ovr_GetPerfStats ovr_GetPerfStats = nullptr; +static pfn_ovr_ResetPerfStats ovr_ResetPerfStats = nullptr; +static pfn_ovr_GetPredictedDisplayTime ovr_GetPredictedDisplayTime = nullptr; +static pfn_ovr_GetTimeInSeconds ovr_GetTimeInSeconds = nullptr; +static pfn_ovr_GetBool ovr_GetBool = nullptr; +static pfn_ovr_SetBool ovr_SetBool = nullptr; +static pfn_ovr_GetInt ovr_GetInt = nullptr; +static pfn_ovr_SetInt ovr_SetInt = nullptr; +static pfn_ovr_GetFloat ovr_GetFloat = nullptr; +static pfn_ovr_SetFloat ovr_SetFloat = nullptr; +static pfn_ovr_GetFloatArray ovr_GetFloatArray = nullptr; +static pfn_ovr_SetFloatArray ovr_SetFloatArray = nullptr; +static pfn_ovr_GetString ovr_GetString = nullptr; +static pfn_ovr_SetString ovr_SetString = nullptr; +static pfn_ovr_GetExternalCameras ovr_GetExternalCameras = nullptr; +static pfn_ovr_SetExternalCameraProperties ovr_SetExternalCameraProperties = + nullptr; + +#ifdef XP_WIN +static pfn_ovr_CreateTextureSwapChainDX ovr_CreateTextureSwapChainDX = nullptr; +static pfn_ovr_GetTextureSwapChainBufferDX ovr_GetTextureSwapChainBufferDX = + nullptr; +static pfn_ovr_CreateMirrorTextureDX ovr_CreateMirrorTextureDX = nullptr; +static pfn_ovr_GetMirrorTextureBufferDX ovr_GetMirrorTextureBufferDX = nullptr; +#endif + +static pfn_ovr_CreateTextureSwapChainGL ovr_CreateTextureSwapChainGL = nullptr; +static pfn_ovr_GetTextureSwapChainBufferGL ovr_GetTextureSwapChainBufferGL = + nullptr; +static pfn_ovr_CreateMirrorTextureGL ovr_CreateMirrorTextureGL = nullptr; +static pfn_ovr_GetMirrorTextureBufferGL ovr_GetMirrorTextureBufferGL = nullptr; + +#ifdef HAVE_64BIT_BUILD +# define BUILD_BITS 64 +#else +# define BUILD_BITS 32 +#endif + +#define OVR_PRODUCT_VERSION 1 +#define OVR_MAJOR_VERSION 1 +#define OVR_MINOR_VERSION 19 + +static const uint32_t kNumOculusButtons = 7; +static const uint32_t kNumOculusHaptcs = 1; +static const uint32_t kNumOculusAxes = 4; +ovrControllerType OculusControllerTypes[2] = {ovrControllerType_LTouch, + ovrControllerType_RTouch}; +const char* OculusControllerNames[2] = {"Oculus Touch (Left)", + "Oculus Touch (Right)"}; +dom::GamepadHand OculusControllerHand[2] = {dom::GamepadHand::Left, + dom::GamepadHand::Right}; + +ovrButton OculusControllerButtons[2][kNumOculusButtons] = { + {(ovrButton)0, (ovrButton)0, (ovrButton)0, ovrButton_LThumb, ovrButton_X, + ovrButton_Y, (ovrButton)0}, + {(ovrButton)0, (ovrButton)0, (ovrButton)0, ovrButton_RThumb, ovrButton_A, + ovrButton_B, (ovrButton)0}, +}; + +ovrTouch OculusControllerTouches[2][kNumOculusButtons] = { + {ovrTouch_LIndexTrigger, (ovrTouch)0, (ovrTouch)0, ovrTouch_LThumb, + ovrTouch_X, ovrTouch_Y, ovrTouch_LThumbRest}, + {ovrTouch_RIndexTrigger, (ovrTouch)0, (ovrTouch)0, ovrTouch_RThumb, + ovrTouch_A, ovrTouch_B, ovrTouch_RThumbRest}, +}; + +void UpdateButton(const ovrInputState& aInputState, uint32_t aHandIdx, + uint32_t aButtonIdx, VRControllerState& aControllerState) { + if (aInputState.Buttons & OculusControllerButtons[aHandIdx][aButtonIdx]) { + aControllerState.buttonPressed |= ((uint64_t)1 << aButtonIdx); + aControllerState.triggerValue[aButtonIdx] = 1.0f; + } else { + aControllerState.triggerValue[aButtonIdx] = 0.0f; + } + if (aInputState.Touches & OculusControllerTouches[aHandIdx][aButtonIdx]) { + aControllerState.buttonTouched |= ((uint64_t)1 << aButtonIdx); + } +} + +VRFieldOfView FromFovPort(const ovrFovPort& aFOV) { + VRFieldOfView fovInfo; + fovInfo.leftDegrees = atan(aFOV.LeftTan) * 180.0 / M_PI; + fovInfo.rightDegrees = atan(aFOV.RightTan) * 180.0 / M_PI; + fovInfo.upDegrees = atan(aFOV.UpTan) * 180.0 / M_PI; + fovInfo.downDegrees = atan(aFOV.DownTan) * 180.0 / M_PI; + return fovInfo; +} + +} // anonymous namespace + +namespace mozilla { +namespace gfx { + +OculusSession::OculusSession() + : VRSession(), + mOvrLib(nullptr), + mSession(nullptr), + mInitFlags((ovrInitFlags)0), + mTextureSet(nullptr), + mQuadVS(nullptr), + mQuadPS(nullptr), + mLinearSamplerState(nullptr), + mVSConstantBuffer(nullptr), + mPSConstantBuffer(nullptr), + mVertexBuffer(nullptr), + mInputLayout(nullptr), + mRemainingVibrateTime{}, + mHapticPulseIntensity{}, + mIsPresenting(false) {} + +OculusSession::~OculusSession() { Shutdown(); } + +bool OculusSession::Initialize(mozilla::gfx::VRSystemState& aSystemState, + bool aDetectRuntimesOnly) { + if (StaticPrefs::dom_vr_puppet_enabled()) { + // Ensure that tests using the VR Puppet do not find real hardware + return false; + } + if (!StaticPrefs::dom_vr_enabled() || !StaticPrefs::dom_vr_oculus_enabled()) { + return false; + } + + if (aDetectRuntimesOnly) { + if (LoadOvrLib()) { + aSystemState.displayState.capabilityFlags |= + VRDisplayCapabilityFlags::Cap_ImmersiveVR; + } + return false; + } + + if (!CreateD3DObjects()) { + return false; + } + if (!CreateShaders()) { + return false; + } + // Ideally, we should move LoadOvrLib() up to the first line to avoid + // unnecessary D3D objects creation. But it will cause a WPT fail in Win 7 + // debug. + if (!LoadOvrLib()) { + return false; + } + // We start off with an invisible session, then re-initialize + // with visible session once WebVR content starts rendering. + if (!ChangeVisibility(false)) { + return false; + } + if (!InitState(aSystemState)) { + return false; + } + + mPresentationSize = IntSize(aSystemState.displayState.eyeResolution.width * 2, + aSystemState.displayState.eyeResolution.height); + return true; +} + +void OculusSession::UpdateVisibility() { + // Do not immediately re-initialize with an invisible session after + // the end of a VR presentation. Waiting for the configured duraction + // ensures that the user will not drop to Oculus Home during VR link + // traversal. + if (mIsPresenting) { + // We are currently rendering immersive content. + // Avoid interrupting the session + return; + } + if (mInitFlags & ovrInit_Invisible) { + // We are already invisible + return; + } + if (mLastPresentationEnd.IsNull()) { + // There has been no presentation yet + return; + } + + TimeDuration duration = TimeStamp::Now() - mLastPresentationEnd; + TimeDuration timeout = TimeDuration::FromMilliseconds( + StaticPrefs::dom_vr_oculus_present_timeout()); + if (timeout <= TimeDuration(0) || duration >= timeout) { + if (!ChangeVisibility(false)) { + gfxWarning() << "OculusSession::ChangeVisibility(false) failed"; + } + } +} + +void OculusSession::CoverTransitions() { + // While content is loading or during immersive-mode link + // traversal, we need to prevent the user from seeing the + // last rendered frame. + // We render black frames to cover up the transition. + MOZ_ASSERT(mSession); + if (mIsPresenting) { + // We are currently rendering immersive content. + // Avoid interrupting the session + return; + } + + if (mInitFlags & ovrInit_Invisible) { + // We are invisible, nothing to cover up + return; + } + + // Render a black frame + ovrLayerEyeFov layer; + memset(&layer, 0, sizeof(layer)); + layer.Header.Type = ovrLayerType_Disabled; + ovrLayerHeader* layers = &layer.Header; + ovr_SubmitFrame(mSession, 0, nullptr, &layers, 1); +} + +bool OculusSession::ChangeVisibility(bool bVisible) { + ovrInitFlags flags = + (ovrInitFlags)(ovrInit_RequestVersion | ovrInit_MixedRendering); + if (StaticPrefs::dom_vr_oculus_invisible_enabled() && !bVisible) { + flags = (ovrInitFlags)(flags | ovrInit_Invisible); + } + if (mInitFlags == flags) { + // The new state is the same, nothing to do + return true; + } + + // Tear everything down + StopRendering(); + StopSession(); + StopLib(); + + // Start it back up + if (!StartLib(flags)) { + return false; + } + if (!StartSession()) { + return false; + } + return true; +} + +void OculusSession::Shutdown() { + StopRendering(); + StopSession(); + StopLib(); + UnloadOvrLib(); + DestroyShaders(); +} + +void OculusSession::ProcessEvents(mozilla::gfx::VRSystemState& aSystemState) { + if (!mSession) { + return; + } + + ovrSessionStatus status; + if (OVR_SUCCESS(ovr_GetSessionStatus(mSession, &status))) { + aSystemState.displayState.isConnected = status.HmdPresent; + aSystemState.displayState.isMounted = status.HmdMounted; + mShouldQuit = status.ShouldQuit; + + } else { + aSystemState.displayState.isConnected = false; + aSystemState.displayState.isMounted = false; + } + UpdateHaptics(); + UpdateVisibility(); + CoverTransitions(); +} + +void OculusSession::StartFrame(mozilla::gfx::VRSystemState& aSystemState) { + UpdateHeadsetPose(aSystemState); + UpdateEyeParameters(aSystemState); + UpdateControllers(aSystemState); + UpdateTelemetry(aSystemState); + aSystemState.sensorState.inputFrameID++; +} + +bool OculusSession::StartPresentation() { + /** + * XXX - We should resolve fail the promise returned by + * VRDisplay.requestPresent() when the DX11 resources fail allocation + * in VRDisplayOculus::StartPresentation(). + * Bailing out here prevents the crash but content should be aware + * that frames are not being presented. + * See Bug 1299309. + **/ + if (!ChangeVisibility(true)) { + return false; + } + if (!StartRendering()) { + StopRendering(); + return false; + } + mIsPresenting = true; + return true; +} + +void OculusSession::StopPresentation() { + mLastPresentationEnd = TimeStamp::Now(); + mIsPresenting = false; +} + +bool OculusSession::SubmitFrame( + const mozilla::gfx::VRLayer_Stereo_Immersive& aLayer, + ID3D11Texture2D* aTexture) { + if (!IsPresentationReady()) { + return false; + } + + D3D11_TEXTURE2D_DESC textureDesc = {0}; + aTexture->GetDesc(&textureDesc); + + int currentRenderTarget = 0; + ovrResult orv = ovr_GetTextureSwapChainCurrentIndex(mSession, mTextureSet, + ¤tRenderTarget); + if (orv != ovrSuccess) { + NS_WARNING("ovr_GetTextureSwapChainCurrentIndex failed."); + return false; + } + + ID3D11RenderTargetView* view = mRTView[currentRenderTarget]; + + float clear[] = {0.0f, 0.0f, 0.0f, 1.0f}; + mContext->ClearRenderTargetView(view, clear); + mContext->OMSetRenderTargets(1, &view, nullptr); + + Matrix viewMatrix = Matrix::Translation(-1.0, 1.0); + viewMatrix.PreScale(2.0f / float(textureDesc.Width), + 2.0f / float(textureDesc.Height)); + viewMatrix.PreScale(1.0f, -1.0f); + Matrix4x4 projection = Matrix4x4::From2D(viewMatrix); + projection._33 = 0.0f; + + Matrix transform2d; + gfx::Matrix4x4 transform = gfx::Matrix4x4::From2D(transform2d); + + D3D11_VIEWPORT viewport; + viewport.MinDepth = 0.0f; + viewport.MaxDepth = 1.0f; + viewport.Width = textureDesc.Width; + viewport.Height = textureDesc.Height; + viewport.TopLeftX = 0; + viewport.TopLeftY = 0; + + D3D11_RECT scissor; + scissor.left = 0; + scissor.right = textureDesc.Width; + scissor.top = 0; + scissor.bottom = textureDesc.Height; + + memcpy(&mVSConstants.layerTransform, &transform._11, + sizeof(mVSConstants.layerTransform)); + memcpy(&mVSConstants.projection, &projection._11, + sizeof(mVSConstants.projection)); + mVSConstants.renderTargetOffset[0] = 0.0f; + mVSConstants.renderTargetOffset[1] = 0.0f; + mVSConstants.layerQuad = + Rect(0.0f, 0.0f, textureDesc.Width, textureDesc.Height); + mVSConstants.textureCoords = Rect(0.0f, 1.0f, 1.0f, -1.0f); + + mPSConstants.layerOpacity[0] = 1.0f; + + ID3D11Buffer* vbuffer = mVertexBuffer; + UINT vsize = sizeof(Vertex); + UINT voffset = 0; + mContext->IASetVertexBuffers(0, 1, &vbuffer, &vsize, &voffset); + mContext->IASetIndexBuffer(nullptr, DXGI_FORMAT_R16_UINT, 0); + mContext->IASetInputLayout(mInputLayout); + mContext->RSSetViewports(1, &viewport); + mContext->RSSetScissorRects(1, &scissor); + mContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP); + mContext->VSSetShader(mQuadVS, nullptr, 0); + mContext->PSSetShader(mQuadPS, nullptr, 0); + + RefPtr<ID3D11ShaderResourceView> srView; + HRESULT hr = mDevice->CreateShaderResourceView(aTexture, nullptr, + getter_AddRefs(srView)); + if (FAILED(hr)) { + gfxWarning() << "Could not create shader resource view for Oculus: " + << hexa(hr); + return false; + } + ID3D11ShaderResourceView* viewPtr = srView.get(); + mContext->PSSetShaderResources(0 /* 0 == TexSlot::RGB */, 1, &viewPtr); + // XXX Use Constant from TexSlot in CompositorD3D11.cpp? + + ID3D11SamplerState* sampler = mLinearSamplerState; + mContext->PSSetSamplers(0, 1, &sampler); + + if (!UpdateConstantBuffers()) { + NS_WARNING("Failed to update constant buffers for Oculus"); + return false; + } + + mContext->Draw(4, 0); + + orv = ovr_CommitTextureSwapChain(mSession, mTextureSet); + if (orv != ovrSuccess) { + NS_WARNING("ovr_CommitTextureSwapChain failed."); + return false; + } + + ovrLayerEyeFov layer; + memset(&layer, 0, sizeof(layer)); + layer.Header.Type = ovrLayerType_EyeFov; + layer.Header.Flags = 0; + layer.ColorTexture[0] = mTextureSet; + layer.ColorTexture[1] = nullptr; + layer.Fov[0] = mFOVPort[0]; + layer.Fov[1] = mFOVPort[1]; + layer.Viewport[0].Pos.x = textureDesc.Width * aLayer.leftEyeRect.x; + layer.Viewport[0].Pos.y = textureDesc.Height * aLayer.leftEyeRect.y; + layer.Viewport[0].Size.w = textureDesc.Width * aLayer.leftEyeRect.width; + layer.Viewport[0].Size.h = textureDesc.Height * aLayer.leftEyeRect.height; + layer.Viewport[1].Pos.x = textureDesc.Width * aLayer.rightEyeRect.x; + layer.Viewport[1].Pos.y = textureDesc.Height * aLayer.rightEyeRect.y; + layer.Viewport[1].Size.w = textureDesc.Width * aLayer.rightEyeRect.width; + layer.Viewport[1].Size.h = textureDesc.Height * aLayer.rightEyeRect.height; + + for (uint32_t i = 0; i < 2; ++i) { + layer.RenderPose[i].Orientation.x = mFrameStartPose[i].Orientation.x; + layer.RenderPose[i].Orientation.y = mFrameStartPose[i].Orientation.y; + layer.RenderPose[i].Orientation.z = mFrameStartPose[i].Orientation.z; + layer.RenderPose[i].Orientation.w = mFrameStartPose[i].Orientation.w; + layer.RenderPose[i].Position.x = mFrameStartPose[i].Position.x; + layer.RenderPose[i].Position.y = mFrameStartPose[i].Position.y; + layer.RenderPose[i].Position.z = mFrameStartPose[i].Position.z; + } + + ovrLayerHeader* layers = &layer.Header; + orv = ovr_SubmitFrame(mSession, 0, nullptr, &layers, 1); + // ovr_SubmitFrame will fail during the Oculus health and safety warning. + // and will start succeeding once the warning has been dismissed by the user. + + if (!OVR_UNQUALIFIED_SUCCESS(orv)) { + /** + * We wish to throttle the framerate for any case that the rendered + * result is not visible. In some cases, such as during the Oculus + * "health and safety warning", orv will be > 0 (OVR_SUCCESS but not + * OVR_UNQUALIFIED_SUCCESS) and ovr_SubmitFrame will not block. + * In this case, returning true would have resulted in an unthrottled + * render loop hiting excessive frame rates and consuming resources. + */ + return false; + } + + return true; +} + +bool OculusSession::LoadOvrLib() { + if (mOvrLib) { + // Already loaded, early exit + return true; + } +#if defined(_WIN32) + nsTArray<nsString> libSearchPaths; + nsString libName; + nsString searchPath; + + for (;;) { + UINT requiredLength = ::GetSystemDirectoryW( + char16ptr_t(searchPath.BeginWriting()), searchPath.Length()); + if (!requiredLength) { + break; + } + if (requiredLength < searchPath.Length()) { + searchPath.Truncate(requiredLength); + libSearchPaths.AppendElement(searchPath); + break; + } + searchPath.SetLength(requiredLength); + } + libName.AppendPrintf("LibOVRRT%d_%d.dll", BUILD_BITS, OVR_PRODUCT_VERSION); + + // search the path/module dir + libSearchPaths.InsertElementsAt(0, 1, u""_ns); + + // If the env var is present, we override libName + if (_wgetenv(L"OVR_LIB_PATH")) { + searchPath = _wgetenv(L"OVR_LIB_PATH"); + libSearchPaths.InsertElementsAt(0, 1, searchPath); + } + + if (_wgetenv(L"OVR_LIB_NAME")) { + libName = _wgetenv(L"OVR_LIB_NAME"); + } + + if (libName.IsEmpty()) { + return false; + } + + for (uint32_t i = 0; i < libSearchPaths.Length(); ++i) { + nsString& libPath = libSearchPaths[i]; + nsString fullName; + if (libPath.Length() == 0) { + fullName.Assign(libName); + } else { + fullName.Assign(libPath + u"\\"_ns + libName); + } + + mOvrLib = LoadLibraryWithFlags(fullName.get()); + if (mOvrLib) { + break; + } + } +#else +# error "Unsupported platform!" +#endif + + if (!mOvrLib) { + return false; + } + +#define REQUIRE_FUNCTION(_x) \ + do { \ + *(void**)&_x = (void*)PR_FindSymbol(mOvrLib, #_x); \ + if (!_x) { \ + printf_stderr(#_x " symbol missing\n"); \ + goto fail; \ + } \ + } while (0) + + REQUIRE_FUNCTION(ovr_Initialize); + REQUIRE_FUNCTION(ovr_Shutdown); + REQUIRE_FUNCTION(ovr_GetLastErrorInfo); + REQUIRE_FUNCTION(ovr_GetVersionString); + REQUIRE_FUNCTION(ovr_TraceMessage); + REQUIRE_FUNCTION(ovr_IdentifyClient); + REQUIRE_FUNCTION(ovr_GetHmdDesc); + REQUIRE_FUNCTION(ovr_GetTrackerCount); + REQUIRE_FUNCTION(ovr_GetTrackerDesc); + REQUIRE_FUNCTION(ovr_Create); + REQUIRE_FUNCTION(ovr_Destroy); + REQUIRE_FUNCTION(ovr_GetSessionStatus); + REQUIRE_FUNCTION(ovr_IsExtensionSupported); + REQUIRE_FUNCTION(ovr_EnableExtension); + REQUIRE_FUNCTION(ovr_SetTrackingOriginType); + REQUIRE_FUNCTION(ovr_GetTrackingOriginType); + REQUIRE_FUNCTION(ovr_RecenterTrackingOrigin); + REQUIRE_FUNCTION(ovr_SpecifyTrackingOrigin); + REQUIRE_FUNCTION(ovr_ClearShouldRecenterFlag); + REQUIRE_FUNCTION(ovr_GetTrackingState); + REQUIRE_FUNCTION(ovr_GetDevicePoses); + REQUIRE_FUNCTION(ovr_GetTrackerPose); + REQUIRE_FUNCTION(ovr_GetInputState); + REQUIRE_FUNCTION(ovr_GetConnectedControllerTypes); + REQUIRE_FUNCTION(ovr_GetTouchHapticsDesc); + REQUIRE_FUNCTION(ovr_SetControllerVibration); + REQUIRE_FUNCTION(ovr_SubmitControllerVibration); + REQUIRE_FUNCTION(ovr_GetControllerVibrationState); + REQUIRE_FUNCTION(ovr_TestBoundary); + REQUIRE_FUNCTION(ovr_TestBoundaryPoint); + REQUIRE_FUNCTION(ovr_SetBoundaryLookAndFeel); + REQUIRE_FUNCTION(ovr_ResetBoundaryLookAndFeel); + REQUIRE_FUNCTION(ovr_GetBoundaryGeometry); + REQUIRE_FUNCTION(ovr_GetBoundaryDimensions); + REQUIRE_FUNCTION(ovr_GetBoundaryVisible); + REQUIRE_FUNCTION(ovr_RequestBoundaryVisible); + REQUIRE_FUNCTION(ovr_GetTextureSwapChainLength); + REQUIRE_FUNCTION(ovr_GetTextureSwapChainCurrentIndex); + REQUIRE_FUNCTION(ovr_GetTextureSwapChainDesc); + REQUIRE_FUNCTION(ovr_CommitTextureSwapChain); + REQUIRE_FUNCTION(ovr_DestroyTextureSwapChain); + REQUIRE_FUNCTION(ovr_DestroyMirrorTexture); + REQUIRE_FUNCTION(ovr_GetFovTextureSize); + REQUIRE_FUNCTION(ovr_GetRenderDesc2); + REQUIRE_FUNCTION(ovr_WaitToBeginFrame); + REQUIRE_FUNCTION(ovr_BeginFrame); + REQUIRE_FUNCTION(ovr_EndFrame); + REQUIRE_FUNCTION(ovr_SubmitFrame); + REQUIRE_FUNCTION(ovr_GetPerfStats); + REQUIRE_FUNCTION(ovr_ResetPerfStats); + REQUIRE_FUNCTION(ovr_GetPredictedDisplayTime); + REQUIRE_FUNCTION(ovr_GetTimeInSeconds); + REQUIRE_FUNCTION(ovr_GetBool); + REQUIRE_FUNCTION(ovr_SetBool); + REQUIRE_FUNCTION(ovr_GetInt); + REQUIRE_FUNCTION(ovr_SetInt); + REQUIRE_FUNCTION(ovr_GetFloat); + REQUIRE_FUNCTION(ovr_SetFloat); + REQUIRE_FUNCTION(ovr_GetFloatArray); + REQUIRE_FUNCTION(ovr_SetFloatArray); + REQUIRE_FUNCTION(ovr_GetString); + REQUIRE_FUNCTION(ovr_SetString); + REQUIRE_FUNCTION(ovr_GetExternalCameras); + REQUIRE_FUNCTION(ovr_SetExternalCameraProperties); + +#ifdef XP_WIN + + REQUIRE_FUNCTION(ovr_CreateTextureSwapChainDX); + REQUIRE_FUNCTION(ovr_GetTextureSwapChainBufferDX); + REQUIRE_FUNCTION(ovr_CreateMirrorTextureDX); + REQUIRE_FUNCTION(ovr_GetMirrorTextureBufferDX); + +#endif + + REQUIRE_FUNCTION(ovr_CreateTextureSwapChainGL); + REQUIRE_FUNCTION(ovr_GetTextureSwapChainBufferGL); + REQUIRE_FUNCTION(ovr_CreateMirrorTextureGL); + REQUIRE_FUNCTION(ovr_GetMirrorTextureBufferGL); + +#undef REQUIRE_FUNCTION + + return true; + +fail: + ovr_Initialize = nullptr; + PR_UnloadLibrary(mOvrLib); + mOvrLib = nullptr; + return false; +} + +void OculusSession::UnloadOvrLib() { + if (mOvrLib) { + PR_UnloadLibrary(mOvrLib); + mOvrLib = nullptr; + } +} + +bool OculusSession::StartLib(ovrInitFlags aFlags) { + if (mInitFlags == 0) { + ovrInitParams params; + memset(¶ms, 0, sizeof(params)); + params.Flags = aFlags; + params.RequestedMinorVersion = OVR_MINOR_VERSION; + params.LogCallback = nullptr; + params.ConnectionTimeoutMS = 0; + + ovrResult orv = ovr_Initialize(¶ms); + + if (orv == ovrSuccess) { + mInitFlags = aFlags; + } else { + return false; + } + } + MOZ_ASSERT(mInitFlags == aFlags); + return true; +} + +void OculusSession::StopLib() { + if (mInitFlags) { + ovr_Shutdown(); + mInitFlags = (ovrInitFlags)0; + } +} + +bool OculusSession::StartSession() { + // ovr_Create can be slow when no HMD is present and we wish + // to keep the same oculus session when possible, so we detect + // presence of an HMD with ovr_GetHmdDesc before calling ovr_Create + ovrHmdDesc desc = ovr_GetHmdDesc(NULL); + if (desc.Type == ovrHmd_None) { + // No HMD connected, destroy any existing session + if (mSession) { + ovr_Destroy(mSession); + mSession = nullptr; + } + return false; + } + if (mSession != nullptr) { + // HMD Detected and we already have a session, let's keep using it. + return true; + } + + // HMD Detected and we don't have a session yet, + // try to create a new session + ovrSession session; + ovrGraphicsLuid luid; + ovrResult orv = ovr_Create(&session, &luid); + if (orv == ovrSuccess) { + orv = ovr_SetTrackingOriginType(session, ovrTrackingOrigin_FloorLevel); + if (orv != ovrSuccess) { + NS_WARNING("ovr_SetTrackingOriginType failed.\n"); + } + mSession = session; + return true; + } + + // Failed to create a session for the HMD + return false; +} + +void OculusSession::StopSession() { + if (mSession) { + ovr_Destroy(mSession); + mSession = nullptr; + } +} + +bool OculusSession::CreateD3DObjects() { + RefPtr<ID3D11Device> device = gfx::DeviceManagerDx::Get()->GetVRDevice(); + if (!device) { + return false; + } + if (!CreateD3DContext(device)) { + return false; + } + return true; +} + +bool OculusSession::CreateShaders() { + if (!mQuadVS) { + if (FAILED(mDevice->CreateVertexShader( + sLayerQuadVS.mData, sLayerQuadVS.mLength, nullptr, &mQuadVS))) { + NS_WARNING("Failed to create vertex shader for Oculus"); + return false; + } + } + + if (!mQuadPS) { + if (FAILED(mDevice->CreatePixelShader(sRGBShader.mData, sRGBShader.mLength, + nullptr, &mQuadPS))) { + NS_WARNING("Failed to create pixel shader for Oculus"); + return false; + } + } + + CD3D11_BUFFER_DESC cBufferDesc(sizeof(layers::VertexShaderConstants), + D3D11_BIND_CONSTANT_BUFFER, + D3D11_USAGE_DYNAMIC, D3D11_CPU_ACCESS_WRITE); + + if (!mVSConstantBuffer) { + if (FAILED(mDevice->CreateBuffer(&cBufferDesc, nullptr, + getter_AddRefs(mVSConstantBuffer)))) { + NS_WARNING("Failed to vertex shader constant buffer for Oculus"); + return false; + } + } + + if (!mPSConstantBuffer) { + cBufferDesc.ByteWidth = sizeof(layers::PixelShaderConstants); + if (FAILED(mDevice->CreateBuffer(&cBufferDesc, nullptr, + getter_AddRefs(mPSConstantBuffer)))) { + NS_WARNING("Failed to pixel shader constant buffer for Oculus"); + return false; + } + } + + if (!mLinearSamplerState) { + CD3D11_SAMPLER_DESC samplerDesc(D3D11_DEFAULT); + if (FAILED(mDevice->CreateSamplerState( + &samplerDesc, getter_AddRefs(mLinearSamplerState)))) { + NS_WARNING("Failed to create sampler state for Oculus"); + return false; + } + } + + if (!mInputLayout) { + D3D11_INPUT_ELEMENT_DESC layout[] = { + {"POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0, + D3D11_INPUT_PER_VERTEX_DATA, 0}, + }; + + if (FAILED(mDevice->CreateInputLayout( + layout, sizeof(layout) / sizeof(D3D11_INPUT_ELEMENT_DESC), + sLayerQuadVS.mData, sLayerQuadVS.mLength, + getter_AddRefs(mInputLayout)))) { + NS_WARNING("Failed to create input layout for Oculus"); + return false; + } + } + + if (!mVertexBuffer) { + Vertex vertices[] = { + {{0.0, 0.0}}, {{1.0, 0.0}}, {{0.0, 1.0}}, {{1.0, 1.0}}}; + CD3D11_BUFFER_DESC bufferDesc(sizeof(vertices), D3D11_BIND_VERTEX_BUFFER); + D3D11_SUBRESOURCE_DATA data; + data.pSysMem = (void*)vertices; + + if (FAILED(mDevice->CreateBuffer(&bufferDesc, &data, + getter_AddRefs(mVertexBuffer)))) { + NS_WARNING("Failed to create vertex buffer for Oculus"); + return false; + } + } + + memset(&mVSConstants, 0, sizeof(mVSConstants)); + memset(&mPSConstants, 0, sizeof(mPSConstants)); + return true; +} + +void OculusSession::DestroyShaders() {} + +bool OculusSession::UpdateConstantBuffers() { + HRESULT hr; + D3D11_MAPPED_SUBRESOURCE resource; + resource.pData = nullptr; + + hr = mContext->Map(mVSConstantBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, + &resource); + if (FAILED(hr) || !resource.pData) { + return false; + } + *(VertexShaderConstants*)resource.pData = mVSConstants; + mContext->Unmap(mVSConstantBuffer, 0); + resource.pData = nullptr; + + hr = mContext->Map(mPSConstantBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, + &resource); + if (FAILED(hr) || !resource.pData) { + return false; + } + *(PixelShaderConstants*)resource.pData = mPSConstants; + mContext->Unmap(mPSConstantBuffer, 0); + + ID3D11Buffer* buffer = mVSConstantBuffer; + mContext->VSSetConstantBuffers(0, 1, &buffer); + buffer = mPSConstantBuffer; + mContext->PSSetConstantBuffers(0, 1, &buffer); + return true; +} + +bool OculusSession::StartRendering() { + if (!mTextureSet) { + /** + * The presentation format is determined by content, which describes the + * left and right eye rectangles in the VRLayer. The default, if no + * coordinates are passed is to place the left and right eye textures + * side-by-side within the buffer. + * + * XXX - An optimization would be to dynamically resize this buffer + * to accomodate sites that are choosing to render in a lower + * resolution or are using space outside of the left and right + * eye textures for other purposes. (Bug 1291443) + */ + + ovrTextureSwapChainDesc desc; + memset(&desc, 0, sizeof(desc)); + desc.Type = ovrTexture_2D; + desc.ArraySize = 1; + desc.Format = OVR_FORMAT_B8G8R8A8_UNORM_SRGB; + desc.Width = mPresentationSize.width; + desc.Height = mPresentationSize.height; + desc.MipLevels = 1; + desc.SampleCount = 1; + desc.StaticImage = false; + desc.MiscFlags = ovrTextureMisc_DX_Typeless; + desc.BindFlags = ovrTextureBind_DX_RenderTarget; + + ovrResult orv = + ovr_CreateTextureSwapChainDX(mSession, mDevice, &desc, &mTextureSet); + if (orv != ovrSuccess) { + NS_WARNING("ovr_CreateTextureSwapChainDX failed"); + return false; + } + + int textureCount = 0; + orv = ovr_GetTextureSwapChainLength(mSession, mTextureSet, &textureCount); + if (orv != ovrSuccess) { + NS_WARNING("ovr_GetTextureSwapChainLength failed"); + return false; + } + mTexture.SetLength(textureCount); + mRTView.SetLength(textureCount); + mSRV.SetLength(textureCount); + for (int i = 0; i < textureCount; ++i) { + ID3D11Texture2D* texture = nullptr; + orv = ovr_GetTextureSwapChainBufferDX(mSession, mTextureSet, i, + IID_PPV_ARGS(&texture)); + if (orv != ovrSuccess) { + NS_WARNING("Failed to create Oculus texture swap chain."); + return false; + } + + RefPtr<ID3D11RenderTargetView> rtView; + CD3D11_RENDER_TARGET_VIEW_DESC rtvDesc(D3D11_RTV_DIMENSION_TEXTURE2D, + DXGI_FORMAT_B8G8R8A8_UNORM); + HRESULT hr = mDevice->CreateRenderTargetView(texture, &rtvDesc, + getter_AddRefs(rtView)); + if (FAILED(hr)) { + NS_WARNING( + "Failed to create RenderTargetView for Oculus texture swap chain."); + texture->Release(); + return false; + } + + RefPtr<ID3D11ShaderResourceView> srv; + CD3D11_SHADER_RESOURCE_VIEW_DESC srvDesc(D3D11_SRV_DIMENSION_TEXTURE2D, + DXGI_FORMAT_B8G8R8A8_UNORM); + hr = mDevice->CreateShaderResourceView(texture, &srvDesc, + getter_AddRefs(srv)); + if (FAILED(hr)) { + NS_WARNING( + "Failed to create ShaderResourceView for Oculus texture swap " + "chain."); + texture->Release(); + return false; + } + + mTexture[i] = texture; + mRTView[i] = rtView; + mSRV[i] = srv; + texture->Release(); + } + } + return true; +} + +bool OculusSession::IsPresentationReady() const { + return mTextureSet != nullptr; +} + +void OculusSession::StopRendering() { + mSRV.Clear(); + mRTView.Clear(); + mTexture.Clear(); + + if (mTextureSet && mSession) { + ovr_DestroyTextureSwapChain(mSession, mTextureSet); + } + mTextureSet = nullptr; + mIsPresenting = false; +} + +bool OculusSession::InitState(VRSystemState& aSystemState) { + VRDisplayState& state = aSystemState.displayState; + strncpy(state.displayName, "Oculus VR HMD", kVRDisplayNameMaxLen); + state.isConnected = true; + state.isMounted = false; + + ovrHmdDesc desc = ovr_GetHmdDesc(mSession); + + state.capabilityFlags = VRDisplayCapabilityFlags::Cap_None; + if (desc.AvailableTrackingCaps & ovrTrackingCap_Orientation) { + state.capabilityFlags |= VRDisplayCapabilityFlags::Cap_Orientation; + state.capabilityFlags |= VRDisplayCapabilityFlags::Cap_AngularAcceleration; + } + if (desc.AvailableTrackingCaps & ovrTrackingCap_Position) { + state.capabilityFlags |= VRDisplayCapabilityFlags::Cap_Position; + state.capabilityFlags |= VRDisplayCapabilityFlags::Cap_LinearAcceleration; + state.capabilityFlags |= VRDisplayCapabilityFlags::Cap_StageParameters; + } + state.capabilityFlags |= VRDisplayCapabilityFlags::Cap_External; + state.capabilityFlags |= VRDisplayCapabilityFlags::Cap_MountDetection; + state.capabilityFlags |= VRDisplayCapabilityFlags::Cap_Present; + state.capabilityFlags |= VRDisplayCapabilityFlags::Cap_ImmersiveVR; + state.blendMode = VRDisplayBlendMode::Opaque; + state.reportsDroppedFrames = true; + + mFOVPort[VRDisplayState::Eye_Left] = desc.DefaultEyeFov[ovrEye_Left]; + mFOVPort[VRDisplayState::Eye_Right] = desc.DefaultEyeFov[ovrEye_Right]; + + state.eyeFOV[VRDisplayState::Eye_Left] = + FromFovPort(mFOVPort[VRDisplayState::Eye_Left]); + state.eyeFOV[VRDisplayState::Eye_Right] = + FromFovPort(mFOVPort[VRDisplayState::Eye_Right]); + + float pixelsPerDisplayPixel = 1.0; + ovrSizei texSize[2]; + + // get eye texture sizes + for (uint32_t eye = 0; eye < VRDisplayState::NumEyes; eye++) { + texSize[eye] = ovr_GetFovTextureSize(mSession, (ovrEyeType)eye, + mFOVPort[eye], pixelsPerDisplayPixel); + } + + // take the max of both for eye resolution + state.eyeResolution.width = std::max(texSize[VRDisplayState::Eye_Left].w, + texSize[VRDisplayState::Eye_Right].w); + state.eyeResolution.height = std::max(texSize[VRDisplayState::Eye_Left].h, + texSize[VRDisplayState::Eye_Right].h); + state.nativeFramebufferScaleFactor = 1.0f; + + // default to an identity quaternion + aSystemState.sensorState.pose.orientation[3] = 1.0f; + + UpdateStageParameters(state); + UpdateEyeParameters(aSystemState); + + VRHMDSensorState& sensorState = aSystemState.sensorState; + sensorState.flags = (VRDisplayCapabilityFlags)( + (int)VRDisplayCapabilityFlags::Cap_Orientation | + (int)VRDisplayCapabilityFlags::Cap_Position); + sensorState.pose.orientation[3] = 1.0f; // Default to an identity quaternion + + return true; +} + +void OculusSession::UpdateStageParameters(VRDisplayState& aState) { + ovrVector3f playArea; + ovrResult res = + ovr_GetBoundaryDimensions(mSession, ovrBoundary_PlayArea, &playArea); + if (res == ovrSuccess) { + aState.stageSize.width = playArea.x; + aState.stageSize.height = playArea.z; + } else { + // If we fail, fall back to reasonable defaults. + // 1m x 1m space + aState.stageSize.width = 1.0f; + aState.stageSize.height = 1.0f; + } + + float eyeHeight = + ovr_GetFloat(mSession, OVR_KEY_EYE_HEIGHT, OVR_DEFAULT_EYE_HEIGHT); + + aState.sittingToStandingTransform[0] = 1.0f; + aState.sittingToStandingTransform[1] = 0.0f; + aState.sittingToStandingTransform[2] = 0.0f; + aState.sittingToStandingTransform[3] = 0.0f; + + aState.sittingToStandingTransform[4] = 0.0f; + aState.sittingToStandingTransform[5] = 1.0f; + aState.sittingToStandingTransform[6] = 0.0f; + aState.sittingToStandingTransform[7] = 0.0f; + + aState.sittingToStandingTransform[8] = 0.0f; + aState.sittingToStandingTransform[9] = 0.0f; + aState.sittingToStandingTransform[10] = 1.0f; + aState.sittingToStandingTransform[11] = 0.0f; + + aState.sittingToStandingTransform[12] = 0.0f; + aState.sittingToStandingTransform[13] = eyeHeight; + aState.sittingToStandingTransform[14] = 0.0f; + aState.sittingToStandingTransform[15] = 1.0f; +} + +void OculusSession::UpdateEyeParameters(VRSystemState& aState) { + if (!mSession) { + return; + } + // This must be called every frame in order to + // account for continuous adjustments to ipd. + gfx::Matrix4x4 headToEyeTransforms[2]; + for (uint32_t eye = 0; eye < VRDisplayState::NumEyes; eye++) { + // As of Oculus 1.17 SDK, we must use the ovr_GetRenderDesc2 function to + // return the updated version of ovrEyeRenderDesc. This is normally done by + // the Oculus static lib shim, but we need to do this explicitly as we are + // loading the Oculus runtime dll directly. + ovrEyeRenderDesc renderDesc = + ovr_GetRenderDesc2(mSession, (ovrEyeType)eye, mFOVPort[eye]); + aState.displayState.eyeTranslation[eye].x = + renderDesc.HmdToEyePose.Position.x; + aState.displayState.eyeTranslation[eye].y = + renderDesc.HmdToEyePose.Position.y; + aState.displayState.eyeTranslation[eye].z = + renderDesc.HmdToEyePose.Position.z; + + Matrix4x4 pose; + pose.SetRotationFromQuaternion( + gfx::Quaternion(-renderDesc.HmdToEyePose.Orientation.x, + -renderDesc.HmdToEyePose.Orientation.y, + -renderDesc.HmdToEyePose.Orientation.z, + renderDesc.HmdToEyePose.Orientation.w)); + pose.PreTranslate(renderDesc.HmdToEyePose.Position.x, + renderDesc.HmdToEyePose.Position.y, + renderDesc.HmdToEyePose.Position.z); + pose.Invert(); + headToEyeTransforms[eye] = pose; + } + aState.sensorState.CalcViewMatrices(headToEyeTransforms); + + Matrix4x4 matView[2]; + memcpy(matView[0].components, aState.sensorState.leftViewMatrix, + sizeof(float) * 16); + memcpy(matView[1].components, aState.sensorState.rightViewMatrix, + sizeof(float) * 16); + + for (uint32_t eye = 0; eye < VRDisplayState::NumEyes; eye++) { + Point3D eyeTranslation; + Quaternion eyeRotation; + Point3D eyeScale; + if (!matView[eye].Decompose(eyeTranslation, eyeRotation, eyeScale)) { + NS_WARNING("Failed to decompose eye pose matrix for Oculus"); + } + + eyeRotation.Invert(); + mFrameStartPose[eye].Orientation.x = eyeRotation.x; + mFrameStartPose[eye].Orientation.y = eyeRotation.y; + mFrameStartPose[eye].Orientation.z = eyeRotation.z; + mFrameStartPose[eye].Orientation.w = eyeRotation.w; + mFrameStartPose[eye].Position.x = eyeTranslation.x; + mFrameStartPose[eye].Position.y = eyeTranslation.y; + mFrameStartPose[eye].Position.z = eyeTranslation.z; + } +} + +void OculusSession::UpdateHeadsetPose(VRSystemState& aState) { + if (!mSession) { + return; + } + double predictedFrameTime = 0.0f; + if (StaticPrefs::dom_vr_poseprediction_enabled()) { + // XXX We might need to call ovr_GetPredictedDisplayTime even if we don't + // use the result. If we don't call it, the Oculus driver will spew out many + // warnings... + predictedFrameTime = ovr_GetPredictedDisplayTime(mSession, 0); + } + ovrTrackingState trackingState = + ovr_GetTrackingState(mSession, predictedFrameTime, true); + ovrPoseStatef& pose(trackingState.HeadPose); + + aState.sensorState.timestamp = pose.TimeInSeconds; + + if (trackingState.StatusFlags & ovrStatus_OrientationTracked) { + aState.sensorState.flags |= VRDisplayCapabilityFlags::Cap_Orientation; + + aState.sensorState.pose.orientation[0] = pose.ThePose.Orientation.x; + aState.sensorState.pose.orientation[1] = pose.ThePose.Orientation.y; + aState.sensorState.pose.orientation[2] = pose.ThePose.Orientation.z; + aState.sensorState.pose.orientation[3] = pose.ThePose.Orientation.w; + + aState.sensorState.pose.angularVelocity[0] = pose.AngularVelocity.x; + aState.sensorState.pose.angularVelocity[1] = pose.AngularVelocity.y; + aState.sensorState.pose.angularVelocity[2] = pose.AngularVelocity.z; + + aState.sensorState.flags |= + VRDisplayCapabilityFlags::Cap_AngularAcceleration; + + aState.sensorState.pose.angularAcceleration[0] = pose.AngularAcceleration.x; + aState.sensorState.pose.angularAcceleration[1] = pose.AngularAcceleration.y; + aState.sensorState.pose.angularAcceleration[2] = pose.AngularAcceleration.z; + } else { + // default to an identity quaternion + aState.sensorState.pose.orientation[3] = 1.0f; + } + + if (trackingState.StatusFlags & ovrStatus_PositionTracked) { + float eyeHeight = + ovr_GetFloat(mSession, OVR_KEY_EYE_HEIGHT, OVR_DEFAULT_EYE_HEIGHT); + aState.sensorState.flags |= VRDisplayCapabilityFlags::Cap_Position; + + aState.sensorState.pose.position[0] = pose.ThePose.Position.x; + aState.sensorState.pose.position[1] = pose.ThePose.Position.y - eyeHeight; + aState.sensorState.pose.position[2] = pose.ThePose.Position.z; + + aState.sensorState.pose.linearVelocity[0] = pose.LinearVelocity.x; + aState.sensorState.pose.linearVelocity[1] = pose.LinearVelocity.y; + aState.sensorState.pose.linearVelocity[2] = pose.LinearVelocity.z; + + aState.sensorState.flags |= + VRDisplayCapabilityFlags::Cap_LinearAcceleration; + + aState.sensorState.pose.linearAcceleration[0] = pose.LinearAcceleration.x; + aState.sensorState.pose.linearAcceleration[1] = pose.LinearAcceleration.y; + aState.sensorState.pose.linearAcceleration[2] = pose.LinearAcceleration.z; + } + aState.sensorState.flags |= VRDisplayCapabilityFlags::Cap_External; + aState.sensorState.flags |= VRDisplayCapabilityFlags::Cap_MountDetection; + aState.sensorState.flags |= VRDisplayCapabilityFlags::Cap_Present; +} + +void OculusSession::UpdateControllers(VRSystemState& aState) { + if (!mSession) { + return; + } + + ovrInputState inputState; + bool hasInputState = ovr_GetInputState(mSession, ovrControllerType_Touch, + &inputState) == ovrSuccess; + + if (!hasInputState) { + return; + } + + EnumerateControllers(aState, inputState); + UpdateControllerInputs(aState, inputState); + UpdateControllerPose(aState, inputState); +} + +void OculusSession::UpdateControllerPose(VRSystemState& aState, + const ovrInputState& aInputState) { + ovrTrackingState trackingState = ovr_GetTrackingState(mSession, 0.0, false); + for (uint32_t handIdx = 0; handIdx < 2; handIdx++) { + // Left Touch Controller will always be at index 0 and + // and Right Touch Controller will always be at index 1 + VRControllerState& controllerState = aState.controllerState[handIdx]; + if (aInputState.ControllerType & OculusControllerTypes[handIdx]) { + ovrPoseStatef& pose = trackingState.HandPoses[handIdx]; + bool bNewController = !(controllerState.flags & + dom::GamepadCapabilityFlags::Cap_Orientation); + if (bNewController) { + controllerState.flags |= dom::GamepadCapabilityFlags::Cap_Orientation; + controllerState.flags |= dom::GamepadCapabilityFlags::Cap_Position; + controllerState.flags |= + dom::GamepadCapabilityFlags::Cap_AngularAcceleration; + controllerState.flags |= + dom::GamepadCapabilityFlags::Cap_LinearAcceleration; + controllerState.flags |= + dom::GamepadCapabilityFlags::Cap_GripSpacePosition; + } + + if (bNewController || trackingState.HandStatusFlags[handIdx] & + ovrStatus_OrientationTracked) { + controllerState.pose.orientation[0] = pose.ThePose.Orientation.x; + controllerState.pose.orientation[1] = pose.ThePose.Orientation.y; + controllerState.pose.orientation[2] = pose.ThePose.Orientation.z; + controllerState.pose.orientation[3] = pose.ThePose.Orientation.w; + controllerState.pose.angularVelocity[0] = pose.AngularVelocity.x; + controllerState.pose.angularVelocity[1] = pose.AngularVelocity.y; + controllerState.pose.angularVelocity[2] = pose.AngularVelocity.z; + controllerState.pose.angularAcceleration[0] = + pose.AngularAcceleration.x; + controllerState.pose.angularAcceleration[1] = + pose.AngularAcceleration.y; + controllerState.pose.angularAcceleration[2] = + pose.AngularAcceleration.z; + controllerState.isOrientationValid = true; + } else { + controllerState.isOrientationValid = false; + } + if (bNewController || + trackingState.HandStatusFlags[handIdx] & ovrStatus_PositionTracked) { + controllerState.pose.position[0] = pose.ThePose.Position.x; + controllerState.pose.position[1] = pose.ThePose.Position.y; + controllerState.pose.position[2] = pose.ThePose.Position.z; + controllerState.pose.linearVelocity[0] = pose.LinearVelocity.x; + controllerState.pose.linearVelocity[1] = pose.LinearVelocity.y; + controllerState.pose.linearVelocity[2] = pose.LinearVelocity.z; + controllerState.pose.linearAcceleration[0] = pose.LinearAcceleration.x; + controllerState.pose.linearAcceleration[1] = pose.LinearAcceleration.y; + controllerState.pose.linearAcceleration[2] = pose.LinearAcceleration.z; + + float eyeHeight = + ovr_GetFloat(mSession, OVR_KEY_EYE_HEIGHT, OVR_DEFAULT_EYE_HEIGHT); + controllerState.pose.position[1] -= eyeHeight; + controllerState.isPositionValid = true; + } else { + controllerState.isPositionValid = false; + } + controllerState.targetRayPose = controllerState.pose; + } + } +} + +void OculusSession::EnumerateControllers(VRSystemState& aState, + const ovrInputState& aInputState) { + for (uint32_t handIdx = 0; handIdx < 2; handIdx++) { + // Left Touch Controller will always be at index 0 and + // and Right Touch Controller will always be at index 1 + VRControllerState& controllerState = aState.controllerState[handIdx]; + if (aInputState.ControllerType & OculusControllerTypes[handIdx]) { + bool bNewController = false; + // Touch Controller detected + if (controllerState.controllerName[0] == '\0') { + // Controller has been just enumerated + strncpy(controllerState.controllerName, OculusControllerNames[handIdx], + kVRControllerNameMaxLen); + controllerState.hand = OculusControllerHand[handIdx]; + controllerState.targetRayMode = gfx::TargetRayMode::TrackedPointer; + controllerState.numButtons = kNumOculusButtons; + controllerState.numAxes = kNumOculusAxes; + controllerState.numHaptics = kNumOculusHaptcs; + controllerState.type = VRControllerType::OculusTouch; + bNewController = true; + } + } else { + // Touch Controller not detected + if (controllerState.controllerName[0] != '\0') { + // Clear any newly disconnected ontrollers + memset(&controllerState, 0, sizeof(VRControllerState)); + } + } + } +} + +void OculusSession::UpdateControllerInputs(VRSystemState& aState, + const ovrInputState& aInputState) { + const float triggerThreshold = + StaticPrefs::dom_vr_controller_trigger_threshold(); + + for (uint32_t handIdx = 0; handIdx < 2; handIdx++) { + // Left Touch Controller will always be at index 0 and + // and Right Touch Controller will always be at index 1 + VRControllerState& controllerState = aState.controllerState[handIdx]; + if (aInputState.ControllerType & OculusControllerTypes[handIdx]) { + // Update Button States + controllerState.buttonPressed = 0; + controllerState.buttonTouched = 0; + uint32_t buttonIdx = 0; + + // Button 0: Trigger + VRSession::UpdateTrigger(controllerState, buttonIdx, + aInputState.IndexTrigger[handIdx], + triggerThreshold); + ++buttonIdx; + // Button 1: Grip + VRSession::UpdateTrigger(controllerState, buttonIdx, + aInputState.HandTrigger[handIdx], + triggerThreshold); + ++buttonIdx; + // Button 2: a placeholder button for trackpad. + UpdateButton(aInputState, handIdx, buttonIdx, controllerState); + ++buttonIdx; + // Button 3: Thumbstick + UpdateButton(aInputState, handIdx, buttonIdx, controllerState); + ++buttonIdx; + // Button 4: A + UpdateButton(aInputState, handIdx, buttonIdx, controllerState); + ++buttonIdx; + // Button 5: B + UpdateButton(aInputState, handIdx, buttonIdx, controllerState); + ++buttonIdx; + // Button 6: ThumbRest + UpdateButton(aInputState, handIdx, buttonIdx, controllerState); + ++buttonIdx; + + MOZ_ASSERT(buttonIdx == kNumOculusButtons); + + // Update Thumbstick axis + uint32_t axisIdx = 0; + // Axis 0, 1: placeholder axes for trackpad. + axisIdx += 2; + + // Axis 2, 3: placeholder axes for thumbstick. + float axisValue = aInputState.Thumbstick[handIdx].x; + if (abs(axisValue) < 0.0000009f) { + axisValue = 0.0f; // Clear noise signal + } + controllerState.axisValue[axisIdx] = axisValue; + axisIdx++; + + // Note that y axis is intentionally inverted! + axisValue = -aInputState.Thumbstick[handIdx].y; + if (abs(axisValue) < 0.0000009f) { + axisValue = 0.0f; // Clear noise signal + } + controllerState.axisValue[axisIdx] = axisValue; + axisIdx++; + + MOZ_ASSERT(axisIdx == kNumOculusAxes); + } + SetControllerSelectionAndSqueezeFrameId( + controllerState, aState.displayState.lastSubmittedFrameId); + } +} + +void OculusSession::UpdateTelemetry(VRSystemState& aSystemState) { + if (!mSession) { + return; + } + ovrPerfStats perfStats; + if (ovr_GetPerfStats(mSession, &perfStats) == ovrSuccess) { + if (perfStats.FrameStatsCount) { + aSystemState.displayState.droppedFrameCount = + perfStats.FrameStats[0].AppDroppedFrameCount; + } + } +} + +void OculusSession::VibrateHaptic(uint32_t aControllerIdx, + uint32_t aHapticIndex, float aIntensity, + float aDuration) { + if (!mSession) { + return; + } + + if (aDuration <= 0.0f) { + StopVibrateHaptic(aControllerIdx); + return; + } + + // Vibration amplitude in the [0.0, 1.0] range + MOZ_ASSERT(aControllerIdx >= 0 && aControllerIdx <= 1); + mHapticPulseIntensity[aControllerIdx] = aIntensity > 1.0 ? 1.0 : aIntensity; + mRemainingVibrateTime[aControllerIdx] = aDuration; + ovrControllerType hand = OculusControllerTypes[aControllerIdx]; + + // The gamepad extensions API does not yet have independent control + // of frequency and amplitude. We are always sending 0.0f (160hz) + // to the frequency argument. + ovrResult result = ovr_SetControllerVibration( + mSession, hand, 0.0f, mHapticPulseIntensity[aControllerIdx]); + if (result != ovrSuccess) { + // This may happen if called when not presenting. + gfxWarning() << "ovr_SetControllerVibration failed."; + } +} + +void OculusSession::StopVibrateHaptic(uint32_t aControllerIdx) { + if (!mSession) { + return; + } + MOZ_ASSERT(aControllerIdx >= 0 && aControllerIdx <= 1); + ovrControllerType hand = OculusControllerTypes[aControllerIdx]; + mRemainingVibrateTime[aControllerIdx] = 0.0f; + mHapticPulseIntensity[aControllerIdx] = 0.0f; + + ovrResult result = ovr_SetControllerVibration(mSession, hand, 0.0f, 0.0f); + if (result != ovrSuccess) { + // This may happen if called when not presenting. + gfxWarning() << "ovr_SetControllerVibration failed."; + } +} + +void OculusSession::StopAllHaptics() { + // Left Oculus Touch + StopVibrateHaptic(0); + // Right Oculus Touch + StopVibrateHaptic(1); +} + +void OculusSession::UpdateHaptics() { + if (!mSession) { + return; + } + // The Oculus API and hardware takes at least 33ms to respond + // to haptic state changes, so it is not beneficial to create + // a dedicated haptic feedback thread and update multiple + // times per frame. + // If we wish to support more accurate effects with sub-frame timing, + // we should use the buffered haptic feedback API's. + + TimeStamp now = TimeStamp::Now(); + if (mLastHapticUpdate.IsNull()) { + mLastHapticUpdate = now; + return; + } + float deltaTime = (float)(now - mLastHapticUpdate).ToSeconds(); + mLastHapticUpdate = now; + for (int i = 0; i < 2; i++) { + if (mRemainingVibrateTime[i] <= 0.0f) { + continue; + } + mRemainingVibrateTime[i] -= deltaTime; + ovrControllerType hand = OculusControllerTypes[i]; + if (mRemainingVibrateTime[i] > 0.0f) { + ovrResult result = ovr_SetControllerVibration(mSession, hand, 0.0f, + mHapticPulseIntensity[i]); + if (result != ovrSuccess) { + // This may happen if called when not presenting. + gfxWarning() << "ovr_SetControllerVibration failed."; + } + } else { + StopVibrateHaptic(i); + } + } +} + +} // namespace gfx +} // namespace mozilla diff --git a/gfx/vr/service/OculusSession.h b/gfx/vr/service/OculusSession.h new file mode 100644 index 0000000000..e1feeb2052 --- /dev/null +++ b/gfx/vr/service/OculusSession.h @@ -0,0 +1,114 @@ +/* -*- 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/. */ + +#ifndef GFX_VR_SERVICE_OCULUSSESSION_H +#define GFX_VR_SERVICE_OCULUSSESSION_H + +#include "VRSession.h" + +#include "mozilla/gfx/2D.h" +#include "moz_external_vr.h" +#include "nsTArray.h" +#include "oculus/ovr_capi_dynamic.h" +#include "prlink.h" +#include "ShaderDefinitionsD3D11.h" // for VertexShaderConstants and PixelShaderConstants + +struct ID3D11Device; + +namespace mozilla { +namespace layers { +struct VertexShaderConstants; +struct PixelShaderConstants; +} // namespace layers +namespace gfx { + +class OculusSession : public VRSession { + public: + OculusSession(); + virtual ~OculusSession(); + + bool Initialize(mozilla::gfx::VRSystemState& aSystemState, + bool aDetectRuntimesOnly) override; + void Shutdown() override; + void ProcessEvents(mozilla::gfx::VRSystemState& aSystemState) override; + void StartFrame(mozilla::gfx::VRSystemState& aSystemState) override; + bool StartPresentation() override; + void StopPresentation() override; + bool SubmitFrame(const mozilla::gfx::VRLayer_Stereo_Immersive& aLayer, + ID3D11Texture2D* aTexture) override; + void VibrateHaptic(uint32_t aControllerIdx, uint32_t aHapticIndex, + float aIntensity, float aDuration) override; + void StopVibrateHaptic(uint32_t aControllerIdx) override; + void StopAllHaptics() override; + + private: + bool LoadOvrLib(); + void UnloadOvrLib(); + bool StartLib(ovrInitFlags aFlags); + void StopLib(); + bool StartSession(); + void StopSession(); + bool StartRendering(); + void StopRendering(); + bool CreateD3DObjects(); + bool CreateShaders(); + void DestroyShaders(); + void CoverTransitions(); + void UpdateVisibility(); + bool ChangeVisibility(bool bVisible); + bool InitState(mozilla::gfx::VRSystemState& aSystemState); + void UpdateStageParameters(mozilla::gfx::VRDisplayState& aState); + void UpdateEyeParameters(mozilla::gfx::VRSystemState& aState); + void UpdateHeadsetPose(mozilla::gfx::VRSystemState& aState); + void UpdateControllers(VRSystemState& aState); + void UpdateControllerInputs(VRSystemState& aState, + const ovrInputState& aInputState); + void UpdateHaptics(); + void EnumerateControllers(VRSystemState& aState, + const ovrInputState& aInputState); + void UpdateControllerPose(VRSystemState& aState, + const ovrInputState& aInputState); + void UpdateTelemetry(VRSystemState& aSystemState); + bool IsPresentationReady() const; + bool UpdateConstantBuffers(); + + PRLibrary* mOvrLib; + ovrSession mSession; + ovrInitFlags mInitFlags; + ovrTextureSwapChain mTextureSet; + nsTArray<RefPtr<ID3D11RenderTargetView>> mRTView; + nsTArray<RefPtr<ID3D11Texture2D>> mTexture; + nsTArray<RefPtr<ID3D11ShaderResourceView>> mSRV; + + ID3D11VertexShader* mQuadVS; + ID3D11PixelShader* mQuadPS; + RefPtr<ID3D11SamplerState> mLinearSamplerState; + layers::VertexShaderConstants mVSConstants; + layers::PixelShaderConstants mPSConstants; + RefPtr<ID3D11Buffer> mVSConstantBuffer; + RefPtr<ID3D11Buffer> mPSConstantBuffer; + RefPtr<ID3D11Buffer> mVertexBuffer; + RefPtr<ID3D11InputLayout> mInputLayout; + + IntSize mPresentationSize; + ovrFovPort mFOVPort[2]; + + // Most recent HMD eye poses, from start of frame + ovrPosef mFrameStartPose[2]; + + float mRemainingVibrateTime[2]; + float mHapticPulseIntensity[2]; + TimeStamp mLastHapticUpdate; + + // The timestamp of the last ending presentation + TimeStamp mLastPresentationEnd; + bool mIsPresenting; +}; + +} // namespace gfx +} // namespace mozilla + +#endif // GFX_VR_SERVICE_OCULUSSESSION_H diff --git a/gfx/vr/service/OpenVRControllerMapper.cpp b/gfx/vr/service/OpenVRControllerMapper.cpp new file mode 100644 index 0000000000..7cb4cfb563 --- /dev/null +++ b/gfx/vr/service/OpenVRControllerMapper.cpp @@ -0,0 +1,89 @@ +/* -*- 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 "OpenVRControllerMapper.h" +#include "mozilla/StaticPrefs_dom.h" + +#include "VRSession.h" + +namespace mozilla::gfx { + +OpenVRControllerMapper::OpenVRControllerMapper() + : mNumButtons(0), mNumAxes(0) {} + +void OpenVRControllerMapper::GetButtonValueFromAction( + VRControllerState& aControllerState, const ControllerAction& aPressAction, + const ControllerAction& aTouchAction) { + vr::InputDigitalActionData_t actionData = {}; + bool bPressed = false; + bool bTouched = false; + uint64_t mask = 0; + + if (aPressAction.handle && + vr::VRInput()->GetDigitalActionData( + aPressAction.handle, &actionData, sizeof(actionData), + vr::k_ulInvalidInputValueHandle) == vr::VRInputError_None && + actionData.bActive) { + bPressed = actionData.bState; + mask = (1ULL << mNumButtons); + aControllerState.triggerValue[mNumButtons] = bPressed ? 1.0 : 0.0f; + if (bPressed) { + aControllerState.buttonPressed |= mask; + } else { + aControllerState.buttonPressed &= ~mask; + } + if (aTouchAction.handle && + vr::VRInput()->GetDigitalActionData( + aTouchAction.handle, &actionData, sizeof(actionData), + vr::k_ulInvalidInputValueHandle) == vr::VRInputError_None) { + bTouched = actionData.bActive && actionData.bState; + mask = (1ULL << mNumButtons); + if (bTouched) { + aControllerState.buttonTouched |= mask; + } else { + aControllerState.buttonTouched &= ~mask; + } + } + ++mNumButtons; + } +} + +void OpenVRControllerMapper::GetTriggerValueFromAction( + VRControllerState& aControllerState, const ControllerAction& aAction) { + vr::InputAnalogActionData_t analogData = {}; + const float triggerThreshold = + StaticPrefs::dom_vr_controller_trigger_threshold(); + + if (aAction.handle && + vr::VRInput()->GetAnalogActionData( + aAction.handle, &analogData, sizeof(analogData), + vr::k_ulInvalidInputValueHandle) == vr::VRInputError_None && + analogData.bActive) { + VRSession::UpdateTrigger(aControllerState, mNumButtons, analogData.x, + triggerThreshold); + ++mNumButtons; + } +} + +void OpenVRControllerMapper::GetAxisValueFromAction( + VRControllerState& aControllerState, const ControllerAction& aAction, + bool aInvertAxis) { + vr::InputAnalogActionData_t analogData = {}; + const float yAxisInvert = (aInvertAxis) ? -1.0f : 1.0f; + + if (aAction.handle && + vr::VRInput()->GetAnalogActionData( + aAction.handle, &analogData, sizeof(analogData), + vr::k_ulInvalidInputValueHandle) == vr::VRInputError_None && + analogData.bActive) { + aControllerState.axisValue[mNumAxes] = analogData.x; + ++mNumAxes; + aControllerState.axisValue[mNumAxes] = analogData.y * yAxisInvert; + ++mNumAxes; + } +} + +} // namespace mozilla::gfx diff --git a/gfx/vr/service/OpenVRControllerMapper.h b/gfx/vr/service/OpenVRControllerMapper.h new file mode 100644 index 0000000000..0fe96c8bbe --- /dev/null +++ b/gfx/vr/service/OpenVRControllerMapper.h @@ -0,0 +1,96 @@ +/* -*- 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/. */ + +#ifndef GFX_VR_SERVICE_OPENVRCONTROLLERMAPPER_H +#define GFX_VR_SERVICE_OPENVRCONTROLLERMAPPER_H + +#include "openvr.h" +#include "nsString.h" + +#include "moz_external_vr.h" + +namespace mozilla { +namespace gfx { + +struct ControllerAction { + nsCString name; + nsCString type; + vr::VRActionHandle_t handle = vr::k_ulInvalidActionHandle; + + ControllerAction() = default; + + ControllerAction(const char* aName, const char* aType) + : name(aName), type(aType) {} +}; + +struct ControllerInfo { + vr::VRInputValueHandle_t mSource = vr::k_ulInvalidInputValueHandle; + + ControllerAction mActionPose; + ControllerAction mActionHaptic; + + ControllerAction mActionTrackpad_Analog; + ControllerAction mActionTrackpad_Pressed; + ControllerAction mActionTrackpad_Touched; + + ControllerAction mActionTrigger_Value; + + ControllerAction mActionGrip_Pressed; + ControllerAction mActionGrip_Touched; + ControllerAction mActionMenu_Pressed; + ControllerAction mActionMenu_Touched; + // It seems like there's no way to get response from a sys. btn. + ControllerAction mActionSystem_Pressed; + ControllerAction mActionSystem_Touched; + + // --- Knuckles & Cosmos + ControllerAction mActionA_Pressed; + ControllerAction mActionA_Touched; + ControllerAction mActionB_Pressed; + ControllerAction mActionB_Touched; + + // --- Knuckles, Cosmos, and WMR + ControllerAction mActionThumbstick_Analog; + ControllerAction mActionThumbstick_Pressed; + ControllerAction mActionThumbstick_Touched; + + // --- Knuckles + ControllerAction mActionFingerIndex_Value; + ControllerAction mActionFingerMiddle_Value; + ControllerAction mActionFingerRing_Value; + ControllerAction mActionFingerPinky_Value; + + // --- Cosmos + ControllerAction mActionBumper_Pressed; +}; + +class OpenVRControllerMapper { + public: + OpenVRControllerMapper(); + virtual ~OpenVRControllerMapper() = default; + + virtual void UpdateButtons(VRControllerState& aControllerState, + ControllerInfo& aControllerInfo) = 0; + uint32_t GetButtonCount() { return mNumButtons; } + uint32_t GetAxisCount() { return mNumAxes; } + + protected: + void GetButtonValueFromAction(VRControllerState& aControllerState, + const ControllerAction& aPressAction, + const ControllerAction& aTouchAction); + void GetTriggerValueFromAction(VRControllerState& aControllerState, + const ControllerAction& aAction); + void GetAxisValueFromAction(VRControllerState& aControllerState, + const ControllerAction& aAction, + bool aInvertAxis = false); + uint32_t mNumButtons; + uint32_t mNumAxes; +}; + +} // namespace gfx +} // namespace mozilla + +#endif // GFX_VR_SERVICE_OPENVRCONTROLLERMAPPER_H diff --git a/gfx/vr/service/OpenVRCosmosMapper.cpp b/gfx/vr/service/OpenVRCosmosMapper.cpp new file mode 100644 index 0000000000..8c5c87253d --- /dev/null +++ b/gfx/vr/service/OpenVRCosmosMapper.cpp @@ -0,0 +1,51 @@ +/* -*- 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 "OpenVRCosmosMapper.h" + +#include "moz_external_vr.h" +#include "VRSession.h" + +namespace mozilla::gfx { + +void OpenVRCosmosMapper::UpdateButtons(VRControllerState& aControllerState, + ControllerInfo& aControllerInfo) { + mNumButtons = mNumAxes = 0; + // Button 0: Trigger + GetTriggerValueFromAction(aControllerState, + aControllerInfo.mActionTrigger_Value); + // Button 1: Grip + GetButtonValueFromAction(aControllerState, + aControllerInfo.mActionGrip_Pressed, + aControllerInfo.mActionGrip_Touched); + // Button 2: a placeholder button for trackpad. + ++mNumButtons; + // Button 3: Thumbstick + GetButtonValueFromAction(aControllerState, + aControllerInfo.mActionThumbstick_Pressed, + aControllerInfo.mActionThumbstick_Touched); + // Button 4: A + GetButtonValueFromAction(aControllerState, aControllerInfo.mActionA_Pressed, + aControllerInfo.mActionA_Touched); + // Button 5: B + GetButtonValueFromAction(aControllerState, aControllerInfo.mActionB_Pressed, + aControllerInfo.mActionB_Touched); + // Button 6: Bumper + GetButtonValueFromAction(aControllerState, + aControllerInfo.mActionBumper_Pressed, + aControllerInfo.mActionBumper_Pressed); + + // Axis 0, 1: placeholder axes for touchpad. + mNumAxes += 2; + // Axis 2, 3: Thumbstick + GetAxisValueFromAction(aControllerState, + aControllerInfo.mActionThumbstick_Analog); + + aControllerState.numButtons = mNumButtons; + aControllerState.numAxes = mNumAxes; +} + +} // namespace mozilla::gfx diff --git a/gfx/vr/service/OpenVRCosmosMapper.h b/gfx/vr/service/OpenVRCosmosMapper.h new file mode 100644 index 0000000000..3796cd1c6d --- /dev/null +++ b/gfx/vr/service/OpenVRCosmosMapper.h @@ -0,0 +1,26 @@ +/* -*- 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/. */ + +#ifndef GFX_VR_SERVICE_OPENVRCOSMOSMAPPER_H +#define GFX_VR_SERVICE_OPENVRCOSMOSMAPPER_H + +#include "OpenVRControllerMapper.h" + +namespace mozilla { +namespace gfx { + +class OpenVRCosmosMapper : public OpenVRControllerMapper { + public: + OpenVRCosmosMapper() = default; + virtual ~OpenVRCosmosMapper() = default; + virtual void UpdateButtons(VRControllerState& aControllerState, + ControllerInfo& aControllerInfo); +}; + +} // namespace gfx +} // namespace mozilla + +#endif // GFX_VR_SERVICE_OPENVRCOSMOSMAPPER_H diff --git a/gfx/vr/service/OpenVRDefaultMapper.cpp b/gfx/vr/service/OpenVRDefaultMapper.cpp new file mode 100644 index 0000000000..ac65369475 --- /dev/null +++ b/gfx/vr/service/OpenVRDefaultMapper.cpp @@ -0,0 +1,70 @@ +/* -*- 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 "OpenVRDefaultMapper.h" + +#include "moz_external_vr.h" +#include "VRSession.h" + +namespace mozilla::gfx { + +void OpenVRDefaultMapper::UpdateButtons(VRControllerState& aControllerState, + ControllerInfo& aControllerInfo) { + mNumButtons = mNumAxes = 0; + // Button 0: Trigger + GetTriggerValueFromAction(aControllerState, + aControllerInfo.mActionTrigger_Value); + // Button 1: Grip + GetButtonValueFromAction(aControllerState, + aControllerInfo.mActionGrip_Pressed, + aControllerInfo.mActionGrip_Touched); + // Button 2: Touchpad. + GetButtonValueFromAction(aControllerState, + aControllerInfo.mActionTrackpad_Pressed, + aControllerInfo.mActionTrackpad_Touched); + // Button 3: Thumbstick + GetButtonValueFromAction(aControllerState, + aControllerInfo.mActionThumbstick_Pressed, + aControllerInfo.mActionThumbstick_Touched); + // Button 4: Menu + GetButtonValueFromAction(aControllerState, + aControllerInfo.mActionMenu_Pressed, + aControllerInfo.mActionMenu_Touched); + // Button 5: A + GetButtonValueFromAction(aControllerState, aControllerInfo.mActionA_Pressed, + aControllerInfo.mActionA_Touched); + // Button 6: B + GetButtonValueFromAction(aControllerState, aControllerInfo.mActionB_Pressed, + aControllerInfo.mActionB_Touched); + // Button 7: Bumper + GetButtonValueFromAction(aControllerState, + aControllerInfo.mActionBumper_Pressed, + aControllerInfo.mActionBumper_Pressed); + // Button 8: Finger index + GetTriggerValueFromAction(aControllerState, + aControllerInfo.mActionFingerIndex_Value); + // Button 9: Finger middle + GetTriggerValueFromAction(aControllerState, + aControllerInfo.mActionFingerMiddle_Value); + // Button 10: Finger ring + GetTriggerValueFromAction(aControllerState, + aControllerInfo.mActionFingerRing_Value); + // Button 11: Finger pinky + GetTriggerValueFromAction(aControllerState, + aControllerInfo.mActionFingerPinky_Value); + + // Axis 0, 1: Touchpad + GetAxisValueFromAction(aControllerState, + aControllerInfo.mActionTrackpad_Analog); + // Axis 2, 3: Thumbstick + GetAxisValueFromAction(aControllerState, + aControllerInfo.mActionThumbstick_Analog); + + aControllerState.numButtons = mNumButtons; + aControllerState.numAxes = mNumAxes; +} + +} // namespace mozilla::gfx diff --git a/gfx/vr/service/OpenVRDefaultMapper.h b/gfx/vr/service/OpenVRDefaultMapper.h new file mode 100644 index 0000000000..0478a90687 --- /dev/null +++ b/gfx/vr/service/OpenVRDefaultMapper.h @@ -0,0 +1,26 @@ +/* -*- 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/. */ + +#ifndef GFX_VR_SERVICE_OPENVRDEFAULTMAPPER_H +#define GFX_VR_SERVICE_OPENVRDEFAULTMAPPER_H + +#include "OpenVRControllerMapper.h" + +namespace mozilla { +namespace gfx { + +class OpenVRDefaultMapper : public OpenVRControllerMapper { + public: + OpenVRDefaultMapper() = default; + virtual ~OpenVRDefaultMapper() = default; + virtual void UpdateButtons(VRControllerState& aControllerState, + ControllerInfo& aControllerInfo); +}; + +} // namespace gfx +} // namespace mozilla + +#endif // GFX_VR_SERVICE_OPENVRDEFAULTMAPPER_H diff --git a/gfx/vr/service/OpenVRKnucklesMapper.cpp b/gfx/vr/service/OpenVRKnucklesMapper.cpp new file mode 100644 index 0000000000..74f38c820c --- /dev/null +++ b/gfx/vr/service/OpenVRKnucklesMapper.cpp @@ -0,0 +1,62 @@ +/* -*- 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 "OpenVRKnucklesMapper.h" + +#include "moz_external_vr.h" +#include "VRSession.h" + +namespace mozilla::gfx { + +void OpenVRKnucklesMapper::UpdateButtons(VRControllerState& aControllerState, + ControllerInfo& aControllerInfo) { + mNumButtons = mNumAxes = 0; + // Button 0: Trigger + GetTriggerValueFromAction(aControllerState, + aControllerInfo.mActionTrigger_Value); + // Button 1: Grip + GetButtonValueFromAction(aControllerState, + aControllerInfo.mActionGrip_Pressed, + aControllerInfo.mActionGrip_Touched); + // Button 2: Touchpad. + GetButtonValueFromAction(aControllerState, + aControllerInfo.mActionTrackpad_Pressed, + aControllerInfo.mActionTrackpad_Touched); + // Button 3: Thumbstick + GetButtonValueFromAction(aControllerState, + aControllerInfo.mActionThumbstick_Pressed, + aControllerInfo.mActionThumbstick_Touched); + // Button 4: A + GetButtonValueFromAction(aControllerState, aControllerInfo.mActionA_Pressed, + aControllerInfo.mActionA_Touched); + // Button 5: B + GetButtonValueFromAction(aControllerState, aControllerInfo.mActionB_Pressed, + aControllerInfo.mActionB_Touched); + // Button 6: Finger index + GetTriggerValueFromAction(aControllerState, + aControllerInfo.mActionFingerIndex_Value); + // Button 7: Finger middle + GetTriggerValueFromAction(aControllerState, + aControllerInfo.mActionFingerMiddle_Value); + // Button 8: Finger ring + GetTriggerValueFromAction(aControllerState, + aControllerInfo.mActionFingerRing_Value); + // Button 9: Finger pinky + GetTriggerValueFromAction(aControllerState, + aControllerInfo.mActionFingerPinky_Value); + + // Axis 0, 1: Touchpad + GetAxisValueFromAction(aControllerState, + aControllerInfo.mActionTrackpad_Analog); + // Axis 2, 3: Thumbstick + GetAxisValueFromAction(aControllerState, + aControllerInfo.mActionThumbstick_Analog); + + aControllerState.numButtons = mNumButtons; + aControllerState.numAxes = mNumAxes; +} + +} // namespace mozilla::gfx diff --git a/gfx/vr/service/OpenVRKnucklesMapper.h b/gfx/vr/service/OpenVRKnucklesMapper.h new file mode 100644 index 0000000000..45168e7749 --- /dev/null +++ b/gfx/vr/service/OpenVRKnucklesMapper.h @@ -0,0 +1,26 @@ +/* -*- 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/. */ + +#ifndef GFX_VR_SERVICE_OPENVRKNUCKLESMAPPER_H +#define GFX_VR_SERVICE_OPENVRKNUCKLESMAPPER_H + +#include "OpenVRControllerMapper.h" + +namespace mozilla { +namespace gfx { + +class OpenVRKnucklesMapper : public OpenVRControllerMapper { + public: + OpenVRKnucklesMapper() = default; + virtual ~OpenVRKnucklesMapper() = default; + virtual void UpdateButtons(VRControllerState& aControllerState, + ControllerInfo& aControllerInfo); +}; + +} // namespace gfx +} // namespace mozilla + +#endif // GFX_VR_SERVICE_OPENVRKNUCKLESMAPPER_H diff --git a/gfx/vr/service/OpenVRSession.cpp b/gfx/vr/service/OpenVRSession.cpp new file mode 100644 index 0000000000..7a50561ffd --- /dev/null +++ b/gfx/vr/service/OpenVRSession.cpp @@ -0,0 +1,1480 @@ +/* -*- 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 <fstream> +#include "mozilla/JSONWriter.h" +#include "mozilla/ClearOnShutdown.h" +#include "nsIThread.h" +#include "nsString.h" + +#include "OpenVRSession.h" +#include "mozilla/StaticPrefs_dom.h" + +#if defined(XP_WIN) +# include <d3d11.h> +# include "mozilla/gfx/DeviceManagerDx.h" +#elif defined(XP_MACOSX) +# include "mozilla/gfx/MacIOSurface.h" +#endif + +#if !defined(XP_WIN) +# include <sys/stat.h> // for umask() +#endif + +#include "mozilla/dom/GamepadEventTypes.h" +#include "mozilla/dom/GamepadBinding.h" +#include "binding/OpenVRCosmosBinding.h" +#include "binding/OpenVRKnucklesBinding.h" +#include "binding/OpenVRViveBinding.h" +#include "OpenVRCosmosMapper.h" +#include "OpenVRDefaultMapper.h" +#include "OpenVRKnucklesMapper.h" +#include "OpenVRViveMapper.h" +#if defined(XP_WIN) // Windows Mixed Reality is only available in Windows. +# include "OpenVRWMRMapper.h" +# include "binding/OpenVRWMRBinding.h" +#endif + +#include "VRParent.h" +#include "VRProcessChild.h" +#include "VRThread.h" + +#if !defined(M_PI) +# define M_PI 3.14159265358979323846264338327950288 +#endif + +#define BTN_MASK_FROM_ID(_id) ::vr::ButtonMaskFromId(vr::EVRButtonId::_id) + +// Haptic feedback is updated every 5ms, as this is +// the minimum period between new haptic pulse requests. +// Effectively, this results in a pulse width modulation +// with an interval of 5ms. Through experimentation, the +// maximum duty cycle was found to be about 3.9ms +const uint32_t kVRHapticUpdateInterval = 5; + +using namespace mozilla::gfx; + +namespace mozilla::gfx { + +namespace { + +// This is for controller action file writer. +struct StringWriteFunc : public JSONWriteFunc { + nsACString& mBuffer; // This struct must not outlive this buffer + + explicit StringWriteFunc(nsACString& buffer) : mBuffer(buffer) {} + + void Write(const Span<const char>& aStr) override { mBuffer.Append(aStr); } +}; + +class ControllerManifestFile { + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ControllerManifestFile) + + public: + static already_AddRefed<ControllerManifestFile> CreateManifest() { + RefPtr<ControllerManifestFile> manifest = new ControllerManifestFile(); + return manifest.forget(); + } + + bool IsExisting() { + if (mFileName.IsEmpty() || + !std::ifstream(mFileName.BeginReading()).good()) { + return false; + } + return true; + } + + void SetFileName(const char* aName) { mFileName = aName; } + + const char* GetFileName() const { return mFileName.BeginReading(); } + + private: + ControllerManifestFile() = default; + + ~ControllerManifestFile() { + if (!mFileName.IsEmpty() && remove(mFileName.BeginReading()) != 0) { + MOZ_ASSERT(false, "Delete controller manifest file failed."); + } + mFileName = ""; + } + + nsCString mFileName; +}; + +// We wanna keep these temporary files existing +// until Firefox is closed instead of following OpenVRSession's lifetime. +StaticRefPtr<ControllerManifestFile> sCosmosBindingFile; +StaticRefPtr<ControllerManifestFile> sKnucklesBindingFile; +StaticRefPtr<ControllerManifestFile> sViveBindingFile; +#if defined(XP_WIN) +StaticRefPtr<ControllerManifestFile> sWMRBindingFile; +#endif +StaticRefPtr<ControllerManifestFile> sControllerActionFile; + +dom::GamepadHand GetControllerHandFromControllerRole(OpenVRHand aRole) { + dom::GamepadHand hand; + switch (aRole) { + case OpenVRHand::None: + hand = dom::GamepadHand::_empty; + break; + case OpenVRHand::Left: + hand = dom::GamepadHand::Left; + break; + case OpenVRHand::Right: + hand = dom::GamepadHand::Right; + break; + default: + hand = dom::GamepadHand::_empty; + MOZ_ASSERT(false); + break; + } + + return hand; +} + +bool FileIsExisting(const nsCString& aPath) { + if (aPath.IsEmpty() || !std::ifstream(aPath.BeginReading()).good()) { + return false; + } + return true; +} + +}; // anonymous namespace + +#if defined(XP_WIN) +bool GenerateTempFileName(nsCString& aPath) { + TCHAR tempPathBuffer[MAX_PATH]; + TCHAR tempFileName[MAX_PATH]; + + // Gets the temp path env string (no guarantee it's a valid path). + DWORD dwRetVal = GetTempPath(MAX_PATH, tempPathBuffer); + if (dwRetVal > MAX_PATH || (dwRetVal == 0)) { + NS_WARNING("OpenVR - Creating temp path failed."); + return false; + } + + // Generates a temporary file name. + UINT uRetVal = GetTempFileName(tempPathBuffer, // directory for tmp files + TEXT("mozvr"), // temp file name prefix + 0, // create unique name + tempFileName); // buffer for name + if (uRetVal == 0) { + NS_WARNING("OpenVR - Creating temp file failed."); + return false; + } + + aPath.Assign(NS_ConvertUTF16toUTF8(tempFileName)); + return true; +} +#else +bool GenerateTempFileName(nsCString& aPath) { + const char tmp[] = "/tmp/mozvrXXXXXX"; + char fileName[PATH_MAX]; + + strcpy(fileName, tmp); + const mode_t prevMask = umask(S_IXUSR | S_IRWXO | S_IRWXG); + const int fd = mkstemp(fileName); + umask(prevMask); + if (fd == -1) { + NS_WARNING(nsPrintfCString("OpenVR - Creating temp file failed: %s", + strerror(errno)) + .get()); + return false; + } + close(fd); + + aPath.Assign(fileName); + return true; +} +#endif // defined(XP_WIN) + +OpenVRSession::OpenVRSession() + : VRSession(), + mVRSystem(nullptr), + mVRChaperone(nullptr), + mVRCompositor(nullptr), + mHapticPulseRemaining{}, + mHapticPulseIntensity{}, + mIsWindowsMR(false), + mControllerHapticStateMutex( + "OpenVRSession::mControllerHapticStateMutex") { + std::fill_n(mControllerDeviceIndex, kVRControllerMaxCount, OpenVRHand::None); +} + +OpenVRSession::~OpenVRSession() { + mActionsetFirefox = ::vr::k_ulInvalidActionSetHandle; + Shutdown(); +} + +bool OpenVRSession::Initialize(mozilla::gfx::VRSystemState& aSystemState, + bool aDetectRuntimesOnly) { + if (StaticPrefs::dom_vr_puppet_enabled()) { + // Ensure that tests using the VR Puppet do not find real hardware + return false; + } + if (!StaticPrefs::dom_vr_enabled() || !StaticPrefs::dom_vr_openvr_enabled()) { + return false; + } + if (mVRSystem != nullptr) { + // Already initialized + return true; + } + if (!::vr::VR_IsRuntimeInstalled()) { + return false; + } + if (aDetectRuntimesOnly) { + aSystemState.displayState.capabilityFlags |= + VRDisplayCapabilityFlags::Cap_ImmersiveVR; + return false; + } + if (!::vr::VR_IsHmdPresent()) { + return false; + } + + ::vr::HmdError err; + + ::vr::VR_Init(&err, ::vr::EVRApplicationType::VRApplication_Scene); + if (err) { + return false; + } + + mVRSystem = (::vr::IVRSystem*)::vr::VR_GetGenericInterface( + ::vr::IVRSystem_Version, &err); + if (err || !mVRSystem) { + Shutdown(); + return false; + } + mVRChaperone = (::vr::IVRChaperone*)::vr::VR_GetGenericInterface( + ::vr::IVRChaperone_Version, &err); + if (err || !mVRChaperone) { + Shutdown(); + return false; + } + mVRCompositor = (::vr::IVRCompositor*)::vr::VR_GetGenericInterface( + ::vr::IVRCompositor_Version, &err); + if (err || !mVRCompositor) { + Shutdown(); + return false; + } + +#if defined(XP_WIN) + if (!CreateD3DObjects()) { + Shutdown(); + return false; + } + +#endif + + // Configure coordinate system + mVRCompositor->SetTrackingSpace(::vr::TrackingUniverseSeated); + + if (!InitState(aSystemState)) { + Shutdown(); + return false; + } + if (!SetupContollerActions()) { + return false; + } + + // Succeeded + return true; +} + +// "actions": [] Action paths must take the form: "/actions/<action +// set>/in|out/<action>" +#define CreateControllerAction(hand, name, type) \ + ControllerAction("/actions/firefox/in/" #hand "Hand_" #name, #type) +#define CreateControllerOutAction(hand, name, type) \ + ControllerAction("/actions/firefox/out/" #hand "Hand_" #name, #type) + +bool OpenVRSession::SetupContollerActions() { + if (!vr::VRInput()) { + NS_WARNING("OpenVR - vr::VRInput() is null."); + return false; + } + + // Check if this device binding file has been created. + // If it didn't exist yet, create a new temp file. + nsCString controllerAction; + nsCString viveManifest; + nsCString WMRManifest; + nsCString knucklesManifest; + nsCString cosmosManifest; + + // Getting / Generating manifest file paths. + if (StaticPrefs::dom_vr_process_enabled_AtStartup()) { + VRParent* vrParent = VRProcessChild::GetVRParent(); + nsCString output; + + if (vrParent->GetOpenVRControllerActionPath(&output)) { + controllerAction = output; + } + + if (vrParent->GetOpenVRControllerManifestPath(VRControllerType::HTCVive, + &output)) { + viveManifest = output; + } + if (!viveManifest.Length() || !FileIsExisting(viveManifest)) { + if (!GenerateTempFileName(viveManifest)) { + return false; + } + OpenVRViveBinding viveBinding; + std::ofstream viveBindingFile(viveManifest.BeginReading()); + if (viveBindingFile.is_open()) { + viveBindingFile << viveBinding.binding; + viveBindingFile.close(); + } + } + +#if defined(XP_WIN) + if (vrParent->GetOpenVRControllerManifestPath(VRControllerType::MSMR, + &output)) { + WMRManifest = output; + } + if (!WMRManifest.Length() || !FileIsExisting(WMRManifest)) { + if (!GenerateTempFileName(WMRManifest)) { + return false; + } + OpenVRWMRBinding WMRBinding; + std::ofstream WMRBindingFile(WMRManifest.BeginReading()); + if (WMRBindingFile.is_open()) { + WMRBindingFile << WMRBinding.binding; + WMRBindingFile.close(); + } + } +#endif + if (vrParent->GetOpenVRControllerManifestPath(VRControllerType::ValveIndex, + &output)) { + knucklesManifest = output; + } + if (!knucklesManifest.Length() || !FileIsExisting(knucklesManifest)) { + if (!GenerateTempFileName(knucklesManifest)) { + return false; + } + OpenVRKnucklesBinding knucklesBinding; + std::ofstream knucklesBindingFile(knucklesManifest.BeginReading()); + if (knucklesBindingFile.is_open()) { + knucklesBindingFile << knucklesBinding.binding; + knucklesBindingFile.close(); + } + } + if (vrParent->GetOpenVRControllerManifestPath( + VRControllerType::HTCViveCosmos, &output)) { + cosmosManifest = output; + } + if (!cosmosManifest.Length() || !FileIsExisting(cosmosManifest)) { + if (!GenerateTempFileName(cosmosManifest)) { + return false; + } + OpenVRCosmosBinding cosmosBinding; + std::ofstream cosmosBindingFile(cosmosManifest.BeginReading()); + if (cosmosBindingFile.is_open()) { + cosmosBindingFile << cosmosBinding.binding; + cosmosBindingFile.close(); + } + } + } else { + // Without using VR process + if (!sControllerActionFile) { + sControllerActionFile = ControllerManifestFile::CreateManifest(); + NS_DispatchToMainThread(NS_NewRunnableFunction( + "ClearOnShutdown ControllerManifestFile", + []() { ClearOnShutdown(&sControllerActionFile); })); + } + controllerAction = sControllerActionFile->GetFileName(); + + if (!sViveBindingFile) { + sViveBindingFile = ControllerManifestFile::CreateManifest(); + NS_DispatchToMainThread( + NS_NewRunnableFunction("ClearOnShutdown ControllerManifestFile", + []() { ClearOnShutdown(&sViveBindingFile); })); + } + if (!sViveBindingFile->IsExisting()) { + nsCString viveBindingPath; + if (!GenerateTempFileName(viveBindingPath)) { + return false; + } + sViveBindingFile->SetFileName(viveBindingPath.BeginReading()); + OpenVRViveBinding viveBinding; + std::ofstream viveBindingFile(sViveBindingFile->GetFileName()); + if (viveBindingFile.is_open()) { + viveBindingFile << viveBinding.binding; + viveBindingFile.close(); + } + } + viveManifest = sViveBindingFile->GetFileName(); + + if (!sKnucklesBindingFile) { + sKnucklesBindingFile = ControllerManifestFile::CreateManifest(); + NS_DispatchToMainThread(NS_NewRunnableFunction( + "ClearOnShutdown ControllerManifestFile", + []() { ClearOnShutdown(&sKnucklesBindingFile); })); + } + if (!sKnucklesBindingFile->IsExisting()) { + nsCString knucklesBindingPath; + if (!GenerateTempFileName(knucklesBindingPath)) { + return false; + } + sKnucklesBindingFile->SetFileName(knucklesBindingPath.BeginReading()); + OpenVRKnucklesBinding knucklesBinding; + std::ofstream knucklesBindingFile(sKnucklesBindingFile->GetFileName()); + if (knucklesBindingFile.is_open()) { + knucklesBindingFile << knucklesBinding.binding; + knucklesBindingFile.close(); + } + } + knucklesManifest = sKnucklesBindingFile->GetFileName(); + + if (!sCosmosBindingFile) { + sCosmosBindingFile = ControllerManifestFile::CreateManifest(); + NS_DispatchToMainThread(NS_NewRunnableFunction( + "ClearOnShutdown ControllerManifestFile", + []() { ClearOnShutdown(&sCosmosBindingFile); })); + } + if (!sCosmosBindingFile->IsExisting()) { + nsCString cosmosBindingPath; + if (!GenerateTempFileName(cosmosBindingPath)) { + return false; + } + sCosmosBindingFile->SetFileName(cosmosBindingPath.BeginReading()); + OpenVRCosmosBinding cosmosBinding; + std::ofstream cosmosBindingFile(sCosmosBindingFile->GetFileName()); + if (cosmosBindingFile.is_open()) { + cosmosBindingFile << cosmosBinding.binding; + cosmosBindingFile.close(); + } + } + cosmosManifest = sCosmosBindingFile->GetFileName(); +#if defined(XP_WIN) + if (!sWMRBindingFile) { + sWMRBindingFile = ControllerManifestFile::CreateManifest(); + NS_DispatchToMainThread( + NS_NewRunnableFunction("ClearOnShutdown ControllerManifestFile", + []() { ClearOnShutdown(&sWMRBindingFile); })); + } + if (!sWMRBindingFile->IsExisting()) { + nsCString WMRBindingPath; + if (!GenerateTempFileName(WMRBindingPath)) { + return false; + } + sWMRBindingFile->SetFileName(WMRBindingPath.BeginReading()); + OpenVRWMRBinding WMRBinding; + std::ofstream WMRBindingFile(sWMRBindingFile->GetFileName()); + if (WMRBindingFile.is_open()) { + WMRBindingFile << WMRBinding.binding; + WMRBindingFile.close(); + } + } + WMRManifest = sWMRBindingFile->GetFileName(); +#endif + } + // End of Getting / Generating manifest file paths. + + // Setup controller actions. + ControllerInfo leftContollerInfo; + leftContollerInfo.mActionPose = CreateControllerAction(L, pose, pose); + leftContollerInfo.mActionTrackpad_Analog = + CreateControllerAction(L, trackpad_analog, vector2); + leftContollerInfo.mActionTrackpad_Pressed = + CreateControllerAction(L, trackpad_pressed, boolean); + leftContollerInfo.mActionTrackpad_Touched = + CreateControllerAction(L, trackpad_touched, boolean); + leftContollerInfo.mActionTrigger_Value = + CreateControllerAction(L, trigger_value, vector1); + leftContollerInfo.mActionGrip_Pressed = + CreateControllerAction(L, grip_pressed, boolean); + leftContollerInfo.mActionGrip_Touched = + CreateControllerAction(L, grip_touched, boolean); + leftContollerInfo.mActionMenu_Pressed = + CreateControllerAction(L, menu_pressed, boolean); + leftContollerInfo.mActionMenu_Touched = + CreateControllerAction(L, menu_touched, boolean); + leftContollerInfo.mActionSystem_Pressed = + CreateControllerAction(L, system_pressed, boolean); + leftContollerInfo.mActionSystem_Touched = + CreateControllerAction(L, system_touched, boolean); + leftContollerInfo.mActionA_Pressed = + CreateControllerAction(L, A_pressed, boolean); + leftContollerInfo.mActionA_Touched = + CreateControllerAction(L, A_touched, boolean); + leftContollerInfo.mActionB_Pressed = + CreateControllerAction(L, B_pressed, boolean); + leftContollerInfo.mActionB_Touched = + CreateControllerAction(L, B_touched, boolean); + leftContollerInfo.mActionThumbstick_Analog = + CreateControllerAction(L, thumbstick_analog, vector2); + leftContollerInfo.mActionThumbstick_Pressed = + CreateControllerAction(L, thumbstick_pressed, boolean); + leftContollerInfo.mActionThumbstick_Touched = + CreateControllerAction(L, thumbstick_touched, boolean); + leftContollerInfo.mActionFingerIndex_Value = + CreateControllerAction(L, finger_index_value, vector1); + leftContollerInfo.mActionFingerMiddle_Value = + CreateControllerAction(L, finger_middle_value, vector1); + leftContollerInfo.mActionFingerRing_Value = + CreateControllerAction(L, finger_ring_value, vector1); + leftContollerInfo.mActionFingerPinky_Value = + CreateControllerAction(L, finger_pinky_value, vector1); + leftContollerInfo.mActionBumper_Pressed = + CreateControllerAction(L, bumper_pressed, boolean); + leftContollerInfo.mActionHaptic = + CreateControllerOutAction(L, haptic, vibration); + + ControllerInfo rightContollerInfo; + rightContollerInfo.mActionPose = CreateControllerAction(R, pose, pose); + rightContollerInfo.mActionTrackpad_Analog = + CreateControllerAction(R, trackpad_analog, vector2); + rightContollerInfo.mActionTrackpad_Pressed = + CreateControllerAction(R, trackpad_pressed, boolean); + rightContollerInfo.mActionTrackpad_Touched = + CreateControllerAction(R, trackpad_touched, boolean); + rightContollerInfo.mActionTrigger_Value = + CreateControllerAction(R, trigger_value, vector1); + rightContollerInfo.mActionGrip_Pressed = + CreateControllerAction(R, grip_pressed, boolean); + rightContollerInfo.mActionGrip_Touched = + CreateControllerAction(R, grip_touched, boolean); + rightContollerInfo.mActionMenu_Pressed = + CreateControllerAction(R, menu_pressed, boolean); + rightContollerInfo.mActionMenu_Touched = + CreateControllerAction(R, menu_touched, boolean); + rightContollerInfo.mActionSystem_Pressed = + CreateControllerAction(R, system_pressed, boolean); + rightContollerInfo.mActionSystem_Touched = + CreateControllerAction(R, system_touched, boolean); + rightContollerInfo.mActionA_Pressed = + CreateControllerAction(R, A_pressed, boolean); + rightContollerInfo.mActionA_Touched = + CreateControllerAction(R, A_touched, boolean); + rightContollerInfo.mActionB_Pressed = + CreateControllerAction(R, B_pressed, boolean); + rightContollerInfo.mActionB_Touched = + CreateControllerAction(R, B_touched, boolean); + rightContollerInfo.mActionThumbstick_Analog = + CreateControllerAction(R, thumbstick_analog, vector2); + rightContollerInfo.mActionThumbstick_Pressed = + CreateControllerAction(R, thumbstick_pressed, boolean); + rightContollerInfo.mActionThumbstick_Touched = + CreateControllerAction(R, thumbstick_touched, boolean); + rightContollerInfo.mActionFingerIndex_Value = + CreateControllerAction(R, finger_index_value, vector1); + rightContollerInfo.mActionFingerMiddle_Value = + CreateControllerAction(R, finger_middle_value, vector1); + rightContollerInfo.mActionFingerRing_Value = + CreateControllerAction(R, finger_ring_value, vector1); + rightContollerInfo.mActionFingerPinky_Value = + CreateControllerAction(R, finger_pinky_value, vector1); + rightContollerInfo.mActionBumper_Pressed = + CreateControllerAction(R, bumper_pressed, boolean); + rightContollerInfo.mActionHaptic = + CreateControllerOutAction(R, haptic, vibration); + + mControllerHand[OpenVRHand::Left] = leftContollerInfo; + mControllerHand[OpenVRHand::Right] = rightContollerInfo; + + if (!controllerAction.Length() || !FileIsExisting(controllerAction)) { + if (!GenerateTempFileName(controllerAction)) { + return false; + } + nsCString actionData; + JSONWriter actionWriter(MakeUnique<StringWriteFunc>(actionData)); + actionWriter.Start(); + + actionWriter.StringProperty("version", + "0.1.0"); // TODO: adding a version check. + // "default_bindings": [] + actionWriter.StartArrayProperty("default_bindings"); + + auto SetupActionWriterByControllerType = [&](const char* aType, + const nsCString& aManifest) { + actionWriter.StartObjectElement(); + actionWriter.StringProperty("controller_type", MakeStringSpan(aType)); + actionWriter.StringProperty("binding_url", aManifest); + actionWriter.EndObject(); + }; + SetupActionWriterByControllerType("vive_controller", viveManifest); + SetupActionWriterByControllerType("knuckles", knucklesManifest); + SetupActionWriterByControllerType("vive_cosmos_controller", cosmosManifest); +#if defined(XP_WIN) + SetupActionWriterByControllerType("holographic_controller", WMRManifest); +#endif + actionWriter.EndArray(); // End "default_bindings": [] + + actionWriter.StartArrayProperty("actions"); + + for (auto& controller : mControllerHand) { + auto SetActionsToWriter = [&](const ControllerAction& aAction) { + actionWriter.StartObjectElement(); + actionWriter.StringProperty("name", aAction.name); + actionWriter.StringProperty("type", aAction.type); + actionWriter.EndObject(); + }; + + SetActionsToWriter(controller.mActionPose); + SetActionsToWriter(controller.mActionTrackpad_Analog); + SetActionsToWriter(controller.mActionTrackpad_Pressed); + SetActionsToWriter(controller.mActionTrackpad_Touched); + SetActionsToWriter(controller.mActionTrigger_Value); + SetActionsToWriter(controller.mActionGrip_Pressed); + SetActionsToWriter(controller.mActionGrip_Touched); + SetActionsToWriter(controller.mActionMenu_Pressed); + SetActionsToWriter(controller.mActionMenu_Touched); + SetActionsToWriter(controller.mActionSystem_Pressed); + SetActionsToWriter(controller.mActionSystem_Touched); + SetActionsToWriter(controller.mActionA_Pressed); + SetActionsToWriter(controller.mActionA_Touched); + SetActionsToWriter(controller.mActionB_Pressed); + SetActionsToWriter(controller.mActionB_Touched); + SetActionsToWriter(controller.mActionThumbstick_Analog); + SetActionsToWriter(controller.mActionThumbstick_Pressed); + SetActionsToWriter(controller.mActionThumbstick_Touched); + SetActionsToWriter(controller.mActionFingerIndex_Value); + SetActionsToWriter(controller.mActionFingerMiddle_Value); + SetActionsToWriter(controller.mActionFingerRing_Value); + SetActionsToWriter(controller.mActionFingerPinky_Value); + SetActionsToWriter(controller.mActionBumper_Pressed); + SetActionsToWriter(controller.mActionHaptic); + } + actionWriter.EndArray(); // End "actions": [] + actionWriter.End(); + + std::ofstream actionfile(controllerAction.BeginReading()); + nsCString actionResult(actionData.get()); + if (actionfile.is_open()) { + actionfile << actionResult.get(); + actionfile.close(); + } + } + + vr::EVRInputError err = + vr::VRInput()->SetActionManifestPath(controllerAction.BeginReading()); + if (err != vr::VRInputError_None) { + NS_WARNING("OpenVR - SetActionManifestPath failed."); + return false; + } + // End of setup controller actions. + + // Notify the parent process these manifest files are already been recorded. + if (StaticPrefs::dom_vr_process_enabled_AtStartup()) { + NS_DispatchToMainThread(NS_NewRunnableFunction( + "SendOpenVRControllerActionPathToParent", + [controllerAction, viveManifest, WMRManifest, knucklesManifest, + cosmosManifest]() { + VRParent* vrParent = VRProcessChild::GetVRParent(); + Unused << vrParent->SendOpenVRControllerActionPathToParent( + controllerAction); + Unused << vrParent->SendOpenVRControllerManifestPathToParent( + VRControllerType::HTCVive, viveManifest); + Unused << vrParent->SendOpenVRControllerManifestPathToParent( + VRControllerType::MSMR, WMRManifest); + Unused << vrParent->SendOpenVRControllerManifestPathToParent( + VRControllerType::ValveIndex, knucklesManifest); + Unused << vrParent->SendOpenVRControllerManifestPathToParent( + VRControllerType::HTCViveCosmos, cosmosManifest); + })); + } else { + sControllerActionFile->SetFileName(controllerAction.BeginReading()); + } + + return true; +} + +#if defined(XP_WIN) +bool OpenVRSession::CreateD3DObjects() { + RefPtr<ID3D11Device> device = gfx::DeviceManagerDx::Get()->GetVRDevice(); + if (!device) { + return false; + } + if (!CreateD3DContext(device)) { + return false; + } + return true; +} +#endif + +void OpenVRSession::Shutdown() { + StopHapticTimer(); + StopHapticThread(); + if (mVRSystem || mVRCompositor || mVRChaperone) { + ::vr::VR_Shutdown(); + mVRCompositor = nullptr; + mVRChaperone = nullptr; + mVRSystem = nullptr; + } +} + +bool OpenVRSession::InitState(VRSystemState& aSystemState) { + VRDisplayState& state = aSystemState.displayState; + strncpy(state.displayName, "OpenVR HMD", kVRDisplayNameMaxLen); + state.eightCC = GFX_VR_EIGHTCC('O', 'p', 'e', 'n', 'V', 'R', ' ', ' '); + state.isConnected = + mVRSystem->IsTrackedDeviceConnected(::vr::k_unTrackedDeviceIndex_Hmd); + state.isMounted = false; + state.capabilityFlags = (VRDisplayCapabilityFlags)( + (int)VRDisplayCapabilityFlags::Cap_None | + (int)VRDisplayCapabilityFlags::Cap_Orientation | + (int)VRDisplayCapabilityFlags::Cap_Position | + (int)VRDisplayCapabilityFlags::Cap_External | + (int)VRDisplayCapabilityFlags::Cap_Present | + (int)VRDisplayCapabilityFlags::Cap_StageParameters | + (int)VRDisplayCapabilityFlags::Cap_ImmersiveVR); + state.blendMode = VRDisplayBlendMode::Opaque; + state.reportsDroppedFrames = true; + + ::vr::ETrackedPropertyError err; + bool bHasProximitySensor = mVRSystem->GetBoolTrackedDeviceProperty( + ::vr::k_unTrackedDeviceIndex_Hmd, ::vr::Prop_ContainsProximitySensor_Bool, + &err); + if (err == ::vr::TrackedProp_Success && bHasProximitySensor) { + state.capabilityFlags = (VRDisplayCapabilityFlags)( + (int)state.capabilityFlags | + (int)VRDisplayCapabilityFlags::Cap_MountDetection); + } + + uint32_t w, h; + mVRSystem->GetRecommendedRenderTargetSize(&w, &h); + state.eyeResolution.width = w; + state.eyeResolution.height = h; + state.nativeFramebufferScaleFactor = 1.0f; + + // default to an identity quaternion + aSystemState.sensorState.pose.orientation[3] = 1.0f; + + UpdateStageParameters(state); + UpdateEyeParameters(aSystemState); + + VRHMDSensorState& sensorState = aSystemState.sensorState; + sensorState.flags = (VRDisplayCapabilityFlags)( + (int)VRDisplayCapabilityFlags::Cap_Orientation | + (int)VRDisplayCapabilityFlags::Cap_Position); + sensorState.pose.orientation[3] = 1.0f; // Default to an identity quaternion + + return true; +} + +void OpenVRSession::UpdateStageParameters(VRDisplayState& aState) { + float sizeX = 0.0f; + float sizeZ = 0.0f; + if (mVRChaperone->GetPlayAreaSize(&sizeX, &sizeZ)) { + ::vr::HmdMatrix34_t t = + mVRSystem->GetSeatedZeroPoseToStandingAbsoluteTrackingPose(); + aState.stageSize.width = sizeX; + aState.stageSize.height = sizeZ; + + aState.sittingToStandingTransform[0] = t.m[0][0]; + aState.sittingToStandingTransform[1] = t.m[1][0]; + aState.sittingToStandingTransform[2] = t.m[2][0]; + aState.sittingToStandingTransform[3] = 0.0f; + + aState.sittingToStandingTransform[4] = t.m[0][1]; + aState.sittingToStandingTransform[5] = t.m[1][1]; + aState.sittingToStandingTransform[6] = t.m[2][1]; + aState.sittingToStandingTransform[7] = 0.0f; + + aState.sittingToStandingTransform[8] = t.m[0][2]; + aState.sittingToStandingTransform[9] = t.m[1][2]; + aState.sittingToStandingTransform[10] = t.m[2][2]; + aState.sittingToStandingTransform[11] = 0.0f; + + aState.sittingToStandingTransform[12] = t.m[0][3]; + aState.sittingToStandingTransform[13] = t.m[1][3]; + aState.sittingToStandingTransform[14] = t.m[2][3]; + aState.sittingToStandingTransform[15] = 1.0f; + } else { + // If we fail, fall back to reasonable defaults. + // 1m x 1m space, 0.75m high in seated position + aState.stageSize.width = 1.0f; + aState.stageSize.height = 1.0f; + + aState.sittingToStandingTransform[0] = 1.0f; + aState.sittingToStandingTransform[1] = 0.0f; + aState.sittingToStandingTransform[2] = 0.0f; + aState.sittingToStandingTransform[3] = 0.0f; + + aState.sittingToStandingTransform[4] = 0.0f; + aState.sittingToStandingTransform[5] = 1.0f; + aState.sittingToStandingTransform[6] = 0.0f; + aState.sittingToStandingTransform[7] = 0.0f; + + aState.sittingToStandingTransform[8] = 0.0f; + aState.sittingToStandingTransform[9] = 0.0f; + aState.sittingToStandingTransform[10] = 1.0f; + aState.sittingToStandingTransform[11] = 0.0f; + + aState.sittingToStandingTransform[12] = 0.0f; + aState.sittingToStandingTransform[13] = 0.75f; + aState.sittingToStandingTransform[14] = 0.0f; + aState.sittingToStandingTransform[15] = 1.0f; + } +} + +void OpenVRSession::UpdateEyeParameters(VRSystemState& aState) { + // This must be called every frame in order to + // account for continuous adjustments to ipd. + gfx::Matrix4x4 headToEyeTransforms[2]; + + for (uint32_t eye = 0; eye < 2; ++eye) { + ::vr::HmdMatrix34_t eyeToHead = + mVRSystem->GetEyeToHeadTransform(static_cast<::vr::Hmd_Eye>(eye)); + aState.displayState.eyeTranslation[eye].x = eyeToHead.m[0][3]; + aState.displayState.eyeTranslation[eye].y = eyeToHead.m[1][3]; + aState.displayState.eyeTranslation[eye].z = eyeToHead.m[2][3]; + + float left, right, up, down; + mVRSystem->GetProjectionRaw(static_cast<::vr::Hmd_Eye>(eye), &left, &right, + &up, &down); + aState.displayState.eyeFOV[eye].upDegrees = atan(-up) * 180.0 / M_PI; + aState.displayState.eyeFOV[eye].rightDegrees = atan(right) * 180.0 / M_PI; + aState.displayState.eyeFOV[eye].downDegrees = atan(down) * 180.0 / M_PI; + aState.displayState.eyeFOV[eye].leftDegrees = atan(-left) * 180.0 / M_PI; + + Matrix4x4 pose; + // NOTE! eyeToHead.m is a 3x4 matrix, not 4x4. But + // because of its arrangement, we can copy the 12 elements in and + // then transpose them to the right place. + memcpy(&pose._11, &eyeToHead.m, sizeof(eyeToHead.m)); + pose.Transpose(); + pose.Invert(); + headToEyeTransforms[eye] = pose; + } + aState.sensorState.CalcViewMatrices(headToEyeTransforms); +} + +void OpenVRSession::UpdateHeadsetPose(VRSystemState& aState) { + const uint32_t posesSize = ::vr::k_unTrackedDeviceIndex_Hmd + 1; + ::vr::TrackedDevicePose_t poses[posesSize]; + // Note: We *must* call WaitGetPoses in order for any rendering to happen at + // all. + mVRCompositor->WaitGetPoses(poses, posesSize, nullptr, 0); + + ::vr::Compositor_FrameTiming timing; + timing.m_nSize = sizeof(::vr::Compositor_FrameTiming); + if (mVRCompositor->GetFrameTiming(&timing)) { + aState.sensorState.timestamp = timing.m_flSystemTimeInSeconds; + } else { + // This should not happen, but log it just in case + fprintf(stderr, "OpenVR - IVRCompositor::GetFrameTiming failed"); + } + + if (poses[::vr::k_unTrackedDeviceIndex_Hmd].bDeviceIsConnected && + poses[::vr::k_unTrackedDeviceIndex_Hmd].bPoseIsValid && + poses[::vr::k_unTrackedDeviceIndex_Hmd].eTrackingResult == + ::vr::TrackingResult_Running_OK) { + const ::vr::TrackedDevicePose_t& pose = + poses[::vr::k_unTrackedDeviceIndex_Hmd]; + + gfx::Matrix4x4 m; + // NOTE! mDeviceToAbsoluteTracking is a 3x4 matrix, not 4x4. But + // because of its arrangement, we can copy the 12 elements in and + // then transpose them to the right place. We do this so we can + // pull out a Quaternion. + memcpy(&m._11, &pose.mDeviceToAbsoluteTracking, + sizeof(pose.mDeviceToAbsoluteTracking)); + m.Transpose(); + + gfx::Quaternion rot; + rot.SetFromRotationMatrix(m); + + aState.sensorState.flags = (VRDisplayCapabilityFlags)( + (int)aState.sensorState.flags | + (int)VRDisplayCapabilityFlags::Cap_Orientation); + aState.sensorState.pose.orientation[0] = rot.x; + aState.sensorState.pose.orientation[1] = rot.y; + aState.sensorState.pose.orientation[2] = rot.z; + aState.sensorState.pose.orientation[3] = rot.w; + aState.sensorState.pose.angularVelocity[0] = pose.vAngularVelocity.v[0]; + aState.sensorState.pose.angularVelocity[1] = pose.vAngularVelocity.v[1]; + aState.sensorState.pose.angularVelocity[2] = pose.vAngularVelocity.v[2]; + + aState.sensorState.flags = + (VRDisplayCapabilityFlags)((int)aState.sensorState.flags | + (int)VRDisplayCapabilityFlags::Cap_Position); + aState.sensorState.pose.position[0] = m._41; + aState.sensorState.pose.position[1] = m._42; + aState.sensorState.pose.position[2] = m._43; + aState.sensorState.pose.linearVelocity[0] = pose.vVelocity.v[0]; + aState.sensorState.pose.linearVelocity[1] = pose.vVelocity.v[1]; + aState.sensorState.pose.linearVelocity[2] = pose.vVelocity.v[2]; + } +} + +void OpenVRSession::EnumerateControllers(VRSystemState& aState) { + MOZ_ASSERT(mVRSystem); + + MutexAutoLock lock(mControllerHapticStateMutex); + + bool controllerPresent[kVRControllerMaxCount] = {false}; + uint32_t stateIndex = 0; + mActionsetFirefox = vr::k_ulInvalidActionSetHandle; + VRControllerType controllerType = VRControllerType::_empty; + + if (vr::VRInput()->GetActionSetHandle( + "/actions/firefox", &mActionsetFirefox) != vr::VRInputError_None) { + return; + } + + for (int8_t handIndex = 0; handIndex < OpenVRHand::Total; ++handIndex) { + if (handIndex == OpenVRHand::Left) { + if (vr::VRInput()->GetInputSourceHandle( + "/user/hand/left", &mControllerHand[OpenVRHand::Left].mSource) != + vr::VRInputError_None) { + continue; + } + } else if (handIndex == OpenVRHand::Right) { + if (vr::VRInput()->GetInputSourceHandle( + "/user/hand/right", + &mControllerHand[OpenVRHand::Right].mSource) != + vr::VRInputError_None) { + continue; + } + } else { + MOZ_ASSERT(false, "Unknown OpenVR hand type."); + } + + vr::InputOriginInfo_t originInfo; + if (vr::VRInput()->GetOriginTrackedDeviceInfo( + mControllerHand[handIndex].mSource, &originInfo, + sizeof(originInfo)) == vr::VRInputError_None && + originInfo.trackedDeviceIndex != vr::k_unTrackedDeviceIndexInvalid && + mVRSystem->IsTrackedDeviceConnected(originInfo.trackedDeviceIndex)) { + const ::vr::ETrackedDeviceClass deviceType = + mVRSystem->GetTrackedDeviceClass(originInfo.trackedDeviceIndex); + if (deviceType != ::vr::TrackedDeviceClass_Controller && + deviceType != ::vr::TrackedDeviceClass_GenericTracker) { + continue; + } + + if (mControllerDeviceIndex[stateIndex] != handIndex) { + VRControllerState& controllerState = aState.controllerState[stateIndex]; + + // Get controllers' action handles. + auto SetActionsToWriter = [&](ControllerAction& aAction) { + vr::VRInput()->GetActionHandle(aAction.name.BeginReading(), + &aAction.handle); + }; + + SetActionsToWriter(mControllerHand[handIndex].mActionPose); + SetActionsToWriter(mControllerHand[handIndex].mActionHaptic); + SetActionsToWriter(mControllerHand[handIndex].mActionTrackpad_Analog); + SetActionsToWriter(mControllerHand[handIndex].mActionTrackpad_Pressed); + SetActionsToWriter(mControllerHand[handIndex].mActionTrackpad_Touched); + SetActionsToWriter(mControllerHand[handIndex].mActionTrigger_Value); + SetActionsToWriter(mControllerHand[handIndex].mActionGrip_Pressed); + SetActionsToWriter(mControllerHand[handIndex].mActionGrip_Touched); + SetActionsToWriter(mControllerHand[handIndex].mActionMenu_Pressed); + SetActionsToWriter(mControllerHand[handIndex].mActionMenu_Touched); + SetActionsToWriter(mControllerHand[handIndex].mActionSystem_Pressed); + SetActionsToWriter(mControllerHand[handIndex].mActionSystem_Touched); + SetActionsToWriter(mControllerHand[handIndex].mActionA_Pressed); + SetActionsToWriter(mControllerHand[handIndex].mActionA_Touched); + SetActionsToWriter(mControllerHand[handIndex].mActionB_Pressed); + SetActionsToWriter(mControllerHand[handIndex].mActionB_Touched); + SetActionsToWriter(mControllerHand[handIndex].mActionThumbstick_Analog); + SetActionsToWriter( + mControllerHand[handIndex].mActionThumbstick_Pressed); + SetActionsToWriter( + mControllerHand[handIndex].mActionThumbstick_Touched); + SetActionsToWriter(mControllerHand[handIndex].mActionFingerIndex_Value); + SetActionsToWriter( + mControllerHand[handIndex].mActionFingerMiddle_Value); + SetActionsToWriter(mControllerHand[handIndex].mActionFingerRing_Value); + SetActionsToWriter(mControllerHand[handIndex].mActionFingerPinky_Value); + SetActionsToWriter(mControllerHand[handIndex].mActionBumper_Pressed); + + nsCString deviceId; + VRControllerType contrlType = VRControllerType::_empty; + GetControllerDeviceId(deviceType, originInfo.trackedDeviceIndex, + deviceId, contrlType); + // Controllers should be the same type with one VR display. + MOZ_ASSERT(controllerType == contrlType || + controllerType == VRControllerType::_empty); + controllerType = contrlType; + strncpy(controllerState.controllerName, deviceId.BeginReading(), + kVRControllerNameMaxLen); + controllerState.numHaptics = kNumOpenVRHaptics; + controllerState.targetRayMode = gfx::TargetRayMode::TrackedPointer; + controllerState.type = controllerType; + } + controllerPresent[stateIndex] = true; + mControllerDeviceIndex[stateIndex] = static_cast<OpenVRHand>(handIndex); + ++stateIndex; + } + } + + // Clear out entries for disconnected controllers + for (uint32_t stateIndex = 0; stateIndex < kVRControllerMaxCount; + stateIndex++) { + if (!controllerPresent[stateIndex] && + mControllerDeviceIndex[stateIndex] != OpenVRHand::None) { + mControllerDeviceIndex[stateIndex] = OpenVRHand::None; + memset(&aState.controllerState[stateIndex], 0, sizeof(VRControllerState)); + } + } + + // Create controller mapper + if (controllerType != VRControllerType::_empty) { + switch (controllerType) { + case VRControllerType::HTCVive: + mControllerMapper = MakeUnique<OpenVRViveMapper>(); + break; + case VRControllerType::HTCViveCosmos: + mControllerMapper = MakeUnique<OpenVRCosmosMapper>(); + break; +#if defined(XP_WIN) + case VRControllerType::MSMR: + mControllerMapper = MakeUnique<OpenVRWMRMapper>(); + break; +#endif + case VRControllerType::ValveIndex: + mControllerMapper = MakeUnique<OpenVRKnucklesMapper>(); + break; + default: + mControllerMapper = MakeUnique<OpenVRDefaultMapper>(); + NS_WARNING("Undefined controller type"); + break; + } + } +} + +void OpenVRSession::UpdateControllerButtons(VRSystemState& aState) { + MOZ_ASSERT(mVRSystem); + + for (uint32_t stateIndex = 0; stateIndex < kVRControllerMaxCount; + ++stateIndex) { + const OpenVRHand role = mControllerDeviceIndex[stateIndex]; + if (role == OpenVRHand::None) { + continue; + } + VRControllerState& controllerState = aState.controllerState[stateIndex]; + controllerState.hand = GetControllerHandFromControllerRole(role); + mControllerMapper->UpdateButtons(controllerState, mControllerHand[role]); + SetControllerSelectionAndSqueezeFrameId( + controllerState, aState.displayState.lastSubmittedFrameId); + } +} + +void OpenVRSession::UpdateControllerPoses(VRSystemState& aState) { + MOZ_ASSERT(mVRSystem); + + for (uint32_t stateIndex = 0; stateIndex < kVRControllerMaxCount; + ++stateIndex) { + const OpenVRHand role = mControllerDeviceIndex[stateIndex]; + if (role == OpenVRHand::None) { + continue; + } + VRControllerState& controllerState = aState.controllerState[stateIndex]; + vr::InputPoseActionData_t poseData; + if (vr::VRInput()->GetPoseActionDataRelativeToNow( + mControllerHand[role].mActionPose.handle, + vr::TrackingUniverseSeated, 0, &poseData, sizeof(poseData), + vr::k_ulInvalidInputValueHandle) != vr::VRInputError_None || + !poseData.bActive || !poseData.pose.bPoseIsValid) { + controllerState.isOrientationValid = false; + controllerState.isPositionValid = false; + } else { + const ::vr::TrackedDevicePose_t& pose = poseData.pose; + if (pose.bDeviceIsConnected) { + controllerState.flags = + (dom::GamepadCapabilityFlags::Cap_Orientation | + dom::GamepadCapabilityFlags::Cap_Position | + dom::GamepadCapabilityFlags::Cap_GripSpacePosition); + } else { + controllerState.flags = dom::GamepadCapabilityFlags::Cap_None; + } + if (pose.bPoseIsValid && + pose.eTrackingResult == ::vr::TrackingResult_Running_OK) { + gfx::Matrix4x4 m; + + // NOTE! mDeviceToAbsoluteTracking is a 3x4 matrix, not 4x4. But + // because of its arrangement, we can copy the 12 elements in and + // then transpose them to the right place. We do this so we can + // pull out a Quaternion. + memcpy(&m.components, &pose.mDeviceToAbsoluteTracking, + sizeof(pose.mDeviceToAbsoluteTracking)); + m.Transpose(); + + gfx::Quaternion rot; + rot.SetFromRotationMatrix(m); + + controllerState.pose.orientation[0] = rot.x; + controllerState.pose.orientation[1] = rot.y; + controllerState.pose.orientation[2] = rot.z; + controllerState.pose.orientation[3] = rot.w; + controllerState.pose.angularVelocity[0] = pose.vAngularVelocity.v[0]; + controllerState.pose.angularVelocity[1] = pose.vAngularVelocity.v[1]; + controllerState.pose.angularVelocity[2] = pose.vAngularVelocity.v[2]; + controllerState.pose.angularAcceleration[0] = 0.0f; + controllerState.pose.angularAcceleration[1] = 0.0f; + controllerState.pose.angularAcceleration[2] = 0.0f; + controllerState.isOrientationValid = true; + + controllerState.pose.position[0] = m._41; + controllerState.pose.position[1] = m._42; + controllerState.pose.position[2] = m._43; + controllerState.pose.linearVelocity[0] = pose.vVelocity.v[0]; + controllerState.pose.linearVelocity[1] = pose.vVelocity.v[1]; + controllerState.pose.linearVelocity[2] = pose.vVelocity.v[2]; + controllerState.pose.linearAcceleration[0] = 0.0f; + controllerState.pose.linearAcceleration[1] = 0.0f; + controllerState.pose.linearAcceleration[2] = 0.0f; + controllerState.isPositionValid = true; + + // Calculate its target ray space by shifting degrees in x-axis + // for ergonomic. + const float kPointerAngleDegrees = -0.698; // 40 degrees. + gfx::Matrix4x4 rayMtx(m); + rayMtx.RotateX(kPointerAngleDegrees); + gfx::Quaternion rayRot; + rayRot.SetFromRotationMatrix(rayMtx); + + controllerState.targetRayPose = controllerState.pose; + controllerState.targetRayPose.orientation[0] = rayRot.x; + controllerState.targetRayPose.orientation[1] = rayRot.y; + controllerState.targetRayPose.orientation[2] = rayRot.z; + controllerState.targetRayPose.orientation[3] = rayRot.w; + controllerState.targetRayPose.position[0] = rayMtx._41; + controllerState.targetRayPose.position[1] = rayMtx._42; + controllerState.targetRayPose.position[2] = rayMtx._43; + } + } + } +} + +void OpenVRSession::GetControllerDeviceId( + ::vr::ETrackedDeviceClass aDeviceType, + ::vr::TrackedDeviceIndex_t aDeviceIndex, nsCString& aId, + VRControllerType& aControllerType) { + switch (aDeviceType) { + case ::vr::TrackedDeviceClass_Controller: { + ::vr::ETrackedPropertyError err; + uint32_t requiredBufferLen; + bool isFound = false; + char charBuf[128]; + requiredBufferLen = mVRSystem->GetStringTrackedDeviceProperty( + aDeviceIndex, ::vr::Prop_RenderModelName_String, charBuf, 128, &err); + if (requiredBufferLen > 128) { + MOZ_CRASH("Larger than the buffer size."); + } + MOZ_ASSERT(requiredBufferLen && err == ::vr::TrackedProp_Success); + nsCString deviceId(charBuf); + if (deviceId.Find("vr_controller_vive") != kNotFound) { + aId.AssignLiteral("OpenVR Gamepad"); + isFound = true; + aControllerType = VRControllerType::HTCVive; + } else if (deviceId.Find("knuckles") != kNotFound || + deviceId.Find("valve_controller_knu") != kNotFound) { + aId.AssignLiteral("OpenVR Knuckles"); + isFound = true; + aControllerType = VRControllerType::ValveIndex; + } else if (deviceId.Find("vive_cosmos_controller") != kNotFound) { + aId.AssignLiteral("OpenVR Cosmos"); + isFound = true; + aControllerType = VRControllerType::HTCViveCosmos; + } + if (!isFound) { + requiredBufferLen = mVRSystem->GetStringTrackedDeviceProperty( + aDeviceIndex, ::vr::Prop_SerialNumber_String, charBuf, 128, &err); + if (requiredBufferLen > 128) { + MOZ_CRASH("Larger than the buffer size."); + } + MOZ_ASSERT(requiredBufferLen && err == ::vr::TrackedProp_Success); + deviceId.Assign(charBuf); + if (deviceId.Find("MRSOURCE") != kNotFound) { + aId.AssignLiteral("Spatial Controller (Spatial Interaction Source) "); + mIsWindowsMR = true; + isFound = true; + aControllerType = VRControllerType::MSMR; + } + } + if (!isFound) { + aId.AssignLiteral("OpenVR Undefined"); + aControllerType = VRControllerType::_empty; + } + break; + } + case ::vr::TrackedDeviceClass_GenericTracker: { + aId.AssignLiteral("OpenVR Tracker"); + aControllerType = VRControllerType::_empty; + break; + } + default: + MOZ_ASSERT(false); + break; + } +} + +void OpenVRSession::StartFrame(mozilla::gfx::VRSystemState& aSystemState) { + UpdateHeadsetPose(aSystemState); + UpdateEyeParameters(aSystemState); + EnumerateControllers(aSystemState); + + vr::VRActiveActionSet_t actionSet = {0}; + actionSet.ulActionSet = mActionsetFirefox; + vr::VRInput()->UpdateActionState(&actionSet, sizeof(actionSet), 1); + UpdateControllerButtons(aSystemState); + UpdateControllerPoses(aSystemState); + UpdateTelemetry(aSystemState); +} + +void OpenVRSession::ProcessEvents(mozilla::gfx::VRSystemState& aSystemState) { + bool isHmdPresent = ::vr::VR_IsHmdPresent(); + if (!isHmdPresent) { + mShouldQuit = true; + } + + ::vr::VREvent_t event; + while (mVRSystem && mVRSystem->PollNextEvent(&event, sizeof(event))) { + switch (event.eventType) { + case ::vr::VREvent_TrackedDeviceUserInteractionStarted: + if (event.trackedDeviceIndex == ::vr::k_unTrackedDeviceIndex_Hmd) { + aSystemState.displayState.isMounted = true; + } + break; + case ::vr::VREvent_TrackedDeviceUserInteractionEnded: + if (event.trackedDeviceIndex == ::vr::k_unTrackedDeviceIndex_Hmd) { + aSystemState.displayState.isMounted = false; + } + break; + case ::vr::EVREventType::VREvent_TrackedDeviceActivated: + if (event.trackedDeviceIndex == ::vr::k_unTrackedDeviceIndex_Hmd) { + aSystemState.displayState.isConnected = true; + } + break; + case ::vr::EVREventType::VREvent_TrackedDeviceDeactivated: + if (event.trackedDeviceIndex == ::vr::k_unTrackedDeviceIndex_Hmd) { + aSystemState.displayState.isConnected = false; + } + break; + case ::vr::EVREventType::VREvent_DriverRequestedQuit: + case ::vr::EVREventType::VREvent_Quit: + // When SteamVR runtime haven't been launched before viewing VR, + // SteamVR will send a VREvent_ProcessQuit event. It will tell the parent + // process to shutdown the VR process, and we need to avoid it. + // case ::vr::EVREventType::VREvent_ProcessQuit: + case ::vr::EVREventType::VREvent_QuitAcknowledged: + mShouldQuit = true; + break; + default: + // ignore + break; + } + } +} + +#if defined(XP_WIN) +bool OpenVRSession::SubmitFrame( + const mozilla::gfx::VRLayer_Stereo_Immersive& aLayer, + ID3D11Texture2D* aTexture) { + return SubmitFrame((void*)aTexture, ::vr::ETextureType::TextureType_DirectX, + aLayer.leftEyeRect, aLayer.rightEyeRect); +} +#elif defined(XP_MACOSX) +bool OpenVRSession::SubmitFrame( + const mozilla::gfx::VRLayer_Stereo_Immersive& aLayer, + const VRLayerTextureHandle& aTexture) { + return SubmitFrame(aTexture, ::vr::ETextureType::TextureType_IOSurface, + aLayer.leftEyeRect, aLayer.rightEyeRect); +} +#endif + +bool OpenVRSession::SubmitFrame(const VRLayerTextureHandle& aTextureHandle, + ::vr::ETextureType aTextureType, + const VRLayerEyeRect& aLeftEyeRect, + const VRLayerEyeRect& aRightEyeRect) { + ::vr::Texture_t tex; +#if defined(XP_MACOSX) + // We get aTextureHandle from get_SurfaceDescriptorMacIOSurface() at + // VRDisplayExternal. scaleFactor and opaque are skipped because they always + // are 1.0 and false. + RefPtr<MacIOSurface> surf = MacIOSurface::LookupSurface(aTextureHandle); + if (!surf) { + NS_WARNING("OpenVRSession::SubmitFrame failed to get a MacIOSurface"); + return false; + } + + CFTypeRefPtr<IOSurfaceRef> ioSurface = surf->GetIOSurfaceRef(); + tex.handle = (void*)ioSurface.get(); +#else + tex.handle = aTextureHandle; +#endif + tex.eType = aTextureType; + tex.eColorSpace = ::vr::EColorSpace::ColorSpace_Auto; + + ::vr::VRTextureBounds_t bounds; + bounds.uMin = aLeftEyeRect.x; + bounds.vMin = 1.0 - aLeftEyeRect.y; + bounds.uMax = aLeftEyeRect.x + aLeftEyeRect.width; + bounds.vMax = 1.0 - (aLeftEyeRect.y + aLeftEyeRect.height); + + ::vr::EVRCompositorError err; + err = mVRCompositor->Submit(::vr::EVREye::Eye_Left, &tex, &bounds); + if (err != ::vr::EVRCompositorError::VRCompositorError_None) { + printf_stderr("OpenVR Compositor Submit() failed.\n"); + } + + bounds.uMin = aRightEyeRect.x; + bounds.vMin = 1.0 - aRightEyeRect.y; + bounds.uMax = aRightEyeRect.x + aRightEyeRect.width; + bounds.vMax = 1.0 - (aRightEyeRect.y + aRightEyeRect.height); + + err = mVRCompositor->Submit(::vr::EVREye::Eye_Right, &tex, &bounds); + if (err != ::vr::EVRCompositorError::VRCompositorError_None) { + printf_stderr("OpenVR Compositor Submit() failed.\n"); + } + + mVRCompositor->PostPresentHandoff(); + return true; +} + +void OpenVRSession::StopPresentation() { + mVRCompositor->ClearLastSubmittedFrame(); + + ::vr::Compositor_CumulativeStats stats; + mVRCompositor->GetCumulativeStats(&stats, + sizeof(::vr::Compositor_CumulativeStats)); +} + +bool OpenVRSession::StartPresentation() { return true; } + +void OpenVRSession::VibrateHaptic(uint32_t aControllerIdx, + uint32_t aHapticIndex, float aIntensity, + float aDuration) { + MutexAutoLock lock(mControllerHapticStateMutex); + + // Initilize the haptic thread when the first time to do vibration. + if (!mHapticThread) { + NS_DispatchToMainThread(NS_NewRunnableFunction( + "OpenVRSession::StartHapticThread", [this]() { StartHapticThread(); })); + } + if (aHapticIndex >= kNumOpenVRHaptics || + aControllerIdx >= kVRControllerMaxCount) { + return; + } + + const OpenVRHand role = mControllerDeviceIndex[aControllerIdx]; + if (role == OpenVRHand::None) { + return; + } + mHapticPulseRemaining[aControllerIdx][aHapticIndex] = aDuration; + mHapticPulseIntensity[aControllerIdx][aHapticIndex] = aIntensity; +} + +void OpenVRSession::StartHapticThread() { + MOZ_ASSERT(NS_IsMainThread()); + if (!mHapticThread) { + mHapticThread = new VRThread("VR_OpenVR_Haptics"_ns); + } + mHapticThread->Start(); + StartHapticTimer(); +} + +void OpenVRSession::StopHapticThread() { + if (mHapticThread) { + NS_DispatchToMainThread(NS_NewRunnableFunction( + "mHapticThread::Shutdown", + [thread = mHapticThread]() { thread->Shutdown(); })); + mHapticThread = nullptr; + } +} + +void OpenVRSession::StartHapticTimer() { + if (!mHapticTimer && mHapticThread) { + mLastHapticUpdate = TimeStamp(); + mHapticTimer = NS_NewTimer(); + mHapticTimer->SetTarget(mHapticThread->GetThread()->EventTarget()); + mHapticTimer->InitWithNamedFuncCallback( + HapticTimerCallback, this, kVRHapticUpdateInterval, + nsITimer::TYPE_REPEATING_PRECISE_CAN_SKIP, + "OpenVRSession::HapticTimerCallback"); + } +} + +void OpenVRSession::StopHapticTimer() { + if (mHapticTimer) { + mHapticTimer->Cancel(); + mHapticTimer = nullptr; + } +} + +/*static*/ +void OpenVRSession::HapticTimerCallback(nsITimer* aTimer, void* aClosure) { + /** + * It is safe to use the pointer passed in aClosure to reference the + * OpenVRSession object as the timer is canceled in OpenVRSession::Shutdown, + * which is called by the OpenVRSession destructor, guaranteeing + * that this function runs if and only if the VRManager object is valid. + */ + OpenVRSession* self = static_cast<OpenVRSession*>(aClosure); + MOZ_ASSERT(self); + self->UpdateHaptics(); +} + +void OpenVRSession::UpdateHaptics() { + MOZ_ASSERT(mHapticThread->GetThread() == NS_GetCurrentThread()); + MOZ_ASSERT(mVRSystem); + + MutexAutoLock lock(mControllerHapticStateMutex); + + TimeStamp now = TimeStamp::Now(); + if (mLastHapticUpdate.IsNull()) { + mLastHapticUpdate = now; + return; + } + float deltaTime = (float)(now - mLastHapticUpdate).ToSeconds(); + mLastHapticUpdate = now; + + for (uint32_t stateIndex = 0; stateIndex < kVRControllerMaxCount; + ++stateIndex) { + const OpenVRHand role = mControllerDeviceIndex[stateIndex]; + if (role == OpenVRHand::None) { + continue; + } + for (uint32_t hapticIdx = 0; hapticIdx < kNumOpenVRHaptics; hapticIdx++) { + float intensity = mHapticPulseIntensity[stateIndex][hapticIdx]; + float duration = mHapticPulseRemaining[stateIndex][hapticIdx]; + if (duration <= 0.0f || intensity <= 0.0f) { + continue; + } + vr::VRInput()->TriggerHapticVibrationAction( + mControllerHand[role].mActionHaptic.handle, 0.0f, deltaTime, 4.0f, + intensity > 1.0f ? 1.0f : intensity, vr::k_ulInvalidInputValueHandle); + + duration -= deltaTime; + if (duration < 0.0f) { + duration = 0.0f; + } + mHapticPulseRemaining[stateIndex][hapticIdx] = duration; + } + } +} + +void OpenVRSession::StopVibrateHaptic(uint32_t aControllerIdx) { + MutexAutoLock lock(mControllerHapticStateMutex); + if (aControllerIdx >= kVRControllerMaxCount) { + return; + } + for (int iHaptic = 0; iHaptic < kNumOpenVRHaptics; iHaptic++) { + mHapticPulseRemaining[aControllerIdx][iHaptic] = 0.0f; + } +} + +void OpenVRSession::StopAllHaptics() { + MutexAutoLock lock(mControllerHapticStateMutex); + for (auto& controller : mHapticPulseRemaining) { + for (auto& haptic : controller) { + haptic = 0.0f; + } + } +} + +void OpenVRSession::UpdateTelemetry(VRSystemState& aSystemState) { + ::vr::Compositor_CumulativeStats stats; + mVRCompositor->GetCumulativeStats(&stats, + sizeof(::vr::Compositor_CumulativeStats)); + aSystemState.displayState.droppedFrameCount = stats.m_nNumReprojectedFrames; +} + +} // namespace mozilla::gfx diff --git a/gfx/vr/service/OpenVRSession.h b/gfx/vr/service/OpenVRSession.h new file mode 100644 index 0000000000..60ddcf4b29 --- /dev/null +++ b/gfx/vr/service/OpenVRSession.h @@ -0,0 +1,112 @@ +/* -*- 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/. */ + +#ifndef GFX_VR_SERVICE_OPENVRSESSION_H +#define GFX_VR_SERVICE_OPENVRSESSION_H + +#include "VRSession.h" + +#include "openvr.h" +#include "mozilla/TimeStamp.h" +#include "moz_external_vr.h" +#include "OpenVRControllerMapper.h" + +#if defined(XP_WIN) +# include <d3d11_1.h> +#endif +class nsITimer; + +namespace mozilla { +namespace gfx { +class VRThread; +class OpenVRControllerMapper; + +static const int kNumOpenVRHaptics = 1; + +enum OpenVRHand : int8_t { + Left = 0, + Right = 1, + Total = 2, + + None = -1 +}; + +class OpenVRSession : public VRSession { + public: + OpenVRSession(); + virtual ~OpenVRSession(); + + bool Initialize(mozilla::gfx::VRSystemState& aSystemState, + bool aDetectRuntimesOnly) override; + void Shutdown() override; + void ProcessEvents(mozilla::gfx::VRSystemState& aSystemState) override; + void StartFrame(mozilla::gfx::VRSystemState& aSystemState) override; + bool StartPresentation() override; + void StopPresentation() override; + void VibrateHaptic(uint32_t aControllerIdx, uint32_t aHapticIndex, + float aIntensity, float aDuration) override; + void StopVibrateHaptic(uint32_t aControllerIdx) override; + void StopAllHaptics() override; + + protected: +#if defined(XP_WIN) + bool SubmitFrame(const mozilla::gfx::VRLayer_Stereo_Immersive& aLayer, + ID3D11Texture2D* aTexture) override; +#elif defined(XP_MACOSX) + bool SubmitFrame(const mozilla::gfx::VRLayer_Stereo_Immersive& aLayer, + const VRLayerTextureHandle& aTexture) override; +#endif + + private: + // OpenVR State + ::vr::IVRSystem* mVRSystem = nullptr; + ::vr::IVRChaperone* mVRChaperone = nullptr; + ::vr::IVRCompositor* mVRCompositor = nullptr; + ::vr::VRActionSetHandle_t mActionsetFirefox = vr::k_ulInvalidActionSetHandle; + OpenVRHand mControllerDeviceIndex[kVRControllerMaxCount]; + ControllerInfo mControllerHand[OpenVRHand::Total]; + float mHapticPulseRemaining[kVRControllerMaxCount][kNumOpenVRHaptics]; + float mHapticPulseIntensity[kVRControllerMaxCount][kNumOpenVRHaptics]; + bool mIsWindowsMR; + TimeStamp mLastHapticUpdate; + + static void HapticTimerCallback(nsITimer* aTimer, void* aClosure); + bool InitState(mozilla::gfx::VRSystemState& aSystemState); + void UpdateStageParameters(mozilla::gfx::VRDisplayState& aState); + void UpdateEyeParameters(mozilla::gfx::VRSystemState& aState); + void UpdateHeadsetPose(mozilla::gfx::VRSystemState& aState); + void EnumerateControllers(VRSystemState& aState); + void UpdateControllerPoses(VRSystemState& aState); + void UpdateControllerButtons(VRSystemState& aState); + void UpdateTelemetry(VRSystemState& aSystemState); + bool SetupContollerActions(); + + bool SubmitFrame(const VRLayerTextureHandle& aTextureHandle, + ::vr::ETextureType aTextureType, + const VRLayerEyeRect& aLeftEyeRect, + const VRLayerEyeRect& aRightEyeRect); +#if defined(XP_WIN) + bool CreateD3DObjects(); +#endif + void GetControllerDeviceId(::vr::ETrackedDeviceClass aDeviceType, + ::vr::TrackedDeviceIndex_t aDeviceIndex, + nsCString& aId, + mozilla::gfx::VRControllerType& aControllerType); + void UpdateHaptics(); + void StartHapticThread(); + void StopHapticThread(); + void StartHapticTimer(); + void StopHapticTimer(); + RefPtr<nsITimer> mHapticTimer; + RefPtr<VRThread> mHapticThread; + mozilla::Mutex mControllerHapticStateMutex; + UniquePtr<OpenVRControllerMapper> mControllerMapper; +}; + +} // namespace gfx +} // namespace mozilla + +#endif // GFX_VR_SERVICE_OPENVRSESSION_H diff --git a/gfx/vr/service/OpenVRViveMapper.cpp b/gfx/vr/service/OpenVRViveMapper.cpp new file mode 100644 index 0000000000..a5fadd6e37 --- /dev/null +++ b/gfx/vr/service/OpenVRViveMapper.cpp @@ -0,0 +1,43 @@ +/* -*- 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 "OpenVRViveMapper.h" + +#include "moz_external_vr.h" +#include "VRSession.h" + +namespace mozilla::gfx { + +void OpenVRViveMapper::UpdateButtons(VRControllerState& aControllerState, + ControllerInfo& aControllerInfo) { + mNumButtons = mNumAxes = 0; + // Button 0: Trigger + GetTriggerValueFromAction(aControllerState, + aControllerInfo.mActionTrigger_Value); + // Button 1: Grip + GetButtonValueFromAction(aControllerState, + aControllerInfo.mActionGrip_Pressed, + aControllerInfo.mActionGrip_Touched); + // Button 2: Trackpad + GetButtonValueFromAction(aControllerState, + aControllerInfo.mActionTrackpad_Pressed, + aControllerInfo.mActionTrackpad_Touched); + // Button 3: a placeholder button for thumbstick. + ++mNumButtons; + // Button 4: Menu + GetButtonValueFromAction(aControllerState, + aControllerInfo.mActionMenu_Pressed, + aControllerInfo.mActionMenu_Touched); + + // Axis 0, 1: Trackpad + GetAxisValueFromAction(aControllerState, + aControllerInfo.mActionTrackpad_Analog); + + aControllerState.numButtons = mNumButtons; + aControllerState.numAxes = mNumAxes; +} + +} // namespace mozilla::gfx diff --git a/gfx/vr/service/OpenVRViveMapper.h b/gfx/vr/service/OpenVRViveMapper.h new file mode 100644 index 0000000000..93b999df5c --- /dev/null +++ b/gfx/vr/service/OpenVRViveMapper.h @@ -0,0 +1,26 @@ +/* -*- 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/. */ + +#ifndef GFX_VR_SERVICE_OPENVRVIVEMAPPER_H +#define GFX_VR_SERVICE_OPENVRVIVEMAPPER_H + +#include "OpenVRControllerMapper.h" + +namespace mozilla { +namespace gfx { + +class OpenVRViveMapper : public OpenVRControllerMapper { + public: + OpenVRViveMapper() = default; + virtual ~OpenVRViveMapper() = default; + virtual void UpdateButtons(VRControllerState& aControllerState, + ControllerInfo& aControllerInfo); +}; + +} // namespace gfx +} // namespace mozilla + +#endif // GFX_VR_SERVICE_OPENVRVIVEMAPPER_H diff --git a/gfx/vr/service/OpenVRWMRMapper.cpp b/gfx/vr/service/OpenVRWMRMapper.cpp new file mode 100644 index 0000000000..4611d9ee0a --- /dev/null +++ b/gfx/vr/service/OpenVRWMRMapper.cpp @@ -0,0 +1,51 @@ +/* -*- 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 "OpenVRWMRMapper.h" + +#include "moz_external_vr.h" +#include "VRSession.h" + +namespace mozilla::gfx { + +void OpenVRWMRMapper::UpdateButtons(VRControllerState& aControllerState, + ControllerInfo& aControllerInfo) { + mNumButtons = mNumAxes = 0; + // Button 0: Trigger + GetTriggerValueFromAction(aControllerState, + aControllerInfo.mActionTrigger_Value); + // Button 1: Grip + GetButtonValueFromAction(aControllerState, + aControllerInfo.mActionGrip_Pressed, + aControllerInfo.mActionGrip_Touched); + // Button 2: Touchpad + GetButtonValueFromAction(aControllerState, + aControllerInfo.mActionTrackpad_Pressed, + aControllerInfo.mActionTrackpad_Touched); + // Button 3: Thumbstick. + GetButtonValueFromAction(aControllerState, + aControllerInfo.mActionThumbstick_Pressed, + aControllerInfo.mActionThumbstick_Touched); + // Button 4: Menu + GetButtonValueFromAction(aControllerState, + aControllerInfo.mActionMenu_Pressed, + aControllerInfo.mActionMenu_Touched); + + // Compared to Edge, we have a wrong implementation for the vertical axis + // value. In order to not affect the current VR content, we add a workaround + // for yAxis. + // Axis 0, 1: Trackpad + GetAxisValueFromAction(aControllerState, + aControllerInfo.mActionTrackpad_Analog, true); + // Axis 2, 3: Thumbstick + GetAxisValueFromAction(aControllerState, + aControllerInfo.mActionThumbstick_Analog, true); + + aControllerState.numButtons = mNumButtons; + aControllerState.numAxes = mNumAxes; +} + +} // namespace mozilla::gfx diff --git a/gfx/vr/service/OpenVRWMRMapper.h b/gfx/vr/service/OpenVRWMRMapper.h new file mode 100644 index 0000000000..538d1edfeb --- /dev/null +++ b/gfx/vr/service/OpenVRWMRMapper.h @@ -0,0 +1,26 @@ +/* -*- 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/. */ + +#ifndef GFX_VR_SERVICE_OPENVRWMRMAPPER_H +#define GFX_VR_SERVICE_OPENVRWMRMAPPER_H + +#include "OpenVRControllerMapper.h" + +namespace mozilla { +namespace gfx { + +class OpenVRWMRMapper : public OpenVRControllerMapper { + public: + OpenVRWMRMapper() = default; + virtual ~OpenVRWMRMapper() = default; + virtual void UpdateButtons(VRControllerState& aControllerState, + ControllerInfo& aControllerInfo); +}; + +} // namespace gfx +} // namespace mozilla + +#endif // GFX_VR_SERVICE_OPENVRWMRMAPPER_H diff --git a/gfx/vr/service/PuppetSession.cpp b/gfx/vr/service/PuppetSession.cpp new file mode 100644 index 0000000000..ab00e3c653 --- /dev/null +++ b/gfx/vr/service/PuppetSession.cpp @@ -0,0 +1,124 @@ +/* -*- 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 "PuppetSession.h" + +#include "nsString.h" +#include "VRPuppetCommandBuffer.h" +#include "mozilla/StaticPrefs_dom.h" + +#if defined(XP_WIN) +# include <d3d11.h> +# include "mozilla/gfx/DeviceManagerDx.h" +#elif defined(XP_MACOSX) +# include "mozilla/gfx/MacIOSurface.h" +#endif + +using namespace mozilla::gfx; + +namespace mozilla::gfx { + +PuppetSession::PuppetSession() : VRSession() {} + +PuppetSession::~PuppetSession() { Shutdown(); } + +bool PuppetSession::Initialize(mozilla::gfx::VRSystemState& aSystemState, + bool aDetectRuntimesOnly) { + if (!StaticPrefs::dom_vr_enabled() || !StaticPrefs::dom_vr_puppet_enabled()) { + return false; + } + if (!VRPuppetCommandBuffer::IsCreated()) { + // We only want to initialize VRPuppetCommandBuffer on the main thread. + // We can assume if it is not initialized, that the puppet display + // would not be enumerated. + return false; + } + if (aDetectRuntimesOnly) { + aSystemState.displayState.capabilityFlags |= + VRDisplayCapabilityFlags::Cap_ImmersiveVR; + return false; + } + VRPuppetCommandBuffer::Get().Run(aSystemState); + if (!aSystemState.displayState.isConnected) { + return false; + } +#if defined(XP_WIN) + if (!CreateD3DObjects()) { + Shutdown(); + return false; + } +#endif + + // Succeeded + return true; +} + +#if defined(XP_WIN) +bool PuppetSession::CreateD3DObjects() { + RefPtr<ID3D11Device> device = gfx::DeviceManagerDx::Get()->GetVRDevice(); + if (!device) { + return false; + } + if (!CreateD3DContext(device)) { + return false; + } + return true; +} +#endif + +void PuppetSession::Shutdown() {} + +void PuppetSession::StartFrame(mozilla::gfx::VRSystemState& aSystemState) { + VRPuppetCommandBuffer::Get().Run(aSystemState); +} + +void PuppetSession::ProcessEvents(mozilla::gfx::VRSystemState& aSystemState) { + VRPuppetCommandBuffer& puppet = VRPuppetCommandBuffer::Get(); + puppet.Run(aSystemState); + if (!aSystemState.displayState.isConnected) { + mShouldQuit = true; + } +} + +#if defined(XP_WIN) +bool PuppetSession::SubmitFrame( + const mozilla::gfx::VRLayer_Stereo_Immersive& aLayer, + ID3D11Texture2D* aTexture) { + return VRPuppetCommandBuffer::Get().SubmitFrame(); +} +#elif defined(XP_MACOSX) +bool PuppetSession::SubmitFrame( + const mozilla::gfx::VRLayer_Stereo_Immersive& aLayer, + const VRLayerTextureHandle& aTexture) { + return VRPuppetCommandBuffer::Get().SubmitFrame(); +} +#endif + +void PuppetSession::StopPresentation() { + VRPuppetCommandBuffer::Get().StopPresentation(); +} + +bool PuppetSession::StartPresentation() { + VRPuppetCommandBuffer::Get().StartPresentation(); + return true; +} + +void PuppetSession::VibrateHaptic(uint32_t aControllerIdx, + uint32_t aHapticIndex, float aIntensity, + float aDuration) { + VRPuppetCommandBuffer::Get().VibrateHaptic(aControllerIdx, aHapticIndex, + aIntensity, aDuration); +} + +void PuppetSession::StopVibrateHaptic(uint32_t aControllerIdx) { + VRPuppetCommandBuffer::Get().StopVibrateHaptic(aControllerIdx); +} + +void PuppetSession::StopAllHaptics() { + VRPuppetCommandBuffer::Get().StopAllHaptics(); +} + +} // namespace mozilla::gfx diff --git a/gfx/vr/service/PuppetSession.h b/gfx/vr/service/PuppetSession.h new file mode 100644 index 0000000000..fb0a7c3517 --- /dev/null +++ b/gfx/vr/service/PuppetSession.h @@ -0,0 +1,58 @@ +/* -*- 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/. */ + +#ifndef GFX_VR_SERVICE_PUPPETSESSION_H +#define GFX_VR_SERVICE_PUPPETSESSION_H + +#include "VRSession.h" + +#include "mozilla/TimeStamp.h" +#include "moz_external_vr.h" + +#if defined(XP_WIN) +# include <d3d11_1.h> +#endif +class nsITimer; + +namespace mozilla { +namespace gfx { + +class PuppetSession : public VRSession { + public: + PuppetSession(); + virtual ~PuppetSession(); + + bool Initialize(mozilla::gfx::VRSystemState& aSystemState, + bool aDetectRuntimesOnly) override; + void Shutdown() override; + void ProcessEvents(mozilla::gfx::VRSystemState& aSystemState) override; + void StartFrame(mozilla::gfx::VRSystemState& aSystemState) override; + bool StartPresentation() override; + void StopPresentation() override; + void VibrateHaptic(uint32_t aControllerIdx, uint32_t aHapticIndex, + float aIntensity, float aDuration) override; + void StopVibrateHaptic(uint32_t aControllerIdx) override; + void StopAllHaptics() override; + + protected: +#if defined(XP_WIN) + bool SubmitFrame(const mozilla::gfx::VRLayer_Stereo_Immersive& aLayer, + ID3D11Texture2D* aTexture) override; +#elif defined(XP_MACOSX) + bool SubmitFrame(const mozilla::gfx::VRLayer_Stereo_Immersive& aLayer, + const VRLayerTextureHandle& aTexture) override; +#endif + + private: +#if defined(XP_WIN) + bool CreateD3DObjects(); +#endif +}; + +} // namespace gfx +} // namespace mozilla + +#endif // GFX_VR_SERVICE_PUPPETSESSION_H diff --git a/gfx/vr/service/VRService.cpp b/gfx/vr/service/VRService.cpp new file mode 100644 index 0000000000..2b774c2531 --- /dev/null +++ b/gfx/vr/service/VRService.cpp @@ -0,0 +1,418 @@ +/* -*- 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 "VRService.h" + +#include <cstring> // for memcmp + +#include "../VRShMem.h" +#include "../gfxVRMutex.h" +#include "PuppetSession.h" +#include "mozilla/BackgroundHangMonitor.h" +#include "mozilla/StaticPrefs_dom.h" +#include "nsThread.h" +#include "nsXULAppAPI.h" + +#if defined(XP_WIN) +# include "OculusSession.h" +#endif + +#if defined(XP_WIN) || defined(XP_MACOSX) || \ + (defined(XP_LINUX) && !defined(MOZ_WIDGET_ANDROID)) +# include "OpenVRSession.h" +#endif +#if !defined(MOZ_WIDGET_ANDROID) +# include "OSVRSession.h" +#endif + +using namespace mozilla; +using namespace mozilla::gfx; + +namespace { + +int64_t FrameIDFromBrowserState(const mozilla::gfx::VRBrowserState& aState) { + for (const auto& layer : aState.layerState) { + if (layer.type == VRLayerType::LayerType_Stereo_Immersive) { + return layer.layer_stereo_immersive.frameId; + } + } + return 0; +} + +bool IsImmersiveContentActive(const mozilla::gfx::VRBrowserState& aState) { + for (const auto& layer : aState.layerState) { + if (layer.type == VRLayerType::LayerType_Stereo_Immersive) { + return true; + } + } + return false; +} + +} // anonymous namespace + +/*static*/ +already_AddRefed<VRService> VRService::Create( + volatile VRExternalShmem* aShmem) { + RefPtr<VRService> service = new VRService(aShmem); + return service.forget(); +} + +VRService::VRService(volatile VRExternalShmem* aShmem) + : mSystemState{}, + mBrowserState{}, + mShutdownRequested(false), + mLastHapticState{}, + mFrameStartTime{} { + // When we have the VR process, we map the memory + // of mAPIShmem from GPU process and pass it to the CTOR. + // If we don't have the VR process, we will instantiate + // mAPIShmem in VRService. + mShmem = new VRShMem(aShmem, aShmem == nullptr /*aRequiresMutex*/); +} + +VRService::~VRService() { + // PSA: We must store the value of any staticPrefs preferences as this + // destructor will be called after staticPrefs has been shut down. + StopInternal(true /*aFromDtor*/); +} + +void VRService::Refresh() { + if (mShmem != nullptr && mShmem->IsDisplayStateShutdown()) { + Stop(); + } +} + +void VRService::Start() { + if (!mServiceThread) { + /** + * We must ensure that any time the service is re-started, that + * the VRSystemState is reset, including mSystemState.enumerationCompleted + * This must happen before VRService::Start returns to the caller, in order + * to prevent the WebVR/WebXR promises from being resolved before the + * enumeration has been completed. + */ + memset(&mSystemState, 0, sizeof(mSystemState)); + PushState(mSystemState); + RefPtr<VRService> self = this; + nsCOMPtr<nsIThread> thread; + nsresult rv = NS_NewNamedThread( + "VRService", getter_AddRefs(thread), + NS_NewRunnableFunction("VRService::ServiceThreadStartup", [self]() { + self->mBackgroundHangMonitor = + MakeUnique<mozilla::BackgroundHangMonitor>( + "VRService", + /* Timeout values are powers-of-two to enable us get better + data. 128ms is chosen for transient hangs because 8Hz + should be the minimally acceptable goal for Compositor + responsiveness (normal goal is 60Hz). */ + 128, + /* 2048ms is chosen for permanent hangs because it's longer + * than most Compositor hangs seen in the wild, but is short + * enough to not miss getting native hang stacks. */ + 2048); + static_cast<nsThread*>(NS_GetCurrentThread()) + ->SetUseHangMonitor(true); + })); + + if (NS_FAILED(rv)) { + return; + } + thread.swap(mServiceThread); + // ServiceInitialize needs mServiceThread to be set in order to be able to + // assert that it's running on the right thread as well as dispatching new + // tasks. It can't be run within the NS_NewRunnableFunction initial event. + MOZ_ALWAYS_SUCCEEDS(mServiceThread->Dispatch( + NewRunnableMethod("gfx::VRService::ServiceInitialize", this, + &VRService::ServiceInitialize))); + } +} + +void VRService::Stop() { StopInternal(false /*aFromDtor*/); } + +void VRService::StopInternal(bool aFromDtor) { + if (mServiceThread) { + // We must disable the background hang monitor before we can shutdown this + // thread. Dispatched a last task to do so. No task will be allowed to run + // on the service thread after this one. + mServiceThread->Dispatch(NS_NewRunnableFunction( + "VRService::StopInternal", [self = RefPtr<VRService>(this), this] { + static_cast<nsThread*>(NS_GetCurrentThread()) + ->SetUseHangMonitor(false); + mBackgroundHangMonitor = nullptr; + })); + mShutdownRequested = true; + mServiceThread->Shutdown(); + mServiceThread = nullptr; + } + + if (mShmem != nullptr && (aFromDtor || !mShmem->IsSharedExternalShmem())) { + // Only leave the VRShMem and clean up the pointer when the struct + // was not passed in. Otherwise, VRService will no longer have a + // way to access that struct if VRService starts again. + mShmem->LeaveShMem(); + delete mShmem; + mShmem = nullptr; + } + + mSession = nullptr; +} + +bool VRService::InitShmem() { return mShmem->JoinShMem(); } + +bool VRService::IsInServiceThread() { + return mServiceThread && mServiceThread->IsOnCurrentThread(); +} + +void VRService::ServiceInitialize() { + MOZ_ASSERT(IsInServiceThread()); + + if (!InitShmem()) { + return; + } + + mShutdownRequested = false; + // Get initial state from the browser + PullState(mBrowserState); + + // Try to start a VRSession + UniquePtr<VRSession> session; + + if (StaticPrefs::dom_vr_puppet_enabled()) { + // When the VR Puppet is enabled, we don't want + // to enumerate any real devices + session = MakeUnique<PuppetSession>(); + if (!session->Initialize(mSystemState, mBrowserState.detectRuntimesOnly)) { + session = nullptr; + } + } else { + // We try Oculus first to ensure we use Oculus + // devices trough the most native interface + // when possible. +#if defined(XP_WIN) + // Try Oculus + if (!session) { + session = MakeUnique<OculusSession>(); + if (!session->Initialize(mSystemState, + mBrowserState.detectRuntimesOnly)) { + session = nullptr; + } + } +#endif + +#if defined(XP_WIN) || defined(XP_MACOSX) || \ + (defined(XP_LINUX) && !defined(MOZ_WIDGET_ANDROID)) + // Try OpenVR + if (!session) { + session = MakeUnique<OpenVRSession>(); + if (!session->Initialize(mSystemState, + mBrowserState.detectRuntimesOnly)) { + session = nullptr; + } + } +#endif +#if !defined(MOZ_WIDGET_ANDROID) + // Try OSVR + if (!session) { + session = MakeUnique<OSVRSession>(); + if (!session->Initialize(mSystemState, + mBrowserState.detectRuntimesOnly)) { + session = nullptr; + } + } +#endif + + } // if (staticPrefs:VRPuppetEnabled()) + + if (session) { + mSession = std::move(session); + // Setting enumerationCompleted to true indicates to the browser + // that it should resolve any promises in the WebVR/WebXR API + // waiting for hardware detection. + mSystemState.enumerationCompleted = true; + PushState(mSystemState); + + mServiceThread->Dispatch( + NewRunnableMethod("gfx::VRService::ServiceWaitForImmersive", this, + &VRService::ServiceWaitForImmersive)); + } else { + // VR hardware was not detected. + // We must inform the browser of the failure so it may try again + // later and resolve WebVR promises. A failure or shutdown is + // indicated by enumerationCompleted being set to true, with all + // other fields remaining zeroed out. + VRDisplayCapabilityFlags capFlags = + mSystemState.displayState.capabilityFlags; + memset(&mSystemState, 0, sizeof(mSystemState)); + mSystemState.enumerationCompleted = true; + + if (mBrowserState.detectRuntimesOnly) { + mSystemState.displayState.capabilityFlags = capFlags; + } else { + mSystemState.displayState.minRestartInterval = + StaticPrefs::dom_vr_external_notdetected_timeout(); + } + mSystemState.displayState.shutdown = true; + PushState(mSystemState); + } +} + +void VRService::ServiceShutdown() { + MOZ_ASSERT(IsInServiceThread()); + + // Notify the browser that we have shut down. + // This is indicated by enumerationCompleted being set + // to true, with all other fields remaining zeroed out. + memset(&mSystemState, 0, sizeof(mSystemState)); + mSystemState.enumerationCompleted = true; + mSystemState.displayState.shutdown = true; + if (mSession && mSession->ShouldQuit()) { + mSystemState.displayState.minRestartInterval = + StaticPrefs::dom_vr_external_quit_timeout(); + } + PushState(mSystemState); + mSession = nullptr; +} + +void VRService::ServiceWaitForImmersive() { + MOZ_ASSERT(IsInServiceThread()); + MOZ_ASSERT(mSession); + + mSession->ProcessEvents(mSystemState); + PushState(mSystemState); + PullState(mBrowserState); + + if (mSession->ShouldQuit() || mShutdownRequested) { + // Shut down + mServiceThread->Dispatch(NewRunnableMethod( + "gfx::VRService::ServiceShutdown", this, &VRService::ServiceShutdown)); + } else if (IsImmersiveContentActive(mBrowserState)) { + // Enter Immersive Mode + mSession->StartPresentation(); + mSession->StartFrame(mSystemState); + PushState(mSystemState); + + mServiceThread->Dispatch( + NewRunnableMethod("gfx::VRService::ServiceImmersiveMode", this, + &VRService::ServiceImmersiveMode)); + } else { + // Continue waiting for immersive mode + mServiceThread->Dispatch( + NewRunnableMethod("gfx::VRService::ServiceWaitForImmersive", this, + &VRService::ServiceWaitForImmersive)); + } +} + +void VRService::ServiceImmersiveMode() { + MOZ_ASSERT(IsInServiceThread()); + MOZ_ASSERT(mSession); + + mSession->ProcessEvents(mSystemState); + UpdateHaptics(); + PushState(mSystemState); + PullState(mBrowserState); + + if (mSession->ShouldQuit() || mShutdownRequested) { + // Shut down + mServiceThread->Dispatch(NewRunnableMethod( + "gfx::VRService::ServiceShutdown", this, &VRService::ServiceShutdown)); + return; + } + + if (!IsImmersiveContentActive(mBrowserState)) { + // Exit immersive mode + mSession->StopAllHaptics(); + mSession->StopPresentation(); + mServiceThread->Dispatch( + NewRunnableMethod("gfx::VRService::ServiceWaitForImmersive", this, + &VRService::ServiceWaitForImmersive)); + return; + } + + uint64_t newFrameId = FrameIDFromBrowserState(mBrowserState); + if (newFrameId != mSystemState.displayState.lastSubmittedFrameId) { + // A new immersive frame has been received. + // Submit the textures to the VR system compositor. + bool success = false; + for (const auto& layer : mBrowserState.layerState) { + if (layer.type == VRLayerType::LayerType_Stereo_Immersive) { + // SubmitFrame may block in order to control the timing for + // the next frame start + success = mSession->SubmitFrame(layer.layer_stereo_immersive); + break; + } + } + + // Changing mLastSubmittedFrameId triggers a new frame to start + // rendering. Changes to mLastSubmittedFrameId and the values + // used for rendering, such as headset pose, must be pushed + // atomically to the browser. + mSystemState.displayState.lastSubmittedFrameId = newFrameId; + mSystemState.displayState.lastSubmittedFrameSuccessful = success; + + // StartFrame may block to control the timing for the next frame start + mSession->StartFrame(mSystemState); + mSystemState.sensorState.inputFrameID++; + size_t historyIndex = + mSystemState.sensorState.inputFrameID % ArrayLength(mFrameStartTime); + mFrameStartTime[historyIndex] = TimeStamp::Now(); + PushState(mSystemState); + } + + // Continue immersive mode + mServiceThread->Dispatch( + NewRunnableMethod("gfx::VRService::ServiceImmersiveMode", this, + &VRService::ServiceImmersiveMode)); +} + +void VRService::UpdateHaptics() { + MOZ_ASSERT(IsInServiceThread()); + MOZ_ASSERT(mSession); + + for (size_t i = 0; i < ArrayLength(mBrowserState.hapticState); i++) { + VRHapticState& state = mBrowserState.hapticState[i]; + VRHapticState& lastState = mLastHapticState[i]; + // Note that VRHapticState is asserted to be a POD type, thus memcmp is safe + if (memcmp(&state, &lastState, sizeof(VRHapticState)) == 0) { + // No change since the last update + continue; + } + if (state.inputFrameID == 0) { + // The haptic feedback was stopped + mSession->StopVibrateHaptic(state.controllerIndex); + } else { + TimeStamp now; + if (now.IsNull()) { + // TimeStamp::Now() is expensive, so we + // must call it only when needed and save the + // output for further loop iterations. + now = TimeStamp::Now(); + } + // This is a new haptic pulse, or we are overriding a prior one + size_t historyIndex = state.inputFrameID % ArrayLength(mFrameStartTime); + float startOffset = + (float)(now - mFrameStartTime[historyIndex]).ToSeconds(); + + // state.pulseStart is guaranteed never to be in the future + mSession->VibrateHaptic( + state.controllerIndex, state.hapticIndex, state.pulseIntensity, + state.pulseDuration + state.pulseStart - startOffset); + } + // Record the state for comparison in the next run + memcpy(&lastState, &state, sizeof(VRHapticState)); + } +} + +void VRService::PushState(const mozilla::gfx::VRSystemState& aState) { + if (mShmem != nullptr) { + mShmem->PushSystemState(aState); + } +} + +void VRService::PullState(mozilla::gfx::VRBrowserState& aState) { + if (mShmem != nullptr) { + mShmem->PullBrowserState(aState); + } +} diff --git a/gfx/vr/service/VRService.h b/gfx/vr/service/VRService.h new file mode 100644 index 0000000000..f964d4eea9 --- /dev/null +++ b/gfx/vr/service/VRService.h @@ -0,0 +1,92 @@ +/* -*- 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/. */ + +#ifndef GFX_VR_SERVICE_VRSERVICE_H +#define GFX_VR_SERVICE_VRSERVICE_H + +#include "moz_external_vr.h" +#include "base/process.h" // for base::ProcessHandle +#include "mozilla/TimeStamp.h" +#include "mozilla/UniquePtr.h" +#include "nsCOMPtr.h" + +class nsIThread; +namespace mozilla { +class BackgroundHangMonitor; +namespace gfx { + +class VRSession; +class VRShMem; + +static const int kVRFrameTimingHistoryDepth = 100; + +class VRService { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VRService) + static already_AddRefed<VRService> Create( + volatile VRExternalShmem* aShmem = nullptr); + + void Refresh(); + void Start(); + void Stop(); + + private: + explicit VRService(volatile VRExternalShmem* aShmem); + ~VRService(); + + void StopInternal(bool aFromDtor); + + bool InitShmem(); + void PushState(const mozilla::gfx::VRSystemState& aState); + void PullState(mozilla::gfx::VRBrowserState& aState); + + /** + * VRSystemState contains the most recent state of the VR + * system, to be shared with the browser by Shmem. + * mSystemState is the VR Service copy of this data, which + * is memcpy'ed atomically to the Shmem. + * VRSystemState is written by the VR Service, but read-only + * by the browser. + */ + VRSystemState mSystemState; + /** + * VRBrowserState contains the most recent state of the browser. + * mBrowserState is memcpy'ed from the Shmem atomically + */ + VRBrowserState mBrowserState; + + UniquePtr<VRSession> mSession; + nsCOMPtr<nsIThread> mServiceThread; + // Only ever accessed on the service thread. + UniquePtr<mozilla::BackgroundHangMonitor> mBackgroundHangMonitor; + + bool mShutdownRequested; + + // Note: mShmem doesn't support RefPtr; thus, do not share this private + // pointer so that its lifetime can still be controlled by VRService + VRShMem* mShmem; + VRHapticState mLastHapticState[kVRHapticsMaxCount]; + TimeStamp mFrameStartTime[kVRFrameTimingHistoryDepth]; + + bool IsInServiceThread(); + void UpdateHaptics(); + + /** + * The VR Service thread is a state machine that always has one + * task queued depending on the state. + * + * VR Service thread state task functions: + */ + void ServiceInitialize(); + void ServiceShutdown(); + void ServiceWaitForImmersive(); + void ServiceImmersiveMode(); +}; + +} // namespace gfx +} // namespace mozilla + +#endif // GFX_VR_SERVICE_VRSERVICE_H diff --git a/gfx/vr/service/VRSession.cpp b/gfx/vr/service/VRSession.cpp new file mode 100644 index 0000000000..44f9fb14bc --- /dev/null +++ b/gfx/vr/service/VRSession.cpp @@ -0,0 +1,196 @@ +/* -*- 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 "VRSession.h" + +#include "moz_external_vr.h" + +#if defined(XP_WIN) +# include <d3d11.h> +#endif // defined(XP_WIN) + +#if defined(MOZILLA_INTERNAL_API) +# if defined(XP_WIN) +# include "mozilla/gfx/Logging.h" +# endif +#else +# define NS_WARNING(s) +#endif + +using namespace mozilla::gfx; + +VRSession::VRSession() + : mShouldQuit(false) +#ifdef XP_WIN + , + mDevice(nullptr), + mContext(nullptr), + mDeviceContextState(nullptr) +#endif +{ +} + +#ifdef XP_WIN +VRSession::~VRSession() { + if (mDevice != nullptr) { + mDevice->Release(); + mDevice = nullptr; + } + + if (mContext != nullptr) { + mContext->Release(); + mContext = nullptr; + } + + if (mDeviceContextState != nullptr) { + mDeviceContextState->Release(); + mDeviceContextState = nullptr; + } +} +#endif + +#if defined(XP_WIN) +bool VRSession::CreateD3DContext(ID3D11Device* aDevice) { + if (!mDevice) { + if (!aDevice) { + NS_WARNING("VRSession::CreateD3DObjects failed to get a D3D11Device"); + return false; + } + if (FAILED(aDevice->QueryInterface(IID_PPV_ARGS(&mDevice)))) { + NS_WARNING("VRSession::CreateD3DObjects failed to get a D3D11Device1"); + return false; + } + } + if (!mContext) { + mDevice->GetImmediateContext1(&mContext); + if (!mContext) { + NS_WARNING( + "VRSession::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, + &mDeviceContextState); + } + if (!mDeviceContextState) { + NS_WARNING( + "VRSession::CreateD3DObjects failed to get a D3D11DeviceContextState"); + return false; + } + return true; +} + +ID3D11Device1* VRSession::GetD3DDevice() { return mDevice; } + +ID3D11DeviceContext1* VRSession::GetD3DDeviceContext() { return mContext; } + +ID3DDeviceContextState* VRSession::GetD3DDeviceContextState() { + return mDeviceContextState; +} + +#endif // defined(XP_WIN) + +bool VRSession::SubmitFrame( + const mozilla::gfx::VRLayer_Stereo_Immersive& aLayer) { +#if defined(XP_WIN) + bool success = false; + if (aLayer.textureType == + VRLayerTextureType::LayerTextureType_D3D10SurfaceDescriptor) { + ID3D11Texture2D* dxTexture = nullptr; + HRESULT hr = mDevice->OpenSharedResource((HANDLE)aLayer.textureHandle, + IID_PPV_ARGS(&dxTexture)); + if (SUCCEEDED(hr) && dxTexture != nullptr) { + // Similar to LockD3DTexture in TextureD3D11.cpp + IDXGIKeyedMutex* mutex = nullptr; + hr = dxTexture->QueryInterface(IID_PPV_ARGS(&mutex)); + if (SUCCEEDED(hr)) { + hr = mutex->AcquireSync(0, 1000); +# ifdef MOZILLA_INTERNAL_API + if (hr == WAIT_TIMEOUT) { + gfxDevCrash(LogReason::D3DLockTimeout) << "D3D lock mutex timeout"; + } else if (hr == WAIT_ABANDONED) { + gfxCriticalNote << "GFX: D3D11 lock mutex abandoned"; + } +# endif + if (SUCCEEDED(hr)) { + success = SubmitFrame(aLayer, dxTexture); + hr = mutex->ReleaseSync(0); + if (FAILED(hr)) { + NS_WARNING("Failed to unlock the texture"); + } + } else { + NS_WARNING("Failed to lock the texture"); + } + + mutex->Release(); + mutex = nullptr; + } + + dxTexture->Release(); + dxTexture = nullptr; + } else { + NS_WARNING("Failed to open shared texture"); + } + + return SUCCEEDED(hr) && success; + } + +#elif defined(XP_MACOSX) + + if (aLayer.textureType == VRLayerTextureType::LayerTextureType_MacIOSurface) { + return SubmitFrame(aLayer, aLayer.textureHandle); + } + +#endif + + return false; +} + +void VRSession::UpdateTrigger(VRControllerState& aState, uint32_t aButtonIndex, + float aValue, float aThreshold) { + // For OpenVR, the threshold value of ButtonPressed and ButtonTouched is 0.55. + // We prefer to let developers to set their own threshold for the adjustment. + // Therefore, we don't check ButtonPressed and ButtonTouched with ButtonMask + // here. we just check the button value is larger than the threshold value or + // not. + uint64_t mask = (1ULL << aButtonIndex); + aState.triggerValue[aButtonIndex] = aValue; + if (aValue > aThreshold) { + aState.buttonPressed |= mask; + aState.buttonTouched |= mask; + } else { + aState.buttonPressed &= ~mask; + aState.buttonTouched &= ~mask; + } +} + +void VRSession::SetControllerSelectionAndSqueezeFrameId( + VRControllerState& controllerState, uint64_t aFrameId) { + // The 1st button, trigger, is its selection action. + const bool selectionPressed = controllerState.buttonPressed & 1ULL; + if (selectionPressed && controllerState.selectActionStopFrameId >= + controllerState.selectActionStartFrameId) { + controllerState.selectActionStartFrameId = aFrameId; + } else if (!selectionPressed && controllerState.selectActionStartFrameId > + controllerState.selectActionStopFrameId) { + controllerState.selectActionStopFrameId = aFrameId; + } + // The 2nd button, squeeze, is its squeeze action. + const bool squeezePressed = controllerState.buttonPressed & (1ULL << 1); + if (squeezePressed && controllerState.squeezeActionStopFrameId >= + controllerState.squeezeActionStartFrameId) { + controllerState.squeezeActionStartFrameId = aFrameId; + } else if (!squeezePressed && controllerState.squeezeActionStartFrameId > + controllerState.squeezeActionStopFrameId) { + controllerState.squeezeActionStopFrameId = aFrameId; + } +} + +bool VRSession::ShouldQuit() const { return mShouldQuit; } diff --git a/gfx/vr/service/VRSession.h b/gfx/vr/service/VRSession.h new file mode 100644 index 0000000000..ab446a26d7 --- /dev/null +++ b/gfx/vr/service/VRSession.h @@ -0,0 +1,94 @@ +/* -*- 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/. */ + +#ifndef GFX_VR_SERVICE_VRSESSION_H +#define GFX_VR_SERVICE_VRSESSION_H + +#include "moz_external_vr.h" + +#if defined(XP_WIN) +# include <d3d11_1.h> +#elif defined(XP_MACOSX) +class MacIOSurface; +#endif + +namespace mozilla { +namespace gfx { + +class VRSession { + public: + VRSession(); + + // Since this class doesn't use smartpointers for its refcounted + // members (so that it can compile outside of mozilla-central), + // prevent copying the addresses without increasing the refcount. + VRSession(const VRSession&) = delete; + VRSession& operator=(const VRSession&) = delete; + +#ifdef XP_WIN + virtual ~VRSession(); +#else + virtual ~VRSession() = default; +#endif + + static void UpdateTrigger(VRControllerState& aState, uint32_t aButtonIndex, + float aValue, float aThreshold); + /** + * In order to support WebXR's navigator.xr.IsSessionSupported call without + * displaying any permission dialogue, it is necessary to have a safe way to + * detect the capability of running a VR or AR session without activating XR + * runtimes or powering on hardware. + * + * API's such as OpenVR make no guarantee that hardware and software won't be + * left activated after enumerating devices, so each backend in gfx/vr/service + * must allow for more granular detection of capabilities. + * + * By passing true to bDetectRuntimesOnly, the initialization exits early + * after reporting the presence of XR runtime software. The Initialize method + * will only enumerate hardware and possibly return true when + * aDetectRuntimesOnly is false. + */ + virtual bool Initialize(mozilla::gfx::VRSystemState& aSystemState, + bool aDetectRuntimesOnly) = 0; + virtual void Shutdown() = 0; + virtual void ProcessEvents(mozilla::gfx::VRSystemState& aSystemState) = 0; + virtual void StartFrame(mozilla::gfx::VRSystemState& aSystemState) = 0; + virtual bool StartPresentation() = 0; + virtual void StopPresentation() = 0; + virtual void VibrateHaptic(uint32_t aControllerIdx, uint32_t aHapticIndex, + float aIntensity, float aDuration) = 0; + virtual void StopVibrateHaptic(uint32_t aControllerIdx) = 0; + virtual void StopAllHaptics() = 0; + bool SubmitFrame(const mozilla::gfx::VRLayer_Stereo_Immersive& aLayer); + bool ShouldQuit() const; + + protected: + bool mShouldQuit; +#if defined(XP_WIN) + virtual bool SubmitFrame(const mozilla::gfx::VRLayer_Stereo_Immersive& aLayer, + ID3D11Texture2D* aTexture) = 0; + bool CreateD3DContext(ID3D11Device* aDevice); + + ID3D11Device1* GetD3DDevice(); + ID3D11DeviceContext1* GetD3DDeviceContext(); + ID3DDeviceContextState* GetD3DDeviceContextState(); + + ID3D11Device1* mDevice; + ID3D11DeviceContext1* mContext; + ID3DDeviceContextState* mDeviceContextState; + +#elif defined(XP_MACOSX) + virtual bool SubmitFrame(const mozilla::gfx::VRLayer_Stereo_Immersive& aLayer, + const VRLayerTextureHandle& aTexture) = 0; +#endif + void SetControllerSelectionAndSqueezeFrameId( + VRControllerState& controllerState, uint64_t aFrameId); +}; + +} // namespace gfx +} // namespace mozilla + +#endif // GFX_VR_SERVICE_VRSESSION_H diff --git a/gfx/vr/service/binding/OpenVRCosmosBinding.h b/gfx/vr/service/binding/OpenVRCosmosBinding.h new file mode 100644 index 0000000000..97980bb087 --- /dev/null +++ b/gfx/vr/service/binding/OpenVRCosmosBinding.h @@ -0,0 +1,204 @@ +/* -*- 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/. */ + +#ifndef GFX_VR_BLINDING_OPENVRCOSMOSBINDING_H +#define GFX_VR_BLINDING_OPENVRCOSMOSBINDING_H + +namespace mozilla { +namespace gfx { + +struct OpenVRCosmosBinding { + const char* binding = + // clang-format off + "{\n" + " \"version\" : \"0.1\", \n" + " \"controller_type\" : \"vive_cosmos_controller\", \n" + " \"description\" : \"Bindings for Firefox OpenVR for the Vive Cosmos controller\", \n" + " \"name\" : \"Firefox bindings for Vive Cosmos Controller\", \n" + " \"bindings\" : { \n" + " \"/actions/firefox\" : { \n" + " \"poses\" : [ \n" + " { \n" + " \"output\" : \"/actions/firefox/in/LHand_pose\", \n" + " \"path\" : \"/user/hand/left/pose/raw\" \n" + " },\n" + " { \n" + " \"output\" : \"/actions/firefox/in/RHand_pose\", \n" + " \"path\" : \"/user/hand/right/pose/raw\" \n" + " }\n" + " ],\n" + " \"haptics\" : [ \n" + " {\n" + " \"output\" : \"/actions/firefox/out/LHand_haptic\", \n" + " \"path\" : \"/user/hand/left/output/haptic\" \n" + " },\n" + " { \n" + " \"output\" : \"/actions/firefox/out/RHand_haptic\", \n" + " \"path\" : \"/user/hand/right/output/haptic\" \n" + " }\n" + " ],\n" + " \"sources\" : [ \n" + " {\n" + " \"inputs\" : { \n" + " \"pull\" : { \n" + " \"output\" : \"/actions/firefox/in/LHand_trigger_value\" \n" + " } \n" + " },\n" + " \"mode\" : \"trigger\", \n" + " \"path\" : \"/user/hand/left/input/trigger\" \n" + " }, \n" + " {\n" + " \"inputs\" : { \n" + " \"pull\" : { \n" + " \"output\" : \"/actions/firefox/in/RHand_trigger_value\" \n" + " } \n" + " },\n" + " \"mode\" : \"trigger\", \n" + " \"path\" : \"/user/hand/right/input/trigger\" \n" + " }, \n" + " {\n" + " \"inputs\" : { \n" + " \"click\" : { \n" + " \"output\" : \"/actions/firefox/in/LHand_grip_pressed\" \n" + " }, \n" + " \"touch\" : { \n" + " \"output\" : \"/actions/firefox/in/LHand_grip_touched\" \n" + " } \n" + " }, \n" + " \"mode\" : \"button\", \n" + " \"path\" : \"/user/hand/left/input/grip\" \n" + " }, \n" + " {\n" + " \"inputs\" : { \n" + " \"click\" : { \n" + " \"output\" : \"/actions/firefox/in/RHand_grip_pressed\" \n" + " }, \n" + " \"touch\" : { \n" + " \"output\" : \"/actions/firefox/in/RHand_grip_touched\" \n" + " } \n" + " }, \n" + " \"mode\" : \"button\", \n" + " \"path\" : \"/user/hand/right/input/grip\" \n" + " }, \n" + " {\n" + " \"inputs\" : { \n" + " \"click\" : { \n" + " \"output\" : \"/actions/firefox/in/LHand_system_pressed\" \n" + " }, \n" + " \"touch\" : { \n" + " \"output\" : \"/actions/firefox/in/LHand_system_touched\" \n" + " } \n" + " },\n" + " \"mode\" : \"button\", \n" + " \"path\" : \"/user/hand/left/input/system\" \n" + " }, \n" + " {\n" + " \"inputs\" : { \n" + " \"click\" : { \n" + " \"output\" : \"/actions/firefox/in/RHand_system_pressed\" \n" + " }, \n" + " \"touch\" : { \n" + " \"output\" : \"/actions/firefox/in/RHand_system_touched\" \n" + " } \n" + " },\n" + " \"mode\" : \"button\", \n" + " \"path\" : \"/user/hand/right/input/system\" \n" + " }, \n" + " {\n" + " \"inputs\" : { \n" + " \"click\" : { \n" + " \"output\" : \"/actions/firefox/in/LHand_a_pressed\" \n" + " } \n" + " },\n" + " \"mode\" : \"button\", \n" + " \"path\" : \"/user/hand/left/input/x\" \n" + " }, \n" + " {\n" + " \"inputs\" : { \n" + " \"click\" : { \n" + " \"output\" : \"/actions/firefox/in/RHand_a_pressed\" \n" + " } \n" + " },\n" + " \"mode\" : \"button\", \n" + " \"path\" : \"/user/hand/right/input/a\" \n" + " }, \n" + " {\n" + " \"inputs\" : { \n" + " \"click\" : { \n" + " \"output\" : \"/actions/firefox/in/LHand_b_pressed\" \n" + " } \n" + " },\n" + " \"mode\" : \"button\", \n" + " \"path\" : \"/user/hand/left/input/y\" \n" + " }, \n" + " {\n" + " \"inputs\" : { \n" + " \"click\" : { \n" + " \"output\" : \"/actions/firefox/in/RHand_b_pressed\" \n" + " } \n" + " },\n" + " \"mode\" : \"button\", \n" + " \"path\" : \"/user/hand/right/input/b\" \n" + " }, \n" + " {\n" + " \"inputs\" : { \n" + " \"position\" : { \n" + " \"output\" : \"/actions/firefox/in/LHand_thumbstick_analog\" \n" + " }, \n" + " \"click\" : { \n" + " \"output\" : \"/actions/firefox/in/LHand_thumbstick_pressed\" \n" + " }, \n" + " \"touch\" : { \n" + " \"output\" : \"/actions/firefox/in/LHand_thumbstick_touched\" \n" + " } \n" + " }, \n" + " \"mode\" : \"joystick\", \n" + " \"path\" : \"/user/hand/left/input/joystick\" \n" + " },\n" + " {\n" + " \"inputs\" : { \n" + " \"position\" : { \n" + " \"output\" : \"/actions/firefox/in/RHand_thumbstick_analog\" \n" + " }, \n" + " \"click\" : { \n" + " \"output\" : \"/actions/firefox/in/RHand_thumbstick_pressed\" \n" + " }, \n" + " \"touch\" : { \n" + " \"output\" : \"/actions/firefox/in/RHand_thumbstick_touched\" \n" + " } \n" + " }, \n" + " \"mode\" : \"joystick\", \n" + " \"path\" : \"/user/hand/right/input/joystick\" \n" + " }, \n" + " {\n" + " \"inputs\" : { \n" + " \"click\" : { \n" + " \"output\" : \"/actions/firefox/in/LHand_bumper_pressed\" \n" + " } \n" + " },\n" + " \"mode\" : \"button\", \n" + " \"path\" : \"/user/hand/left/input/bumper\" \n" + " }, \n" + " {\n" + " \"inputs\" : { \n" + " \"click\" : { \n" + " \"output\" : \"/actions/firefox/in/RHand_bumper_pressed\" \n" + " } \n" + " },\n" + " \"mode\" : \"button\", \n" + " \"path\" : \"/user/hand/right/input/bumper\" \n" + " } \n" + " ]\n" + " }\n" + " }\n" + "}"; + // clang-format on +}; + +} // namespace gfx +} // namespace mozilla + +#endif // GFX_VR_BLINDING_OPENVRCOSMOSBINDING_H diff --git a/gfx/vr/service/binding/OpenVRKnucklesBinding.h b/gfx/vr/service/binding/OpenVRKnucklesBinding.h new file mode 100644 index 0000000000..508e5f64c7 --- /dev/null +++ b/gfx/vr/service/binding/OpenVRKnucklesBinding.h @@ -0,0 +1,300 @@ +/* -*- 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/. */ + +#ifndef GFX_VR_BLINDING_OPENVRKNUCKLESBINDING_H +#define GFX_VR_BLINDING_OPENVRKNUCKLESBINDING_H + +namespace mozilla { +namespace gfx { + +struct OpenVRKnucklesBinding { + const char* binding = + // clang-format off + "{\n" + " \"version\" : \"0.1\", \n" + " \"controller_type\" : \"knuckles\", \n" + " \"description\" : \"Bindings for Firefox OpenVR for the Knuckles controller\", \n" + " \"name\" : \"Firefox bindings for Knuckles Controller\", \n" + " \"bindings\" : { \n" + " \"/actions/firefox\" : { \n" + " \"poses\" : [ \n" + " { \n" + " \"output\" : \"/actions/firefox/in/LHand_pose\", \n" + " \"path\" : \"/user/hand/left/pose/raw\" \n" + " },\n" + " { \n" + " \"output\" : \"/actions/firefox/in/RHand_pose\", \n" + " \"path\" : \"/user/hand/right/pose/raw\" \n" + " }\n" + " ],\n" + " \"haptics\" : [ \n" + " {\n" + " \"output\" : \"/actions/firefox/out/LHand_haptic\", \n" + " \"path\" : \"/user/hand/left/output/haptic\" \n" + " },\n" + " { \n" + " \"output\" : \"/actions/firefox/out/RHand_haptic\", \n" + " \"path\" : \"/user/hand/right/output/haptic\" \n" + " }\n" + " ],\n" + " \"sources\" : [ \n" + " {\n" + " \"inputs\" : { \n" + " \"position\" : { \n" + " \"output\" : \"/actions/firefox/in/LHand_trackpad_analog\" \n" + " }, \n" + " \"click\" : { \n" + " \"output\" : \"/actions/firefox/in/LHand_trackpad_pressed\" \n" + " }, \n" + " \"touch\" : { \n" + " \"output\" : \"/actions/firefox/in/LHand_trackpad_touched\" \n" + " } \n" + " }, \n" + " \"mode\" : \"trackpad\", \n" + " \"path\" : \"/user/hand/left/input/trackpad\" \n" + " },\n" + " {\n" + " \"inputs\" : { \n" + " \"position\" : { \n" + " \"output\" : \"/actions/firefox/in/RHand_trackpad_analog\" \n" + " }, \n" + " \"click\" : { \n" + " \"output\" : \"/actions/firefox/in/RHand_trackpad_pressed\" \n" + " }, \n" + " \"touch\" : { \n" + " \"output\" : \"/actions/firefox/in/RHand_trackpad_touched\" \n" + " } \n" + " }, \n" + " \"mode\" : \"trackpad\", \n" + " \"path\" : \"/user/hand/right/input/trackpad\" \n" + " }, \n" + " {\n" + " \"inputs\" : { \n" + " \"pull\" : { \n" + " \"output\" : \"/actions/firefox/in/LHand_trigger_value\" \n" + " } \n" + " },\n" + " \"mode\" : \"trigger\", \n" + " \"path\" : \"/user/hand/left/input/trigger\" \n" + " }, \n" + " {\n" + " \"inputs\" : { \n" + " \"pull\" : { \n" + " \"output\" : \"/actions/firefox/in/RHand_trigger_value\" \n" + " } \n" + " },\n" + " \"mode\" : \"trigger\", \n" + " \"path\" : \"/user/hand/right/input/trigger\" \n" + " }, \n" + " {\n" + " \"inputs\" : { \n" + " \"click\" : { \n" + " \"output\" : \"/actions/firefox/in/LHand_grip_pressed\" \n" + " }, \n" + " \"touch\" : { \n" + " \"output\" : \"/actions/firefox/in/LHand_grip_touched\" \n" + " } \n" + " }, \n" + " \"mode\" : \"button\", \n" + " \"path\" : \"/user/hand/left/input/grip\" \n" + " }, \n" + " {\n" + " \"inputs\" : { \n" + " \"click\" : { \n" + " \"output\" : \"/actions/firefox/in/RHand_grip_pressed\" \n" + " }, \n" + " \"touch\" : { \n" + " \"output\" : \"/actions/firefox/in/RHand_grip_touched\" \n" + " } \n" + " }, \n" + " \"mode\" : \"button\", \n" + " \"path\" : \"/user/hand/right/input/grip\" \n" + " }, \n" + " {\n" + " \"inputs\" : { \n" + " \"click\" : { \n" + " \"output\" : \"/actions/firefox/in/LHand_system_pressed\" \n" + " }, \n" + " \"touch\" : { \n" + " \"output\" : \"/actions/firefox/in/LHand_system_touched\" \n" + " } \n" + " },\n" + " \"mode\" : \"button\", \n" + " \"path\" : \"/user/hand/left/input/system\" \n" + " }, \n" + " {\n" + " \"inputs\" : { \n" + " \"click\" : { \n" + " \"output\" : \"/actions/firefox/in/RHand_system_pressed\" \n" + " }, \n" + " \"touch\" : { \n" + " \"output\" : \"/actions/firefox/in/RHand_system_touched\" \n" + " } \n" + " },\n" + " \"mode\" : \"button\", \n" + " \"path\" : \"/user/hand/right/input/system\" \n" + " }, \n" + " {\n" + " \"inputs\" : { \n" + " \"click\" : { \n" + " \"output\" : \"/actions/firefox/in/LHand_a_pressed\" \n" + " }, \n" + " \"touch\" : { \n" + " \"output\" : \"/actions/firefox/in/LHand_a_touched\" \n" + " } \n" + " },\n" + " \"mode\" : \"button\", \n" + " \"path\" : \"/user/hand/left/input/a\" \n" + " }, \n" + " {\n" + " \"inputs\" : { \n" + " \"click\" : { \n" + " \"output\" : \"/actions/firefox/in/RHand_a_pressed\" \n" + " }, \n" + " \"touch\" : { \n" + " \"output\" : \"/actions/firefox/in/RHand_a_touched\" \n" + " } \n" + " },\n" + " \"mode\" : \"button\", \n" + " \"path\" : \"/user/hand/right/input/a\" \n" + " }, \n" + " {\n" + " \"inputs\" : { \n" + " \"click\" : { \n" + " \"output\" : \"/actions/firefox/in/LHand_b_pressed\" \n" + " }, \n" + " \"touch\" : { \n" + " \"output\" : \"/actions/firefox/in/LHand_b_touched\" \n" + " } \n" + " },\n" + " \"mode\" : \"button\", \n" + " \"path\" : \"/user/hand/left/input/b\" \n" + " }, \n" + " {\n" + " \"inputs\" : { \n" + " \"click\" : { \n" + " \"output\" : \"/actions/firefox/in/RHand_b_pressed\" \n" + " }, \n" + " \"touch\" : { \n" + " \"output\" : \"/actions/firefox/in/RHand_b_touched\" \n" + " } \n" + " },\n" + " \"mode\" : \"button\", \n" + " \"path\" : \"/user/hand/right/input/b\" \n" + " }, \n" + " {\n" + " \"inputs\" : { \n" + " \"position\" : { \n" + " \"output\" : \"/actions/firefox/in/LHand_thumbstick_analog\" \n" + " }, \n" + " \"click\" : { \n" + " \"output\" : \"/actions/firefox/in/LHand_thumbstick_pressed\" \n" + " }, \n" + " \"touch\" : { \n" + " \"output\" : \"/actions/firefox/in/LHand_thumbstick_touched\" \n" + " } \n" + " }, \n" + " \"mode\" : \"joystick\", \n" + " \"path\" : \"/user/hand/left/input/thumbstick\" \n" + " },\n" + " {\n" + " \"inputs\" : { \n" + " \"position\" : { \n" + " \"output\" : \"/actions/firefox/in/RHand_thumbstick_analog\" \n" + " }, \n" + " \"click\" : { \n" + " \"output\" : \"/actions/firefox/in/RHand_thumbstick_pressed\" \n" + " }, \n" + " \"touch\" : { \n" + " \"output\" : \"/actions/firefox/in/RHand_thumbstick_touched\" \n" + " } \n" + " }, \n" + " \"mode\" : \"joystick\", \n" + " \"path\" : \"/user/hand/right/input/thumbstick\" \n" + " }, \n" + " {\n" + " \"inputs\" : { \n" + " \"pull\" : { \n" + " \"output\" : \"/actions/firefox/in/LHand_finger_index_value\" \n" + " } \n" + " }, \n" + " \"mode\" : \"trigger\", \n" + " \"path\" : \"/user/hand/left/input/finger/index\" \n" + " }, \n" + " {\n" + " \"inputs\" : { \n" + " \"pull\" : { \n" + " \"output\" : \"/actions/firefox/in/RHand_finger_index_value\" \n" + " } \n" + " }, \n" + " \"mode\" : \"trigger\", \n" + " \"path\" : \"/user/hand/right/input/finger/index\" \n" + " }, \n" + " {\n" + " \"inputs\" : { \n" + " \"pull\" : { \n" + " \"output\" : \"/actions/firefox/in/LHand_finger_middle_value\" \n" + " } \n" + " }, \n" + " \"mode\" : \"trigger\", \n" + " \"path\" : \"/user/hand/left/input/finger/middle\" \n" + " }, \n" + " {\n" + " \"inputs\" : { \n" + " \"pull\" : { \n" + " \"output\" : \"/actions/firefox/in/RHand_finger_middle_value\" \n" + " } \n" + " }, \n" + " \"mode\" : \"trigger\", \n" + " \"path\" : \"/user/hand/right/input/finger/middle\" \n" + " }, \n" + " {\n" + " \"inputs\" : { \n" + " \"pull\" : { \n" + " \"output\" : \"/actions/firefox/in/LHand_finger_ring_value\" \n" + " } \n" + " }, \n" + " \"mode\" : \"trigger\", \n" + " \"path\" : \"/user/hand/left/input/finger/ring\" \n" + " }, \n" + " {\n" + " \"inputs\" : { \n" + " \"pull\" : { \n" + " \"output\" : \"/actions/firefox/in/RHand_finger_ring_value\" \n" + " } \n" + " }, \n" + " \"mode\" : \"trigger\", \n" + " \"path\" : \"/user/hand/right/input/finger/ring\" \n" + " }, \n" + " {\n" + " \"inputs\" : { \n" + " \"pull\" : { \n" + " \"output\" : \"/actions/firefox/in/LHand_finger_pinky_value\" \n" + " } \n" + " }, \n" + " \"mode\" : \"trigger\", \n" + " \"path\" : \"/user/hand/left/input/finger/pinky\" \n" + " }, \n" + " {\n" + " \"inputs\" : { \n" + " \"pull\" : { \n" + " \"output\" : \"/actions/firefox/in/RHand_finger_pinky_value\" \n" + " } \n" + " }, \n" + " \"mode\" : \"trigger\", \n" + " \"path\" : \"/user/hand/right/input/finger/pinky\" \n" + " } \n" + " ]\n" + " }\n" + " }\n" + "}"; + // clang-format on +}; + +} // namespace gfx +} // namespace mozilla + +#endif // GFX_VR_BLINDING_OPENVRKNUCKLESBINDING_H diff --git a/gfx/vr/service/binding/OpenVRViveBinding.h b/gfx/vr/service/binding/OpenVRViveBinding.h new file mode 100644 index 0000000000..c3fb7fd6e1 --- /dev/null +++ b/gfx/vr/service/binding/OpenVRViveBinding.h @@ -0,0 +1,174 @@ +/* -*- 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/. */ + +#ifndef GFX_VR_BLINDING_OPENVRVIVEBINDING_H +#define GFX_VR_BLINDING_OPENVRVIVEBINDING_H + +namespace mozilla { +namespace gfx { + +struct OpenVRViveBinding { + const char* binding = + // clang-format off + "{\n" + " \"version\" : \"0.1\", \n" + " \"controller_type\" : \"vive_controller\", \n" + " \"description\" : \"Bindings for Firefox OpenVR for the Vive controller\", \n" + " \"name\" : \"Firefox bindings for Vive Controller\", \n" + " \"bindings\" : { \n" + " \"/actions/firefox\" : { \n" + " \"poses\" : [ \n" + " { \n" + " \"output\" : \"/actions/firefox/in/LHand_pose\", \n" + " \"path\" : \"/user/hand/left/pose/raw\" \n" + " },\n" + " { \n" + " \"output\" : \"/actions/firefox/in/RHand_pose\", \n" + " \"path\" : \"/user/hand/right/pose/raw\" \n" + " }\n" + " ],\n" + " \"haptics\" : [ \n" + " {\n" + " \"output\" : \"/actions/firefox/out/LHand_haptic\", \n" + " \"path\" : \"/user/hand/left/output/haptic\" \n" + " },\n" + " { \n" + " \"output\" : \"/actions/firefox/out/RHand_haptic\", \n" + " \"path\" : \"/user/hand/right/output/haptic\" \n" + " }\n" + " ],\n" + " \"sources\" : [ \n" + " {\n" + " \"inputs\" : { \n" + " \"position\" : { \n" + " \"output\" : \"/actions/firefox/in/LHand_trackpad_analog\" \n" + " }, \n" + " \"click\" : { \n" + " \"output\" : \"/actions/firefox/in/LHand_trackpad_pressed\" \n" + " }, \n" + " \"touch\" : { \n" + " \"output\" : \"/actions/firefox/in/LHand_trackpad_touched\" \n" + " } \n" + " }, \n" + " \"mode\" : \"trackpad\", \n" + " \"path\" : \"/user/hand/left/input/trackpad\" \n" + " },\n" + " {\n" + " \"inputs\" : { \n" + " \"position\" : { \n" + " \"output\" : \"/actions/firefox/in/RHand_trackpad_analog\" \n" + " }, \n" + " \"click\" : { \n" + " \"output\" : \"/actions/firefox/in/RHand_trackpad_pressed\" \n" + " }, \n" + " \"touch\" : { \n" + " \"output\" : \"/actions/firefox/in/RHand_trackpad_touched\" \n" + " } \n" + " }, \n" + " \"mode\" : \"trackpad\", \n" + " \"path\" : \"/user/hand/right/input/trackpad\" \n" + " }, \n" + " {\n" + " \"inputs\" : { \n" + " \"pull\" : { \n" + " \"output\" : \"/actions/firefox/in/LHand_trigger_value\" \n" + " } \n" + " },\n" + " \"mode\" : \"trigger\", \n" + " \"path\" : \"/user/hand/left/input/trigger\" \n" + " }, \n" + " {\n" + " \"inputs\" : { \n" + " \"pull\" : { \n" + " \"output\" : \"/actions/firefox/in/RHand_trigger_value\" \n" + " } \n" + " },\n" + " \"mode\" : \"trigger\", \n" + " \"path\" : \"/user/hand/right/input/trigger\" \n" + " }, \n" + " {\n" + " \"inputs\" : { \n" + " \"click\" : { \n" + " \"output\" : \"/actions/firefox/in/LHand_grip_pressed\" \n" + " }, \n" + " \"touch\" : { \n" + " \"output\" : \"/actions/firefox/in/LHand_grip_touched\" \n" + " } \n" + " }, \n" + " \"mode\" : \"button\", \n" + " \"path\" : \"/user/hand/left/input/grip\" \n" + " }, \n" + " {\n" + " \"inputs\" : { \n" + " \"click\" : { \n" + " \"output\" : \"/actions/firefox/in/RHand_grip_pressed\" \n" + " }, \n" + " \"touch\" : { \n" + " \"output\" : \"/actions/firefox/in/RHand_grip_touched\" \n" + " } \n" + " }, \n" + " \"mode\" : \"button\", \n" + " \"path\" : \"/user/hand/right/input/grip\" \n" + " }, \n" + " {\n" + " \"inputs\" : { \n" + " \"click\" : { \n" + " \"output\" : \"/actions/firefox/in/LHand_menu_pressed\" \n" + " }, \n" + " \"touch\" : { \n" + " \"output\" : \"/actions/firefox/in/LHand_menu_touched\" \n" + " } \n" + " },\n" + " \"mode\" : \"button\", \n" + " \"path\" : \"/user/hand/left/input/application_menu\" \n" + " }, \n" + " {\n" + " \"inputs\" : { \n" + " \"click\" : { \n" + " \"output\" : \"/actions/firefox/in/RHand_menu_pressed\" \n" + " }, \n" + " \"touch\" : { \n" + " \"output\" : \"/actions/firefox/in/RHand_menu_touched\" \n" + " } \n" + " },\n" + " \"mode\" : \"button\", \n" + " \"path\" : \"/user/hand/right/input/application_menu\" \n" + " }, \n" + " {\n" + " \"inputs\" : { \n" + " \"click\" : { \n" + " \"output\" : \"/actions/firefox/in/LHand_system_pressed\" \n" + " }, \n" + " \"touch\" : { \n" + " \"output\" : \"/actions/firefox/in/LHand_system_touched\" \n" + " } \n" + " },\n" + " \"mode\" : \"button\", \n" + " \"path\" : \"/user/hand/left/input/system\" \n" + " }, \n" + " {\n" + " \"inputs\" : { \n" + " \"click\" : { \n" + " \"output\" : \"/actions/firefox/in/RHand_system_pressed\" \n" + " }, \n" + " \"touch\" : { \n" + " \"output\" : \"/actions/firefox/in/RHand_system_touched\" \n" + " } \n" + " },\n" + " \"mode\" : \"button\", \n" + " \"path\" : \"/user/hand/right/input/system\" \n" + " } \n" + " ]\n" + " }\n" + " }\n" + "}"; + // clang-format on +}; + +} // namespace gfx +} // namespace mozilla + +#endif // GFX_VR_BLINDING_OPENVRVIVEBINDING_H diff --git a/gfx/vr/service/binding/OpenVRWMRBinding.h b/gfx/vr/service/binding/OpenVRWMRBinding.h new file mode 100644 index 0000000000..09d1529b86 --- /dev/null +++ b/gfx/vr/service/binding/OpenVRWMRBinding.h @@ -0,0 +1,192 @@ +/* -*- 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/. */ + +#ifndef GFX_VR_BLINDING_OPENVRWMRBINDING_H +#define GFX_VR_BLINDING_OPENVRWMRBINDING_H + +namespace mozilla { +namespace gfx { + +struct OpenVRWMRBinding { + const char* binding = + // clang-format off + "{\n" + " \"version\" : \"0.1\", \n" + " \"controller_type\" : \"holographic_controller\", \n" + " \"description\" : \"Bindings for Firefox OpenVR for the Windows Mixed Reality controller\", \n" + " \"name\" : \"Firefox bindings for Windows Mixed Reality Controller\", \n" + " \"bindings\" : { \n" + " \"/actions/firefox\" : { \n" + " \"poses\" : [ \n" + " { \n" + " \"output\" : \"/actions/firefox/in/LHand_pose\", \n" + " \"path\" : \"/user/hand/left/pose/raw\" \n" + " },\n" + " { \n" + " \"output\" : \"/actions/firefox/in/RHand_pose\", \n" + " \"path\" : \"/user/hand/right/pose/raw\" \n" + " }\n" + " ],\n" + " \"haptics\" : [ \n" + " {\n" + " \"output\" : \"/actions/firefox/out/LHand_haptic\", \n" + " \"path\" : \"/user/hand/left/output/haptic\" \n" + " },\n" + " { \n" + " \"output\" : \"/actions/firefox/out/RHand_haptic\", \n" + " \"path\" : \"/user/hand/right/output/haptic\" \n" + " }\n" + " ],\n" + " \"sources\" : [ \n" + " {\n" + " \"inputs\" : { \n" + " \"position\" : { \n" + " \"output\" : \"/actions/firefox/in/LHand_trackpad_analog\" \n" + " }, \n" + " \"click\" : { \n" + " \"output\" : \"/actions/firefox/in/LHand_trackpad_pressed\" \n" + " }, \n" + " \"touch\" : { \n" + " \"output\" : \"/actions/firefox/in/LHand_trackpad_touched\" \n" + " } \n" + " }, \n" + " \"mode\" : \"trackpad\", \n" + " \"path\" : \"/user/hand/left/input/trackpad\" \n" + " },\n" + " {\n" + " \"inputs\" : { \n" + " \"position\" : { \n" + " \"output\" : \"/actions/firefox/in/RHand_trackpad_analog\" \n" + " }, \n" + " \"click\" : { \n" + " \"output\" : \"/actions/firefox/in/RHand_trackpad_pressed\" \n" + " }, \n" + " \"touch\" : { \n" + " \"output\" : \"/actions/firefox/in/RHand_trackpad_touched\" \n" + " } \n" + " }, \n" + " \"mode\" : \"trackpad\", \n" + " \"path\" : \"/user/hand/right/input/trackpad\" \n" + " }, \n" + " {\n" + " \"inputs\" : { \n" + " \"pull\" : { \n" + " \"output\" : \"/actions/firefox/in/LHand_trigger_value\" \n" + " } \n" + " },\n" + " \"mode\" : \"trigger\", \n" + " \"path\" : \"/user/hand/left/input/trigger\" \n" + " }, \n" + " {\n" + " \"inputs\" : { \n" + " \"pull\" : { \n" + " \"output\" : \"/actions/firefox/in/RHand_trigger_value\" \n" + " } \n" + " },\n" + " \"mode\" : \"trigger\", \n" + " \"path\" : \"/user/hand/right/input/trigger\" \n" + " }, \n" + " {\n" + " \"inputs\" : { \n" + " \"click\" : { \n" + " \"output\" : \"/actions/firefox/in/LHand_grip_pressed\" \n" + " }, \n" + " \"touch\" : { \n" + " \"output\" : \"/actions/firefox/in/LHand_grip_touched\" \n" + " } \n" + " }, \n" + " \"mode\" : \"button\", \n" + " \"path\" : \"/user/hand/left/input/grip\" \n" + " }, \n" + " {\n" + " \"inputs\" : { \n" + " \"click\" : { \n" + " \"output\" : \"/actions/firefox/in/RHand_grip_pressed\" \n" + " }, \n" + " \"touch\" : { \n" + " \"output\" : \"/actions/firefox/in/RHand_grip_touched\" \n" + " } \n" + " }, \n" + " \"mode\" : \"button\", \n" + " \"path\" : \"/user/hand/right/input/grip\" \n" + " }, \n" + " {\n" + " \"inputs\" : { \n" + " \"click\" : { \n" + " \"output\" : \"/actions/firefox/in/LHand_menu_pressed\" \n" + " }, \n" + " \"touch\" : { \n" + " \"output\" : \"/actions/firefox/in/LHand_menu_touched\" \n" + " } \n" + " },\n" + " \"mode\" : \"button\", \n" + " \"path\" : \"/user/hand/left/input/application_menu\" \n" + " }, \n" + " {\n" + " \"inputs\" : { \n" + " \"click\" : { \n" + " \"output\" : \"/actions/firefox/in/RHand_menu_pressed\" \n" + " }, \n" + " \"touch\" : { \n" + " \"output\" : \"/actions/firefox/in/RHand_menu_touched\" \n" + " } \n" + " },\n" + " \"mode\" : \"button\", \n" + " \"path\" : \"/user/hand/right/input/application_menu\" \n" + " }, \n" + " {\n" + " \"inputs\" : { \n" + " \"click\" : { \n" + " \"output\" : \"/actions/firefox/in/LHand_system_pressed\" \n" + " }, \n" + " \"touch\" : { \n" + " \"output\" : \"/actions/firefox/in/LHand_system_touched\" \n" + " } \n" + " },\n" + " \"mode\" : \"button\", \n" + " \"path\" : \"/user/hand/left/input/system\" \n" + " }, \n" + " {\n" + " \"inputs\" : { \n" + " \"click\" : { \n" + " \"output\" : \"/actions/firefox/in/RHand_system_pressed\" \n" + " }, \n" + " \"touch\" : { \n" + " \"output\" : \"/actions/firefox/in/RHand_system_touched\" \n" + " } \n" + " },\n" + " \"mode\" : \"button\", \n" + " \"path\" : \"/user/hand/right/input/system\" \n" + " }, \n" + " {\n" + " \"inputs\" : { \n" + " \"position\" : { \n" + " \"output\" : \"/actions/firefox/in/LHand_thumbstick_analog\" \n" + " } \n" + " }, \n" + " \"mode\" : \"joystick\", \n" + " \"path\" : \"/user/hand/left/input/joystick\" \n" + " },\n" + " {\n" + " \"inputs\" : { \n" + " \"position\" : { \n" + " \"output\" : \"/actions/firefox/in/RHand_thumbstick_analog\" \n" + " } \n" + " }, \n" + " \"mode\" : \"joystick\", \n" + " \"path\" : \"/user/hand/right/input/joystick\" \n" + " } \n" + " ]\n" + " }\n" + " }\n" + "}"; + // clang-format on +}; + +} // namespace gfx +} // namespace mozilla + +#endif // GFX_VR_BLINDING_OPENVRWMRBINDING_H diff --git a/gfx/vr/service/moz.build b/gfx/vr/service/moz.build new file mode 100644 index 0000000000..0edd36ceec --- /dev/null +++ b/gfx/vr/service/moz.build @@ -0,0 +1,48 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +# Build Oculus support on Windows only +if CONFIG["OS_TARGET"] == "WINNT": + SOURCES += [ + "OculusSession.cpp", + "OpenVRWMRMapper.cpp", + ] + +# Build OSVR on all platforms except Android +if CONFIG["OS_TARGET"] != "Android": + UNIFIED_SOURCES += [ + "OSVRSession.cpp", + "VRService.cpp", + "VRSession.cpp", + ] + # PuppetSession includes MacIOSurface.h which includes Mac headers + # which define Size and Points types in the root namespace that + # often conflict with our own types. + SOURCES += [ + "PuppetSession.cpp", + ] + include("/ipc/chromium/chromium-config.mozbuild") + +# Build OpenVR on Windows, Linux, and macOS desktop targets +if CONFIG["OS_TARGET"] in ("WINNT", "Linux", "Darwin"): + DIRS += [ + "openvr", + ] + LOCAL_INCLUDES += ["/dom/base", "/gfx/layers/d3d11"] + + # OpenVRSession includes MacIOSurface.h which includes Mac headers + # which define Size and Points types in the root namespace that + # often conflict with our own types. + SOURCES += [ + "OpenVRControllerMapper.cpp", + "OpenVRCosmosMapper.cpp", + "OpenVRDefaultMapper.cpp", + "OpenVRKnucklesMapper.cpp", + "OpenVRSession.cpp", + "OpenVRViveMapper.cpp", + ] + +FINAL_LIBRARY = "xul" diff --git a/gfx/vr/service/oculus/ovr_capi_dynamic.h b/gfx/vr/service/oculus/ovr_capi_dynamic.h new file mode 100644 index 0000000000..77eb0a3aa0 --- /dev/null +++ b/gfx/vr/service/oculus/ovr_capi_dynamic.h @@ -0,0 +1,984 @@ +/* -*- 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/. */ + +/* This file contains just the needed struct definitions for + * interacting with the Oculus VR C API, without needing to #include + * OVR_CAPI.h directly. Note that it uses the same type names as the + * CAPI, and cannot be #included at the same time as OVR_CAPI.h. It + * does not include the entire C API, just want's needed. + */ + +#ifdef OVR_CAPI_h +# ifdef _MSC_VER +# pragma message( \ + "ovr_capi_dyanmic.h: OVR_CAPI.h included before ovr_capi_dynamic.h, skipping this") +# else +# warning OVR_CAPI.h included before ovr_capi_dynamic.h, skipping this +# endif +# define mozilla_ovr_capi_dynamic_h_ + +#else + +# ifndef mozilla_ovr_capi_dynamic_h_ +# define mozilla_ovr_capi_dynamic_h_ + +# ifdef HAVE_64BIT_BUILD +# define OVR_PTR_SIZE 8 +# define OVR_ON64(x) x +# else +# define OVR_PTR_SIZE 4 +# define OVR_ON64(x) /**/ +# endif + +# if defined(_WIN32) +# define OVR_PFN __cdecl +# else +# define OVR_PFN +# endif + +# if !defined(OVR_ALIGNAS) +# if defined(__GNUC__) || defined(__clang__) +# define OVR_ALIGNAS(n) __attribute__((aligned(n))) +# elif defined(_MSC_VER) || defined(__INTEL_COMPILER) +# define OVR_ALIGNAS(n) __declspec(align(n)) +# elif defined(__CC_ARM) +# define OVR_ALIGNAS(n) __align(n) +# else +# error Need to define OVR_ALIGNAS +# endif +# endif + +# if !defined(OVR_UNUSED_STRUCT_PAD) +# define OVR_UNUSED_STRUCT_PAD(padName, size) char padName[size]; +# endif + +# ifdef __cplusplus +extern "C" { +# endif + +typedef int32_t ovrResult; + +typedef enum { + ovrSuccess = 0, +} ovrSuccessType; + +typedef char ovrBool; +typedef struct OVR_ALIGNAS(4) { + float r, g, b, a; +} ovrColorf; +typedef struct OVR_ALIGNAS(4) { + int x, y; +} ovrVector2i; +typedef struct OVR_ALIGNAS(4) { + int w, h; +} ovrSizei; +typedef struct OVR_ALIGNAS(4) { + ovrVector2i Pos; + ovrSizei Size; +} ovrRecti; +typedef struct OVR_ALIGNAS(4) { + float x, y, z, w; +} ovrQuatf; +typedef struct OVR_ALIGNAS(4) { + float x, y; +} ovrVector2f; +typedef struct OVR_ALIGNAS(4) { + float x, y, z; +} ovrVector3f; +typedef struct OVR_ALIGNAS(4) { + float M[4][4]; +} ovrMatrix4f; + +typedef struct OVR_ALIGNAS(4) { + ovrQuatf Orientation; + ovrVector3f Position; +} ovrPosef; + +typedef struct OVR_ALIGNAS(8) { + ovrPosef ThePose; + ovrVector3f AngularVelocity; + ovrVector3f LinearVelocity; + ovrVector3f AngularAcceleration; + ovrVector3f LinearAcceleration; + OVR_UNUSED_STRUCT_PAD(pad0, 4) + double TimeInSeconds; +} ovrPoseStatef; + +typedef struct { + float UpTan; + float DownTan; + float LeftTan; + float RightTan; +} ovrFovPort; + +typedef enum { + ovrHmd_None = 0, + ovrHmd_DK1 = 3, + ovrHmd_DKHD = 4, + ovrHmd_DK2 = 6, + ovrHmd_CB = 8, + ovrHmd_Other = 9, + ovrHmd_E3_2015 = 10, + ovrHmd_ES06 = 11, + ovrHmd_ES09 = 12, + ovrHmd_ES11 = 13, + ovrHmd_CV1 = 14, + ovrHmd_EnumSize = 0x7fffffff +} ovrHmdType; + +typedef enum { + ovrHmdCap_DebugDevice = 0x0010, + ovrHmdCap_EnumSize = 0x7fffffff +} ovrHmdCaps; + +typedef enum { + ovrTrackingCap_Orientation = 0x0010, + ovrTrackingCap_MagYawCorrection = 0x0020, + ovrTrackingCap_Position = 0x0040, + ovrTrackingCap_EnumSize = 0x7fffffff +} ovrTrackingCaps; + +typedef enum { + ovrExtension_TextureLayout_Octilinear = 0, + ovrExtension_Count, + ovrExtension_EnumSize = 0x7fffffff +} ovrExtensions; + +typedef enum { + ovrEye_Left = 0, + ovrEye_Right = 1, + ovrEye_Count = 2, + ovrEye_EnumSize = 0x7fffffff +} ovrEyeType; + +typedef enum { + ovrTrackingOrigin_EyeLevel = 0, + ovrTrackingOrigin_FloorLevel = 1, + ovrTrackingOrigin_Count = 2, ///< \internal Count of enumerated elements. + ovrTrackingOrigin_EnumSize = 0x7fffffff ///< \internal Force type int32_t. +} ovrTrackingOrigin; + +typedef struct OVR_ALIGNAS(OVR_PTR_SIZE) { + char Reserved[8]; +} ovrGraphicsLuid; + +typedef struct OVR_ALIGNAS(OVR_PTR_SIZE) { + ovrHmdType Type; + OVR_ON64(OVR_UNUSED_STRUCT_PAD(pad0, 4)) + char ProductName[64]; + char Manufacturer[64]; + short VendorId; + short ProductId; + char SerialNumber[24]; + short FirmwareMajor; + short FirmwareMinor; + unsigned int AvailableHmdCaps; + unsigned int DefaultHmdCaps; + unsigned int AvailableTrackingCaps; + unsigned int DefaultTrackingCaps; + ovrFovPort DefaultEyeFov[ovrEye_Count]; + ovrFovPort MaxEyeFov[ovrEye_Count]; + ovrSizei Resolution; + float DisplayRefreshRate; + OVR_ON64(OVR_UNUSED_STRUCT_PAD(pad1, 4)) +} ovrHmdDesc; + +typedef struct ovrHmdStruct* ovrSession; + +# ifdef XP_WIN +typedef uint32_t ovrProcessId; +# else +typedef pid_t ovrProcessId; +# endif + +typedef enum { + ovrStatus_OrientationTracked = 0x0001, + ovrStatus_PositionTracked = 0x0002, + ovrStatus_EnumSize = 0x7fffffff +} ovrStatusBits; + +typedef struct OVR_ALIGNAS(OVR_PTR_SIZE) { + float FrustumHFovInRadians; + float FrustumVFovInRadians; + float FrustumNearZInMeters; + float FrustumFarZInMeters; +} ovrTrackerDesc; + +typedef enum { + ovrTracker_Connected = 0x0020, + ovrTracker_PoseTracked = 0x0004 +} ovrTrackerFlags; + +typedef struct OVR_ALIGNAS(8) { + unsigned int TrackerFlags; + ovrPosef Pose; + ovrPosef LeveledPose; + OVR_UNUSED_STRUCT_PAD(pad0, 4) +} ovrTrackerPose; + +typedef struct OVR_ALIGNAS(8) { + ovrPoseStatef HeadPose; + unsigned int StatusFlags; + ovrPoseStatef HandPoses[2]; + unsigned int HandStatusFlags[2]; + ovrPosef CalibratedOrigin; +} ovrTrackingState; + +typedef struct OVR_ALIGNAS(4) { + ovrEyeType Eye; + ovrFovPort Fov; + ovrRecti DistortedViewport; + ovrVector2f PixelsPerTanAngleAtCenter; + ovrPosef HmdToEyePose; +} ovrEyeRenderDesc; + +typedef struct OVR_ALIGNAS(4) { + float Projection22; + float Projection23; + float Projection32; +} ovrTimewarpProjectionDesc; + +typedef struct OVR_ALIGNAS(4) { + ovrPosef HmdToEyePose[ovrEye_Count]; + float HmdSpaceToWorldScaleInMeters; +} ovrViewScaleDesc; + +typedef enum { + ovrTexture_2D, + ovrTexture_2D_External, + ovrTexture_Cube, + ovrTexture_Count, + ovrTexture_EnumSize = 0x7fffffff +} ovrTextureType; + +typedef enum { + ovrTextureBind_None, + ovrTextureBind_DX_RenderTarget = 0x0001, + ovrTextureBind_DX_UnorderedAccess = 0x0002, + ovrTextureBind_DX_DepthStencil = 0x0004, + ovrTextureBind_EnumSize = 0x7fffffff +} ovrTextureBindFlags; + +typedef enum { + OVR_FORMAT_UNKNOWN = 0, + OVR_FORMAT_B5G6R5_UNORM = 1, + OVR_FORMAT_B5G5R5A1_UNORM = 2, + OVR_FORMAT_B4G4R4A4_UNORM = 3, + OVR_FORMAT_R8G8B8A8_UNORM = 4, + OVR_FORMAT_R8G8B8A8_UNORM_SRGB = 5, + OVR_FORMAT_B8G8R8A8_UNORM = 6, + OVR_FORMAT_B8G8R8A8_UNORM_SRGB = 7, + OVR_FORMAT_B8G8R8X8_UNORM = 8, + OVR_FORMAT_B8G8R8X8_UNORM_SRGB = 9, + OVR_FORMAT_R16G16B16A16_FLOAT = 10, + OVR_FORMAT_R11G11B10_FLOAT = 25, + OVR_FORMAT_D16_UNORM = 11, + OVR_FORMAT_D24_UNORM_S8_UINT = 12, + OVR_FORMAT_D32_FLOAT = 13, + OVR_FORMAT_D32_FLOAT_S8X24_UINT = 14, + OVR_FORMAT_BC1_UNORM = 15, + OVR_FORMAT_BC1_UNORM_SRGB = 16, + OVR_FORMAT_BC2_UNORM = 17, + OVR_FORMAT_BC2_UNORM_SRGB = 18, + OVR_FORMAT_BC3_UNORM = 19, + OVR_FORMAT_BC3_UNORM_SRGB = 20, + OVR_FORMAT_BC6H_UF16 = 21, + OVR_FORMAT_BC6H_SF16 = 22, + OVR_FORMAT_BC7_UNORM = 23, + OVR_FORMAT_BC7_UNORM_SRGB = 24, + + OVR_FORMAT_ENUMSIZE = 0x7fffffff +} ovrTextureFormat; + +typedef enum { + ovrTextureMisc_None, + ovrTextureMisc_DX_Typeless = 0x0001, + ovrTextureMisc_AllowGenerateMips = 0x0002, + ovrTextureMisc_ProtectedContent = 0x0004, + ovrTextureMisc_AutoGenerateMips = 0x0008, + ovrTextureMisc_EnumSize = 0x7fffffff +} ovrTextureFlags; + +typedef struct { + ovrTextureType Type; + ovrTextureFormat Format; + int ArraySize; + int Width; + int Height; + int MipLevels; + int SampleCount; + ovrBool StaticImage; + unsigned int MiscFlags; + unsigned int BindFlags; +} ovrTextureSwapChainDesc; + +typedef struct { + ovrTextureFormat Format; + int Width; + int Height; + unsigned int MiscFlags; + unsigned int MirrorOptions; +} ovrMirrorTextureDesc; + +typedef struct ovrTextureSwapChainData* ovrTextureSwapChain; +typedef struct ovrMirrorTextureData* ovrMirrorTexture; + +typedef enum { + ovrButton_A = 0x00000001, + ovrButton_B = 0x00000002, + ovrButton_RThumb = 0x00000004, + ovrButton_RShoulder = 0x00000008, + ovrButton_X = 0x00000100, + ovrButton_Y = 0x00000200, + ovrButton_LThumb = 0x00000400, + ovrButton_LShoulder = 0x00000800, + ovrButton_Up = 0x00010000, + ovrButton_Down = 0x00020000, + ovrButton_Left = 0x00040000, + ovrButton_Right = 0x00080000, + ovrButton_Enter = 0x00100000, + ovrButton_Back = 0x00200000, + ovrButton_VolUp = 0x00400000, + ovrButton_VolDown = 0x00800000, + ovrButton_Home = 0x01000000, + ovrButton_Private = ovrButton_VolUp | ovrButton_VolDown | ovrButton_Home, + ovrButton_RMask = + ovrButton_A | ovrButton_B | ovrButton_RThumb | ovrButton_RShoulder, + ovrButton_LMask = ovrButton_X | ovrButton_Y | ovrButton_LThumb | + ovrButton_LShoulder | ovrButton_Enter, + ovrButton_EnumSize = 0x7fffffff +} ovrButton; + +typedef enum { + ovrTouch_A = ovrButton_A, + ovrTouch_B = ovrButton_B, + ovrTouch_RThumb = ovrButton_RThumb, + ovrTouch_RThumbRest = 0x00000008, + ovrTouch_RIndexTrigger = 0x00000010, + ovrTouch_RButtonMask = ovrTouch_A | ovrTouch_B | ovrTouch_RThumb | + ovrTouch_RThumbRest | ovrTouch_RIndexTrigger, + ovrTouch_X = ovrButton_X, + ovrTouch_Y = ovrButton_Y, + ovrTouch_LThumb = ovrButton_LThumb, + ovrTouch_LThumbRest = 0x00000800, + ovrTouch_LIndexTrigger = 0x00001000, + ovrTouch_LButtonMask = ovrTouch_X | ovrTouch_Y | ovrTouch_LThumb | + ovrTouch_LThumbRest | ovrTouch_LIndexTrigger, + ovrTouch_RIndexPointing = 0x00000020, + ovrTouch_RThumbUp = 0x00000040, + ovrTouch_LIndexPointing = 0x00002000, + ovrTouch_LThumbUp = 0x00004000, + ovrTouch_RPoseMask = ovrTouch_RIndexPointing | ovrTouch_RThumbUp, + ovrTouch_LPoseMask = ovrTouch_LIndexPointing | ovrTouch_LThumbUp, + ovrTouch_EnumSize = 0x7fffffff +} ovrTouch; + +typedef struct OVR_ALIGNAS(OVR_PTR_SIZE) { + int SampleRateHz; + int SampleSizeInBytes; + int QueueMinSizeToAvoidStarvation; + int SubmitMinSamples; + int SubmitMaxSamples; + int SubmitOptimalSamples; +} ovrTouchHapticsDesc; + +typedef enum { + ovrControllerType_None = 0x0000, + ovrControllerType_LTouch = 0x0001, + ovrControllerType_RTouch = 0x0002, + ovrControllerType_Touch = + (ovrControllerType_LTouch | ovrControllerType_RTouch), + ovrControllerType_Remote = 0x0004, + ovrControllerType_XBox = 0x0010, + ovrControllerType_Object0 = 0x0100, + ovrControllerType_Object1 = 0x0200, + ovrControllerType_Object2 = 0x0400, + ovrControllerType_Object3 = 0x0800, + ovrControllerType_Active = 0xffffffff, + ovrControllerType_EnumSize = 0x7fffffff +} ovrControllerType; + +typedef enum { ovrHapticsBufferSubmit_Enqueue } ovrHapticsBufferSubmitMode; + +# define OVR_HAPTICS_BUFFER_SAMPLES_MAX 256 + +typedef struct { + const void* Samples; + int SamplesCount; + ovrHapticsBufferSubmitMode SubmitMode; +} ovrHapticsBuffer; + +typedef struct { + int RemainingQueueSpace; + int SamplesQueued; +} ovrHapticsPlaybackState; + +typedef enum { + ovrTrackedDevice_None = 0x0000, + ovrTrackedDevice_HMD = 0x0001, + ovrTrackedDevice_LTouch = 0x0002, + ovrTrackedDevice_RTouch = 0x0004, + ovrTrackedDevice_Touch = (ovrTrackedDevice_LTouch | ovrTrackedDevice_RTouch), + ovrTrackedDevice_Object0 = 0x0010, + ovrTrackedDevice_Object1 = 0x0020, + ovrTrackedDevice_Object2 = 0x0040, + ovrTrackedDevice_Object3 = 0x0080, + ovrTrackedDevice_All = 0xFFFF, +} ovrTrackedDeviceType; + +typedef enum { + ovrBoundary_Outer = 0x0001, + ovrBoundary_PlayArea = 0x0100, +} ovrBoundaryType; + +typedef struct { + ovrColorf Color; +} ovrBoundaryLookAndFeel; + +typedef struct { + ovrBool IsTriggering; + float ClosestDistance; + ovrVector3f ClosestPoint; + ovrVector3f ClosestPointNormal; +} ovrBoundaryTestResult; + +typedef enum { + ovrHand_Left = 0, + ovrHand_Right = 1, + ovrHand_Count = 2, + ovrHand_EnumSize = 0x7fffffff +} ovrHandType; + +typedef struct { + double TimeInSeconds; + unsigned int Buttons; + unsigned int Touches; + float IndexTrigger[ovrHand_Count]; + float HandTrigger[ovrHand_Count]; + ovrVector2f Thumbstick[ovrHand_Count]; + ovrControllerType ControllerType; + float IndexTriggerNoDeadzone[ovrHand_Count]; + float HandTriggerNoDeadzone[ovrHand_Count]; + ovrVector2f ThumbstickNoDeadzone[ovrHand_Count]; + float IndexTriggerRaw[ovrHand_Count]; + float HandTriggerRaw[ovrHand_Count]; + ovrVector2f ThumbstickRaw[ovrHand_Count]; +} ovrInputState; + +typedef struct { + double LastChangedTime; + ovrFovPort FOVPort; + float VirtualNearPlaneDistanceMeters; + float VirtualFarPlaneDistanceMeters; + ovrSizei ImageSensorPixelResolution; + ovrMatrix4f LensDistortionMatrix; + double ExposurePeriodSeconds; + double ExposureDurationSeconds; +} ovrCameraIntrinsics; + +typedef enum { + ovrCameraStatus_None = 0x0, + ovrCameraStatus_Connected = 0x1, + ovrCameraStatus_Calibrating = 0x2, + ovrCameraStatus_CalibrationFailed = 0x4, + ovrCameraStatus_Calibrated = 0x8, + ovrCameraStatus_EnumSize = 0x7fffffff +} ovrCameraStatusFlags; + +typedef struct { + double LastChangedTimeSeconds; + unsigned int CameraStatusFlags; + ovrTrackedDeviceType AttachedToDevice; + ovrPosef RelativePose; + double LastExposureTimeSeconds; + double ExposureLatencySeconds; + double AdditionalLatencySeconds; +} ovrCameraExtrinsics; + +# define OVR_EXTERNAL_CAMERA_NAME_SIZE 32 +typedef struct { + char Name[OVR_EXTERNAL_CAMERA_NAME_SIZE]; + ovrCameraIntrinsics Intrinsics; + ovrCameraExtrinsics Extrinsics; +} ovrExternalCamera; + +typedef enum { + ovrInit_Debug = 0x00000001, + ovrInit_RequestVersion = 0x00000004, + ovrInit_Invisible = 0x00000010, + ovrInit_MixedRendering = 0x00000020, + ovrInit_FocusAware = 0x00000040, + ovrinit_WritableBits = 0x00ffffff, + ovrInit_EnumSize = 0x7fffffff +} ovrInitFlags; + +typedef enum { + ovrLogLevel_Debug = 0, + ovrLogLevel_Info = 1, + ovrLogLevel_Error = 2, + ovrLogLevel_EnumSize = 0x7fffffff +} ovrLogLevel; + +typedef void(OVR_PFN* ovrLogCallback)(uintptr_t userData, int level, + const char* message); + +typedef struct OVR_ALIGNAS(8) { + uint32_t Flags; + uint32_t RequestedMinorVersion; + ovrLogCallback LogCallback; + uintptr_t UserData; + uint32_t ConnectionTimeoutMS; + OVR_ON64(OVR_UNUSED_STRUCT_PAD(pad0, 4)) +} ovrInitParams; + +typedef ovrResult(OVR_PFN* pfn_ovr_Initialize)(const ovrInitParams* params); +typedef void(OVR_PFN* pfn_ovr_Shutdown)(); + +typedef struct { + ovrResult Result; + char ErrorString[512]; +} ovrErrorInfo; + +typedef void(OVR_PFN* pfn_ovr_GetLastErrorInfo)(ovrErrorInfo* errorInfo); +typedef const char*(OVR_PFN* pfn_ovr_GetVersionString)(); +typedef int(OVR_PFN* pfn_ovr_TraceMessage)(int level, const char* message); +typedef ovrResult(OVR_PFN* pfn_ovr_IdentifyClient)(const char* identity); +typedef ovrHmdDesc(OVR_PFN* pfn_ovr_GetHmdDesc)(ovrSession session); +typedef unsigned int(OVR_PFN* pfn_ovr_GetTrackerCount)(ovrSession session); +typedef ovrTrackerDesc(OVR_PFN* pfn_ovr_GetTrackerDesc)( + ovrSession session, unsigned int trackerDescIndex); +typedef ovrResult(OVR_PFN* pfn_ovr_Create)(ovrSession* pSession, + ovrGraphicsLuid* pLuid); +typedef void(OVR_PFN* pfn_ovr_Destroy)(ovrSession session); + +typedef struct { + ovrBool IsVisible; + ovrBool HmdPresent; + ovrBool HmdMounted; + ovrBool DisplayLost; + ovrBool ShouldQuit; + ovrBool ShouldRecenter; + ovrBool HasInputFocus; + ovrBool OverlayPresent; +} ovrSessionStatus; + +typedef ovrResult(OVR_PFN* pfn_ovr_GetSessionStatus)( + ovrSession session, ovrSessionStatus* sessionStatus); +typedef ovrResult(OVR_PFN* pfn_ovr_IsExtensionSupported)( + ovrSession session, ovrExtensions extension, + ovrBool* outExtensionSupported); +typedef ovrResult(OVR_PFN* pfn_ovr_EnableExtension)(ovrSession session, + ovrExtensions extension); +typedef ovrResult(OVR_PFN* pfn_ovr_SetTrackingOriginType)( + ovrSession session, ovrTrackingOrigin origin); +typedef ovrTrackingOrigin(OVR_PFN* pfn_ovr_GetTrackingOriginType)( + ovrSession session); +typedef ovrResult(OVR_PFN* pfn_ovr_RecenterTrackingOrigin)(ovrSession session); +typedef ovrResult(OVR_PFN* pfn_ovr_SpecifyTrackingOrigin)(ovrSession session, + ovrPosef originPose); +typedef void(OVR_PFN* pfn_ovr_ClearShouldRecenterFlag)(ovrSession session); +typedef ovrTrackingState(OVR_PFN* pfn_ovr_GetTrackingState)( + ovrSession session, double absTime, ovrBool latencyMarker); +typedef ovrResult(OVR_PFN* pfn_ovr_GetDevicePoses)( + ovrSession session, ovrTrackedDeviceType* deviceTypes, int deviceCount, + double absTime, ovrPoseStatef* outDevicePoses); +typedef ovrTrackerPose(OVR_PFN* pfn_ovr_GetTrackerPose)( + ovrSession session, unsigned int trackerPoseIndex); +typedef ovrResult(OVR_PFN* pfn_ovr_GetInputState)( + ovrSession session, ovrControllerType controllerType, + ovrInputState* inputState); +typedef unsigned int(OVR_PFN* pfn_ovr_GetConnectedControllerTypes)( + ovrSession session); +typedef ovrTouchHapticsDesc(OVR_PFN* pfn_ovr_GetTouchHapticsDesc)( + ovrSession session, ovrControllerType controllerType); +typedef ovrResult(OVR_PFN* pfn_ovr_SetControllerVibration)( + ovrSession session, ovrControllerType controllerType, float frequency, + float amplitude); +typedef ovrResult(OVR_PFN* pfn_ovr_SubmitControllerVibration)( + ovrSession session, ovrControllerType controllerType, + const ovrHapticsBuffer* buffer); +typedef ovrResult(OVR_PFN* pfn_ovr_GetControllerVibrationState)( + ovrSession session, ovrControllerType controllerType, + ovrHapticsPlaybackState* outState); +typedef ovrResult(OVR_PFN* pfn_ovr_TestBoundary)( + ovrSession session, ovrTrackedDeviceType deviceBitmask, + ovrBoundaryType boundaryType, ovrBoundaryTestResult* outTestResult); +typedef ovrResult(OVR_PFN* pfn_ovr_TestBoundaryPoint)( + ovrSession session, const ovrVector3f* point, + ovrBoundaryType singleBoundaryType, ovrBoundaryTestResult* outTestResult); +typedef ovrResult(OVR_PFN* pfn_ovr_SetBoundaryLookAndFeel)( + ovrSession session, const ovrBoundaryLookAndFeel* lookAndFeel); +typedef ovrResult(OVR_PFN* pfn_ovr_ResetBoundaryLookAndFeel)( + ovrSession session); +typedef ovrResult(OVR_PFN* pfn_ovr_GetBoundaryGeometry)( + ovrSession session, ovrBoundaryType boundaryType, + ovrVector3f* outFloorPoints, int* outFloorPointsCount); +typedef ovrResult(OVR_PFN* pfn_ovr_GetBoundaryDimensions)( + ovrSession session, ovrBoundaryType boundaryType, + ovrVector3f* outDimensions); +typedef ovrResult(OVR_PFN* pfn_ovr_GetBoundaryVisible)(ovrSession session, + ovrBool* outIsVisible); +typedef ovrResult(OVR_PFN* pfn_ovr_RequestBoundaryVisible)(ovrSession session, + ovrBool visible); + +enum { ovrMaxLayerCount = 16 }; + +typedef enum { + ovrLayerType_Disabled = 0, + ovrLayerType_EyeFov = 1, + ovrLayerType_Quad = 3, + ovrLayerType_EyeMatrix = 5, + ovrLayerType_EyeFovMultires = 7, + ovrLayerType_Cube = 10, + ovrLayerType_EnumSize = 0x7fffffff +} ovrLayerType; + +typedef enum { + ovrLayerFlag_HighQuality = 0x01, + ovrLayerFlag_TextureOriginAtBottomLeft = 0x02, + ovrLayerFlag_HeadLocked = 0x04 +} ovrLayerFlags; + +typedef struct OVR_ALIGNAS(OVR_PTR_SIZE) { + ovrLayerType Type; + unsigned Flags; +} ovrLayerHeader; + +typedef struct OVR_ALIGNAS(OVR_PTR_SIZE) { + ovrLayerHeader Header; + ovrTextureSwapChain ColorTexture[ovrEye_Count]; + ovrRecti Viewport[ovrEye_Count]; + ovrFovPort Fov[ovrEye_Count]; + ovrPosef RenderPose[ovrEye_Count]; + double SensorSampleTime; +} ovrLayerEyeFov; + +typedef enum { + ovrTextureLayout_Rectilinear = 0, + ovrTextureLayout_Octilinear = 1, + ovrTextureLayout_EnumSize = 0x7fffffff +} ovrTextureLayout; + +typedef struct OVR_ALIGNAS(OVR_PTR_SIZE) { + float WarpLeft; + float WarpRight; + float WarpUp; + float WarpDown; + float SizeLeft; + float SizeRight; + float SizeUp; + float SizeDown; + +} ovrTextureLayoutOctilinear; + +typedef union OVR_ALIGNAS(OVR_PTR_SIZE) { + ovrTextureLayoutOctilinear Octilinear[ovrEye_Count]; +} ovrTextureLayoutDesc_Union; + +typedef struct OVR_ALIGNAS(OVR_PTR_SIZE) { + ovrLayerHeader Header; + ovrTextureSwapChain ColorTexture[ovrEye_Count]; + ovrRecti Viewport[ovrEye_Count]; + ovrFovPort Fov[ovrEye_Count]; + ovrPosef RenderPose[ovrEye_Count]; + double SensorSampleTime; + ovrTextureLayout TextureLayout; + ovrTextureLayoutDesc_Union TextureLayoutDesc; +} ovrLayerEyeFovMultires; + +typedef struct OVR_ALIGNAS(OVR_PTR_SIZE) { + ovrLayerHeader Header; + ovrTextureSwapChain ColorTexture[ovrEye_Count]; + ovrRecti Viewport[ovrEye_Count]; + ovrPosef RenderPose[ovrEye_Count]; + ovrMatrix4f Matrix[ovrEye_Count]; + double SensorSampleTime; +} ovrLayerEyeMatrix; + +typedef struct OVR_ALIGNAS(OVR_PTR_SIZE) { + ovrLayerHeader Header; + ovrTextureSwapChain ColorTexture; + ovrRecti Viewport; + ovrPosef QuadPoseCenter; + ovrVector2f QuadSize; +} ovrLayerQuad; + +typedef struct OVR_ALIGNAS(OVR_PTR_SIZE) { + ovrLayerHeader Header; + ovrQuatf Orientation; + ovrTextureSwapChain CubeMapTexture; +} ovrLayerCube; + +typedef union { + ovrLayerHeader Header; + ovrLayerEyeFov EyeFov; + ovrLayerQuad Quad; + ovrLayerEyeFovMultires Multires; + ovrLayerCube Cube; +} ovrLayer_Union; + +typedef ovrResult(OVR_PFN* pfn_ovr_GetTextureSwapChainLength)( + ovrSession session, ovrTextureSwapChain chain, int* out_Length); +typedef ovrResult(OVR_PFN* pfn_ovr_GetTextureSwapChainCurrentIndex)( + ovrSession session, ovrTextureSwapChain chain, int* out_Index); +typedef ovrResult(OVR_PFN* pfn_ovr_GetTextureSwapChainDesc)( + ovrSession session, ovrTextureSwapChain chain, + ovrTextureSwapChainDesc* out_Desc); +typedef ovrResult(OVR_PFN* pfn_ovr_CommitTextureSwapChain)( + ovrSession session, ovrTextureSwapChain chain); +typedef void(OVR_PFN* pfn_ovr_DestroyTextureSwapChain)( + ovrSession session, ovrTextureSwapChain chain); +typedef void(OVR_PFN* pfn_ovr_DestroyMirrorTexture)( + ovrSession session, ovrMirrorTexture mirrorTexture); +typedef ovrSizei(OVR_PFN* pfn_ovr_GetFovTextureSize)( + ovrSession session, ovrEyeType eye, ovrFovPort fov, + float pixelsPerDisplayPixel); +typedef ovrEyeRenderDesc(OVR_PFN* pfn_ovr_GetRenderDesc2)(ovrSession session, + ovrEyeType eyeType, + ovrFovPort fov); +typedef ovrResult(OVR_PFN* pfn_ovr_WaitToBeginFrame)(ovrSession session, + long long frameIndex); +typedef ovrResult(OVR_PFN* pfn_ovr_BeginFrame)(ovrSession session, + long long frameIndex); +typedef ovrResult(OVR_PFN* pfn_ovr_EndFrame)( + ovrSession session, long long frameIndex, + const ovrViewScaleDesc* viewScaleDesc, + ovrLayerHeader const* const* layerPtrList, unsigned int layerCount); +typedef ovrResult(OVR_PFN* pfn_ovr_SubmitFrame)( + ovrSession session, long long frameIndex, + const ovrViewScaleDesc* viewScaleDesc, + ovrLayerHeader const* const* layerPtrList, unsigned int layerCount); + +typedef struct OVR_ALIGNAS(4) { + int HmdVsyncIndex; + int AppFrameIndex; + int AppDroppedFrameCount; + float AppMotionToPhotonLatency; + float AppQueueAheadTime; + float AppCpuElapsedTime; + float AppGpuElapsedTime; + int CompositorFrameIndex; + int CompositorDroppedFrameCount; + float CompositorLatency; + float CompositorCpuElapsedTime; + float CompositorGpuElapsedTime; + float CompositorCpuStartToGpuEndElapsedTime; + float CompositorGpuEndToVsyncElapsedTime; + ovrBool AswIsActive; + int AswActivatedToggleCount; + int AswPresentedFrameCount; + int AswFailedFrameCount; +} ovrPerfStatsPerCompositorFrame; + +enum { ovrMaxProvidedFrameStats = 5 }; + +typedef struct OVR_ALIGNAS(4) { + ovrPerfStatsPerCompositorFrame FrameStats[ovrMaxProvidedFrameStats]; + int FrameStatsCount; + ovrBool AnyFrameStatsDropped; + float AdaptiveGpuPerformanceScale; + ovrBool AswIsAvailable; + ovrProcessId VisibleProcessId; +} ovrPerfStats; + +typedef ovrResult(OVR_PFN* pfn_ovr_GetPerfStats)(ovrSession session, + ovrPerfStats* outStats); +typedef ovrResult(OVR_PFN* pfn_ovr_ResetPerfStats)(ovrSession session); +typedef double(OVR_PFN* pfn_ovr_GetPredictedDisplayTime)(ovrSession session, + long long frameIndex); +typedef double(OVR_PFN* pfn_ovr_GetTimeInSeconds)(); + +typedef enum { + ovrPerfHud_Off = 0, + ovrPerfHud_PerfSummary = 1, + ovrPerfHud_LatencyTiming = 2, + ovrPerfHud_AppRenderTiming = 3, + ovrPerfHud_CompRenderTiming = 4, + ovrPerfHud_AswStats = 6, + ovrPerfHud_VersionInfo = 5, + ovrPerfHud_Count = 7, + ovrPerfHud_EnumSize = 0x7fffffff +} ovrPerfHudMode; + +typedef enum { + ovrLayerHud_Off = 0, + ovrLayerHud_Info = 1, + ovrLayerHud_EnumSize = 0x7fffffff +} ovrLayerHudMode; + +typedef enum { + ovrDebugHudStereo_Off = 0, + ovrDebugHudStereo_Quad = 1, + ovrDebugHudStereo_QuadWithCrosshair = 2, + ovrDebugHudStereo_CrosshairAtInfinity = 3, + ovrDebugHudStereo_Count, + ovrDebugHudStereo_EnumSize = 0x7fffffff +} ovrDebugHudStereoMode; + +typedef ovrBool(OVR_PFN* pfn_ovr_GetBool)(ovrSession session, + const char* propertyName, + ovrBool defaultVal); +typedef ovrBool(OVR_PFN* pfn_ovr_SetBool)(ovrSession session, + const char* propertyName, + ovrBool value); +typedef int(OVR_PFN* pfn_ovr_GetInt)(ovrSession session, + const char* propertyName, int defaultVal); +typedef ovrBool(OVR_PFN* pfn_ovr_SetInt)(ovrSession session, + const char* propertyName, int value); +typedef float(OVR_PFN* pfn_ovr_GetFloat)(ovrSession session, + const char* propertyName, + float defaultVal); +typedef ovrBool(OVR_PFN* pfn_ovr_SetFloat)(ovrSession session, + const char* propertyName, + float value); +typedef unsigned int(OVR_PFN* pfn_ovr_GetFloatArray)( + ovrSession session, const char* propertyName, float values[], + unsigned int valuesCapacity); +typedef ovrBool(OVR_PFN* pfn_ovr_SetFloatArray)(ovrSession session, + const char* propertyName, + const float values[], + unsigned int valuesSize); +typedef const char*(OVR_PFN* pfn_ovr_GetString)(ovrSession session, + const char* propertyName, + const char* defaultVal); +typedef ovrBool(OVR_PFN* pfn_ovr_SetString)(ovrSession session, + const char* propertyName, + const char* value); + +typedef ovrResult(OVR_PFN* pfn_ovr_GetExternalCameras)( + ovrSession session, ovrExternalCamera* cameras, + unsigned int* inoutCameraCount); +typedef ovrResult(OVR_PFN* pfn_ovr_SetExternalCameraProperties)( + ovrSession session, const char* name, + const ovrCameraIntrinsics* const intrinsics, + const ovrCameraExtrinsics* const extrinsics); + +typedef enum { + ovrSuccess_NotVisible = 1000, + ovrSuccess_BoundaryInvalid = 1001, + ovrSuccess_DeviceUnavailable = 1002, +} ovrSuccessTypes; + +typedef enum { + ovrError_MemoryAllocationFailure = -1000, + ovrError_InvalidSession = -1002, + ovrError_Timeout = -1003, + ovrError_NotInitialized = -1004, + ovrError_InvalidParameter = -1005, + ovrError_ServiceError = -1006, + ovrError_NoHmd = -1007, + ovrError_Unsupported = -1009, + ovrError_DeviceUnavailable = -1010, + ovrError_InvalidHeadsetOrientation = -1011, + ovrError_ClientSkippedDestroy = -1012, + ovrError_ClientSkippedShutdown = -1013, + ovrError_ServiceDeadlockDetected = -1014, + ovrError_InvalidOperation = -1015, + ovrError_InsufficientArraySize = -1016, + ovrError_NoExternalCameraInfo = -1017, + ovrError_LostTracking = -1018, + ovrError_AudioDeviceNotFound = -2001, + ovrError_AudioComError = -2002, + ovrError_Initialize = -3000, + ovrError_LibLoad = -3001, + ovrError_LibVersion = -3002, + ovrError_ServiceConnection = -3003, + ovrError_ServiceVersion = -3004, + ovrError_IncompatibleOS = -3005, + ovrError_DisplayInit = -3006, + ovrError_ServerStart = -3007, + ovrError_Reinitialization = -3008, + ovrError_MismatchedAdapters = -3009, + ovrError_LeakingResources = -3010, + ovrError_ClientVersion = -3011, + ovrError_OutOfDateOS = -3012, + ovrError_OutOfDateGfxDriver = -3013, + ovrError_IncompatibleGPU = -3014, + ovrError_NoValidVRDisplaySystem = -3015, + ovrError_Obsolete = -3016, + ovrError_DisabledOrDefaultAdapter = -3017, + ovrError_HybridGraphicsNotSupported = -3018, + ovrError_DisplayManagerInit = -3019, + ovrError_TrackerDriverInit = -3020, + ovrError_LibSignCheck = -3021, + ovrError_LibPath = -3022, + ovrError_LibSymbols = -3023, + ovrError_RemoteSession = -3024, + ovrError_InitializeVulkan = -3025, + ovrError_DisplayLost = -6000, + ovrError_TextureSwapChainFull = -6001, + ovrError_TextureSwapChainInvalid = -6002, + ovrError_GraphicsDeviceReset = -6003, + ovrError_DisplayRemoved = -6004, + ovrError_ContentProtectionNotAvailable = -6005, + ovrError_ApplicationInvisible = -6006, + ovrError_Disallowed = -6007, + ovrError_DisplayPluggedIncorrectly = -6008, + ovrError_RuntimeException = -7000, + ovrError_NoCalibration = -9000, + ovrError_OldVersion = -9001, + ovrError_MisformattedBlock = -9002, +} ovrErrorType; + +# ifdef XP_WIN + +struct IUnknown; + +typedef ovrResult(OVR_PFN* pfn_ovr_CreateTextureSwapChainDX)( + ovrSession session, IUnknown* d3dPtr, const ovrTextureSwapChainDesc* desc, + ovrTextureSwapChain* out_TextureSwapChain); + +typedef ovrResult(OVR_PFN* pfn_ovr_GetTextureSwapChainBufferDX)( + ovrSession session, ovrTextureSwapChain chain, int index, IID iid, + void** out_Buffer); + +typedef ovrResult(OVR_PFN* pfn_ovr_CreateMirrorTextureDX)( + ovrSession session, IUnknown* d3dPtr, const ovrMirrorTextureDesc* desc, + ovrMirrorTexture* out_MirrorTexture); + +typedef ovrResult(OVR_PFN* pfn_ovr_GetMirrorTextureBufferDX)( + ovrSession session, ovrMirrorTexture mirrorTexture, IID iid, + void** out_Buffer); + +# endif + +typedef ovrResult(OVR_PFN* pfn_ovr_CreateTextureSwapChainGL)( + ovrSession session, const ovrTextureSwapChainDesc* desc, + ovrTextureSwapChain* out_TextureSwapChain); + +typedef ovrResult(OVR_PFN* pfn_ovr_GetTextureSwapChainBufferGL)( + ovrSession session, ovrTextureSwapChain chain, int index, + unsigned int* out_TexId); + +typedef ovrResult(OVR_PFN* pfn_ovr_CreateMirrorTextureGL)( + ovrSession session, const ovrMirrorTextureDesc* desc, + ovrMirrorTexture* out_MirrorTexture); + +typedef ovrResult(OVR_PFN* pfn_ovr_GetMirrorTextureBufferGL)( + ovrSession session, ovrMirrorTexture mirrorTexture, + unsigned int* out_TexId); + +# define OVR_KEY_EYE_HEIGHT "EyeHeight" // float meters +# define OVR_DEFAULT_EYE_HEIGHT 1.675f + +# if !defined(OVR_SUCCESS) +# define OVR_SUCCESS(result) (result >= 0) +# endif + +# if !defined(OVR_UNQUALIFIED_SUCCESS) +# define OVR_UNQUALIFIED_SUCCESS(result) (result == ovrSuccess) +# endif + +# if !defined(OVR_FAILURE) +# define OVR_FAILURE(result) (!OVR_SUCCESS(result)) +# endif + +# ifdef __cplusplus +} +# endif + +# endif /* mozilla_ovr_capi_dynamic_h_ */ +#endif /* OVR_CAPI_h */ diff --git a/gfx/vr/service/openvr/LICENSE b/gfx/vr/service/openvr/LICENSE new file mode 100644 index 0000000000..ee83337d7f --- /dev/null +++ b/gfx/vr/service/openvr/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2015, Valve Corporation +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/gfx/vr/service/openvr/README.md b/gfx/vr/service/openvr/README.md new file mode 100644 index 0000000000..2161e0dba9 --- /dev/null +++ b/gfx/vr/service/openvr/README.md @@ -0,0 +1,13 @@ +OpenVR SDK
+---
+
+OpenVR is an API and runtime that allows access to VR hardware from multiple
+vendors without requiring that applications have specific knowledge of the
+hardware they are targeting. This repository is an SDK that contains the API
+and samples. The runtime is under SteamVR in Tools on Steam.
+
+### Documentation
+
+Documentation for the API is available on the [Github Wiki](https://github.com/ValveSoftware/openvr/wiki/API-Documentation)
+
+More information on OpenVR and SteamVR can be found on http://steamvr.com
diff --git a/gfx/vr/service/openvr/README.mozilla b/gfx/vr/service/openvr/README.mozilla new file mode 100644 index 0000000000..5a8e502192 --- /dev/null +++ b/gfx/vr/service/openvr/README.mozilla @@ -0,0 +1,80 @@ +This directory contains files from the OpenVR SDK, version 1.11.11. + +This SDK contains the OpenVR API interface headers and functions to load the +OpenVR runtime libraries which actually implement the functionality. The +loading functions parse a .json file in a pre-defined location on the +end-user's machine to get details used to bind the correct runtime library. +The OpenVR implementation ensures forward and backwards compatibility as of +the current version. + +Updated versions of the OpenVR SDK are available on Github: + +https://github.com/ValveSoftware/openvr + + +We only use some files from the SDK: + +- We strip out files such as C# headers, plain C API versions, and files + needed only when implementing OpenVR drivers. + +- CMake related files, such as CMakeLists.txt are skipped as we use moz.build + files to configure the library. + +- The "src/jsoncpp.cpp" file and the "src/json" directory can be skipped. OpenVR + uses the jsoncpp library, which we have already imported elsewhere. If + warnings about using deprecated jsoncpp classes show up during compilation + you might need to reapply the patch in bug 1598288. It replaces uses of the + `Json::Reader` and `Json::StyledWriter` classes with the more modern + `Json::CharReaderBuilder` and `Json::StreamWriterBuilder`. + + +Steps to update the library: + +- Copy "README.md" from the root of the openvr repo to the "gfx/vr/service/openvr" + directory. + +- Copy "headers/openvr.h" to "gfx/vr/service/openvr/headers" directory. The other files + in this directory can be ignored. + +- The rest of the files in the "src" directory and the "src/vrcommon" are copied + to the "gfx/vr/service/openvr/src" directory. + +- Update "gfx/vr/service/openvr/moz.build" when files are added or removed. + +- Update the "strtools_public.h" and "strtools_public.cpp" files, commenting out + the "Uint64ToString", "wcsncpy_s", and "strncpy_s" functions. + The "Uint64ToString" function name conflicts with another used in Gecko and + the "errno_t" return type returned by the other functions is not defined in + Mozilla's macOS continuous integration build environments. Fortunately, the + OpenVR SDK does not use these functions. + +- Replace the #define VR_INTERFACE in openvr.h to avoid extern'ing the functions. + Unlike the usual OpenVR API builds, we are not building a separate dll. + +- Add explicit in CVRSettingHelper constructor. + +- In strtools_public.cpp/.h, ensure that UTF16to8 and UTF8to16 are only + compiled under + #if defined( _WIN32 ) + and redefine those functions to use ::WideCharToMultiByte and + MultiByteToWideChar, respectively. These are modified because the original + implementations contain unsupported try-catch. + +- In strtools_public.cpp, remove the definition of convert_type. + +- In strtools_public.cpp, remove the include of <codecvt>, as this causes + problems in compiling on Linux. + +- In pathtools_public.cpp/.h, comment out Path_UrlToFilePath and + Path_FilePathToUrl to avoid a compile error because 'alloca' isn't defined. + +- In vrpathregistry_public.cpp, CVRPathRegistry_Public::BLoadFromFile contains + a try-catch, which is not permitted. This code is simply commented out, but + Bug 1640068 - OpenVR code can fail JSON parsing and raise exceptions + is filed to address a safe fallback in the error condition. + +- Update this README.mozilla file with the new OpenVR SDK version and any + additional steps needed for newer versions. + +- Ensure that any changes made within the OpenVR files have comments including + the string "Mozilla" and reference this file for easy identification. diff --git a/gfx/vr/service/openvr/headers/openvr.h b/gfx/vr/service/openvr/headers/openvr.h new file mode 100644 index 0000000000..b95fb1db7e --- /dev/null +++ b/gfx/vr/service/openvr/headers/openvr.h @@ -0,0 +1,5509 @@ +#pragma once + +// openvr.h +//========= Copyright Valve Corporation ============// +// Dynamically generated file. Do not modify this file directly. + +#ifndef _OPENVR_API +#define _OPENVR_API + +#include <stdint.h> + + + +// version.h +namespace vr +{ + static const uint32_t k_nSteamVRVersionMajor = 1; + static const uint32_t k_nSteamVRVersionMinor = 11; + static const uint32_t k_nSteamVRVersionBuild = 11; +} // namespace vr + +// vrtypes.h +#ifndef _INCLUDE_VRTYPES_H +#define _INCLUDE_VRTYPES_H + +// Forward declarations to avoid requiring vulkan.h +struct VkDevice_T; +struct VkPhysicalDevice_T; +struct VkInstance_T; +struct VkQueue_T; + +// Forward declarations to avoid requiring d3d12.h +struct ID3D12Resource; +struct ID3D12CommandQueue; + +namespace vr +{ +#pragma pack( push, 8 ) + +/** A handle for a spatial anchor. This handle is only valid during the session it was created in. +* Anchors that live beyond one session should be saved by their string descriptors. */ +typedef uint32_t SpatialAnchorHandle_t; + +typedef void* glSharedTextureHandle_t; +typedef int32_t glInt_t; +typedef uint32_t glUInt_t; + +// right-handed system +// +y is up +// +x is to the right +// -z is forward +// Distance unit is meters +struct HmdMatrix34_t +{ + float m[3][4]; +}; + +struct HmdMatrix33_t +{ + float m[3][3]; +}; + +struct HmdMatrix44_t +{ + float m[4][4]; +}; + +struct HmdVector3_t +{ + float v[3]; +}; + +struct HmdVector4_t +{ + float v[4]; +}; + +struct HmdVector3d_t +{ + double v[3]; +}; + +struct HmdVector2_t +{ + float v[2]; +}; + +struct HmdQuaternion_t +{ + double w, x, y, z; +}; + +struct HmdQuaternionf_t +{ + float w, x, y, z; +}; + +struct HmdColor_t +{ + float r, g, b, a; +}; + +struct HmdQuad_t +{ + HmdVector3_t vCorners[ 4 ]; +}; + +struct HmdRect2_t +{ + HmdVector2_t vTopLeft; + HmdVector2_t vBottomRight; +}; + +/** Used to return the post-distortion UVs for each color channel. +* UVs range from 0 to 1 with 0,0 in the upper left corner of the +* source render target. The 0,0 to 1,1 range covers a single eye. */ +struct DistortionCoordinates_t +{ + float rfRed[2]; + float rfGreen[2]; + float rfBlue[2]; +}; + +enum EVREye +{ + Eye_Left = 0, + Eye_Right = 1 +}; + +enum ETextureType +{ + TextureType_Invalid = -1, // Handle has been invalidated + TextureType_DirectX = 0, // Handle is an ID3D11Texture + TextureType_OpenGL = 1, // Handle is an OpenGL texture name or an OpenGL render buffer name, depending on submit flags + TextureType_Vulkan = 2, // Handle is a pointer to a VRVulkanTextureData_t structure + TextureType_IOSurface = 3, // Handle is a macOS cross-process-sharable IOSurfaceRef, deprecated in favor of TextureType_Metal on supported platforms + TextureType_DirectX12 = 4, // Handle is a pointer to a D3D12TextureData_t structure + TextureType_DXGISharedHandle = 5, // Handle is a HANDLE DXGI share handle, only supported for Overlay render targets. + // this texture is used directly by our renderer, so only perform atomic (copyresource or resolve) on it + TextureType_Metal = 6, // Handle is a MTLTexture conforming to the MTLSharedTexture protocol. Textures submitted to IVRCompositor::Submit which + // are of type MTLTextureType2DArray assume layer 0 is the left eye texture (vr::EVREye::Eye_left), layer 1 is the right + // eye texture (vr::EVREye::Eye_Right) +}; + +enum EColorSpace +{ + ColorSpace_Auto = 0, // Assumes 'gamma' for 8-bit per component formats, otherwise 'linear'. This mirrors the DXGI formats which have _SRGB variants. + ColorSpace_Gamma = 1, // Texture data can be displayed directly on the display without any conversion (a.k.a. display native format). + ColorSpace_Linear = 2, // Same as gamma but has been converted to a linear representation using DXGI's sRGB conversion algorithm. +}; + +struct Texture_t +{ + void* handle; // See ETextureType definition above + ETextureType eType; + EColorSpace eColorSpace; +}; + +// Handle to a shared texture (HANDLE on Windows obtained using OpenSharedResource). +typedef uint64_t SharedTextureHandle_t; +#define INVALID_SHARED_TEXTURE_HANDLE ((vr::SharedTextureHandle_t)0) + +enum ETrackingResult +{ + TrackingResult_Uninitialized = 1, + + TrackingResult_Calibrating_InProgress = 100, + TrackingResult_Calibrating_OutOfRange = 101, + + TrackingResult_Running_OK = 200, + TrackingResult_Running_OutOfRange = 201, + + TrackingResult_Fallback_RotationOnly = 300, +}; + +typedef uint32_t DriverId_t; +static const uint32_t k_nDriverNone = 0xFFFFFFFF; + +static const uint32_t k_unMaxDriverDebugResponseSize = 32768; + +/** Used to pass device IDs to API calls */ +typedef uint32_t TrackedDeviceIndex_t; +static const uint32_t k_unTrackedDeviceIndex_Hmd = 0; +static const uint32_t k_unMaxTrackedDeviceCount = 64; +static const uint32_t k_unTrackedDeviceIndexOther = 0xFFFFFFFE; +static const uint32_t k_unTrackedDeviceIndexInvalid = 0xFFFFFFFF; + +/** Describes what kind of object is being tracked at a given ID */ +enum ETrackedDeviceClass +{ + TrackedDeviceClass_Invalid = 0, // the ID was not valid. + TrackedDeviceClass_HMD = 1, // Head-Mounted Displays + TrackedDeviceClass_Controller = 2, // Tracked controllers + TrackedDeviceClass_GenericTracker = 3, // Generic trackers, similar to controllers + TrackedDeviceClass_TrackingReference = 4, // Camera and base stations that serve as tracking reference points + TrackedDeviceClass_DisplayRedirect = 5, // Accessories that aren't necessarily tracked themselves, but may redirect video output from other tracked devices + + TrackedDeviceClass_Max +}; + + +/** Describes what specific role associated with a tracked device */ +enum ETrackedControllerRole +{ + TrackedControllerRole_Invalid = 0, // Invalid value for controller type + TrackedControllerRole_LeftHand = 1, // Tracked device associated with the left hand + TrackedControllerRole_RightHand = 2, // Tracked device associated with the right hand + TrackedControllerRole_OptOut = 3, // Tracked device is opting out of left/right hand selection + TrackedControllerRole_Treadmill = 4, // Tracked device is a treadmill or other locomotion device + TrackedControllerRole_Stylus = 5, // Tracked device is a stylus + TrackedControllerRole_Max = 5 +}; + + +/** Returns true if the tracked controller role is allowed to be a hand */ +inline bool IsRoleAllowedAsHand( ETrackedControllerRole eRole ) +{ + switch ( eRole ) + { + case TrackedControllerRole_Invalid: + case TrackedControllerRole_LeftHand: + case TrackedControllerRole_RightHand: + return true; + default: + return false; + } +} + + +/** describes a single pose for a tracked object */ +struct TrackedDevicePose_t +{ + HmdMatrix34_t mDeviceToAbsoluteTracking; + HmdVector3_t vVelocity; // velocity in tracker space in m/s + HmdVector3_t vAngularVelocity; // angular velocity in radians/s (?) + ETrackingResult eTrackingResult; + bool bPoseIsValid; + + // This indicates that there is a device connected for this spot in the pose array. + // It could go from true to false if the user unplugs the device. + bool bDeviceIsConnected; +}; + +/** Identifies which style of tracking origin the application wants to use +* for the poses it is requesting */ +enum ETrackingUniverseOrigin +{ + TrackingUniverseSeated = 0, // Poses are provided relative to the seated zero pose + TrackingUniverseStanding = 1, // Poses are provided relative to the safe bounds configured by the user + TrackingUniverseRawAndUncalibrated = 2, // Poses are provided in the coordinate system defined by the driver. It has Y up and is unified for devices of the same driver. You usually don't want this one. +}; + +enum EAdditionalRadioFeatures +{ + AdditionalRadioFeatures_None = 0x00000000, + AdditionalRadioFeatures_HTCLinkBox = 0x00000001, + AdditionalRadioFeatures_InternalDongle = 0x00000002, + AdditionalRadioFeatures_ExternalDongle = 0x00000004, +}; + +typedef uint64_t WebConsoleHandle_t; +#define INVALID_WEB_CONSOLE_HANDLE ((vr::WebConsoleHandle_t)0) + +// Refers to a single container of properties +typedef uint64_t PropertyContainerHandle_t; +typedef uint32_t PropertyTypeTag_t; + +static const PropertyContainerHandle_t k_ulInvalidPropertyContainer = 0; +static const PropertyTypeTag_t k_unInvalidPropertyTag = 0; + +typedef PropertyContainerHandle_t DriverHandle_t; +static const PropertyContainerHandle_t k_ulInvalidDriverHandle = 0; + +// Use these tags to set/get common types as struct properties +static const PropertyTypeTag_t k_unFloatPropertyTag = 1; +static const PropertyTypeTag_t k_unInt32PropertyTag = 2; +static const PropertyTypeTag_t k_unUint64PropertyTag = 3; +static const PropertyTypeTag_t k_unBoolPropertyTag = 4; +static const PropertyTypeTag_t k_unStringPropertyTag = 5; +static const PropertyTypeTag_t k_unErrorPropertyTag = 6; +static const PropertyTypeTag_t k_unDoublePropertyTag = 7; + +static const PropertyTypeTag_t k_unHmdMatrix34PropertyTag = 20; +static const PropertyTypeTag_t k_unHmdMatrix44PropertyTag = 21; +static const PropertyTypeTag_t k_unHmdVector3PropertyTag = 22; +static const PropertyTypeTag_t k_unHmdVector4PropertyTag = 23; +static const PropertyTypeTag_t k_unHmdVector2PropertyTag = 24; +static const PropertyTypeTag_t k_unHmdQuadPropertyTag = 25; + +static const PropertyTypeTag_t k_unHiddenAreaPropertyTag = 30; +static const PropertyTypeTag_t k_unPathHandleInfoTag = 31; +static const PropertyTypeTag_t k_unActionPropertyTag = 32; +static const PropertyTypeTag_t k_unInputValuePropertyTag = 33; +static const PropertyTypeTag_t k_unWildcardPropertyTag = 34; +static const PropertyTypeTag_t k_unHapticVibrationPropertyTag = 35; +static const PropertyTypeTag_t k_unSkeletonPropertyTag = 36; + +static const PropertyTypeTag_t k_unSpatialAnchorPosePropertyTag = 40; +static const PropertyTypeTag_t k_unJsonPropertyTag = 41; +static const PropertyTypeTag_t k_unActiveActionSetPropertyTag = 42; + +static const PropertyTypeTag_t k_unOpenVRInternalReserved_Start = 1000; +static const PropertyTypeTag_t k_unOpenVRInternalReserved_End = 10000; + + +/** Each entry in this enum represents a property that can be retrieved about a +* tracked device. Many fields are only valid for one ETrackedDeviceClass. */ +enum ETrackedDeviceProperty +{ + Prop_Invalid = 0, + + // general properties that apply to all device classes + Prop_TrackingSystemName_String = 1000, + Prop_ModelNumber_String = 1001, + Prop_SerialNumber_String = 1002, + Prop_RenderModelName_String = 1003, + Prop_WillDriftInYaw_Bool = 1004, + Prop_ManufacturerName_String = 1005, + Prop_TrackingFirmwareVersion_String = 1006, + Prop_HardwareRevision_String = 1007, + Prop_AllWirelessDongleDescriptions_String = 1008, + Prop_ConnectedWirelessDongle_String = 1009, + Prop_DeviceIsWireless_Bool = 1010, + Prop_DeviceIsCharging_Bool = 1011, + Prop_DeviceBatteryPercentage_Float = 1012, // 0 is empty, 1 is full + Prop_StatusDisplayTransform_Matrix34 = 1013, + Prop_Firmware_UpdateAvailable_Bool = 1014, + Prop_Firmware_ManualUpdate_Bool = 1015, + Prop_Firmware_ManualUpdateURL_String = 1016, + Prop_HardwareRevision_Uint64 = 1017, + Prop_FirmwareVersion_Uint64 = 1018, + Prop_FPGAVersion_Uint64 = 1019, + Prop_VRCVersion_Uint64 = 1020, + Prop_RadioVersion_Uint64 = 1021, + Prop_DongleVersion_Uint64 = 1022, + Prop_BlockServerShutdown_Bool = 1023, + Prop_CanUnifyCoordinateSystemWithHmd_Bool = 1024, + Prop_ContainsProximitySensor_Bool = 1025, + Prop_DeviceProvidesBatteryStatus_Bool = 1026, + Prop_DeviceCanPowerOff_Bool = 1027, + Prop_Firmware_ProgrammingTarget_String = 1028, + Prop_DeviceClass_Int32 = 1029, + Prop_HasCamera_Bool = 1030, + Prop_DriverVersion_String = 1031, + Prop_Firmware_ForceUpdateRequired_Bool = 1032, + Prop_ViveSystemButtonFixRequired_Bool = 1033, + Prop_ParentDriver_Uint64 = 1034, + Prop_ResourceRoot_String = 1035, + Prop_RegisteredDeviceType_String = 1036, + Prop_InputProfilePath_String = 1037, // input profile to use for this device in the input system. Will default to tracking system name if this isn't provided + Prop_NeverTracked_Bool = 1038, // Used for devices that will never have a valid pose by design + Prop_NumCameras_Int32 = 1039, + Prop_CameraFrameLayout_Int32 = 1040, // EVRTrackedCameraFrameLayout value + Prop_CameraStreamFormat_Int32 = 1041, // ECameraVideoStreamFormat value + Prop_AdditionalDeviceSettingsPath_String = 1042, // driver-relative path to additional device and global configuration settings + Prop_Identifiable_Bool = 1043, // Whether device supports being identified from vrmonitor (e.g. blink LED, vibrate haptics, etc) + Prop_BootloaderVersion_Uint64 = 1044, + Prop_AdditionalSystemReportData_String = 1045, // additional string to include in system reports about a tracked device + Prop_CompositeFirmwareVersion_String = 1046, // additional FW components from a device that gets propagated into reports + Prop_Firmware_RemindUpdate_Bool = 1047, + Prop_PeripheralApplicationVersion_Uint64 = 1048, + Prop_ManufacturerSerialNumber_String = 1049, + Prop_ComputedSerialNumber_String = 1050, + Prop_EstimatedDeviceFirstUseTime_Int32 = 1051, + + // Properties that are unique to TrackedDeviceClass_HMD + Prop_ReportsTimeSinceVSync_Bool = 2000, + Prop_SecondsFromVsyncToPhotons_Float = 2001, + Prop_DisplayFrequency_Float = 2002, + Prop_UserIpdMeters_Float = 2003, + Prop_CurrentUniverseId_Uint64 = 2004, + Prop_PreviousUniverseId_Uint64 = 2005, + Prop_DisplayFirmwareVersion_Uint64 = 2006, + Prop_IsOnDesktop_Bool = 2007, + Prop_DisplayMCType_Int32 = 2008, + Prop_DisplayMCOffset_Float = 2009, + Prop_DisplayMCScale_Float = 2010, + Prop_EdidVendorID_Int32 = 2011, + Prop_DisplayMCImageLeft_String = 2012, + Prop_DisplayMCImageRight_String = 2013, + Prop_DisplayGCBlackClamp_Float = 2014, + Prop_EdidProductID_Int32 = 2015, + Prop_CameraToHeadTransform_Matrix34 = 2016, + Prop_DisplayGCType_Int32 = 2017, + Prop_DisplayGCOffset_Float = 2018, + Prop_DisplayGCScale_Float = 2019, + Prop_DisplayGCPrescale_Float = 2020, + Prop_DisplayGCImage_String = 2021, + Prop_LensCenterLeftU_Float = 2022, + Prop_LensCenterLeftV_Float = 2023, + Prop_LensCenterRightU_Float = 2024, + Prop_LensCenterRightV_Float = 2025, + Prop_UserHeadToEyeDepthMeters_Float = 2026, + Prop_CameraFirmwareVersion_Uint64 = 2027, + Prop_CameraFirmwareDescription_String = 2028, + Prop_DisplayFPGAVersion_Uint64 = 2029, + Prop_DisplayBootloaderVersion_Uint64 = 2030, + Prop_DisplayHardwareVersion_Uint64 = 2031, + Prop_AudioFirmwareVersion_Uint64 = 2032, + Prop_CameraCompatibilityMode_Int32 = 2033, + Prop_ScreenshotHorizontalFieldOfViewDegrees_Float = 2034, + Prop_ScreenshotVerticalFieldOfViewDegrees_Float = 2035, + Prop_DisplaySuppressed_Bool = 2036, + Prop_DisplayAllowNightMode_Bool = 2037, + Prop_DisplayMCImageWidth_Int32 = 2038, + Prop_DisplayMCImageHeight_Int32 = 2039, + Prop_DisplayMCImageNumChannels_Int32 = 2040, + Prop_DisplayMCImageData_Binary = 2041, + Prop_SecondsFromPhotonsToVblank_Float = 2042, + Prop_DriverDirectModeSendsVsyncEvents_Bool = 2043, + Prop_DisplayDebugMode_Bool = 2044, + Prop_GraphicsAdapterLuid_Uint64 = 2045, + Prop_DriverProvidedChaperonePath_String = 2048, + Prop_ExpectedTrackingReferenceCount_Int32 = 2049, // expected number of sensors or basestations to reserve UI space for + Prop_ExpectedControllerCount_Int32 = 2050, // expected number of tracked controllers to reserve UI space for + Prop_NamedIconPathControllerLeftDeviceOff_String = 2051, // placeholder icon for "left" controller if not yet detected/loaded + Prop_NamedIconPathControllerRightDeviceOff_String = 2052, // placeholder icon for "right" controller if not yet detected/loaded + Prop_NamedIconPathTrackingReferenceDeviceOff_String = 2053, // placeholder icon for sensor/base if not yet detected/loaded + Prop_DoNotApplyPrediction_Bool = 2054, // currently no effect. was used to disable HMD pose prediction on MR, which is now done by MR driver setting velocity=0 + Prop_CameraToHeadTransforms_Matrix34_Array = 2055, + Prop_DistortionMeshResolution_Int32 = 2056, // custom resolution of compositor calls to IVRSystem::ComputeDistortion + Prop_DriverIsDrawingControllers_Bool = 2057, + Prop_DriverRequestsApplicationPause_Bool = 2058, + Prop_DriverRequestsReducedRendering_Bool = 2059, + Prop_MinimumIpdStepMeters_Float = 2060, + Prop_AudioBridgeFirmwareVersion_Uint64 = 2061, + Prop_ImageBridgeFirmwareVersion_Uint64 = 2062, + Prop_ImuToHeadTransform_Matrix34 = 2063, + Prop_ImuFactoryGyroBias_Vector3 = 2064, + Prop_ImuFactoryGyroScale_Vector3 = 2065, + Prop_ImuFactoryAccelerometerBias_Vector3 = 2066, + Prop_ImuFactoryAccelerometerScale_Vector3 = 2067, + // reserved 2068 + Prop_ConfigurationIncludesLighthouse20Features_Bool = 2069, + Prop_AdditionalRadioFeatures_Uint64 = 2070, + Prop_CameraWhiteBalance_Vector4_Array = 2071, // Prop_NumCameras_Int32-sized array of float[4] RGBG white balance calibration data (max size is vr::k_unMaxCameras) + Prop_CameraDistortionFunction_Int32_Array = 2072, // Prop_NumCameras_Int32-sized array of vr::EVRDistortionFunctionType values (max size is vr::k_unMaxCameras) + Prop_CameraDistortionCoefficients_Float_Array = 2073, // Prop_NumCameras_Int32-sized array of double[vr::k_unMaxDistortionFunctionParameters] (max size is vr::k_unMaxCameras) + Prop_ExpectedControllerType_String = 2074, + Prop_HmdTrackingStyle_Int32 = 2075, // one of EHmdTrackingStyle + Prop_DriverProvidedChaperoneVisibility_Bool = 2076, + Prop_HmdColumnCorrectionSettingPrefix_String = 2077, + Prop_CameraSupportsCompatibilityModes_Bool = 2078, + + Prop_DisplayAvailableFrameRates_Float_Array = 2080, // populated by compositor from actual EDID list when available from GPU driver + Prop_DisplaySupportsMultipleFramerates_Bool = 2081, // if this is true but Prop_DisplayAvailableFrameRates_Float_Array is empty, explain to user + Prop_DisplayColorMultLeft_Vector3 = 2082, + Prop_DisplayColorMultRight_Vector3 = 2083, + Prop_DisplaySupportsRuntimeFramerateChange_Bool = 2084, + Prop_DisplaySupportsAnalogGain_Bool = 2085, + Prop_DisplayMinAnalogGain_Float = 2086, + Prop_DisplayMaxAnalogGain_Float = 2087, + + // Prop_DashboardLayoutPathName_String = 2090, // DELETED + Prop_DashboardScale_Float = 2091, + Prop_IpdUIRangeMinMeters_Float = 2100, + Prop_IpdUIRangeMaxMeters_Float = 2101, + + // Driver requested mura correction properties + Prop_DriverRequestedMuraCorrectionMode_Int32 = 2200, + Prop_DriverRequestedMuraFeather_InnerLeft_Int32 = 2201, + Prop_DriverRequestedMuraFeather_InnerRight_Int32 = 2202, + Prop_DriverRequestedMuraFeather_InnerTop_Int32 = 2203, + Prop_DriverRequestedMuraFeather_InnerBottom_Int32 = 2204, + Prop_DriverRequestedMuraFeather_OuterLeft_Int32 = 2205, + Prop_DriverRequestedMuraFeather_OuterRight_Int32 = 2206, + Prop_DriverRequestedMuraFeather_OuterTop_Int32 = 2207, + Prop_DriverRequestedMuraFeather_OuterBottom_Int32 = 2208, + + Prop_Audio_DefaultPlaybackDeviceId_String = 2300, + Prop_Audio_DefaultRecordingDeviceId_String = 2301, + Prop_Audio_DefaultPlaybackDeviceVolume_Float = 2302, + + // Properties that are unique to TrackedDeviceClass_Controller + Prop_AttachedDeviceId_String = 3000, + Prop_SupportedButtons_Uint64 = 3001, + Prop_Axis0Type_Int32 = 3002, // Return value is of type EVRControllerAxisType + Prop_Axis1Type_Int32 = 3003, // Return value is of type EVRControllerAxisType + Prop_Axis2Type_Int32 = 3004, // Return value is of type EVRControllerAxisType + Prop_Axis3Type_Int32 = 3005, // Return value is of type EVRControllerAxisType + Prop_Axis4Type_Int32 = 3006, // Return value is of type EVRControllerAxisType + Prop_ControllerRoleHint_Int32 = 3007, // Return value is of type ETrackedControllerRole + + // Properties that are unique to TrackedDeviceClass_TrackingReference + Prop_FieldOfViewLeftDegrees_Float = 4000, + Prop_FieldOfViewRightDegrees_Float = 4001, + Prop_FieldOfViewTopDegrees_Float = 4002, + Prop_FieldOfViewBottomDegrees_Float = 4003, + Prop_TrackingRangeMinimumMeters_Float = 4004, + Prop_TrackingRangeMaximumMeters_Float = 4005, + Prop_ModeLabel_String = 4006, + Prop_CanWirelessIdentify_Bool = 4007, // volatile, based on radio presence and fw discovery + Prop_Nonce_Int32 = 4008, + + // Properties that are used for user interface like icons names + Prop_IconPathName_String = 5000, // DEPRECATED. Value not referenced. Now expected to be part of icon path properties. + Prop_NamedIconPathDeviceOff_String = 5001, // {driver}/icons/icon_filename - PNG for static icon, or GIF for animation, 50x32 for headsets and 32x32 for others + Prop_NamedIconPathDeviceSearching_String = 5002, // {driver}/icons/icon_filename - PNG for static icon, or GIF for animation, 50x32 for headsets and 32x32 for others + Prop_NamedIconPathDeviceSearchingAlert_String = 5003, // {driver}/icons/icon_filename - PNG for static icon, or GIF for animation, 50x32 for headsets and 32x32 for others + Prop_NamedIconPathDeviceReady_String = 5004, // {driver}/icons/icon_filename - PNG for static icon, or GIF for animation, 50x32 for headsets and 32x32 for others + Prop_NamedIconPathDeviceReadyAlert_String = 5005, // {driver}/icons/icon_filename - PNG for static icon, or GIF for animation, 50x32 for headsets and 32x32 for others + Prop_NamedIconPathDeviceNotReady_String = 5006, // {driver}/icons/icon_filename - PNG for static icon, or GIF for animation, 50x32 for headsets and 32x32 for others + Prop_NamedIconPathDeviceStandby_String = 5007, // {driver}/icons/icon_filename - PNG for static icon, or GIF for animation, 50x32 for headsets and 32x32 for others + Prop_NamedIconPathDeviceAlertLow_String = 5008, // {driver}/icons/icon_filename - PNG for static icon, or GIF for animation, 50x32 for headsets and 32x32 for others + Prop_NamedIconPathDeviceStandbyAlert_String = 5009, // {driver}/icons/icon_filename - PNG for static icon, or GIF for animation, 50x32 for headsets and 32x32 for others + + // Properties that are used by helpers, but are opaque to applications + Prop_DisplayHiddenArea_Binary_Start = 5100, + Prop_DisplayHiddenArea_Binary_End = 5150, + Prop_ParentContainer = 5151, + Prop_OverrideContainer_Uint64 = 5152, + + // Properties that are unique to drivers + Prop_UserConfigPath_String = 6000, + Prop_InstallPath_String = 6001, + Prop_HasDisplayComponent_Bool = 6002, + Prop_HasControllerComponent_Bool = 6003, + Prop_HasCameraComponent_Bool = 6004, + Prop_HasDriverDirectModeComponent_Bool = 6005, + Prop_HasVirtualDisplayComponent_Bool = 6006, + Prop_HasSpatialAnchorsSupport_Bool = 6007, + + // Properties that are set internally based on other information provided by drivers + Prop_ControllerType_String = 7000, + //Prop_LegacyInputProfile_String = 7001, // This is no longer used. See "legacy_binding" in the input profile instead. + Prop_ControllerHandSelectionPriority_Int32 = 7002, // Allows hand assignments to prefer some controllers over others. High numbers are selected over low numbers + + // Vendors are free to expose private debug data in this reserved region + Prop_VendorSpecific_Reserved_Start = 10000, + Prop_VendorSpecific_Reserved_End = 10999, + + Prop_TrackedDeviceProperty_Max = 1000000, +}; + +/** No string property will ever be longer than this length */ +static const uint32_t k_unMaxPropertyStringSize = 32 * 1024; + +/** Used to return errors that occur when reading properties. */ +enum ETrackedPropertyError +{ + TrackedProp_Success = 0, + TrackedProp_WrongDataType = 1, + TrackedProp_WrongDeviceClass = 2, + TrackedProp_BufferTooSmall = 3, + TrackedProp_UnknownProperty = 4, // Driver has not set the property (and may not ever). + TrackedProp_InvalidDevice = 5, + TrackedProp_CouldNotContactServer = 6, + TrackedProp_ValueNotProvidedByDevice = 7, + TrackedProp_StringExceedsMaximumLength = 8, + TrackedProp_NotYetAvailable = 9, // The property value isn't known yet, but is expected soon. Call again later. + TrackedProp_PermissionDenied = 10, + TrackedProp_InvalidOperation = 11, + TrackedProp_CannotWriteToWildcards = 12, + TrackedProp_IPCReadFailure = 13, + TrackedProp_OutOfMemory = 14, + TrackedProp_InvalidContainer = 15, +}; + +/** Used to drive certain text in the UI when talking about the tracking system for the HMD */ +enum EHmdTrackingStyle +{ + HmdTrackingStyle_Unknown = 0, + + HmdTrackingStyle_Lighthouse = 1, // base stations and lasers + HmdTrackingStyle_OutsideInCameras = 2, // Cameras and LED, Rift 1 style + HmdTrackingStyle_InsideOutCameras = 3, // Cameras on HMD looking at the world +}; + +typedef uint64_t VRActionHandle_t; +typedef uint64_t VRActionSetHandle_t; +typedef uint64_t VRInputValueHandle_t; + +static const VRActionHandle_t k_ulInvalidActionHandle = 0; +static const VRActionSetHandle_t k_ulInvalidActionSetHandle = 0; +static const VRInputValueHandle_t k_ulInvalidInputValueHandle = 0; + + +/** Allows the application to control what part of the provided texture will be used in the +* frame buffer. */ +struct VRTextureBounds_t +{ + float uMin, vMin; + float uMax, vMax; +}; + +/** Allows specifying pose used to render provided scene texture (if different from value returned by WaitGetPoses). */ +struct VRTextureWithPose_t : public Texture_t +{ + HmdMatrix34_t mDeviceToAbsoluteTracking; // Actual pose used to render scene textures. +}; + +struct VRTextureDepthInfo_t +{ + void* handle; // See ETextureType definition above + HmdMatrix44_t mProjection; + HmdVector2_t vRange; // 0..1 +}; + +struct VRTextureWithDepth_t : public Texture_t +{ + VRTextureDepthInfo_t depth; +}; + +struct VRTextureWithPoseAndDepth_t : public VRTextureWithPose_t +{ + VRTextureDepthInfo_t depth; +}; + +/** Allows the application to control how scene textures are used by the compositor when calling Submit. */ +enum EVRSubmitFlags +{ + // Simple render path. App submits rendered left and right eye images with no lens distortion correction applied. + Submit_Default = 0x00, + + // App submits final left and right eye images with lens distortion already applied (lens distortion makes the images appear + // barrel distorted with chromatic aberration correction applied). The app would have used the data returned by + // vr::IVRSystem::ComputeDistortion() to apply the correct distortion to the rendered images before calling Submit(). + Submit_LensDistortionAlreadyApplied = 0x01, + + // If the texture pointer passed in is actually a renderbuffer (e.g. for MSAA in OpenGL) then set this flag. + Submit_GlRenderBuffer = 0x02, + + // Do not use + Submit_Reserved = 0x04, + + // Set to indicate that pTexture is a pointer to a VRTextureWithPose_t. + // This flag can be combined with Submit_TextureWithDepth to pass a VRTextureWithPoseAndDepth_t. + Submit_TextureWithPose = 0x08, + + // Set to indicate that pTexture is a pointer to a VRTextureWithDepth_t. + // This flag can be combined with Submit_TextureWithPose to pass a VRTextureWithPoseAndDepth_t. + Submit_TextureWithDepth = 0x10, + + // Set to indicate a discontinuity between this and the last frame. + // This will prevent motion smoothing from attempting to extrapolate using the pair. + Submit_FrameDiscontinuty = 0x20, +}; + +/** Data required for passing Vulkan textures to IVRCompositor::Submit. +* Be sure to call OpenVR_Shutdown before destroying these resources. +* Please see https://github.com/ValveSoftware/openvr/wiki/Vulkan for Vulkan-specific documentation */ +struct VRVulkanTextureData_t +{ + uint64_t m_nImage; // VkImage + VkDevice_T *m_pDevice; + VkPhysicalDevice_T *m_pPhysicalDevice; + VkInstance_T *m_pInstance; + VkQueue_T *m_pQueue; + uint32_t m_nQueueFamilyIndex; + uint32_t m_nWidth, m_nHeight, m_nFormat, m_nSampleCount; +}; + +/** Data required for passing D3D12 textures to IVRCompositor::Submit. +* Be sure to call OpenVR_Shutdown before destroying these resources. */ +struct D3D12TextureData_t +{ + ID3D12Resource *m_pResource; + ID3D12CommandQueue *m_pCommandQueue; + uint32_t m_nNodeMask; +}; + +/** Status of the overall system or tracked objects */ +enum EVRState +{ + VRState_Undefined = -1, + VRState_Off = 0, + VRState_Searching = 1, + VRState_Searching_Alert = 2, + VRState_Ready = 3, + VRState_Ready_Alert = 4, + VRState_NotReady = 5, + VRState_Standby = 6, + VRState_Ready_Alert_Low = 7, +}; + +/** The types of events that could be posted (and what the parameters mean for each event type) */ +enum EVREventType +{ + VREvent_None = 0, + + VREvent_TrackedDeviceActivated = 100, + VREvent_TrackedDeviceDeactivated = 101, + VREvent_TrackedDeviceUpdated = 102, + VREvent_TrackedDeviceUserInteractionStarted = 103, + VREvent_TrackedDeviceUserInteractionEnded = 104, + VREvent_IpdChanged = 105, + VREvent_EnterStandbyMode = 106, + VREvent_LeaveStandbyMode = 107, + VREvent_TrackedDeviceRoleChanged = 108, + VREvent_WatchdogWakeUpRequested = 109, + VREvent_LensDistortionChanged = 110, + VREvent_PropertyChanged = 111, + VREvent_WirelessDisconnect = 112, + VREvent_WirelessReconnect = 113, + + VREvent_ButtonPress = 200, // data is controller + VREvent_ButtonUnpress = 201, // data is controller + VREvent_ButtonTouch = 202, // data is controller + VREvent_ButtonUntouch = 203, // data is controller + + // VREvent_DualAnalog_Press = 250, // No longer sent + // VREvent_DualAnalog_Unpress = 251, // No longer sent + // VREvent_DualAnalog_Touch = 252, // No longer sent + // VREvent_DualAnalog_Untouch = 253, // No longer sent + // VREvent_DualAnalog_Move = 254, // No longer sent + // VREvent_DualAnalog_ModeSwitch1 = 255, // No longer sent + // VREvent_DualAnalog_ModeSwitch2 = 256, // No longer sent + VREvent_Modal_Cancel = 257, // Sent to overlays with the + + VREvent_MouseMove = 300, // data is mouse + VREvent_MouseButtonDown = 301, // data is mouse + VREvent_MouseButtonUp = 302, // data is mouse + VREvent_FocusEnter = 303, // data is overlay + VREvent_FocusLeave = 304, // data is overlay + VREvent_ScrollDiscrete = 305, // data is scroll + VREvent_TouchPadMove = 306, // data is mouse + VREvent_OverlayFocusChanged = 307, // data is overlay, global event + VREvent_ReloadOverlays = 308, + VREvent_ScrollSmooth = 309, // data is scroll + VREvent_LockMousePosition = 310, + VREvent_UnlockMousePosition = 311, + + VREvent_InputFocusCaptured = 400, // data is process DEPRECATED + VREvent_InputFocusReleased = 401, // data is process DEPRECATED + // VREvent_SceneFocusLost = 402, // data is process + // VREvent_SceneFocusGained = 403, // data is process + VREvent_SceneApplicationChanged = 404, // data is process - The App actually drawing the scene changed (usually to or from the compositor) + VREvent_SceneFocusChanged = 405, // data is process - New app got access to draw the scene + VREvent_InputFocusChanged = 406, // data is process + // VREvent_SceneApplicationSecondaryRenderingStarted = 407, + VREvent_SceneApplicationUsingWrongGraphicsAdapter = 408, // data is process + VREvent_ActionBindingReloaded = 409, // data is process - The App that action binds reloaded for + + VREvent_HideRenderModels = 410, // Sent to the scene application to request hiding render models temporarily + VREvent_ShowRenderModels = 411, // Sent to the scene application to request restoring render model visibility + + VREvent_SceneApplicationStateChanged = 412, // No data; but query VRApplications()->GetSceneApplicationState(); + + VREvent_ConsoleOpened = 420, + VREvent_ConsoleClosed = 421, + + VREvent_OverlayShown = 500, + VREvent_OverlayHidden = 501, + VREvent_DashboardActivated = 502, + VREvent_DashboardDeactivated = 503, + //VREvent_DashboardThumbSelected = 504, // Sent to the overlay manager - data is overlay - No longer sent + VREvent_DashboardRequested = 505, // Sent to the overlay manager - data is overlay + VREvent_ResetDashboard = 506, // Send to the overlay manager + //VREvent_RenderToast = 507, // Send to the dashboard to render a toast - data is the notification ID -- no longer sent + VREvent_ImageLoaded = 508, // Sent to overlays when a SetOverlayRaw or SetOverlayFromFile call finishes loading + VREvent_ShowKeyboard = 509, // Sent to keyboard renderer in the dashboard to invoke it + VREvent_HideKeyboard = 510, // Sent to keyboard renderer in the dashboard to hide it + VREvent_OverlayGamepadFocusGained = 511, // Sent to an overlay when IVROverlay::SetFocusOverlay is called on it + VREvent_OverlayGamepadFocusLost = 512, // Send to an overlay when it previously had focus and IVROverlay::SetFocusOverlay is called on something else + VREvent_OverlaySharedTextureChanged = 513, + //VREvent_DashboardGuideButtonDown = 514, // These are no longer sent + //VREvent_DashboardGuideButtonUp = 515, + VREvent_ScreenshotTriggered = 516, // Screenshot button combo was pressed, Dashboard should request a screenshot + VREvent_ImageFailed = 517, // Sent to overlays when a SetOverlayRaw or SetOverlayfromFail fails to load + VREvent_DashboardOverlayCreated = 518, + VREvent_SwitchGamepadFocus = 519, + + // Screenshot API + VREvent_RequestScreenshot = 520, // Sent by vrclient application to compositor to take a screenshot + VREvent_ScreenshotTaken = 521, // Sent by compositor to the application that the screenshot has been taken + VREvent_ScreenshotFailed = 522, // Sent by compositor to the application that the screenshot failed to be taken + VREvent_SubmitScreenshotToDashboard = 523, // Sent by compositor to the dashboard that a completed screenshot was submitted + VREvent_ScreenshotProgressToDashboard = 524, // Sent by compositor to the dashboard that a completed screenshot was submitted + + VREvent_PrimaryDashboardDeviceChanged = 525, + VREvent_RoomViewShown = 526, // Sent by compositor whenever room-view is enabled + VREvent_RoomViewHidden = 527, // Sent by compositor whenever room-view is disabled + VREvent_ShowUI = 528, // data is showUi + VREvent_ShowDevTools = 529, // data is showDevTools + + VREvent_Notification_Shown = 600, + VREvent_Notification_Hidden = 601, + VREvent_Notification_BeginInteraction = 602, + VREvent_Notification_Destroyed = 603, + + VREvent_Quit = 700, // data is process + VREvent_ProcessQuit = 701, // data is process + //VREvent_QuitAborted_UserPrompt = 702, // data is process + VREvent_QuitAcknowledged = 703, // data is process + VREvent_DriverRequestedQuit = 704, // The driver has requested that SteamVR shut down + VREvent_RestartRequested = 705, // A driver or other component wants the user to restart SteamVR + + VREvent_ChaperoneDataHasChanged = 800, // this will never happen with the new chaperone system + VREvent_ChaperoneUniverseHasChanged = 801, + VREvent_ChaperoneTempDataHasChanged = 802, // this will never happen with the new chaperone system + VREvent_ChaperoneSettingsHaveChanged = 803, + VREvent_SeatedZeroPoseReset = 804, + VREvent_ChaperoneFlushCache = 805, // Sent when the process needs to reload any cached data it retrieved from VRChaperone() + VREvent_ChaperoneRoomSetupStarting = 806, // Triggered by CVRChaperoneClient::RoomSetupStarting + VREvent_ChaperoneRoomSetupFinished = 807, // Triggered by CVRChaperoneClient::CommitWorkingCopy + + VREvent_AudioSettingsHaveChanged = 820, + + VREvent_BackgroundSettingHasChanged = 850, + VREvent_CameraSettingsHaveChanged = 851, + VREvent_ReprojectionSettingHasChanged = 852, + VREvent_ModelSkinSettingsHaveChanged = 853, + VREvent_EnvironmentSettingsHaveChanged = 854, + VREvent_PowerSettingsHaveChanged = 855, + VREvent_EnableHomeAppSettingsHaveChanged = 856, + VREvent_SteamVRSectionSettingChanged = 857, + VREvent_LighthouseSectionSettingChanged = 858, + VREvent_NullSectionSettingChanged = 859, + VREvent_UserInterfaceSectionSettingChanged = 860, + VREvent_NotificationsSectionSettingChanged = 861, + VREvent_KeyboardSectionSettingChanged = 862, + VREvent_PerfSectionSettingChanged = 863, + VREvent_DashboardSectionSettingChanged = 864, + VREvent_WebInterfaceSectionSettingChanged = 865, + VREvent_TrackersSectionSettingChanged = 866, + VREvent_LastKnownSectionSettingChanged = 867, + VREvent_DismissedWarningsSectionSettingChanged = 868, + VREvent_GpuSpeedSectionSettingChanged = 869, + VREvent_WindowsMRSectionSettingChanged = 870, + VREvent_OtherSectionSettingChanged = 871, + + VREvent_StatusUpdate = 900, + + VREvent_WebInterface_InstallDriverCompleted = 950, + + VREvent_MCImageUpdated = 1000, + + VREvent_FirmwareUpdateStarted = 1100, + VREvent_FirmwareUpdateFinished = 1101, + + VREvent_KeyboardClosed = 1200, + VREvent_KeyboardCharInput = 1201, + VREvent_KeyboardDone = 1202, // Sent when DONE button clicked on keyboard + + //VREvent_ApplicationTransitionStarted = 1300, + //VREvent_ApplicationTransitionAborted = 1301, + //VREvent_ApplicationTransitionNewAppStarted = 1302, + VREvent_ApplicationListUpdated = 1303, + VREvent_ApplicationMimeTypeLoad = 1304, + // VREvent_ApplicationTransitionNewAppLaunchComplete = 1305, + VREvent_ProcessConnected = 1306, + VREvent_ProcessDisconnected = 1307, + + //VREvent_Compositor_MirrorWindowShown = 1400, // DEPRECATED + //VREvent_Compositor_MirrorWindowHidden = 1401, // DEPRECATED + VREvent_Compositor_ChaperoneBoundsShown = 1410, + VREvent_Compositor_ChaperoneBoundsHidden = 1411, + VREvent_Compositor_DisplayDisconnected = 1412, + VREvent_Compositor_DisplayReconnected = 1413, + VREvent_Compositor_HDCPError = 1414, // data is hdcpError + VREvent_Compositor_ApplicationNotResponding = 1415, + VREvent_Compositor_ApplicationResumed = 1416, + VREvent_Compositor_OutOfVideoMemory = 1417, + VREvent_Compositor_DisplayModeNotSupported = 1418, // k_pch_SteamVR_PreferredRefreshRate + VREvent_Compositor_StageOverrideReady = 1419, + + VREvent_TrackedCamera_StartVideoStream = 1500, + VREvent_TrackedCamera_StopVideoStream = 1501, + VREvent_TrackedCamera_PauseVideoStream = 1502, + VREvent_TrackedCamera_ResumeVideoStream = 1503, + VREvent_TrackedCamera_EditingSurface = 1550, + + VREvent_PerformanceTest_EnableCapture = 1600, + VREvent_PerformanceTest_DisableCapture = 1601, + VREvent_PerformanceTest_FidelityLevel = 1602, + + VREvent_MessageOverlay_Closed = 1650, + VREvent_MessageOverlayCloseRequested = 1651, + + VREvent_Input_HapticVibration = 1700, // data is hapticVibration + VREvent_Input_BindingLoadFailed = 1701, // data is inputBinding + VREvent_Input_BindingLoadSuccessful = 1702, // data is inputBinding + VREvent_Input_ActionManifestReloaded = 1703, // no data + VREvent_Input_ActionManifestLoadFailed = 1704, // data is actionManifest + VREvent_Input_ProgressUpdate = 1705, // data is progressUpdate + VREvent_Input_TrackerActivated = 1706, + VREvent_Input_BindingsUpdated = 1707, + VREvent_Input_BindingSubscriptionChanged = 1708, + + VREvent_SpatialAnchors_PoseUpdated = 1800, // data is spatialAnchor. broadcast + VREvent_SpatialAnchors_DescriptorUpdated = 1801, // data is spatialAnchor. broadcast + VREvent_SpatialAnchors_RequestPoseUpdate = 1802, // data is spatialAnchor. sent to specific driver + VREvent_SpatialAnchors_RequestDescriptorUpdate = 1803, // data is spatialAnchor. sent to specific driver + + VREvent_SystemReport_Started = 1900, // user or system initiated generation of a system report. broadcast + + VREvent_Monitor_ShowHeadsetView = 2000, // data is process + VREvent_Monitor_HideHeadsetView = 2001, // data is process + + // Vendors are free to expose private events in this reserved region + VREvent_VendorSpecific_Reserved_Start = 10000, + VREvent_VendorSpecific_Reserved_End = 19999, +}; + + +/** Level of Hmd activity */ +// UserInteraction_Timeout means the device is in the process of timing out. +// InUse = ( k_EDeviceActivityLevel_UserInteraction || k_EDeviceActivityLevel_UserInteraction_Timeout ) +// VREvent_TrackedDeviceUserInteractionStarted fires when the devices transitions from Standby -> UserInteraction or Idle -> UserInteraction. +// VREvent_TrackedDeviceUserInteractionEnded fires when the devices transitions from UserInteraction_Timeout -> Idle +enum EDeviceActivityLevel +{ + k_EDeviceActivityLevel_Unknown = -1, + k_EDeviceActivityLevel_Idle = 0, // No activity for the last 10 seconds + k_EDeviceActivityLevel_UserInteraction = 1, // Activity (movement or prox sensor) is happening now + k_EDeviceActivityLevel_UserInteraction_Timeout = 2, // No activity for the last 0.5 seconds + k_EDeviceActivityLevel_Standby = 3, // Idle for at least 5 seconds (configurable in Settings -> Power Management) + k_EDeviceActivityLevel_Idle_Timeout = 4, +}; + + +/** VR controller button and axis IDs */ +enum EVRButtonId +{ + k_EButton_System = 0, + k_EButton_ApplicationMenu = 1, + k_EButton_Grip = 2, + k_EButton_DPad_Left = 3, + k_EButton_DPad_Up = 4, + k_EButton_DPad_Right = 5, + k_EButton_DPad_Down = 6, + k_EButton_A = 7, + + k_EButton_ProximitySensor = 31, + + k_EButton_Axis0 = 32, + k_EButton_Axis1 = 33, + k_EButton_Axis2 = 34, + k_EButton_Axis3 = 35, + k_EButton_Axis4 = 36, + + // aliases for well known controllers + k_EButton_SteamVR_Touchpad = k_EButton_Axis0, + k_EButton_SteamVR_Trigger = k_EButton_Axis1, + + k_EButton_Dashboard_Back = k_EButton_Grip, + + k_EButton_IndexController_A = k_EButton_Grip, + k_EButton_IndexController_B = k_EButton_ApplicationMenu, + k_EButton_IndexController_JoyStick = k_EButton_Axis3, + + k_EButton_Max = 64 +}; + +inline uint64_t ButtonMaskFromId( EVRButtonId id ) { return 1ull << id; } + +/** used for controller button events */ +struct VREvent_Controller_t +{ + uint32_t button; // EVRButtonId enum +}; + + +/** used for simulated mouse events in overlay space */ +enum EVRMouseButton +{ + VRMouseButton_Left = 0x0001, + VRMouseButton_Right = 0x0002, + VRMouseButton_Middle = 0x0004, +}; + + +/** used for simulated mouse events in overlay space */ +struct VREvent_Mouse_t +{ + float x, y; // co-ords are in GL space, bottom left of the texture is 0,0 + uint32_t button; // EVRMouseButton enum +}; + +/** used for simulated mouse wheel scroll */ +struct VREvent_Scroll_t +{ + float xdelta, ydelta; + uint32_t unused; + float viewportscale; // For scrolling on an overlay with laser mouse, this is the overlay's vertical size relative to the overlay height. Range: [0,1] +}; + +/** when in mouse input mode you can receive data from the touchpad, these events are only sent if the users finger + is on the touchpad (or just released from it). These events are sent to overlays with the VROverlayFlags_SendVRTouchpadEvents + flag set. +**/ +struct VREvent_TouchPadMove_t +{ + // true if the users finger is detected on the touch pad + bool bFingerDown; + + // How long the finger has been down in seconds + float flSecondsFingerDown; + + // These values indicate the starting finger position (so you can do some basic swipe stuff) + float fValueXFirst; + float fValueYFirst; + + // This is the raw sampled coordinate without deadzoning + float fValueXRaw; + float fValueYRaw; +}; + +/** notification related events. Details will still change at this point */ +struct VREvent_Notification_t +{ + uint64_t ulUserValue; + uint32_t notificationId; +}; + +/** Used for events about processes */ +struct VREvent_Process_t +{ + uint32_t pid; + uint32_t oldPid; + bool bForced; + // If the associated event was triggered by a connection loss + bool bConnectionLost; +}; + + +/** Used for a few events about overlays */ +struct VREvent_Overlay_t +{ + uint64_t overlayHandle; + uint64_t devicePath; +}; + + +/** Used for a few events about overlays */ +struct VREvent_Status_t +{ + uint32_t statusState; // EVRState enum +}; + +/** Used for keyboard events **/ +struct VREvent_Keyboard_t +{ + char cNewInput[8]; // Up to 11 bytes of new input + uint64_t uUserValue; // Possible flags about the new input +}; + +struct VREvent_Ipd_t +{ + float ipdMeters; +}; + +struct VREvent_Chaperone_t +{ + uint64_t m_nPreviousUniverse; + uint64_t m_nCurrentUniverse; +}; + +/** Not actually used for any events */ +struct VREvent_Reserved_t +{ + uint64_t reserved0; + uint64_t reserved1; + uint64_t reserved2; + uint64_t reserved3; + uint64_t reserved4; + uint64_t reserved5; +}; + +struct VREvent_PerformanceTest_t +{ + uint32_t m_nFidelityLevel; +}; + +struct VREvent_SeatedZeroPoseReset_t +{ + bool bResetBySystemMenu; +}; + +struct VREvent_Screenshot_t +{ + uint32_t handle; + uint32_t type; +}; + +struct VREvent_ScreenshotProgress_t +{ + float progress; +}; + +struct VREvent_ApplicationLaunch_t +{ + uint32_t pid; + uint32_t unArgsHandle; +}; + +struct VREvent_EditingCameraSurface_t +{ + uint64_t overlayHandle; + uint32_t nVisualMode; +}; + +struct VREvent_MessageOverlay_t +{ + uint32_t unVRMessageOverlayResponse; // vr::VRMessageOverlayResponse enum +}; + +struct VREvent_Property_t +{ + PropertyContainerHandle_t container; + ETrackedDeviceProperty prop; +}; + +struct VREvent_HapticVibration_t +{ + uint64_t containerHandle; // property container handle of the device with the haptic component + uint64_t componentHandle; // Which haptic component needs to vibrate + float fDurationSeconds; + float fFrequency; + float fAmplitude; +}; + +struct VREvent_WebConsole_t +{ + WebConsoleHandle_t webConsoleHandle; +}; + +struct VREvent_InputBindingLoad_t +{ + vr::PropertyContainerHandle_t ulAppContainer; + uint64_t pathMessage; + uint64_t pathUrl; + uint64_t pathControllerType; +}; + +struct VREvent_InputActionManifestLoad_t +{ + uint64_t pathAppKey; + uint64_t pathMessage; + uint64_t pathMessageParam; + uint64_t pathManifestPath; +}; + +struct VREvent_SpatialAnchor_t +{ + SpatialAnchorHandle_t unHandle; +}; + +struct VREvent_ProgressUpdate_t +{ + uint64_t ulApplicationPropertyContainer; + uint64_t pathDevice; + uint64_t pathInputSource; + uint64_t pathProgressAction; + uint64_t pathIcon; + float fProgress; +}; + +enum EShowUIType +{ + ShowUI_ControllerBinding = 0, + ShowUI_ManageTrackers = 1, + // ShowUI_QuickStart = 2, // Deprecated + ShowUI_Pairing = 3, + ShowUI_Settings = 4, + ShowUI_DebugCommands = 5, + ShowUI_FullControllerBinding = 6, + ShowUI_ManageDrivers = 7, +}; + +struct VREvent_ShowUI_t +{ + EShowUIType eType; +}; + +struct VREvent_ShowDevTools_t +{ + int32_t nBrowserIdentifier; +}; + +enum EHDCPError +{ + HDCPError_None = 0, + HDCPError_LinkLost = 1, + HDCPError_Tampered = 2, + HDCPError_DeviceRevoked = 3, + HDCPError_Unknown = 4 +}; + +struct VREvent_HDCPError_t +{ + EHDCPError eCode; +}; + +typedef union +{ + VREvent_Reserved_t reserved; + VREvent_Controller_t controller; + VREvent_Mouse_t mouse; + VREvent_Scroll_t scroll; + VREvent_Process_t process; + VREvent_Notification_t notification; + VREvent_Overlay_t overlay; + VREvent_Status_t status; + VREvent_Keyboard_t keyboard; + VREvent_Ipd_t ipd; + VREvent_Chaperone_t chaperone; + VREvent_PerformanceTest_t performanceTest; + VREvent_TouchPadMove_t touchPadMove; + VREvent_SeatedZeroPoseReset_t seatedZeroPoseReset; + VREvent_Screenshot_t screenshot; + VREvent_ScreenshotProgress_t screenshotProgress; + VREvent_ApplicationLaunch_t applicationLaunch; + VREvent_EditingCameraSurface_t cameraSurface; + VREvent_MessageOverlay_t messageOverlay; + VREvent_Property_t property; + VREvent_HapticVibration_t hapticVibration; + VREvent_WebConsole_t webConsole; + VREvent_InputBindingLoad_t inputBinding; + VREvent_InputActionManifestLoad_t actionManifest; + VREvent_SpatialAnchor_t spatialAnchor; + VREvent_ProgressUpdate_t progressUpdate; + VREvent_ShowUI_t showUi; + VREvent_ShowDevTools_t showDevTools; + VREvent_HDCPError_t hdcpError; + /** NOTE!!! If you change this you MUST manually update openvr_interop.cs.py */ +} VREvent_Data_t; + + +#if defined(__linux__) || defined(__APPLE__) +// This structure was originally defined mis-packed on Linux, preserved for +// compatibility. +#pragma pack( push, 4 ) +#endif + +/** An event posted by the server to all running applications */ +struct VREvent_t +{ + uint32_t eventType; // EVREventType enum + TrackedDeviceIndex_t trackedDeviceIndex; + float eventAgeSeconds; + // event data must be the end of the struct as its size is variable + VREvent_Data_t data; +}; + +#if defined(__linux__) || defined(__APPLE__) +#pragma pack( pop ) +#endif + +typedef uint32_t VRComponentProperties; + +enum EVRComponentProperty +{ + VRComponentProperty_IsStatic = (1 << 0), + VRComponentProperty_IsVisible = (1 << 1), + VRComponentProperty_IsTouched = (1 << 2), + VRComponentProperty_IsPressed = (1 << 3), + VRComponentProperty_IsScrolled = (1 << 4), + VRComponentProperty_IsHighlighted = (1 << 5), +}; + + +/** Describes state information about a render-model component, including transforms and other dynamic properties */ +struct RenderModel_ComponentState_t +{ + HmdMatrix34_t mTrackingToComponentRenderModel; // Transform required when drawing the component render model + HmdMatrix34_t mTrackingToComponentLocal; // Transform available for attaching to a local component coordinate system (-Z out from surface ) + VRComponentProperties uProperties; +}; + + +enum EVRInputError +{ + VRInputError_None = 0, + VRInputError_NameNotFound = 1, + VRInputError_WrongType = 2, + VRInputError_InvalidHandle = 3, + VRInputError_InvalidParam = 4, + VRInputError_NoSteam = 5, + VRInputError_MaxCapacityReached = 6, + VRInputError_IPCError = 7, + VRInputError_NoActiveActionSet = 8, + VRInputError_InvalidDevice = 9, + VRInputError_InvalidSkeleton = 10, + VRInputError_InvalidBoneCount = 11, + VRInputError_InvalidCompressedData = 12, + VRInputError_NoData = 13, + VRInputError_BufferTooSmall = 14, + VRInputError_MismatchedActionManifest = 15, + VRInputError_MissingSkeletonData = 16, + VRInputError_InvalidBoneIndex = 17, + VRInputError_InvalidPriority = 18, + VRInputError_PermissionDenied = 19, + VRInputError_InvalidRenderModel = 20, +}; + +enum EVRSpatialAnchorError +{ + VRSpatialAnchorError_Success = 0, + VRSpatialAnchorError_Internal = 1, + VRSpatialAnchorError_UnknownHandle = 2, + VRSpatialAnchorError_ArrayTooSmall = 3, + VRSpatialAnchorError_InvalidDescriptorChar = 4, + VRSpatialAnchorError_NotYetAvailable = 5, + VRSpatialAnchorError_NotAvailableInThisUniverse = 6, + VRSpatialAnchorError_PermanentlyUnavailable = 7, + VRSpatialAnchorError_WrongDriver = 8, + VRSpatialAnchorError_DescriptorTooLong = 9, + VRSpatialAnchorError_Unknown = 10, + VRSpatialAnchorError_NoRoomCalibration = 11, + VRSpatialAnchorError_InvalidArgument = 12, + VRSpatialAnchorError_UnknownDriver = 13, +}; + +/** The mesh to draw into the stencil (or depth) buffer to perform +* early stencil (or depth) kills of pixels that will never appear on the HMD. +* This mesh draws on all the pixels that will be hidden after distortion. +* +* If the HMD does not provide a visible area mesh pVertexData will be +* NULL and unTriangleCount will be 0. */ +struct HiddenAreaMesh_t +{ + const HmdVector2_t *pVertexData; + uint32_t unTriangleCount; +}; + + +enum EHiddenAreaMeshType +{ + k_eHiddenAreaMesh_Standard = 0, + k_eHiddenAreaMesh_Inverse = 1, + k_eHiddenAreaMesh_LineLoop = 2, + + k_eHiddenAreaMesh_Max = 3, +}; + + +/** Identifies what kind of axis is on the controller at index n. Read this type +* with pVRSystem->Get( nControllerDeviceIndex, Prop_Axis0Type_Int32 + n ); +*/ +enum EVRControllerAxisType +{ + k_eControllerAxis_None = 0, + k_eControllerAxis_TrackPad = 1, + k_eControllerAxis_Joystick = 2, + k_eControllerAxis_Trigger = 3, // Analog trigger data is in the X axis +}; + + +/** contains information about one axis on the controller */ +struct VRControllerAxis_t +{ + float x; // Ranges from -1.0 to 1.0 for joysticks and track pads. Ranges from 0.0 to 1.0 for triggers were 0 is fully released. + float y; // Ranges from -1.0 to 1.0 for joysticks and track pads. Is always 0.0 for triggers. +}; + + +/** the number of axes in the controller state */ +static const uint32_t k_unControllerStateAxisCount = 5; + + +#if defined(__linux__) || defined(__APPLE__) +// This structure was originally defined mis-packed on Linux, preserved for +// compatibility. +#pragma pack( push, 4 ) +#endif + +/** Holds all the state of a controller at one moment in time. */ +struct VRControllerState001_t +{ + // If packet num matches that on your prior call, then the controller state hasn't been changed since + // your last call and there is no need to process it + uint32_t unPacketNum; + + // bit flags for each of the buttons. Use ButtonMaskFromId to turn an ID into a mask + uint64_t ulButtonPressed; + uint64_t ulButtonTouched; + + // Axis data for the controller's analog inputs + VRControllerAxis_t rAxis[ k_unControllerStateAxisCount ]; +}; +#if defined(__linux__) || defined(__APPLE__) +#pragma pack( pop ) +#endif + + +typedef VRControllerState001_t VRControllerState_t; + + +/** determines how to provide output to the application of various event processing functions. */ +enum EVRControllerEventOutputType +{ + ControllerEventOutput_OSEvents = 0, + ControllerEventOutput_VREvents = 1, +}; + + + +/** Collision Bounds Style */ +enum ECollisionBoundsStyle +{ + COLLISION_BOUNDS_STYLE_BEGINNER = 0, + COLLISION_BOUNDS_STYLE_INTERMEDIATE, + COLLISION_BOUNDS_STYLE_SQUARES, + COLLISION_BOUNDS_STYLE_ADVANCED, + COLLISION_BOUNDS_STYLE_NONE, + + COLLISION_BOUNDS_STYLE_COUNT +}; + +/** used to refer to a single VR overlay */ +typedef uint64_t VROverlayHandle_t; + +static const VROverlayHandle_t k_ulOverlayHandleInvalid = 0; + +/** Errors that can occur around VR overlays */ +enum EVROverlayError +{ + VROverlayError_None = 0, + + VROverlayError_UnknownOverlay = 10, + VROverlayError_InvalidHandle = 11, + VROverlayError_PermissionDenied = 12, + VROverlayError_OverlayLimitExceeded = 13, // No more overlays could be created because the maximum number already exist + VROverlayError_WrongVisibilityType = 14, + VROverlayError_KeyTooLong = 15, + VROverlayError_NameTooLong = 16, + VROverlayError_KeyInUse = 17, + VROverlayError_WrongTransformType = 18, + VROverlayError_InvalidTrackedDevice = 19, + VROverlayError_InvalidParameter = 20, + VROverlayError_ThumbnailCantBeDestroyed = 21, + VROverlayError_ArrayTooSmall = 22, + VROverlayError_RequestFailed = 23, + VROverlayError_InvalidTexture = 24, + VROverlayError_UnableToLoadFile = 25, + VROverlayError_KeyboardAlreadyInUse = 26, + VROverlayError_NoNeighbor = 27, + VROverlayError_TooManyMaskPrimitives = 29, + VROverlayError_BadMaskPrimitive = 30, + VROverlayError_TextureAlreadyLocked = 31, + VROverlayError_TextureLockCapacityReached = 32, + VROverlayError_TextureNotLocked = 33, +}; + +/** enum values to pass in to VR_Init to identify whether the application will +* draw a 3D scene. */ +enum EVRApplicationType +{ + VRApplication_Other = 0, // Some other kind of application that isn't covered by the other entries + VRApplication_Scene = 1, // Application will submit 3D frames + VRApplication_Overlay = 2, // Application only interacts with overlays + VRApplication_Background = 3, // Application should not start SteamVR if it's not already running, and should not + // keep it running if everything else quits. + VRApplication_Utility = 4, // Init should not try to load any drivers. The application needs access to utility + // interfaces (like IVRSettings and IVRApplications) but not hardware. + VRApplication_VRMonitor = 5, // Reserved for vrmonitor + VRApplication_SteamWatchdog = 6,// Reserved for Steam + VRApplication_Bootstrapper = 7, // reserved for vrstartup + VRApplication_WebHelper = 8, // reserved for vrwebhelper + + VRApplication_Max +}; + + +/** error codes for firmware */ +enum EVRFirmwareError +{ + VRFirmwareError_None = 0, + VRFirmwareError_Success = 1, + VRFirmwareError_Fail = 2, +}; + + +/** error codes for notifications */ +enum EVRNotificationError +{ + VRNotificationError_OK = 0, + VRNotificationError_InvalidNotificationId = 100, + VRNotificationError_NotificationQueueFull = 101, + VRNotificationError_InvalidOverlayHandle = 102, + VRNotificationError_SystemWithUserValueAlreadyExists = 103, +}; + + +enum EVRSkeletalMotionRange +{ + // The range of motion of the skeleton takes into account any physical limits imposed by + // the controller itself. This will tend to be the most accurate pose compared to the user's + // actual hand pose, but might not allow a closed fist for example + VRSkeletalMotionRange_WithController = 0, + + // Retarget the range of motion provided by the input device to make the hand appear to move + // as if it was not holding a controller. eg: map "hand grasping controller" to "closed fist" + VRSkeletalMotionRange_WithoutController = 1, +}; + +enum EVRSkeletalTrackingLevel +{ + // body part location can't be directly determined by the device. Any skeletal pose provided by + // the device is estimated by assuming the position required to active buttons, triggers, joysticks, + // or other input sensors. + // E.g. Vive Controller, Gamepad + VRSkeletalTracking_Estimated = 0, + + // body part location can be measured directly but with fewer degrees of freedom than the actual body + // part. Certain body part positions may be unmeasured by the device and estimated from other input data. + // E.g. Index Controllers, gloves that only measure finger curl + VRSkeletalTracking_Partial = 1, + + // Body part location can be measured directly throughout the entire range of motion of the body part. + // E.g. Mocap suit for the full body, gloves that measure rotation of each finger segment + VRSkeletalTracking_Full = 2, + + VRSkeletalTrackingLevel_Count, + VRSkeletalTrackingLevel_Max = VRSkeletalTrackingLevel_Count - 1 +}; + + + +/** Holds the transform for a single bone */ +struct VRBoneTransform_t +{ + HmdVector4_t position; + HmdQuaternionf_t orientation; +}; + +/** Type used for referring to bones by their index */ +typedef int32_t BoneIndex_t; +const BoneIndex_t k_unInvalidBoneIndex = -1; + + +/** error codes returned by Vr_Init */ + +// Please add adequate error description to https://developer.valvesoftware.com/w/index.php?title=Category:SteamVRHelp +enum EVRInitError +{ + VRInitError_None = 0, + VRInitError_Unknown = 1, + + VRInitError_Init_InstallationNotFound = 100, + VRInitError_Init_InstallationCorrupt = 101, + VRInitError_Init_VRClientDLLNotFound = 102, + VRInitError_Init_FileNotFound = 103, + VRInitError_Init_FactoryNotFound = 104, + VRInitError_Init_InterfaceNotFound = 105, + VRInitError_Init_InvalidInterface = 106, + VRInitError_Init_UserConfigDirectoryInvalid = 107, + VRInitError_Init_HmdNotFound = 108, + VRInitError_Init_NotInitialized = 109, + VRInitError_Init_PathRegistryNotFound = 110, + VRInitError_Init_NoConfigPath = 111, + VRInitError_Init_NoLogPath = 112, + VRInitError_Init_PathRegistryNotWritable = 113, + VRInitError_Init_AppInfoInitFailed = 114, + VRInitError_Init_Retry = 115, // Used internally to cause retries to vrserver + VRInitError_Init_InitCanceledByUser = 116, // The calling application should silently exit. The user canceled app startup + VRInitError_Init_AnotherAppLaunching = 117, + VRInitError_Init_SettingsInitFailed = 118, + VRInitError_Init_ShuttingDown = 119, + VRInitError_Init_TooManyObjects = 120, + VRInitError_Init_NoServerForBackgroundApp = 121, + VRInitError_Init_NotSupportedWithCompositor = 122, + VRInitError_Init_NotAvailableToUtilityApps = 123, + VRInitError_Init_Internal = 124, + VRInitError_Init_HmdDriverIdIsNone = 125, + VRInitError_Init_HmdNotFoundPresenceFailed = 126, + VRInitError_Init_VRMonitorNotFound = 127, + VRInitError_Init_VRMonitorStartupFailed = 128, + VRInitError_Init_LowPowerWatchdogNotSupported = 129, + VRInitError_Init_InvalidApplicationType = 130, + VRInitError_Init_NotAvailableToWatchdogApps = 131, + VRInitError_Init_WatchdogDisabledInSettings = 132, + VRInitError_Init_VRDashboardNotFound = 133, + VRInitError_Init_VRDashboardStartupFailed = 134, + VRInitError_Init_VRHomeNotFound = 135, + VRInitError_Init_VRHomeStartupFailed = 136, + VRInitError_Init_RebootingBusy = 137, + VRInitError_Init_FirmwareUpdateBusy = 138, + VRInitError_Init_FirmwareRecoveryBusy = 139, + VRInitError_Init_USBServiceBusy = 140, + VRInitError_Init_VRWebHelperStartupFailed = 141, + VRInitError_Init_TrackerManagerInitFailed = 142, + VRInitError_Init_AlreadyRunning = 143, + VRInitError_Init_FailedForVrMonitor = 144, + VRInitError_Init_PropertyManagerInitFailed = 145, + VRInitError_Init_WebServerFailed = 146, + + VRInitError_Driver_Failed = 200, + VRInitError_Driver_Unknown = 201, + VRInitError_Driver_HmdUnknown = 202, + VRInitError_Driver_NotLoaded = 203, + VRInitError_Driver_RuntimeOutOfDate = 204, + VRInitError_Driver_HmdInUse = 205, + VRInitError_Driver_NotCalibrated = 206, + VRInitError_Driver_CalibrationInvalid = 207, + VRInitError_Driver_HmdDisplayNotFound = 208, + VRInitError_Driver_TrackedDeviceInterfaceUnknown = 209, + // VRInitError_Driver_HmdDisplayNotFoundAfterFix = 210, // not needed: here for historic reasons + VRInitError_Driver_HmdDriverIdOutOfBounds = 211, + VRInitError_Driver_HmdDisplayMirrored = 212, + VRInitError_Driver_HmdDisplayNotFoundLaptop = 213, + // Never make error 259 because we return it from main and it would conflict with STILL_ACTIVE + + VRInitError_IPC_ServerInitFailed = 300, + VRInitError_IPC_ConnectFailed = 301, + VRInitError_IPC_SharedStateInitFailed = 302, + VRInitError_IPC_CompositorInitFailed = 303, + VRInitError_IPC_MutexInitFailed = 304, + VRInitError_IPC_Failed = 305, + VRInitError_IPC_CompositorConnectFailed = 306, + VRInitError_IPC_CompositorInvalidConnectResponse = 307, + VRInitError_IPC_ConnectFailedAfterMultipleAttempts = 308, + VRInitError_IPC_ConnectFailedAfterTargetExited = 309, + VRInitError_IPC_NamespaceUnavailable = 310, + + VRInitError_Compositor_Failed = 400, + VRInitError_Compositor_D3D11HardwareRequired = 401, + VRInitError_Compositor_FirmwareRequiresUpdate = 402, + VRInitError_Compositor_OverlayInitFailed = 403, + VRInitError_Compositor_ScreenshotsInitFailed = 404, + VRInitError_Compositor_UnableToCreateDevice = 405, + VRInitError_Compositor_SharedStateIsNull = 406, + VRInitError_Compositor_NotificationManagerIsNull = 407, + VRInitError_Compositor_ResourceManagerClientIsNull = 408, + VRInitError_Compositor_MessageOverlaySharedStateInitFailure = 409, + VRInitError_Compositor_PropertiesInterfaceIsNull = 410, + VRInitError_Compositor_CreateFullscreenWindowFailed = 411, + VRInitError_Compositor_SettingsInterfaceIsNull = 412, + VRInitError_Compositor_FailedToShowWindow = 413, + VRInitError_Compositor_DistortInterfaceIsNull = 414, + VRInitError_Compositor_DisplayFrequencyFailure = 415, + VRInitError_Compositor_RendererInitializationFailed = 416, + VRInitError_Compositor_DXGIFactoryInterfaceIsNull = 417, + VRInitError_Compositor_DXGIFactoryCreateFailed = 418, + VRInitError_Compositor_DXGIFactoryQueryFailed = 419, + VRInitError_Compositor_InvalidAdapterDesktop = 420, + VRInitError_Compositor_InvalidHmdAttachment = 421, + VRInitError_Compositor_InvalidOutputDesktop = 422, + VRInitError_Compositor_InvalidDeviceProvided = 423, + VRInitError_Compositor_D3D11RendererInitializationFailed = 424, + VRInitError_Compositor_FailedToFindDisplayMode = 425, + VRInitError_Compositor_FailedToCreateSwapChain = 426, + VRInitError_Compositor_FailedToGetBackBuffer = 427, + VRInitError_Compositor_FailedToCreateRenderTarget = 428, + VRInitError_Compositor_FailedToCreateDXGI2SwapChain = 429, + VRInitError_Compositor_FailedtoGetDXGI2BackBuffer = 430, + VRInitError_Compositor_FailedToCreateDXGI2RenderTarget = 431, + VRInitError_Compositor_FailedToGetDXGIDeviceInterface = 432, + VRInitError_Compositor_SelectDisplayMode = 433, + VRInitError_Compositor_FailedToCreateNvAPIRenderTargets = 434, + VRInitError_Compositor_NvAPISetDisplayMode = 435, + VRInitError_Compositor_FailedToCreateDirectModeDisplay = 436, + VRInitError_Compositor_InvalidHmdPropertyContainer = 437, + VRInitError_Compositor_UpdateDisplayFrequency = 438, + VRInitError_Compositor_CreateRasterizerState = 439, + VRInitError_Compositor_CreateWireframeRasterizerState = 440, + VRInitError_Compositor_CreateSamplerState = 441, + VRInitError_Compositor_CreateClampToBorderSamplerState = 442, + VRInitError_Compositor_CreateAnisoSamplerState = 443, + VRInitError_Compositor_CreateOverlaySamplerState = 444, + VRInitError_Compositor_CreatePanoramaSamplerState = 445, + VRInitError_Compositor_CreateFontSamplerState = 446, + VRInitError_Compositor_CreateNoBlendState = 447, + VRInitError_Compositor_CreateBlendState = 448, + VRInitError_Compositor_CreateAlphaBlendState = 449, + VRInitError_Compositor_CreateBlendStateMaskR = 450, + VRInitError_Compositor_CreateBlendStateMaskG = 451, + VRInitError_Compositor_CreateBlendStateMaskB = 452, + VRInitError_Compositor_CreateDepthStencilState = 453, + VRInitError_Compositor_CreateDepthStencilStateNoWrite = 454, + VRInitError_Compositor_CreateDepthStencilStateNoDepth = 455, + VRInitError_Compositor_CreateFlushTexture = 456, + VRInitError_Compositor_CreateDistortionSurfaces = 457, + VRInitError_Compositor_CreateConstantBuffer = 458, + VRInitError_Compositor_CreateHmdPoseConstantBuffer = 459, + VRInitError_Compositor_CreateHmdPoseStagingConstantBuffer = 460, + VRInitError_Compositor_CreateSharedFrameInfoConstantBuffer = 461, + VRInitError_Compositor_CreateOverlayConstantBuffer = 462, + VRInitError_Compositor_CreateSceneTextureIndexConstantBuffer = 463, + VRInitError_Compositor_CreateReadableSceneTextureIndexConstantBuffer = 464, + VRInitError_Compositor_CreateLayerGraphicsTextureIndexConstantBuffer = 465, + VRInitError_Compositor_CreateLayerComputeTextureIndexConstantBuffer = 466, + VRInitError_Compositor_CreateLayerComputeSceneTextureIndexConstantBuffer = 467, + VRInitError_Compositor_CreateComputeHmdPoseConstantBuffer = 468, + VRInitError_Compositor_CreateGeomConstantBuffer = 469, + VRInitError_Compositor_CreatePanelMaskConstantBuffer = 470, + VRInitError_Compositor_CreatePixelSimUBO = 471, + VRInitError_Compositor_CreateMSAARenderTextures = 472, + VRInitError_Compositor_CreateResolveRenderTextures = 473, + VRInitError_Compositor_CreateComputeResolveRenderTextures = 474, + VRInitError_Compositor_CreateDriverDirectModeResolveTextures = 475, + VRInitError_Compositor_OpenDriverDirectModeResolveTextures = 476, + VRInitError_Compositor_CreateFallbackSyncTexture = 477, + VRInitError_Compositor_ShareFallbackSyncTexture = 478, + VRInitError_Compositor_CreateOverlayIndexBuffer = 479, + VRInitError_Compositor_CreateOverlayVertexBuffer = 480, + VRInitError_Compositor_CreateTextVertexBuffer = 481, + VRInitError_Compositor_CreateTextIndexBuffer = 482, + VRInitError_Compositor_CreateMirrorTextures = 483, + VRInitError_Compositor_CreateLastFrameRenderTexture = 484, + VRInitError_Compositor_CreateMirrorOverlay = 485, + VRInitError_Compositor_FailedToCreateVirtualDisplayBackbuffer = 486, + VRInitError_Compositor_DisplayModeNotSupported = 487, + VRInitError_Compositor_CreateOverlayInvalidCall = 488, + VRInitError_Compositor_CreateOverlayAlreadyInitialized = 489, + VRInitError_Compositor_FailedToCreateMailbox = 490, + + VRInitError_VendorSpecific_UnableToConnectToOculusRuntime = 1000, + VRInitError_VendorSpecific_WindowsNotInDevMode = 1001, + + VRInitError_VendorSpecific_HmdFound_CantOpenDevice = 1101, + VRInitError_VendorSpecific_HmdFound_UnableToRequestConfigStart = 1102, + VRInitError_VendorSpecific_HmdFound_NoStoredConfig = 1103, + VRInitError_VendorSpecific_HmdFound_ConfigTooBig = 1104, + VRInitError_VendorSpecific_HmdFound_ConfigTooSmall = 1105, + VRInitError_VendorSpecific_HmdFound_UnableToInitZLib = 1106, + VRInitError_VendorSpecific_HmdFound_CantReadFirmwareVersion = 1107, + VRInitError_VendorSpecific_HmdFound_UnableToSendUserDataStart = 1108, + VRInitError_VendorSpecific_HmdFound_UnableToGetUserDataStart = 1109, + VRInitError_VendorSpecific_HmdFound_UnableToGetUserDataNext = 1110, + VRInitError_VendorSpecific_HmdFound_UserDataAddressRange = 1111, + VRInitError_VendorSpecific_HmdFound_UserDataError = 1112, + VRInitError_VendorSpecific_HmdFound_ConfigFailedSanityCheck = 1113, + VRInitError_VendorSpecific_OculusRuntimeBadInstall = 1114, + + VRInitError_Steam_SteamInstallationNotFound = 2000, + + // Strictly a placeholder + VRInitError_LastError +}; + +enum EVRScreenshotType +{ + VRScreenshotType_None = 0, + VRScreenshotType_Mono = 1, // left eye only + VRScreenshotType_Stereo = 2, + VRScreenshotType_Cubemap = 3, + VRScreenshotType_MonoPanorama = 4, + VRScreenshotType_StereoPanorama = 5 +}; + +enum EVRScreenshotPropertyFilenames +{ + VRScreenshotPropertyFilenames_Preview = 0, + VRScreenshotPropertyFilenames_VR = 1, +}; + +enum EVRTrackedCameraError +{ + VRTrackedCameraError_None = 0, + VRTrackedCameraError_OperationFailed = 100, + VRTrackedCameraError_InvalidHandle = 101, + VRTrackedCameraError_InvalidFrameHeaderVersion = 102, + VRTrackedCameraError_OutOfHandles = 103, + VRTrackedCameraError_IPCFailure = 104, + VRTrackedCameraError_NotSupportedForThisDevice = 105, + VRTrackedCameraError_SharedMemoryFailure = 106, + VRTrackedCameraError_FrameBufferingFailure = 107, + VRTrackedCameraError_StreamSetupFailure = 108, + VRTrackedCameraError_InvalidGLTextureId = 109, + VRTrackedCameraError_InvalidSharedTextureHandle = 110, + VRTrackedCameraError_FailedToGetGLTextureId = 111, + VRTrackedCameraError_SharedTextureFailure = 112, + VRTrackedCameraError_NoFrameAvailable = 113, + VRTrackedCameraError_InvalidArgument = 114, + VRTrackedCameraError_InvalidFrameBufferSize = 115, +}; + +enum EVRTrackedCameraFrameLayout +{ + EVRTrackedCameraFrameLayout_Mono = 0x0001, + EVRTrackedCameraFrameLayout_Stereo = 0x0002, + EVRTrackedCameraFrameLayout_VerticalLayout = 0x0010, // Stereo frames are Top/Bottom (left/right) + EVRTrackedCameraFrameLayout_HorizontalLayout = 0x0020, // Stereo frames are Left/Right +}; + +enum EVRTrackedCameraFrameType +{ + VRTrackedCameraFrameType_Distorted = 0, // This is the camera video frame size in pixels, still distorted. + VRTrackedCameraFrameType_Undistorted, // In pixels, an undistorted inscribed rectangle region without invalid regions. This size is subject to changes shortly. + VRTrackedCameraFrameType_MaximumUndistorted, // In pixels, maximum undistorted with invalid regions. Non zero alpha component identifies valid regions. + MAX_CAMERA_FRAME_TYPES +}; + +enum EVRDistortionFunctionType +{ + VRDistortionFunctionType_None, + VRDistortionFunctionType_FTheta, + VRDistortionFunctionType_Extended_FTheta, + MAX_DISTORTION_FUNCTION_TYPES, +}; + +static const uint32_t k_unMaxDistortionFunctionParameters = 8; + +typedef uint64_t TrackedCameraHandle_t; +#define INVALID_TRACKED_CAMERA_HANDLE ((vr::TrackedCameraHandle_t)0) + +struct CameraVideoStreamFrameHeader_t +{ + EVRTrackedCameraFrameType eFrameType; + + uint32_t nWidth; + uint32_t nHeight; + uint32_t nBytesPerPixel; + + uint32_t nFrameSequence; + + TrackedDevicePose_t trackedDevicePose; + + uint64_t ulFrameExposureTime; // mid-point of the exposure of the image in host system ticks +}; + +// Screenshot types +typedef uint32_t ScreenshotHandle_t; + +static const uint32_t k_unScreenshotHandleInvalid = 0; + +/** Compositor frame timing reprojection flags. */ +const uint32_t VRCompositor_ReprojectionReason_Cpu = 0x01; +const uint32_t VRCompositor_ReprojectionReason_Gpu = 0x02; +const uint32_t VRCompositor_ReprojectionAsync = 0x04; // This flag indicates the async reprojection mode is active, + // but does not indicate if reprojection actually happened or not. + // Use the ReprojectionReason flags above to check if reprojection + // was actually applied (i.e. scene texture was reused). + // NumFramePresents > 1 also indicates the scene texture was reused, + // and also the number of times that it was presented in total. + +const uint32_t VRCompositor_ReprojectionMotion = 0x08; // This flag indicates whether or not motion smoothing was triggered for this frame + +const uint32_t VRCompositor_PredictionMask = 0x30; // The runtime may predict more than one frame (up to four) ahead if + // it detects the application is taking too long to render. These two + // bits will contain the count of additional frames (normally zero). + // Use the VR_COMPOSITOR_ADDITIONAL_PREDICTED_FRAMES macro to read from + // the latest frame timing entry. + +const uint32_t VRCompositor_ThrottleMask = 0xC0; // Number of frames the compositor is throttling the application. + // Use the VR_COMPOSITOR_NUMBER_OF_THROTTLED_FRAMES macro to read from + // the latest frame timing entry. + +#define VR_COMPOSITOR_ADDITIONAL_PREDICTED_FRAMES( timing ) ( ( ( timing ).m_nReprojectionFlags & vr::VRCompositor_PredictionMask ) >> 4 ) +#define VR_COMPOSITOR_NUMBER_OF_THROTTLED_FRAMES( timing ) ( ( ( timing ).m_nReprojectionFlags & vr::VRCompositor_ThrottleMask ) >> 6 ) + +/** Provides a single frame's timing information to the app */ +struct Compositor_FrameTiming +{ + uint32_t m_nSize; // Set to sizeof( Compositor_FrameTiming ) + uint32_t m_nFrameIndex; + uint32_t m_nNumFramePresents; // number of times this frame was presented + uint32_t m_nNumMisPresented; // number of times this frame was presented on a vsync other than it was originally predicted to + uint32_t m_nNumDroppedFrames; // number of additional times previous frame was scanned out + uint32_t m_nReprojectionFlags; + + /** Absolute time reference for comparing frames. This aligns with the vsync that running start is relative to. */ + double m_flSystemTimeInSeconds; + + /** These times may include work from other processes due to OS scheduling. + * The fewer packets of work these are broken up into, the less likely this will happen. + * GPU work can be broken up by calling Flush. This can sometimes be useful to get the GPU started + * processing that work earlier in the frame. */ + float m_flPreSubmitGpuMs; // time spent rendering the scene (gpu work submitted between WaitGetPoses and second Submit) + float m_flPostSubmitGpuMs; // additional time spent rendering by application (e.g. companion window) + float m_flTotalRenderGpuMs; // time between work submitted immediately after present (ideally vsync) until the end of compositor submitted work + float m_flCompositorRenderGpuMs; // time spend performing distortion correction, rendering chaperone, overlays, etc. + float m_flCompositorRenderCpuMs; // time spent on cpu submitting the above work for this frame + float m_flCompositorIdleCpuMs; // time spent waiting for running start (application could have used this much more time) + + /** Miscellaneous measured intervals. */ + float m_flClientFrameIntervalMs; // time between calls to WaitGetPoses + float m_flPresentCallCpuMs; // time blocked on call to present (usually 0.0, but can go long) + float m_flWaitForPresentCpuMs; // time spent spin-waiting for frame index to change (not near-zero indicates wait object failure) + float m_flSubmitFrameMs; // time spent in IVRCompositor::Submit (not near-zero indicates driver issue) + + /** The following are all relative to this frame's SystemTimeInSeconds */ + float m_flWaitGetPosesCalledMs; + float m_flNewPosesReadyMs; + float m_flNewFrameReadyMs; // second call to IVRCompositor::Submit + float m_flCompositorUpdateStartMs; + float m_flCompositorUpdateEndMs; + float m_flCompositorRenderStartMs; + + vr::TrackedDevicePose_t m_HmdPose; // pose used by app to render this frame + + uint32_t m_nNumVSyncsReadyForUse; + uint32_t m_nNumVSyncsToFirstView; +}; + +/** Provides compositor benchmark results to the app */ +struct Compositor_BenchmarkResults +{ + float m_flMegaPixelsPerSecond; // Measurement of GPU MP/s performed by compositor benchmark + float m_flHmdRecommendedMegaPixelsPerSecond; // Recommended default MP/s given the HMD resolution, refresh, and panel mask. +}; + +/** Frame timing data provided by direct mode drivers. */ +struct DriverDirectMode_FrameTiming +{ + uint32_t m_nSize; // Set to sizeof( DriverDirectMode_FrameTiming ) + uint32_t m_nNumFramePresents; // number of times frame was presented + uint32_t m_nNumMisPresented; // number of times frame was presented on a vsync other than it was originally predicted to + uint32_t m_nNumDroppedFrames; // number of additional times previous frame was scanned out (i.e. compositor missed vsync) + uint32_t m_nReprojectionFlags; +}; + +/** These flags will be set on DriverDirectMode_FrameTiming::m_nReprojectionFlags when IVRDriverDirectModeComponent::GetFrameTiming is called for drivers to optionally respond to. */ +const uint32_t VRCompositor_ReprojectionMotion_Enabled = 0x100; // Motion Smoothing is enabled in the UI for the currently running application +const uint32_t VRCompositor_ReprojectionMotion_ForcedOn = 0x200; // Motion Smoothing is forced on in the UI for the currently running application +const uint32_t VRCompositor_ReprojectionMotion_AppThrottled = 0x400; // Application is requesting throttling via ForceInterleavedReprojectionOn + + +enum EVSync +{ + VSync_None, + VSync_WaitRender, // block following render work until vsync + VSync_NoWaitRender, // do not block following render work (allow to get started early) +}; + +enum EVRMuraCorrectionMode +{ + EVRMuraCorrectionMode_Default = 0, + EVRMuraCorrectionMode_NoCorrection +}; + +/** raw IMU data provided by IVRIOBuffer from paths to tracked devices with IMUs */ +enum Imu_OffScaleFlags +{ + OffScale_AccelX = 0x01, + OffScale_AccelY = 0x02, + OffScale_AccelZ = 0x04, + OffScale_GyroX = 0x08, + OffScale_GyroY = 0x10, + OffScale_GyroZ = 0x20, +}; + +struct ImuSample_t +{ + double fSampleTime; + HmdVector3d_t vAccel; + HmdVector3d_t vGyro; + uint32_t unOffScaleFlags; +}; + +#pragma pack( pop ) + +#define VR_INTERFACE + +// Mozilla: see README.mozilla for more details +// figure out how to import from the VR API dll +// #if defined(_WIN32) + +// #if !defined(OPENVR_BUILD_STATIC) +// #ifdef VR_API_EXPORT +// #define VR_INTERFACE extern "C" __declspec( dllexport ) +// #else +// #define VR_INTERFACE extern "C" __declspec( dllimport ) +// #endif +// #else +// #define VR_INTERFACE extern "C" +// #endif + +// #elif defined(__GNUC__) || defined(COMPILER_GCC) || defined(__APPLE__) + +// #ifdef VR_API_EXPORT +// #define VR_INTERFACE extern "C" __attribute__((visibility("default"))) +// #else +// #define VR_INTERFACE extern "C" +// #endif + +// #else +// #error "Unsupported Platform." +// #endif + + +#if defined( _WIN32 ) + #define VR_CALLTYPE __cdecl +#else + #define VR_CALLTYPE +#endif + +} // namespace vr + +#endif // _INCLUDE_VRTYPES_H + + +// vrannotation.h +#ifdef API_GEN +# define VR_CLANG_ATTR(ATTR) __attribute__((annotate( ATTR ))) +#else +# define VR_CLANG_ATTR(ATTR) +#endif + +#define VR_METHOD_DESC(DESC) VR_CLANG_ATTR( "desc:" #DESC ";" ) +#define VR_IGNOREATTR() VR_CLANG_ATTR( "ignore" ) +#define VR_OUT_STRUCT() VR_CLANG_ATTR( "out_struct: ;" ) +#define VR_OUT_STRING() VR_CLANG_ATTR( "out_string: ;" ) +#define VR_OUT_ARRAY_CALL(COUNTER,FUNCTION,PARAMS) VR_CLANG_ATTR( "out_array_call:" #COUNTER "," #FUNCTION "," #PARAMS ";" ) +#define VR_OUT_ARRAY_COUNT(COUNTER) VR_CLANG_ATTR( "out_array_count:" #COUNTER ";" ) +#define VR_ARRAY_COUNT(COUNTER) VR_CLANG_ATTR( "array_count:" #COUNTER ";" ) +#define VR_ARRAY_COUNT_D(COUNTER, DESC) VR_CLANG_ATTR( "array_count:" #COUNTER ";desc:" #DESC ) +#define VR_BUFFER_COUNT(COUNTER) VR_CLANG_ATTR( "buffer_count:" #COUNTER ";" ) +#define VR_OUT_BUFFER_COUNT(COUNTER) VR_CLANG_ATTR( "out_buffer_count:" #COUNTER ";" ) +#define VR_OUT_STRING_COUNT(COUNTER) VR_CLANG_ATTR( "out_string_count:" #COUNTER ";" ) + +// ivrsystem.h +namespace vr +{ + +class IVRSystem +{ +public: + + + // ------------------------------------ + // Display Methods + // ------------------------------------ + + /** Suggested size for the intermediate render target that the distortion pulls from. */ + virtual void GetRecommendedRenderTargetSize( uint32_t *pnWidth, uint32_t *pnHeight ) = 0; + + /** The projection matrix for the specified eye */ + virtual HmdMatrix44_t GetProjectionMatrix( EVREye eEye, float fNearZ, float fFarZ ) = 0; + + /** The components necessary to build your own projection matrix in case your + * application is doing something fancy like infinite Z */ + virtual void GetProjectionRaw( EVREye eEye, float *pfLeft, float *pfRight, float *pfTop, float *pfBottom ) = 0; + + /** Gets the result of the distortion function for the specified eye and input UVs. UVs go from 0,0 in + * the upper left of that eye's viewport and 1,1 in the lower right of that eye's viewport. + * Returns true for success. Otherwise, returns false, and distortion coordinates are not suitable. */ + virtual bool ComputeDistortion( EVREye eEye, float fU, float fV, DistortionCoordinates_t *pDistortionCoordinates ) = 0; + + /** Returns the transform from eye space to the head space. Eye space is the per-eye flavor of head + * space that provides stereo disparity. Instead of Model * View * Projection the sequence is Model * View * Eye^-1 * Projection. + * Normally View and Eye^-1 will be multiplied together and treated as View in your application. + */ + virtual HmdMatrix34_t GetEyeToHeadTransform( EVREye eEye ) = 0; + + /** Returns the number of elapsed seconds since the last recorded vsync event. This + * will come from a vsync timer event in the timer if possible or from the application-reported + * time if that is not available. If no vsync times are available the function will + * return zero for vsync time and frame counter and return false from the method. */ + virtual bool GetTimeSinceLastVsync( float *pfSecondsSinceLastVsync, uint64_t *pulFrameCounter ) = 0; + + /** [D3D9 Only] + * Returns the adapter index that the user should pass into CreateDevice to set up D3D9 in such + * a way that it can go full screen exclusive on the HMD. Returns -1 if there was an error. + */ + virtual int32_t GetD3D9AdapterIndex() = 0; + + /** [D3D10/11 Only] + * Returns the adapter index that the user should pass into EnumAdapters to create the device + * and swap chain in DX10 and DX11. If an error occurs the index will be set to -1. + */ + virtual void GetDXGIOutputInfo( int32_t *pnAdapterIndex ) = 0; + + /** + * Returns platform- and texture-type specific adapter identification so that applications and the + * compositor are creating textures and swap chains on the same GPU. If an error occurs the device + * will be set to 0. + * pInstance is an optional parameter that is required only when textureType is TextureType_Vulkan. + * [D3D10/11/12 Only (D3D9 Not Supported)] + * Returns the adapter LUID that identifies the GPU attached to the HMD. The user should + * enumerate all adapters using IDXGIFactory::EnumAdapters and IDXGIAdapter::GetDesc to find + * the adapter with the matching LUID, or use IDXGIFactory4::EnumAdapterByLuid. + * The discovered IDXGIAdapter should be used to create the device and swap chain. + * [Vulkan Only] + * Returns the VkPhysicalDevice that should be used by the application. + * pInstance must be the instance the application will use to query for the VkPhysicalDevice. The application + * must create the VkInstance with extensions returned by IVRCompositor::GetVulkanInstanceExtensionsRequired enabled. + * [macOS Only] + * For TextureType_IOSurface returns the id<MTLDevice> that should be used by the application. + * On 10.13+ for TextureType_OpenGL returns the 'registryId' of the renderer which should be used + * by the application. See Apple Technical Q&A QA1168 for information on enumerating GL Renderers, and the + * new kCGLRPRegistryIDLow and kCGLRPRegistryIDHigh CGLRendererProperty values in the 10.13 SDK. + * Pre 10.13 for TextureType_OpenGL returns 0, as there is no dependable way to correlate the HMDs MTLDevice + * with a GL Renderer. + */ + virtual void GetOutputDevice( uint64_t *pnDevice, ETextureType textureType, VkInstance_T *pInstance = nullptr ) = 0; + + // ------------------------------------ + // Display Mode methods + // ------------------------------------ + + /** Use to determine if the headset display is part of the desktop (i.e. extended) or hidden (i.e. direct mode). */ + virtual bool IsDisplayOnDesktop() = 0; + + /** Set the display visibility (true = extended, false = direct mode). Return value of true indicates that the change was successful. */ + virtual bool SetDisplayVisibility( bool bIsVisibleOnDesktop ) = 0; + + // ------------------------------------ + // Tracking Methods + // ------------------------------------ + + /** The pose that the tracker thinks that the HMD will be in at the specified number of seconds into the + * future. Pass 0 to get the state at the instant the method is called. Most of the time the application should + * calculate the time until the photons will be emitted from the display and pass that time into the method. + * + * This is roughly analogous to the inverse of the view matrix in most applications, though + * many games will need to do some additional rotation or translation on top of the rotation + * and translation provided by the head pose. + * + * For devices where bPoseIsValid is true the application can use the pose to position the device + * in question. The provided array can be any size up to k_unMaxTrackedDeviceCount. + * + * Seated experiences should call this method with TrackingUniverseSeated and receive poses relative + * to the seated zero pose. Standing experiences should call this method with TrackingUniverseStanding + * and receive poses relative to the Chaperone Play Area. TrackingUniverseRawAndUncalibrated should + * probably not be used unless the application is the Chaperone calibration tool itself, but will provide + * poses relative to the hardware-specific coordinate system in the driver. + */ + virtual void GetDeviceToAbsoluteTrackingPose( ETrackingUniverseOrigin eOrigin, float fPredictedSecondsToPhotonsFromNow, VR_ARRAY_COUNT(unTrackedDevicePoseArrayCount) TrackedDevicePose_t *pTrackedDevicePoseArray, uint32_t unTrackedDevicePoseArrayCount ) = 0; + + /** Sets the zero pose for the seated tracker coordinate system to the current position and yaw of the HMD. After + * ResetSeatedZeroPose all GetDeviceToAbsoluteTrackingPose calls that pass TrackingUniverseSeated as the origin + * will be relative to this new zero pose. The new zero coordinate system will not change the fact that the Y axis + * is up in the real world, so the next pose returned from GetDeviceToAbsoluteTrackingPose after a call to + * ResetSeatedZeroPose may not be exactly an identity matrix. + * + * NOTE: This function overrides the user's previously saved seated zero pose and should only be called as the result of a user action. + * Users are also able to set their seated zero pose via the OpenVR Dashboard. + **/ + virtual void ResetSeatedZeroPose() = 0; + + /** Returns the transform from the seated zero pose to the standing absolute tracking system. This allows + * applications to represent the seated origin to used or transform object positions from one coordinate + * system to the other. + * + * The seated origin may or may not be inside the Play Area or Collision Bounds returned by IVRChaperone. Its position + * depends on what the user has set from the Dashboard settings and previous calls to ResetSeatedZeroPose. */ + virtual HmdMatrix34_t GetSeatedZeroPoseToStandingAbsoluteTrackingPose() = 0; + + /** Returns the transform from the tracking origin to the standing absolute tracking system. This allows + * applications to convert from raw tracking space to the calibrated standing coordinate system. */ + virtual HmdMatrix34_t GetRawZeroPoseToStandingAbsoluteTrackingPose() = 0; + + /** Get a sorted array of device indices of a given class of tracked devices (e.g. controllers). Devices are sorted right to left + * relative to the specified tracked device (default: hmd -- pass in -1 for absolute tracking space). Returns the number of devices + * in the list, or the size of the array needed if not large enough. */ + virtual uint32_t GetSortedTrackedDeviceIndicesOfClass( ETrackedDeviceClass eTrackedDeviceClass, VR_ARRAY_COUNT(unTrackedDeviceIndexArrayCount) vr::TrackedDeviceIndex_t *punTrackedDeviceIndexArray, uint32_t unTrackedDeviceIndexArrayCount, vr::TrackedDeviceIndex_t unRelativeToTrackedDeviceIndex = k_unTrackedDeviceIndex_Hmd ) = 0; + + /** Returns the level of activity on the device. */ + virtual EDeviceActivityLevel GetTrackedDeviceActivityLevel( vr::TrackedDeviceIndex_t unDeviceId ) = 0; + + /** Convenience utility to apply the specified transform to the specified pose. + * This properly transforms all pose components, including velocity and angular velocity + */ + virtual void ApplyTransform( TrackedDevicePose_t *pOutputPose, const TrackedDevicePose_t *pTrackedDevicePose, const HmdMatrix34_t *pTransform ) = 0; + + /** Returns the device index associated with a specific role, for example the left hand or the right hand. This function is deprecated in favor of the new IVRInput system. */ + virtual vr::TrackedDeviceIndex_t GetTrackedDeviceIndexForControllerRole( vr::ETrackedControllerRole unDeviceType ) = 0; + + /** Returns the controller type associated with a device index. This function is deprecated in favor of the new IVRInput system. */ + virtual vr::ETrackedControllerRole GetControllerRoleForTrackedDeviceIndex( vr::TrackedDeviceIndex_t unDeviceIndex ) = 0; + + // ------------------------------------ + // Property methods + // ------------------------------------ + + /** Returns the device class of a tracked device. If there has not been a device connected in this slot + * since the application started this function will return TrackedDevice_Invalid. For previous detected + * devices the function will return the previously observed device class. + * + * To determine which devices exist on the system, just loop from 0 to k_unMaxTrackedDeviceCount and check + * the device class. Every device with something other than TrackedDevice_Invalid is associated with an + * actual tracked device. */ + virtual ETrackedDeviceClass GetTrackedDeviceClass( vr::TrackedDeviceIndex_t unDeviceIndex ) = 0; + + /** Returns true if there is a device connected in this slot. */ + virtual bool IsTrackedDeviceConnected( vr::TrackedDeviceIndex_t unDeviceIndex ) = 0; + + /** Returns a bool property. If the device index is not valid or the property is not a bool type this function will return false. */ + virtual bool GetBoolTrackedDeviceProperty( vr::TrackedDeviceIndex_t unDeviceIndex, ETrackedDeviceProperty prop, ETrackedPropertyError *pError = 0L ) = 0; + + /** Returns a float property. If the device index is not valid or the property is not a float type this function will return 0. */ + virtual float GetFloatTrackedDeviceProperty( vr::TrackedDeviceIndex_t unDeviceIndex, ETrackedDeviceProperty prop, ETrackedPropertyError *pError = 0L ) = 0; + + /** Returns an int property. If the device index is not valid or the property is not a int type this function will return 0. */ + virtual int32_t GetInt32TrackedDeviceProperty( vr::TrackedDeviceIndex_t unDeviceIndex, ETrackedDeviceProperty prop, ETrackedPropertyError *pError = 0L ) = 0; + + /** Returns a uint64 property. If the device index is not valid or the property is not a uint64 type this function will return 0. */ + virtual uint64_t GetUint64TrackedDeviceProperty( vr::TrackedDeviceIndex_t unDeviceIndex, ETrackedDeviceProperty prop, ETrackedPropertyError *pError = 0L ) = 0; + + /** Returns a matrix property. If the device index is not valid or the property is not a matrix type, this function will return identity. */ + virtual HmdMatrix34_t GetMatrix34TrackedDeviceProperty( vr::TrackedDeviceIndex_t unDeviceIndex, ETrackedDeviceProperty prop, ETrackedPropertyError *pError = 0L ) = 0; + + /** Returns an array of one type of property. If the device index is not valid or the property is not a single value or an array of the specified type, + * this function will return 0. Otherwise it returns the number of bytes necessary to hold the array of properties. If unBufferSize is + * greater than the returned size and pBuffer is non-NULL, pBuffer is filled with the contents of array of properties. */ + virtual uint32_t GetArrayTrackedDeviceProperty( vr::TrackedDeviceIndex_t unDeviceIndex, ETrackedDeviceProperty prop, PropertyTypeTag_t propType, void *pBuffer, uint32_t unBufferSize, ETrackedPropertyError *pError = 0L ) = 0; + + /** Returns a string property. If the device index is not valid or the property is not a string type this function will + * return 0. Otherwise it returns the length of the number of bytes necessary to hold this string including the trailing + * null. Strings will always fit in buffers of k_unMaxPropertyStringSize characters. */ + virtual uint32_t GetStringTrackedDeviceProperty( vr::TrackedDeviceIndex_t unDeviceIndex, ETrackedDeviceProperty prop, VR_OUT_STRING() char *pchValue, uint32_t unBufferSize, ETrackedPropertyError *pError = 0L ) = 0; + + /** returns a string that corresponds with the specified property error. The string will be the name + * of the error enum value for all valid error codes */ + virtual const char *GetPropErrorNameFromEnum( ETrackedPropertyError error ) = 0; + + // ------------------------------------ + // Event methods + // ------------------------------------ + + /** Returns true and fills the event with the next event on the queue if there is one. If there are no events + * this method returns false. uncbVREvent should be the size in bytes of the VREvent_t struct */ + virtual bool PollNextEvent( VREvent_t *pEvent, uint32_t uncbVREvent ) = 0; + + /** Returns true and fills the event with the next event on the queue if there is one. If there are no events + * this method returns false. Fills in the pose of the associated tracked device in the provided pose struct. + * This pose will always be older than the call to this function and should not be used to render the device. + uncbVREvent should be the size in bytes of the VREvent_t struct */ + virtual bool PollNextEventWithPose( ETrackingUniverseOrigin eOrigin, VREvent_t *pEvent, uint32_t uncbVREvent, vr::TrackedDevicePose_t *pTrackedDevicePose ) = 0; + + /** returns the name of an EVREvent enum value */ + virtual const char *GetEventTypeNameFromEnum( EVREventType eType ) = 0; + + // ------------------------------------ + // Rendering helper methods + // ------------------------------------ + + /** Returns the hidden area mesh for the current HMD. The pixels covered by this mesh will never be seen by the user after the lens distortion is + * applied based on visibility to the panels. If this HMD does not have a hidden area mesh, the vertex data and count will be NULL and 0 respectively. + * This mesh is meant to be rendered into the stencil buffer (or into the depth buffer setting nearz) before rendering each eye's view. + * This will improve performance by letting the GPU early-reject pixels the user will never see before running the pixel shader. + * NOTE: Render this mesh with backface culling disabled since the winding order of the vertices can be different per-HMD or per-eye. + * Setting the bInverse argument to true will produce the visible area mesh that is commonly used in place of full-screen quads. The visible area mesh covers all of the pixels the hidden area mesh does not cover. + * Setting the bLineLoop argument will return a line loop of vertices in HiddenAreaMesh_t->pVertexData with HiddenAreaMesh_t->unTriangleCount set to the number of vertices. + */ + virtual HiddenAreaMesh_t GetHiddenAreaMesh( EVREye eEye, EHiddenAreaMeshType type = k_eHiddenAreaMesh_Standard ) = 0; + + // ------------------------------------ + // Controller methods + // ------------------------------------ + + /** Fills the supplied struct with the current state of the controller. Returns false if the controller index + * is invalid. This function is deprecated in favor of the new IVRInput system. */ + virtual bool GetControllerState( vr::TrackedDeviceIndex_t unControllerDeviceIndex, vr::VRControllerState_t *pControllerState, uint32_t unControllerStateSize ) = 0; + + /** fills the supplied struct with the current state of the controller and the provided pose with the pose of + * the controller when the controller state was updated most recently. Use this form if you need a precise controller + * pose as input to your application when the user presses or releases a button. This function is deprecated in favor of the new IVRInput system. */ + virtual bool GetControllerStateWithPose( ETrackingUniverseOrigin eOrigin, vr::TrackedDeviceIndex_t unControllerDeviceIndex, vr::VRControllerState_t *pControllerState, uint32_t unControllerStateSize, TrackedDevicePose_t *pTrackedDevicePose ) = 0; + + /** Trigger a single haptic pulse on a controller. After this call the application may not trigger another haptic pulse on this controller + * and axis combination for 5ms. This function is deprecated in favor of the new IVRInput system. */ + virtual void TriggerHapticPulse( vr::TrackedDeviceIndex_t unControllerDeviceIndex, uint32_t unAxisId, unsigned short usDurationMicroSec ) = 0; + + /** returns the name of an EVRButtonId enum value. This function is deprecated in favor of the new IVRInput system. */ + virtual const char *GetButtonIdNameFromEnum( EVRButtonId eButtonId ) = 0; + + /** returns the name of an EVRControllerAxisType enum value. This function is deprecated in favor of the new IVRInput system. */ + virtual const char *GetControllerAxisTypeNameFromEnum( EVRControllerAxisType eAxisType ) = 0; + + /** Returns true if this application is receiving input from the system. This would return false if + * system-related functionality is consuming the input stream. */ + virtual bool IsInputAvailable() = 0; + + /** Returns true SteamVR is drawing controllers on top of the application. Applications should consider + * not drawing anything attached to the user's hands in this case. */ + virtual bool IsSteamVRDrawingControllers() = 0; + + /** Returns true if the user has put SteamVR into a mode that is distracting them from the application. + * For applications where this is appropriate, the application should pause ongoing activity. */ + virtual bool ShouldApplicationPause() = 0; + + /** Returns true if SteamVR is doing significant rendering work and the game should do what it can to reduce + * its own workload. One common way to do this is to reduce the size of the render target provided for each eye. */ + virtual bool ShouldApplicationReduceRenderingWork() = 0; + + // ------------------------------------ + // Firmware methods + // ------------------------------------ + + /** Performs the actual firmware update if applicable. + * The following events will be sent, if VRFirmwareError_None was returned: VREvent_FirmwareUpdateStarted, VREvent_FirmwareUpdateFinished + * Use the properties Prop_Firmware_UpdateAvailable_Bool, Prop_Firmware_ManualUpdate_Bool, and Prop_Firmware_ManualUpdateURL_String + * to figure our whether a firmware update is available, and to figure out whether its a manual update + * Prop_Firmware_ManualUpdateURL_String should point to an URL describing the manual update process */ + virtual vr::EVRFirmwareError PerformFirmwareUpdate( vr::TrackedDeviceIndex_t unDeviceIndex ) = 0; + + // ------------------------------------ + // Application life cycle methods + // ------------------------------------ + + /** Call this to acknowledge to the system that VREvent_Quit has been received and that the process is exiting. + * This extends the timeout until the process is killed. */ + virtual void AcknowledgeQuit_Exiting() = 0; + + // ------------------------------------- + // App container sandbox methods + // ------------------------------------- + + /** Retrieves a null-terminated, semicolon-delimited list of UTF8 file paths that an application + * must have read access to when running inside of an app container. Returns the number of bytes + * needed to hold the list. */ + virtual uint32_t GetAppContainerFilePaths( VR_OUT_STRING() char *pchBuffer, uint32_t unBufferSize ) = 0; + + // ------------------------------------- + // System methods + // ------------------------------------- + + /** Returns the current version of the SteamVR runtime. The returned string will remain valid until VR_Shutdown is called. + * + * NOTE: Is it not appropriate to use this version to test for the presence of any SteamVR feature. Only use this version + * number for logging or showing to a user, and not to try to detect anything at runtime. When appropriate, feature-specific + * presence information is provided by other APIs. */ + virtual const char *GetRuntimeVersion() = 0; + +}; + +static const char * const IVRSystem_Version = "IVRSystem_021"; + +} + + +// ivrapplications.h +namespace vr +{ + + /** Used for all errors reported by the IVRApplications interface */ + enum EVRApplicationError + { + VRApplicationError_None = 0, + + VRApplicationError_AppKeyAlreadyExists = 100, // Only one application can use any given key + VRApplicationError_NoManifest = 101, // the running application does not have a manifest + VRApplicationError_NoApplication = 102, // No application is running + VRApplicationError_InvalidIndex = 103, + VRApplicationError_UnknownApplication = 104, // the application could not be found + VRApplicationError_IPCFailed = 105, // An IPC failure caused the request to fail + VRApplicationError_ApplicationAlreadyRunning = 106, + VRApplicationError_InvalidManifest = 107, + VRApplicationError_InvalidApplication = 108, + VRApplicationError_LaunchFailed = 109, // the process didn't start + VRApplicationError_ApplicationAlreadyStarting = 110, // the system was already starting the same application + VRApplicationError_LaunchInProgress = 111, // The system was already starting a different application + VRApplicationError_OldApplicationQuitting = 112, + VRApplicationError_TransitionAborted = 113, + VRApplicationError_IsTemplate = 114, // error when you try to call LaunchApplication() on a template type app (use LaunchTemplateApplication) + VRApplicationError_SteamVRIsExiting = 115, + + VRApplicationError_BufferTooSmall = 200, // The provided buffer was too small to fit the requested data + VRApplicationError_PropertyNotSet = 201, // The requested property was not set + VRApplicationError_UnknownProperty = 202, + VRApplicationError_InvalidParameter = 203, + }; + + /** The maximum length of an application key */ + static const uint32_t k_unMaxApplicationKeyLength = 128; + + /** these are the properties available on applications. */ + enum EVRApplicationProperty + { + VRApplicationProperty_Name_String = 0, + + VRApplicationProperty_LaunchType_String = 11, + VRApplicationProperty_WorkingDirectory_String = 12, + VRApplicationProperty_BinaryPath_String = 13, + VRApplicationProperty_Arguments_String = 14, + VRApplicationProperty_URL_String = 15, + + VRApplicationProperty_Description_String = 50, + VRApplicationProperty_NewsURL_String = 51, + VRApplicationProperty_ImagePath_String = 52, + VRApplicationProperty_Source_String = 53, + VRApplicationProperty_ActionManifestURL_String = 54, + + VRApplicationProperty_IsDashboardOverlay_Bool = 60, + VRApplicationProperty_IsTemplate_Bool = 61, + VRApplicationProperty_IsInstanced_Bool = 62, + VRApplicationProperty_IsInternal_Bool = 63, + VRApplicationProperty_WantsCompositorPauseInStandby_Bool = 64, + VRApplicationProperty_IsHidden_Bool = 65, + + VRApplicationProperty_LastLaunchTime_Uint64 = 70, + }; + + enum EVRSceneApplicationState + { + EVRSceneApplicationState_None = 0, // Scene Application is not running + EVRSceneApplicationState_Starting = 1, // Scene Application is starting + EVRSceneApplicationState_Quitting = 2, // Scene Application is quitting + EVRSceneApplicationState_Running = 3, // Scene Application is running, and submitting frames, a custom skybox, or a visible overlay + EVRSceneApplicationState_Waiting = 4, // Scene Application is running, but not drawing anything + }; + + struct AppOverrideKeys_t + { + const char *pchKey; + const char *pchValue; + }; + + /** Currently recognized mime types */ + static const char * const k_pch_MimeType_HomeApp = "vr/home"; + static const char * const k_pch_MimeType_GameTheater = "vr/game_theater"; + + class IVRApplications + { + public: + + // --------------- Application management --------------- // + + /** Adds an application manifest to the list to load when building the list of installed applications. + * Temporary manifests are not automatically loaded */ + virtual EVRApplicationError AddApplicationManifest( const char *pchApplicationManifestFullPath, bool bTemporary = false ) = 0; + + /** Removes an application manifest from the list to load when building the list of installed applications. */ + virtual EVRApplicationError RemoveApplicationManifest( const char *pchApplicationManifestFullPath ) = 0; + + /** Returns true if an application is installed */ + virtual bool IsApplicationInstalled( const char *pchAppKey ) = 0; + + /** Returns the number of applications available in the list */ + virtual uint32_t GetApplicationCount() = 0; + + /** Returns the key of the specified application. The index is at least 0 and is less than the return + * value of GetApplicationCount(). The buffer should be at least k_unMaxApplicationKeyLength in order to + * fit the key. */ + virtual EVRApplicationError GetApplicationKeyByIndex( uint32_t unApplicationIndex, VR_OUT_STRING() char *pchAppKeyBuffer, uint32_t unAppKeyBufferLen ) = 0; + + /** Returns the key of the application for the specified Process Id. The buffer should be at least + * k_unMaxApplicationKeyLength in order to fit the key. */ + virtual EVRApplicationError GetApplicationKeyByProcessId( uint32_t unProcessId, VR_OUT_STRING() char *pchAppKeyBuffer, uint32_t unAppKeyBufferLen ) = 0; + + /** Launches the application. The existing scene application will exit and then the new application will start. + * This call is not valid for dashboard overlay applications. */ + virtual EVRApplicationError LaunchApplication( const char *pchAppKey ) = 0; + + /** Launches an instance of an application of type template, with its app key being pchNewAppKey (which must be unique) and optionally override sections + * from the manifest file via AppOverrideKeys_t + */ + virtual EVRApplicationError LaunchTemplateApplication( const char *pchTemplateAppKey, const char *pchNewAppKey, VR_ARRAY_COUNT( unKeys ) const AppOverrideKeys_t *pKeys, uint32_t unKeys ) = 0; + + /** launches the application currently associated with this mime type and passes it the option args, typically the filename or object name of the item being launched */ + virtual vr::EVRApplicationError LaunchApplicationFromMimeType( const char *pchMimeType, const char *pchArgs ) = 0; + + /** Launches the dashboard overlay application if it is not already running. This call is only valid for + * dashboard overlay applications. */ + virtual EVRApplicationError LaunchDashboardOverlay( const char *pchAppKey ) = 0; + + /** Cancel a pending launch for an application */ + virtual bool CancelApplicationLaunch( const char *pchAppKey ) = 0; + + /** Identifies a running application. OpenVR can't always tell which process started in response + * to a URL. This function allows a URL handler (or the process itself) to identify the app key + * for the now running application. Passing a process ID of 0 identifies the calling process. + * The application must be one that's known to the system via a call to AddApplicationManifest. */ + virtual EVRApplicationError IdentifyApplication( uint32_t unProcessId, const char *pchAppKey ) = 0; + + /** Returns the process ID for an application. Return 0 if the application was not found or is not running. */ + virtual uint32_t GetApplicationProcessId( const char *pchAppKey ) = 0; + + /** Returns a string for an applications error */ + virtual const char *GetApplicationsErrorNameFromEnum( EVRApplicationError error ) = 0; + + // --------------- Application properties --------------- // + + /** Returns a value for an application property. The required buffer size to fit this value will be returned. */ + virtual uint32_t GetApplicationPropertyString( const char *pchAppKey, EVRApplicationProperty eProperty, VR_OUT_STRING() char *pchPropertyValueBuffer, uint32_t unPropertyValueBufferLen, EVRApplicationError *peError = nullptr ) = 0; + + /** Returns a bool value for an application property. Returns false in all error cases. */ + virtual bool GetApplicationPropertyBool( const char *pchAppKey, EVRApplicationProperty eProperty, EVRApplicationError *peError = nullptr ) = 0; + + /** Returns a uint64 value for an application property. Returns 0 in all error cases. */ + virtual uint64_t GetApplicationPropertyUint64( const char *pchAppKey, EVRApplicationProperty eProperty, EVRApplicationError *peError = nullptr ) = 0; + + /** Sets the application auto-launch flag. This is only valid for applications which return true for VRApplicationProperty_IsDashboardOverlay_Bool. */ + virtual EVRApplicationError SetApplicationAutoLaunch( const char *pchAppKey, bool bAutoLaunch ) = 0; + + /** Gets the application auto-launch flag. This is only valid for applications which return true for VRApplicationProperty_IsDashboardOverlay_Bool. */ + virtual bool GetApplicationAutoLaunch( const char *pchAppKey ) = 0; + + /** Adds this mime-type to the list of supported mime types for this application*/ + virtual EVRApplicationError SetDefaultApplicationForMimeType( const char *pchAppKey, const char *pchMimeType ) = 0; + + /** return the app key that will open this mime type */ + virtual bool GetDefaultApplicationForMimeType( const char *pchMimeType, VR_OUT_STRING() char *pchAppKeyBuffer, uint32_t unAppKeyBufferLen ) = 0; + + /** Get the list of supported mime types for this application, comma-delimited */ + virtual bool GetApplicationSupportedMimeTypes( const char *pchAppKey, VR_OUT_STRING() char *pchMimeTypesBuffer, uint32_t unMimeTypesBuffer ) = 0; + + /** Get the list of app-keys that support this mime type, comma-delimited, the return value is number of bytes you need to return the full string */ + virtual uint32_t GetApplicationsThatSupportMimeType( const char *pchMimeType, VR_OUT_STRING() char *pchAppKeysThatSupportBuffer, uint32_t unAppKeysThatSupportBuffer ) = 0; + + /** Get the args list from an app launch that had the process already running, you call this when you get a VREvent_ApplicationMimeTypeLoad */ + virtual uint32_t GetApplicationLaunchArguments( uint32_t unHandle, VR_OUT_STRING() char *pchArgs, uint32_t unArgs ) = 0; + + // --------------- Transition methods --------------- // + + /** Returns the app key for the application that is starting up */ + virtual EVRApplicationError GetStartingApplication( VR_OUT_STRING() char *pchAppKeyBuffer, uint32_t unAppKeyBufferLen ) = 0; + + /** Returns the application transition state */ + virtual EVRSceneApplicationState GetSceneApplicationState() = 0; + + /** Returns errors that would prevent the specified application from launching immediately. Calling this function will + * cause the current scene application to quit, so only call it when you are actually about to launch something else. + * What the caller should do about these failures depends on the failure: + * VRApplicationError_OldApplicationQuitting - An existing application has been told to quit. Wait for a VREvent_ProcessQuit + * and try again. + * VRApplicationError_ApplicationAlreadyStarting - This application is already starting. This is a permanent failure. + * VRApplicationError_LaunchInProgress - A different application is already starting. This is a permanent failure. + * VRApplicationError_None - Go ahead and launch. Everything is clear. + */ + virtual EVRApplicationError PerformApplicationPrelaunchCheck( const char *pchAppKey ) = 0; + + /** Returns a string for an application transition state */ + virtual const char *GetSceneApplicationStateNameFromEnum( EVRSceneApplicationState state ) = 0; + + /** Starts a subprocess within the calling application. This + * suppresses all application transition UI and automatically identifies the new executable + * as part of the same application. On success the calling process should exit immediately. + * If working directory is NULL or "" the directory portion of the binary path will be + * the working directory. */ + virtual EVRApplicationError LaunchInternalProcess( const char *pchBinaryPath, const char *pchArguments, const char *pchWorkingDirectory ) = 0; + + /** Returns the current scene process ID according to the application system. A scene process will get scene + * focus once it starts rendering, but it will appear here once it calls VR_Init with the Scene application + * type. */ + virtual uint32_t GetCurrentSceneProcessId() = 0; + }; + + static const char * const IVRApplications_Version = "IVRApplications_007"; + +} // namespace vr + +// ivrsettings.h +#include <string> + +namespace vr +{ + enum EVRSettingsError + { + VRSettingsError_None = 0, + VRSettingsError_IPCFailed = 1, + VRSettingsError_WriteFailed = 2, + VRSettingsError_ReadFailed = 3, + VRSettingsError_JsonParseFailed = 4, + VRSettingsError_UnsetSettingHasNoDefault = 5, // This will be returned if the setting does not appear in the appropriate default file and has not been set + }; + + // The maximum length of a settings key + static const uint32_t k_unMaxSettingsKeyLength = 128; + + class IVRSettings + { + public: + virtual const char *GetSettingsErrorNameFromEnum( EVRSettingsError eError ) = 0; + + virtual void SetBool( const char *pchSection, const char *pchSettingsKey, bool bValue, EVRSettingsError *peError = nullptr ) = 0; + virtual void SetInt32( const char *pchSection, const char *pchSettingsKey, int32_t nValue, EVRSettingsError *peError = nullptr ) = 0; + virtual void SetFloat( const char *pchSection, const char *pchSettingsKey, float flValue, EVRSettingsError *peError = nullptr ) = 0; + virtual void SetString( const char *pchSection, const char *pchSettingsKey, const char *pchValue, EVRSettingsError *peError = nullptr ) = 0; + + // Users of the system need to provide a proper default in default.vrsettings in the resources/settings/ directory + // of either the runtime or the driver_xxx directory. Otherwise the default will be false, 0, 0.0 or "" + virtual bool GetBool( const char *pchSection, const char *pchSettingsKey, EVRSettingsError *peError = nullptr ) = 0; + virtual int32_t GetInt32( const char *pchSection, const char *pchSettingsKey, EVRSettingsError *peError = nullptr ) = 0; + virtual float GetFloat( const char *pchSection, const char *pchSettingsKey, EVRSettingsError *peError = nullptr ) = 0; + virtual void GetString( const char *pchSection, const char *pchSettingsKey, VR_OUT_STRING() char *pchValue, uint32_t unValueLen, EVRSettingsError *peError = nullptr ) = 0; + + virtual void RemoveSection( const char *pchSection, EVRSettingsError *peError = nullptr ) = 0; + virtual void RemoveKeyInSection( const char *pchSection, const char *pchSettingsKey, EVRSettingsError *peError = nullptr ) = 0; + }; + + //----------------------------------------------------------------------------- + static const char * const IVRSettings_Version = "IVRSettings_003"; + + class CVRSettingHelper + { + IVRSettings *m_pSettings; + public: + // Mozilla: see README.mozilla for more details + explicit CVRSettingHelper( IVRSettings *pSettings ) + { + m_pSettings = pSettings; + } + + const char *GetSettingsErrorNameFromEnum( EVRSettingsError eError ) + { + return m_pSettings->GetSettingsErrorNameFromEnum( eError ); + } + + void SetBool( const char *pchSection, const char *pchSettingsKey, bool bValue, EVRSettingsError *peError = nullptr ) + { + m_pSettings->SetBool( pchSection, pchSettingsKey, bValue, peError ); + } + + void SetInt32( const char *pchSection, const char *pchSettingsKey, int32_t nValue, EVRSettingsError *peError = nullptr ) + { + m_pSettings->SetInt32( pchSection, pchSettingsKey, nValue, peError ); + } + void SetFloat( const char *pchSection, const char *pchSettingsKey, float flValue, EVRSettingsError *peError = nullptr ) + { + m_pSettings->SetFloat( pchSection, pchSettingsKey, flValue, peError ); + } + void SetString( const char *pchSection, const char *pchSettingsKey, const char *pchValue, EVRSettingsError *peError = nullptr ) + { + m_pSettings->SetString( pchSection, pchSettingsKey, pchValue, peError ); + } + void SetString( const std::string & sSection, const std::string & sSettingsKey, const std::string & sValue, EVRSettingsError *peError = nullptr ) + { + m_pSettings->SetString( sSection.c_str(), sSettingsKey.c_str(), sValue.c_str(), peError ); + } + + bool GetBool( const char *pchSection, const char *pchSettingsKey, EVRSettingsError *peError = nullptr ) + { + return m_pSettings->GetBool( pchSection, pchSettingsKey, peError ); + } + int32_t GetInt32( const char *pchSection, const char *pchSettingsKey, EVRSettingsError *peError = nullptr ) + { + return m_pSettings->GetInt32( pchSection, pchSettingsKey, peError ); + } + float GetFloat( const char *pchSection, const char *pchSettingsKey, EVRSettingsError *peError = nullptr ) + { + return m_pSettings->GetFloat( pchSection, pchSettingsKey, peError ); + } + void GetString( const char *pchSection, const char *pchSettingsKey, VR_OUT_STRING() char *pchValue, uint32_t unValueLen, EVRSettingsError *peError = nullptr ) + { + m_pSettings->GetString( pchSection, pchSettingsKey, pchValue, unValueLen, peError ); + } + std::string GetString( const std::string & sSection, const std::string & sSettingsKey, EVRSettingsError *peError = nullptr ) + { + char buf[4096]; + vr::EVRSettingsError eError; + m_pSettings->GetString( sSection.c_str(), sSettingsKey.c_str(), buf, sizeof( buf ), &eError ); + if ( peError ) + *peError = eError; + if ( eError == vr::VRSettingsError_None ) + return buf; + else + return ""; + } + + void RemoveSection( const char *pchSection, EVRSettingsError *peError = nullptr ) + { + m_pSettings->RemoveSection( pchSection, peError ); + } + void RemoveKeyInSection( const char *pchSection, const char *pchSettingsKey, EVRSettingsError *peError = nullptr ) + { + m_pSettings->RemoveKeyInSection( pchSection, pchSettingsKey, peError ); + } + }; + + + //----------------------------------------------------------------------------- + // steamvr keys + static const char * const k_pch_SteamVR_Section = "steamvr"; + static const char * const k_pch_SteamVR_RequireHmd_String = "requireHmd"; + static const char * const k_pch_SteamVR_ForcedDriverKey_String = "forcedDriver"; + static const char * const k_pch_SteamVR_ForcedHmdKey_String = "forcedHmd"; + static const char * const k_pch_SteamVR_DisplayDebug_Bool = "displayDebug"; + static const char * const k_pch_SteamVR_DebugProcessPipe_String = "debugProcessPipe"; + static const char * const k_pch_SteamVR_DisplayDebugX_Int32 = "displayDebugX"; + static const char * const k_pch_SteamVR_DisplayDebugY_Int32 = "displayDebugY"; + static const char * const k_pch_SteamVR_SendSystemButtonToAllApps_Bool= "sendSystemButtonToAllApps"; + static const char * const k_pch_SteamVR_LogLevel_Int32 = "loglevel"; + static const char * const k_pch_SteamVR_IPD_Float = "ipd"; + static const char * const k_pch_SteamVR_Background_String = "background"; + static const char * const k_pch_SteamVR_BackgroundUseDomeProjection_Bool = "backgroundUseDomeProjection"; + static const char * const k_pch_SteamVR_BackgroundCameraHeight_Float = "backgroundCameraHeight"; + static const char * const k_pch_SteamVR_BackgroundDomeRadius_Float = "backgroundDomeRadius"; + static const char * const k_pch_SteamVR_GridColor_String = "gridColor"; + static const char * const k_pch_SteamVR_PlayAreaColor_String = "playAreaColor"; + static const char * const k_pch_SteamVR_TrackingLossColor_String = "trackingLossColor"; + static const char * const k_pch_SteamVR_ShowStage_Bool = "showStage"; + static const char * const k_pch_SteamVR_ActivateMultipleDrivers_Bool = "activateMultipleDrivers"; + static const char * const k_pch_SteamVR_UsingSpeakers_Bool = "usingSpeakers"; + static const char * const k_pch_SteamVR_SpeakersForwardYawOffsetDegrees_Float = "speakersForwardYawOffsetDegrees"; + static const char * const k_pch_SteamVR_BaseStationPowerManagement_Int32 = "basestationPowerManagement"; + static const char * const k_pch_SteamVR_ShowBaseStationPowerManagementTip_Int32 = "ShowBaseStationPowerManagementTip"; + static const char * const k_pch_SteamVR_NeverKillProcesses_Bool = "neverKillProcesses"; + static const char * const k_pch_SteamVR_SupersampleScale_Float = "supersampleScale"; + static const char * const k_pch_SteamVR_MaxRecommendedResolution_Int32 = "maxRecommendedResolution"; + static const char * const k_pch_SteamVR_MotionSmoothing_Bool = "motionSmoothing"; + static const char * const k_pch_SteamVR_MotionSmoothingOverride_Int32 = "motionSmoothingOverride"; + static const char * const k_pch_SteamVR_DisableAsyncReprojection_Bool = "disableAsync"; + static const char * const k_pch_SteamVR_ForceFadeOnBadTracking_Bool = "forceFadeOnBadTracking"; + static const char * const k_pch_SteamVR_DefaultMirrorView_Int32 = "mirrorView"; + static const char * const k_pch_SteamVR_ShowLegacyMirrorView_Bool = "showLegacyMirrorView"; + static const char * const k_pch_SteamVR_MirrorViewVisibility_Bool = "showMirrorView"; + static const char * const k_pch_SteamVR_MirrorViewDisplayMode_Int32 = "mirrorViewDisplayMode"; + static const char * const k_pch_SteamVR_MirrorViewEye_Int32 = "mirrorViewEye"; + static const char * const k_pch_SteamVR_MirrorViewGeometry_String = "mirrorViewGeometry"; + static const char * const k_pch_SteamVR_MirrorViewGeometryMaximized_String = "mirrorViewGeometryMaximized"; + static const char * const k_pch_SteamVR_PerfGraphVisibility_Bool = "showPerfGraph"; + static const char * const k_pch_SteamVR_StartMonitorFromAppLaunch = "startMonitorFromAppLaunch"; + static const char * const k_pch_SteamVR_StartCompositorFromAppLaunch_Bool = "startCompositorFromAppLaunch"; + static const char * const k_pch_SteamVR_StartDashboardFromAppLaunch_Bool = "startDashboardFromAppLaunch"; + static const char * const k_pch_SteamVR_StartOverlayAppsFromDashboard_Bool = "startOverlayAppsFromDashboard"; + static const char * const k_pch_SteamVR_EnableHomeApp = "enableHomeApp"; + static const char * const k_pch_SteamVR_CycleBackgroundImageTimeSec_Int32 = "CycleBackgroundImageTimeSec"; + static const char * const k_pch_SteamVR_RetailDemo_Bool = "retailDemo"; + static const char * const k_pch_SteamVR_IpdOffset_Float = "ipdOffset"; + static const char * const k_pch_SteamVR_AllowSupersampleFiltering_Bool = "allowSupersampleFiltering"; + static const char * const k_pch_SteamVR_SupersampleManualOverride_Bool = "supersampleManualOverride"; + static const char * const k_pch_SteamVR_EnableLinuxVulkanAsync_Bool = "enableLinuxVulkanAsync"; + static const char * const k_pch_SteamVR_AllowDisplayLockedMode_Bool = "allowDisplayLockedMode"; + static const char * const k_pch_SteamVR_HaveStartedTutorialForNativeChaperoneDriver_Bool = "haveStartedTutorialForNativeChaperoneDriver"; + static const char * const k_pch_SteamVR_ForceWindows32bitVRMonitor = "forceWindows32BitVRMonitor"; + static const char * const k_pch_SteamVR_DebugInputBinding = "debugInputBinding"; + static const char * const k_pch_SteamVR_DoNotFadeToGrid = "doNotFadeToGrid"; + static const char * const k_pch_SteamVR_RenderCameraMode = "renderCameraMode"; + static const char * const k_pch_SteamVR_EnableSharedResourceJournaling = "enableSharedResourceJournaling"; + static const char * const k_pch_SteamVR_EnableSafeMode = "enableSafeMode"; + static const char * const k_pch_SteamVR_PreferredRefreshRate = "preferredRefreshRate"; + static const char * const k_pch_SteamVR_LastVersionNotice = "lastVersionNotice"; + static const char * const k_pch_SteamVR_LastVersionNoticeDate = "lastVersionNoticeDate"; + static const char * const k_pch_SteamVR_HmdDisplayColorGainR_Float = "hmdDisplayColorGainR"; + static const char * const k_pch_SteamVR_HmdDisplayColorGainG_Float = "hmdDisplayColorGainG"; + static const char * const k_pch_SteamVR_HmdDisplayColorGainB_Float = "hmdDisplayColorGainB"; + static const char * const k_pch_SteamVR_CustomIconStyle_String = "customIconStyle"; + static const char * const k_pch_SteamVR_CustomOffIconStyle_String = "customOffIconStyle"; + static const char * const k_pch_SteamVR_CustomIconForceUpdate_String = "customIconForceUpdate"; + static const char * const k_pch_SteamVR_AllowGlobalActionSetPriority = "globalActionSetPriority"; + static const char * const k_pch_SteamVR_OverlayRenderQuality = "overlayRenderQuality_2"; + + //----------------------------------------------------------------------------- + // direct mode keys + static const char * const k_pch_DirectMode_Section = "direct_mode"; + static const char * const k_pch_DirectMode_Enable_Bool = "enable"; + static const char * const k_pch_DirectMode_Count_Int32 = "count"; + static const char * const k_pch_DirectMode_EdidVid_Int32 = "edidVid"; + static const char * const k_pch_DirectMode_EdidPid_Int32 = "edidPid"; + + //----------------------------------------------------------------------------- + // lighthouse keys + static const char * const k_pch_Lighthouse_Section = "driver_lighthouse"; + static const char * const k_pch_Lighthouse_DisableIMU_Bool = "disableimu"; + static const char * const k_pch_Lighthouse_DisableIMUExceptHMD_Bool = "disableimuexcepthmd"; + static const char * const k_pch_Lighthouse_UseDisambiguation_String = "usedisambiguation"; + static const char * const k_pch_Lighthouse_DisambiguationDebug_Int32 = "disambiguationdebug"; + static const char * const k_pch_Lighthouse_PrimaryBasestation_Int32 = "primarybasestation"; + static const char * const k_pch_Lighthouse_DBHistory_Bool = "dbhistory"; + static const char * const k_pch_Lighthouse_EnableBluetooth_Bool = "enableBluetooth"; + static const char * const k_pch_Lighthouse_PowerManagedBaseStations_String = "PowerManagedBaseStations"; + static const char * const k_pch_Lighthouse_PowerManagedBaseStations2_String = "PowerManagedBaseStations2"; + static const char * const k_pch_Lighthouse_InactivityTimeoutForBaseStations_Int32 = "InactivityTimeoutForBaseStations"; + static const char * const k_pch_Lighthouse_EnableImuFallback_Bool = "enableImuFallback"; + + //----------------------------------------------------------------------------- + // null keys + static const char * const k_pch_Null_Section = "driver_null"; + static const char * const k_pch_Null_SerialNumber_String = "serialNumber"; + static const char * const k_pch_Null_ModelNumber_String = "modelNumber"; + static const char * const k_pch_Null_WindowX_Int32 = "windowX"; + static const char * const k_pch_Null_WindowY_Int32 = "windowY"; + static const char * const k_pch_Null_WindowWidth_Int32 = "windowWidth"; + static const char * const k_pch_Null_WindowHeight_Int32 = "windowHeight"; + static const char * const k_pch_Null_RenderWidth_Int32 = "renderWidth"; + static const char * const k_pch_Null_RenderHeight_Int32 = "renderHeight"; + static const char * const k_pch_Null_SecondsFromVsyncToPhotons_Float = "secondsFromVsyncToPhotons"; + static const char * const k_pch_Null_DisplayFrequency_Float = "displayFrequency"; + + //----------------------------------------------------------------------------- + // Windows MR keys + static const char * const k_pch_WindowsMR_Section = "driver_holographic"; + + //----------------------------------------------------------------------------- + // user interface keys + static const char * const k_pch_UserInterface_Section = "userinterface"; + static const char * const k_pch_UserInterface_StatusAlwaysOnTop_Bool = "StatusAlwaysOnTop"; + static const char * const k_pch_UserInterface_MinimizeToTray_Bool = "MinimizeToTray"; + static const char * const k_pch_UserInterface_HidePopupsWhenStatusMinimized_Bool = "HidePopupsWhenStatusMinimized"; + static const char * const k_pch_UserInterface_Screenshots_Bool = "screenshots"; + static const char * const k_pch_UserInterface_ScreenshotType_Int = "screenshotType"; + + //----------------------------------------------------------------------------- + // notification keys + static const char * const k_pch_Notifications_Section = "notifications"; + static const char * const k_pch_Notifications_DoNotDisturb_Bool = "DoNotDisturb"; + + //----------------------------------------------------------------------------- + // keyboard keys + static const char * const k_pch_Keyboard_Section = "keyboard"; + static const char * const k_pch_Keyboard_TutorialCompletions = "TutorialCompletions"; + static const char * const k_pch_Keyboard_ScaleX = "ScaleX"; + static const char * const k_pch_Keyboard_ScaleY = "ScaleY"; + static const char * const k_pch_Keyboard_OffsetLeftX = "OffsetLeftX"; + static const char * const k_pch_Keyboard_OffsetRightX = "OffsetRightX"; + static const char * const k_pch_Keyboard_OffsetY = "OffsetY"; + static const char * const k_pch_Keyboard_Smoothing = "Smoothing"; + + //----------------------------------------------------------------------------- + // perf keys + static const char * const k_pch_Perf_Section = "perfcheck"; + static const char * const k_pch_Perf_PerfGraphInHMD_Bool = "perfGraphInHMD"; + static const char * const k_pch_Perf_AllowTimingStore_Bool = "allowTimingStore"; + static const char * const k_pch_Perf_SaveTimingsOnExit_Bool = "saveTimingsOnExit"; + static const char * const k_pch_Perf_TestData_Float = "perfTestData"; + static const char * const k_pch_Perf_GPUProfiling_Bool = "GPUProfiling"; + + //----------------------------------------------------------------------------- + // collision bounds keys + static const char * const k_pch_CollisionBounds_Section = "collisionBounds"; + static const char * const k_pch_CollisionBounds_Style_Int32 = "CollisionBoundsStyle"; + static const char * const k_pch_CollisionBounds_GroundPerimeterOn_Bool = "CollisionBoundsGroundPerimeterOn"; + static const char * const k_pch_CollisionBounds_CenterMarkerOn_Bool = "CollisionBoundsCenterMarkerOn"; + static const char * const k_pch_CollisionBounds_PlaySpaceOn_Bool = "CollisionBoundsPlaySpaceOn"; + static const char * const k_pch_CollisionBounds_FadeDistance_Float = "CollisionBoundsFadeDistance"; + static const char * const k_pch_CollisionBounds_WallHeight_Float = "CollisionBoundsWallHeight"; + static const char * const k_pch_CollisionBounds_ColorGammaR_Int32 = "CollisionBoundsColorGammaR"; + static const char * const k_pch_CollisionBounds_ColorGammaG_Int32 = "CollisionBoundsColorGammaG"; + static const char * const k_pch_CollisionBounds_ColorGammaB_Int32 = "CollisionBoundsColorGammaB"; + static const char * const k_pch_CollisionBounds_ColorGammaA_Int32 = "CollisionBoundsColorGammaA"; + static const char * const k_pch_CollisionBounds_EnableDriverImport = "enableDriverBoundsImport"; + + //----------------------------------------------------------------------------- + // camera keys + static const char * const k_pch_Camera_Section = "camera"; + static const char * const k_pch_Camera_EnableCamera_Bool = "enableCamera"; + static const char * const k_pch_Camera_EnableCameraInDashboard_Bool = "enableCameraInDashboard"; + static const char * const k_pch_Camera_EnableCameraForCollisionBounds_Bool = "enableCameraForCollisionBounds"; + static const char * const k_pch_Camera_EnableCameraForRoomView_Bool = "enableCameraForRoomView"; + static const char * const k_pch_Camera_BoundsColorGammaR_Int32 = "cameraBoundsColorGammaR"; + static const char * const k_pch_Camera_BoundsColorGammaG_Int32 = "cameraBoundsColorGammaG"; + static const char * const k_pch_Camera_BoundsColorGammaB_Int32 = "cameraBoundsColorGammaB"; + static const char * const k_pch_Camera_BoundsColorGammaA_Int32 = "cameraBoundsColorGammaA"; + static const char * const k_pch_Camera_BoundsStrength_Int32 = "cameraBoundsStrength"; + static const char * const k_pch_Camera_RoomViewMode_Int32 = "cameraRoomViewMode"; + + //----------------------------------------------------------------------------- + // audio keys + static const char * const k_pch_audio_Section = "audio"; + static const char * const k_pch_audio_SetOsDefaultPlaybackDevice_Bool = "setOsDefaultPlaybackDevice"; + static const char * const k_pch_audio_EnablePlaybackDeviceOverride_Bool = "enablePlaybackDeviceOverride"; + static const char * const k_pch_audio_PlaybackDeviceOverride_String = "playbackDeviceOverride"; + static const char * const k_pch_audio_PlaybackDeviceOverrideName_String = "playbackDeviceOverrideName"; + static const char * const k_pch_audio_SetOsDefaultRecordingDevice_Bool = "setOsDefaultRecordingDevice"; + static const char * const k_pch_audio_EnableRecordingDeviceOverride_Bool = "enableRecordingDeviceOverride"; + static const char * const k_pch_audio_RecordingDeviceOverride_String = "recordingDeviceOverride"; + static const char * const k_pch_audio_RecordingDeviceOverrideName_String = "recordingDeviceOverrideName"; + static const char * const k_pch_audio_EnablePlaybackMirror_Bool = "enablePlaybackMirror"; + static const char * const k_pch_audio_PlaybackMirrorDevice_String = "playbackMirrorDevice"; + static const char * const k_pch_audio_PlaybackMirrorDeviceName_String = "playbackMirrorDeviceName"; + static const char * const k_pch_audio_OldPlaybackMirrorDevice_String = "onPlaybackMirrorDevice"; + static const char * const k_pch_audio_ActiveMirrorDevice_String = "activePlaybackMirrorDevice"; + static const char * const k_pch_audio_EnablePlaybackMirrorIndependentVolume_Bool = "enablePlaybackMirrorIndependentVolume"; + static const char * const k_pch_audio_LastHmdPlaybackDeviceId_String = "lastHmdPlaybackDeviceId"; + static const char * const k_pch_audio_VIVEHDMIGain = "viveHDMIGain"; + + //----------------------------------------------------------------------------- + // power management keys + static const char * const k_pch_Power_Section = "power"; + static const char * const k_pch_Power_PowerOffOnExit_Bool = "powerOffOnExit"; + static const char * const k_pch_Power_TurnOffScreensTimeout_Float = "turnOffScreensTimeout"; + static const char * const k_pch_Power_TurnOffControllersTimeout_Float = "turnOffControllersTimeout"; + static const char * const k_pch_Power_ReturnToWatchdogTimeout_Float = "returnToWatchdogTimeout"; + static const char * const k_pch_Power_AutoLaunchSteamVROnButtonPress = "autoLaunchSteamVROnButtonPress"; + static const char * const k_pch_Power_PauseCompositorOnStandby_Bool = "pauseCompositorOnStandby"; + + //----------------------------------------------------------------------------- + // dashboard keys + static const char * const k_pch_Dashboard_Section = "dashboard"; + static const char * const k_pch_Dashboard_EnableDashboard_Bool = "enableDashboard"; + static const char * const k_pch_Dashboard_ArcadeMode_Bool = "arcadeMode"; + static const char * const k_pch_Dashboard_Position = "position"; + static const char * const k_pch_Dashboard_DesktopScale = "desktopScale"; + static const char * const k_pch_Dashboard_DashboardScale = "dashboardScale"; + + //----------------------------------------------------------------------------- + // model skin keys + static const char * const k_pch_modelskin_Section = "modelskins"; + + //----------------------------------------------------------------------------- + // driver keys - These could be checked in any driver_<name> section + static const char * const k_pch_Driver_Enable_Bool = "enable"; + static const char * const k_pch_Driver_BlockedBySafemode_Bool = "blocked_by_safe_mode"; + static const char * const k_pch_Driver_LoadPriority_Int32 = "loadPriority"; + + //----------------------------------------------------------------------------- + // web interface keys + static const char* const k_pch_WebInterface_Section = "WebInterface"; + + //----------------------------------------------------------------------------- + // vrwebhelper keys + static const char* const k_pch_VRWebHelper_Section = "VRWebHelper"; + static const char* const k_pch_VRWebHelper_DebuggerEnabled_Bool = "DebuggerEnabled"; + static const char* const k_pch_VRWebHelper_DebuggerPort_Int32 = "DebuggerPort"; + + //----------------------------------------------------------------------------- + // tracking overrides - keys are device paths, values are the device paths their + // tracking/pose information overrides + static const char* const k_pch_TrackingOverride_Section = "TrackingOverrides"; + + //----------------------------------------------------------------------------- + // per-app keys - the section name for these is the app key itself. Some of these are prefixed by the controller type + static const char* const k_pch_App_BindingAutosaveURLSuffix_String = "AutosaveURL"; + static const char* const k_pch_App_BindingLegacyAPISuffix_String = "_legacy"; + static const char* const k_pch_App_BindingSteamVRInputAPISuffix_String = "_steamvrinput"; + static const char* const k_pch_App_BindingCurrentURLSuffix_String = "CurrentURL"; + static const char* const k_pch_App_BindingPreviousURLSuffix_String = "PreviousURL"; + static const char* const k_pch_App_NeedToUpdateAutosaveSuffix_Bool = "NeedToUpdateAutosave"; + static const char* const k_pch_App_DominantHand_Int32 = "DominantHand"; + + //----------------------------------------------------------------------------- + // configuration for trackers + static const char * const k_pch_Trackers_Section = "trackers"; + + //----------------------------------------------------------------------------- + // configuration for desktop UI windows + static const char * const k_pch_DesktopUI_Section = "DesktopUI"; + + //----------------------------------------------------------------------------- + // Last known keys for righting recovery + static const char * const k_pch_LastKnown_Section = "LastKnown"; + static const char* const k_pch_LastKnown_HMDManufacturer_String = "HMDManufacturer"; + static const char* const k_pch_LastKnown_HMDModel_String = "HMDModel"; + + //----------------------------------------------------------------------------- + // Dismissed warnings + static const char * const k_pch_DismissedWarnings_Section = "DismissedWarnings"; + + //----------------------------------------------------------------------------- + // Input Settings + static const char * const k_pch_Input_Section = "input"; + static const char* const k_pch_Input_LeftThumbstickRotation_Float = "leftThumbstickRotation"; + static const char* const k_pch_Input_RightThumbstickRotation_Float = "rightThumbstickRotation"; + static const char* const k_pch_Input_ThumbstickDeadzone_Float = "thumbstickDeadzone"; + + //----------------------------------------------------------------------------- + // Log of GPU performance + static const char * const k_pch_GpuSpeed_Section = "GpuSpeed"; + +} // namespace vr + +// ivrchaperone.h +namespace vr +{ + +#pragma pack( push, 8 ) + +enum ChaperoneCalibrationState +{ + // OK! + ChaperoneCalibrationState_OK = 1, // Chaperone is fully calibrated and working correctly + + // Warnings + ChaperoneCalibrationState_Warning = 100, + ChaperoneCalibrationState_Warning_BaseStationMayHaveMoved = 101, // A base station thinks that it might have moved + ChaperoneCalibrationState_Warning_BaseStationRemoved = 102, // There are less base stations than when calibrated + ChaperoneCalibrationState_Warning_SeatedBoundsInvalid = 103, // Seated bounds haven't been calibrated for the current tracking center + + // Errors + ChaperoneCalibrationState_Error = 200, // The UniverseID is invalid + ChaperoneCalibrationState_Error_BaseStationUninitialized = 201, // Tracking center hasn't be calibrated for at least one of the base stations + ChaperoneCalibrationState_Error_BaseStationConflict = 202, // Tracking center is calibrated, but base stations disagree on the tracking space + ChaperoneCalibrationState_Error_PlayAreaInvalid = 203, // Play Area hasn't been calibrated for the current tracking center + ChaperoneCalibrationState_Error_CollisionBoundsInvalid = 204, // Collision Bounds haven't been calibrated for the current tracking center +}; + + +/** HIGH LEVEL TRACKING SPACE ASSUMPTIONS: +* 0,0,0 is the preferred standing area center. +* 0Y is the floor height. +* -Z is the preferred forward facing direction. */ +class IVRChaperone +{ +public: + + /** Get the current state of Chaperone calibration. This state can change at any time during a session due to physical base station changes. **/ + virtual ChaperoneCalibrationState GetCalibrationState() = 0; + + /** Returns the width and depth of the Play Area (formerly named Soft Bounds) in X and Z. + * Tracking space center (0,0,0) is the center of the Play Area. **/ + virtual bool GetPlayAreaSize( float *pSizeX, float *pSizeZ ) = 0; + + /** Returns the 4 corner positions of the Play Area (formerly named Soft Bounds). + * Corners are in counter-clockwise order. + * Standing center (0,0,0) is the center of the Play Area. + * It's a rectangle. + * 2 sides are parallel to the X axis and 2 sides are parallel to the Z axis. + * Height of every corner is 0Y (on the floor). **/ + virtual bool GetPlayAreaRect( HmdQuad_t *rect ) = 0; + + /** Reload Chaperone data from the .vrchap file on disk. */ + virtual void ReloadInfo( void ) = 0; + + /** Optionally give the chaperone system a hit about the color and brightness in the scene **/ + virtual void SetSceneColor( HmdColor_t color ) = 0; + + /** Get the current chaperone bounds draw color and brightness **/ + virtual void GetBoundsColor( HmdColor_t *pOutputColorArray, int nNumOutputColors, float flCollisionBoundsFadeDistance, HmdColor_t *pOutputCameraColor ) = 0; + + /** Determine whether the bounds are showing right now **/ + virtual bool AreBoundsVisible() = 0; + + /** Force the bounds to show, mostly for utilities **/ + virtual void ForceBoundsVisible( bool bForce ) = 0; +}; + +static const char * const IVRChaperone_Version = "IVRChaperone_003"; + +#pragma pack( pop ) + +} + +// ivrchaperonesetup.h +namespace vr +{ + +enum EChaperoneConfigFile +{ + EChaperoneConfigFile_Live = 1, // The live chaperone config, used by most applications and games + EChaperoneConfigFile_Temp = 2, // The temporary chaperone config, used to live-preview collision bounds in room setup +}; + +enum EChaperoneImportFlags +{ + EChaperoneImport_BoundsOnly = 0x0001, +}; + +/** Manages the working copy of the chaperone info. By default this will be the same as the +* live copy. Any changes made with this interface will stay in the working copy until +* CommitWorkingCopy() is called, at which point the working copy and the live copy will be +* the same again. */ +class IVRChaperoneSetup +{ +public: + + /** Saves the current working copy to disk */ + virtual bool CommitWorkingCopy( EChaperoneConfigFile configFile ) = 0; + + /** Reverts the working copy to match the live chaperone calibration. + * To modify existing data this MUST be do WHILE getting a non-error ChaperoneCalibrationStatus. + * Only after this should you do gets and sets on the existing data. */ + virtual void RevertWorkingCopy() = 0; + + /** Returns the width and depth of the Play Area (formerly named Soft Bounds) in X and Z from the working copy. + * Tracking space center (0,0,0) is the center of the Play Area. */ + virtual bool GetWorkingPlayAreaSize( float *pSizeX, float *pSizeZ ) = 0; + + /** Returns the 4 corner positions of the Play Area (formerly named Soft Bounds) from the working copy. + * Corners are in clockwise order. + * Tracking space center (0,0,0) is the center of the Play Area. + * It's a rectangle. + * 2 sides are parallel to the X axis and 2 sides are parallel to the Z axis. + * Height of every corner is 0Y (on the floor). **/ + virtual bool GetWorkingPlayAreaRect( HmdQuad_t *rect ) = 0; + + /** Returns the number of Quads if the buffer points to null. Otherwise it returns Quads + * into the buffer up to the max specified from the working copy. */ + virtual bool GetWorkingCollisionBoundsInfo( VR_OUT_ARRAY_COUNT(punQuadsCount) HmdQuad_t *pQuadsBuffer, uint32_t* punQuadsCount ) = 0; + + /** Returns the number of Quads if the buffer points to null. Otherwise it returns Quads + * into the buffer up to the max specified. */ + virtual bool GetLiveCollisionBoundsInfo( VR_OUT_ARRAY_COUNT(punQuadsCount) HmdQuad_t *pQuadsBuffer, uint32_t* punQuadsCount ) = 0; + + /** Returns the preferred seated position from the working copy. */ + virtual bool GetWorkingSeatedZeroPoseToRawTrackingPose( HmdMatrix34_t *pmatSeatedZeroPoseToRawTrackingPose ) = 0; + + /** Returns the standing origin from the working copy. */ + virtual bool GetWorkingStandingZeroPoseToRawTrackingPose( HmdMatrix34_t *pmatStandingZeroPoseToRawTrackingPose ) = 0; + + /** Sets the Play Area in the working copy. */ + virtual void SetWorkingPlayAreaSize( float sizeX, float sizeZ ) = 0; + + /** Sets the Collision Bounds in the working copy. Note: ceiling height is ignored. */ + virtual void SetWorkingCollisionBoundsInfo( VR_ARRAY_COUNT(unQuadsCount) HmdQuad_t *pQuadsBuffer, uint32_t unQuadsCount ) = 0; + + /** Sets the Collision Bounds in the working copy. */ + virtual void SetWorkingPerimeter( VR_ARRAY_COUNT( unPointCount ) HmdVector2_t *pPointBuffer, uint32_t unPointCount ) = 0; + + /** Sets the preferred seated position in the working copy. */ + virtual void SetWorkingSeatedZeroPoseToRawTrackingPose( const HmdMatrix34_t *pMatSeatedZeroPoseToRawTrackingPose ) = 0; + + /** Sets the preferred standing position in the working copy. */ + virtual void SetWorkingStandingZeroPoseToRawTrackingPose( const HmdMatrix34_t *pMatStandingZeroPoseToRawTrackingPose ) = 0; + + /** Tear everything down and reload it from the file on disk */ + virtual void ReloadFromDisk( EChaperoneConfigFile configFile ) = 0; + + /** Returns the preferred seated position. */ + virtual bool GetLiveSeatedZeroPoseToRawTrackingPose( HmdMatrix34_t *pmatSeatedZeroPoseToRawTrackingPose ) = 0; + + virtual bool ExportLiveToBuffer( VR_OUT_STRING() char *pBuffer, uint32_t *pnBufferLength ) = 0; + virtual bool ImportFromBufferToWorking( const char *pBuffer, uint32_t nImportFlags ) = 0; + + /** Shows the chaperone data in the working set to preview in the compositor.*/ + virtual void ShowWorkingSetPreview() = 0; + + /** Hides the chaperone data in the working set to preview in the compositor (if it was visible).*/ + virtual void HideWorkingSetPreview() = 0; + + /** Fire an event that the tracking system can use to know room setup is about to begin. This lets the tracking + * system make any last minute adjustments that should be incorporated into the new setup. If the user is adjusting + * live in HMD using a tweak tool, keep in mind that calling this might cause the user to see the room jump. */ + virtual void RoomSetupStarting() = 0; +}; + +static const char * const IVRChaperoneSetup_Version = "IVRChaperoneSetup_006"; + + +} + +// ivrcompositor.h +namespace vr +{ + +#pragma pack( push, 8 ) + +/** Errors that can occur with the VR compositor */ +enum EVRCompositorError +{ + VRCompositorError_None = 0, + VRCompositorError_RequestFailed = 1, + VRCompositorError_IncompatibleVersion = 100, + VRCompositorError_DoNotHaveFocus = 101, + VRCompositorError_InvalidTexture = 102, + VRCompositorError_IsNotSceneApplication = 103, + VRCompositorError_TextureIsOnWrongDevice = 104, + VRCompositorError_TextureUsesUnsupportedFormat = 105, + VRCompositorError_SharedTexturesNotSupported = 106, + VRCompositorError_IndexOutOfRange = 107, + VRCompositorError_AlreadySubmitted = 108, + VRCompositorError_InvalidBounds = 109, + VRCompositorError_AlreadySet = 110, +}; + +/** Timing mode passed to SetExplicitTimingMode(); see that function for documentation */ +enum EVRCompositorTimingMode +{ + VRCompositorTimingMode_Implicit = 0, + VRCompositorTimingMode_Explicit_RuntimePerformsPostPresentHandoff = 1, + VRCompositorTimingMode_Explicit_ApplicationPerformsPostPresentHandoff = 2, +}; + +/** Cumulative stats for current application. These are not cleared until a new app connects, +* but they do stop accumulating once the associated app disconnects. */ +struct Compositor_CumulativeStats +{ + uint32_t m_nPid; // Process id associated with these stats (may no longer be running). + uint32_t m_nNumFramePresents; // total number of times we called present (includes reprojected frames) + uint32_t m_nNumDroppedFrames; // total number of times an old frame was re-scanned out (without reprojection) + uint32_t m_nNumReprojectedFrames; // total number of times a frame was scanned out a second time (with reprojection) + + /** Values recorded at startup before application has fully faded in the first time. */ + uint32_t m_nNumFramePresentsOnStartup; + uint32_t m_nNumDroppedFramesOnStartup; + uint32_t m_nNumReprojectedFramesOnStartup; + + /** Applications may explicitly fade to the compositor. This is usually to handle level transitions, and loading often causes + * system wide hitches. The following stats are collected during this period. Does not include values recorded during startup. */ + uint32_t m_nNumLoading; + uint32_t m_nNumFramePresentsLoading; + uint32_t m_nNumDroppedFramesLoading; + uint32_t m_nNumReprojectedFramesLoading; + + /** If we don't get a new frame from the app in less than 2.5 frames, then we assume the app has hung and start + * fading back to the compositor. The following stats are a result of this, and are a subset of those recorded above. + * Does not include values recorded during start up or loading. */ + uint32_t m_nNumTimedOut; + uint32_t m_nNumFramePresentsTimedOut; + uint32_t m_nNumDroppedFramesTimedOut; + uint32_t m_nNumReprojectedFramesTimedOut; +}; + +struct Compositor_StageRenderSettings +{ + /** Primary color is applied as a tint to (i.e. multiplied with) the model's texture */ + HmdColor_t m_PrimaryColor; + HmdColor_t m_SecondaryColor; + + /** Vignette radius is in meters and is used to fade to the specified secondary solid color over + * that 3D distance from the origin of the playspace. */ + float m_flVignetteInnerRadius; + float m_flVignetteOuterRadius; + + /** Fades to the secondary color based on view incidence. This variable controls the linearity + * of the effect. It is mutually exclusive with vignette. Additionally, it treats the mesh as faceted. */ + float m_flFresnelStrength; + + /** Controls backface culling. */ + bool m_bBackfaceCulling; + + /** Converts the render model's texture to luma and applies to rgb equally. This is useful to + * combat compression artifacts that can occur on desaturated source material. */ + bool m_bGreyscale; + + /** Renders mesh as a wireframe. */ + bool m_bWireframe; +}; + +static inline Compositor_StageRenderSettings DefaultStageRenderSettings() +{ + Compositor_StageRenderSettings settings; + settings.m_PrimaryColor.r = 1.0f; + settings.m_PrimaryColor.g = 1.0f; + settings.m_PrimaryColor.b = 1.0f; + settings.m_PrimaryColor.a = 1.0f; + settings.m_SecondaryColor.r = 1.0f; + settings.m_SecondaryColor.g = 1.0f; + settings.m_SecondaryColor.b = 1.0f; + settings.m_SecondaryColor.a = 1.0f; + settings.m_flVignetteInnerRadius = 0.0f; + settings.m_flVignetteOuterRadius = 0.0f; + settings.m_flFresnelStrength = 0.0f; + settings.m_bBackfaceCulling = false; + settings.m_bGreyscale = false; + settings.m_bWireframe = false; + return settings; +} + +#pragma pack( pop ) + +/** Allows the application to interact with the compositor */ +class IVRCompositor +{ +public: + /** Sets tracking space returned by WaitGetPoses */ + virtual void SetTrackingSpace( ETrackingUniverseOrigin eOrigin ) = 0; + + /** Gets current tracking space returned by WaitGetPoses */ + virtual ETrackingUniverseOrigin GetTrackingSpace() = 0; + + /** Scene applications should call this function to get poses to render with (and optionally poses predicted an additional frame out to use for gameplay). + * This function will block until "running start" milliseconds before the start of the frame, and should be called at the last moment before needing to + * start rendering. + * + * Return codes: + * - IsNotSceneApplication (make sure to call VR_Init with VRApplicaiton_Scene) + * - DoNotHaveFocus (some other app has taken focus - this will throttle the call to 10hz to reduce the impact on that app) + */ + virtual EVRCompositorError WaitGetPoses( VR_ARRAY_COUNT( unRenderPoseArrayCount ) TrackedDevicePose_t* pRenderPoseArray, uint32_t unRenderPoseArrayCount, + VR_ARRAY_COUNT( unGamePoseArrayCount ) TrackedDevicePose_t* pGamePoseArray, uint32_t unGamePoseArrayCount ) = 0; + + /** Get the last set of poses returned by WaitGetPoses. */ + virtual EVRCompositorError GetLastPoses( VR_ARRAY_COUNT( unRenderPoseArrayCount ) TrackedDevicePose_t* pRenderPoseArray, uint32_t unRenderPoseArrayCount, + VR_ARRAY_COUNT( unGamePoseArrayCount ) TrackedDevicePose_t* pGamePoseArray, uint32_t unGamePoseArrayCount ) = 0; + + /** Interface for accessing last set of poses returned by WaitGetPoses one at a time. + * Returns VRCompositorError_IndexOutOfRange if unDeviceIndex not less than k_unMaxTrackedDeviceCount otherwise VRCompositorError_None. + * It is okay to pass NULL for either pose if you only want one of the values. */ + virtual EVRCompositorError GetLastPoseForTrackedDeviceIndex( TrackedDeviceIndex_t unDeviceIndex, TrackedDevicePose_t *pOutputPose, TrackedDevicePose_t *pOutputGamePose ) = 0; + + /** Updated scene texture to display. If pBounds is NULL the entire texture will be used. If called from an OpenGL app, consider adding a glFlush after + * Submitting both frames to signal the driver to start processing, otherwise it may wait until the command buffer fills up, causing the app to miss frames. + * + * OpenGL dirty state: + * glBindTexture + * + * Return codes: + * - IsNotSceneApplication (make sure to call VR_Init with VRApplicaiton_Scene) + * - DoNotHaveFocus (some other app has taken focus) + * - TextureIsOnWrongDevice (application did not use proper AdapterIndex - see IVRSystem.GetDXGIOutputInfo) + * - SharedTexturesNotSupported (application needs to call CreateDXGIFactory1 or later before creating DX device) + * - TextureUsesUnsupportedFormat (scene textures must be compatible with DXGI sharing rules - e.g. uncompressed, no mips, etc.) + * - InvalidTexture (usually means bad arguments passed in) + * - AlreadySubmitted (app has submitted two left textures or two right textures in a single frame - i.e. before calling WaitGetPoses again) + */ + virtual EVRCompositorError Submit( EVREye eEye, const Texture_t *pTexture, const VRTextureBounds_t* pBounds = 0, EVRSubmitFlags nSubmitFlags = Submit_Default ) = 0; + + /** Clears the frame that was sent with the last call to Submit. This will cause the + * compositor to show the grid until Submit is called again. */ + virtual void ClearLastSubmittedFrame() = 0; + + /** Call immediately after presenting your app's window (i.e. companion window) to unblock the compositor. + * This is an optional call, which only needs to be used if you can't instead call WaitGetPoses immediately after Present. + * For example, if your engine's render and game loop are not on separate threads, or blocking the render thread until 3ms before the next vsync would + * introduce a deadlock of some sort. This function tells the compositor that you have finished all rendering after having Submitted buffers for both + * eyes, and it is free to start its rendering work. This should only be called from the same thread you are rendering on. */ + virtual void PostPresentHandoff() = 0; + + /** Returns true if timing data is filled it. Sets oldest timing info if nFramesAgo is larger than the stored history. + * Be sure to set timing.size = sizeof(Compositor_FrameTiming) on struct passed in before calling this function. */ + virtual bool GetFrameTiming( Compositor_FrameTiming *pTiming, uint32_t unFramesAgo = 0 ) = 0; + + /** Interface for copying a range of timing data. Frames are returned in ascending order (oldest to newest) with the last being the most recent frame. + * Only the first entry's m_nSize needs to be set, as the rest will be inferred from that. Returns total number of entries filled out. */ + virtual uint32_t GetFrameTimings( VR_ARRAY_COUNT( nFrames ) Compositor_FrameTiming *pTiming, uint32_t nFrames ) = 0; + + /** Returns the time in seconds left in the current (as identified by FrameTiming's frameIndex) frame. + * Due to "running start", this value may roll over to the next frame before ever reaching 0.0. */ + virtual float GetFrameTimeRemaining() = 0; + + /** Fills out stats accumulated for the last connected application. Pass in sizeof( Compositor_CumulativeStats ) as second parameter. */ + virtual void GetCumulativeStats( Compositor_CumulativeStats *pStats, uint32_t nStatsSizeInBytes ) = 0; + + /** Fades the view on the HMD to the specified color. The fade will take fSeconds, and the color values are between + * 0.0 and 1.0. This color is faded on top of the scene based on the alpha parameter. Removing the fade color instantly + * would be FadeToColor( 0.0, 0.0, 0.0, 0.0, 0.0 ). Values are in un-premultiplied alpha space. */ + virtual void FadeToColor( float fSeconds, float fRed, float fGreen, float fBlue, float fAlpha, bool bBackground = false ) = 0; + + /** Get current fade color value. */ + virtual HmdColor_t GetCurrentFadeColor( bool bBackground = false ) = 0; + + /** Fading the Grid in or out in fSeconds */ + virtual void FadeGrid( float fSeconds, bool bFadeIn ) = 0; + + /** Get current alpha value of grid. */ + virtual float GetCurrentGridAlpha() = 0; + + /** Override the skybox used in the compositor (e.g. for during level loads when the app can't feed scene images fast enough) + * Order is Front, Back, Left, Right, Top, Bottom. If only a single texture is passed, it is assumed in lat-long format. + * If two are passed, it is assumed a lat-long stereo pair. */ + virtual EVRCompositorError SetSkyboxOverride( VR_ARRAY_COUNT( unTextureCount ) const Texture_t *pTextures, uint32_t unTextureCount ) = 0; + + /** Resets compositor skybox back to defaults. */ + virtual void ClearSkyboxOverride() = 0; + + /** Brings the compositor window to the front. This is useful for covering any other window that may be on the HMD + * and is obscuring the compositor window. */ + virtual void CompositorBringToFront() = 0; + + /** Pushes the compositor window to the back. This is useful for allowing other applications to draw directly to the HMD. */ + virtual void CompositorGoToBack() = 0; + + /** Tells the compositor process to clean up and exit. You do not need to call this function at shutdown. Under normal + * circumstances the compositor will manage its own life cycle based on what applications are running. */ + virtual void CompositorQuit() = 0; + + /** Return whether the compositor is fullscreen */ + virtual bool IsFullscreen() = 0; + + /** Returns the process ID of the process that is currently rendering the scene */ + virtual uint32_t GetCurrentSceneFocusProcess() = 0; + + /** Returns the process ID of the process that rendered the last frame (or 0 if the compositor itself rendered the frame.) + * Returns 0 when fading out from an app and the app's process Id when fading into an app. */ + virtual uint32_t GetLastFrameRenderer() = 0; + + /** Returns true if the current process has the scene focus */ + virtual bool CanRenderScene() = 0; + + /** DEPRECATED: Opens the headset view (as either a window or docked widget depending on user's preferences) that displays what the user + * sees in the headset. */ + virtual void ShowMirrorWindow() = 0; + + /** DEPRECATED: Closes the headset view, either as a window or docked widget. */ + virtual void HideMirrorWindow() = 0; + + /** DEPRECATED: Returns true if the headset view (either as a window or docked widget) is shown. */ + virtual bool IsMirrorWindowVisible() = 0; + + /** Writes back buffer and stereo left/right pair from the application to a 'screenshots' folder in the SteamVR runtime root. */ + virtual void CompositorDumpImages() = 0; + + /** Let an app know it should be rendering with low resources. */ + virtual bool ShouldAppRenderWithLowResources() = 0; + + /** Override interleaved reprojection logic to force on. */ + virtual void ForceInterleavedReprojectionOn( bool bOverride ) = 0; + + /** Force reconnecting to the compositor process. */ + virtual void ForceReconnectProcess() = 0; + + /** Temporarily suspends rendering (useful for finer control over scene transitions). */ + virtual void SuspendRendering( bool bSuspend ) = 0; + + /** Opens a shared D3D11 texture with the undistorted composited image for each eye. Use ReleaseMirrorTextureD3D11 when finished + * instead of calling Release on the resource itself. */ + virtual vr::EVRCompositorError GetMirrorTextureD3D11( vr::EVREye eEye, void *pD3D11DeviceOrResource, void **ppD3D11ShaderResourceView ) = 0; + virtual void ReleaseMirrorTextureD3D11( void *pD3D11ShaderResourceView ) = 0; + + /** Access to mirror textures from OpenGL. */ + virtual vr::EVRCompositorError GetMirrorTextureGL( vr::EVREye eEye, vr::glUInt_t *pglTextureId, vr::glSharedTextureHandle_t *pglSharedTextureHandle ) = 0; + virtual bool ReleaseSharedGLTexture( vr::glUInt_t glTextureId, vr::glSharedTextureHandle_t glSharedTextureHandle ) = 0; + virtual void LockGLSharedTextureForAccess( vr::glSharedTextureHandle_t glSharedTextureHandle ) = 0; + virtual void UnlockGLSharedTextureForAccess( vr::glSharedTextureHandle_t glSharedTextureHandle ) = 0; + + /** [Vulkan Only] + * return 0. Otherwise it returns the length of the number of bytes necessary to hold this string including the trailing + * null. The string will be a space separated list of-required instance extensions to enable in VkCreateInstance */ + virtual uint32_t GetVulkanInstanceExtensionsRequired( VR_OUT_STRING() char *pchValue, uint32_t unBufferSize ) = 0; + + /** [Vulkan only] + * return 0. Otherwise it returns the length of the number of bytes necessary to hold this string including the trailing + * null. The string will be a space separated list of required device extensions to enable in VkCreateDevice */ + virtual uint32_t GetVulkanDeviceExtensionsRequired( VkPhysicalDevice_T *pPhysicalDevice, VR_OUT_STRING() char *pchValue, uint32_t unBufferSize ) = 0; + + /** [ Vulkan/D3D12 Only ] + * There are two purposes for SetExplicitTimingMode: + * 1. To get a more accurate GPU timestamp for when the frame begins in Vulkan/D3D12 applications. + * 2. (Optional) To avoid having WaitGetPoses access the Vulkan queue so that the queue can be accessed from + * another thread while WaitGetPoses is executing. + * + * More accurate GPU timestamp for the start of the frame is achieved by the application calling + * SubmitExplicitTimingData immediately before its first submission to the Vulkan/D3D12 queue. + * This is more accurate because normally this GPU timestamp is recorded during WaitGetPoses. In D3D11, + * WaitGetPoses queues a GPU timestamp write, but it does not actually get submitted to the GPU until the + * application flushes. By using SubmitExplicitTimingData, the timestamp is recorded at the same place for + * Vulkan/D3D12 as it is for D3D11, resulting in a more accurate GPU time measurement for the frame. + * + * Avoiding WaitGetPoses accessing the Vulkan queue can be achieved using SetExplicitTimingMode as well. If this is desired, + * the application should set the timing mode to Explicit_ApplicationPerformsPostPresentHandoff and *MUST* call PostPresentHandoff + * itself. If these conditions are met, then WaitGetPoses is guaranteed not to access the queue. Note that PostPresentHandoff + * and SubmitExplicitTimingData will access the queue, so only WaitGetPoses becomes safe for accessing the queue from another + * thread. */ + virtual void SetExplicitTimingMode( EVRCompositorTimingMode eTimingMode ) = 0; + + /** [ Vulkan/D3D12 Only ] + * Submit explicit timing data. When SetExplicitTimingMode is true, this must be called immediately before + * the application's first vkQueueSubmit (Vulkan) or ID3D12CommandQueue::ExecuteCommandLists (D3D12) of each frame. + * This function will insert a GPU timestamp write just before the application starts its rendering. This function + * will perform a vkQueueSubmit on Vulkan so must not be done simultaneously with VkQueue operations on another thread. + * Returns VRCompositorError_RequestFailed if SetExplicitTimingMode is not enabled. */ + virtual EVRCompositorError SubmitExplicitTimingData() = 0; + + /** Indicates whether or not motion smoothing is enabled by the user settings. + * If you want to know if motion smoothing actually triggered due to a late frame, check Compositor_FrameTiming + * m_nReprojectionFlags & VRCompositor_ReprojectionMotion instead. */ + virtual bool IsMotionSmoothingEnabled() = 0; + + /** Indicates whether or not motion smoothing is supported by the current hardware. */ + virtual bool IsMotionSmoothingSupported() = 0; + + /** Indicates whether or not the current scene focus app is currently loading. This is inferred from its use of FadeGrid to + * explicitly fade to the compositor to cover up the fact that it cannot render at a sustained full framerate during this time. */ + virtual bool IsCurrentSceneFocusAppLoading() = 0; + + /** Override the stage model used in the compositor to replace the grid. RenderModelPath is a full path the an OBJ file to load. + * This file will be loaded asynchronously from disk and uploaded to the gpu by the runtime. Once ready for rendering, the + * VREvent StageOverrideReady will be sent. Use FadeToGrid to reveal. Call ClearStageOverride to free the associated resources when finished. */ + virtual EVRCompositorError SetStageOverride_Async( const char *pchRenderModelPath, const HmdMatrix34_t *pTransform = 0, + const Compositor_StageRenderSettings *pRenderSettings = 0, uint32_t nSizeOfRenderSettings = 0 ) = 0; + + /** Resets the stage to its default user specified setting. */ + virtual void ClearStageOverride() = 0; + + /** Returns true if pBenchmarkResults is filled it. Sets pBenchmarkResults with the result of the compositor benchmark. + * nSizeOfBenchmarkResults should be set to sizeof(Compositor_BenchmarkResults) */ + virtual bool GetCompositorBenchmarkResults( Compositor_BenchmarkResults *pBenchmarkResults, uint32_t nSizeOfBenchmarkResults ) = 0; + + /** Returns the frame id associated with the poses last returned by WaitGetPoses. Deltas between IDs correspond to number of headset vsync intervals. */ + virtual EVRCompositorError GetLastPosePredictionIDs( uint32_t *pRenderPosePredictionID, uint32_t *pGamePosePredictionID ) = 0; + + /** Get the most up-to-date predicted (or recorded - up to 100ms old) set of poses for a given frame id. */ + virtual EVRCompositorError GetPosesForFrame( uint32_t unPosePredictionID, VR_ARRAY_COUNT( unPoseArrayCount ) TrackedDevicePose_t* pPoseArray, uint32_t unPoseArrayCount ) = 0; +}; + +static const char * const IVRCompositor_Version = "IVRCompositor_026"; + +} // namespace vr + + + +// ivrheadsetview.h +namespace vr +{ + enum HeadsetViewMode_t + { + HeadsetViewMode_Left = 0, + HeadsetViewMode_Right, + HeadsetViewMode_Both + }; + + class IVRHeadsetView + { + public: + /** Sets the resolution in pixels to render the headset view. These values are clamped to k_unHeadsetViewMaxWidth + * and k_unHeadsetViewMaxHeight respectively. For cropped views, the rendered output will be fit to aspect ratio + * defined by the the specified dimensions. For uncropped views, the caller should use GetHeadsetViewAspectRation + * to adjust the requested render size to avoid squashing or stretching, and then apply letterboxing to compensate + * when displaying the results. */ + virtual void SetHeadsetViewSize( uint32_t nWidth, uint32_t nHeight ) = 0; + + /** Gets the current resolution used to render the headset view. */ + virtual void GetHeadsetViewSize( uint32_t *pnWidth, uint32_t *pnHeight ) = 0; + + /** Set the mode used to render the headset view. */ + virtual void SetHeadsetViewMode( HeadsetViewMode_t eHeadsetViewMode ) = 0; + + /** Get the current mode used to render the headset view. */ + virtual HeadsetViewMode_t GetHeadsetViewMode() = 0; + + /** Set whether or not the headset view should be rendered cropped to hide the hidden area mesh or not. */ + virtual void SetHeadsetViewCropped( bool bCropped ) = 0; + + /** Get the current cropping status of the headset view. */ + virtual bool GetHeadsetViewCropped() = 0; + + /** Get the aspect ratio (width:height) of the uncropped headset view (accounting for the current set mode). */ + virtual float GetHeadsetViewAspectRatio() = 0; + + /** Set the range [0..1] that the headset view blends across the stereo overlapped area in cropped both mode. */ + virtual void SetHeadsetViewBlendRange( float flStartPct, float flEndPct ) = 0; + + /** Get the current range [0..1] that the headset view blends across the stereo overlapped area in cropped both mode. */ + virtual void GetHeadsetViewBlendRange( float *pStartPct, float *pEndPct ) = 0; + }; + + static const uint32_t k_unHeadsetViewMaxWidth = 3840; + static const uint32_t k_unHeadsetViewMaxHeight = 2160; + static const char * const k_pchHeadsetViewOverlayKey = "system.HeadsetView"; + + static const char * const IVRHeadsetView_Version = "IVRHeadsetView_001"; + + /** Returns the current IVRHeadsetView pointer or NULL the interface could not be found. */ + VR_INTERFACE vr::IVRHeadsetView *VR_CALLTYPE VRHeadsetView(); + +} // namespace vr + + +// ivrnotifications.h +namespace vr +{ + +#pragma pack( push, 8 ) + +// Used for passing graphic data +struct NotificationBitmap_t +{ + NotificationBitmap_t() + : m_pImageData( nullptr ) + , m_nWidth( 0 ) + , m_nHeight( 0 ) + , m_nBytesPerPixel( 0 ) + { + } + + void *m_pImageData; + int32_t m_nWidth; + int32_t m_nHeight; + int32_t m_nBytesPerPixel; +}; + + +/** Be aware that the notification type is used as 'priority' to pick the next notification */ +enum EVRNotificationType +{ + /** Transient notifications are automatically hidden after a period of time set by the user. + * They are used for things like information and chat messages that do not require user interaction. */ + EVRNotificationType_Transient = 0, + + /** Persistent notifications are shown to the user until they are hidden by calling RemoveNotification(). + * They are used for things like phone calls and alarms that require user interaction. */ + EVRNotificationType_Persistent = 1, + + /** System notifications are shown no matter what. It is expected, that the ulUserValue is used as ID. + * If there is already a system notification in the queue with that ID it is not accepted into the queue + * to prevent spamming with system notification */ + EVRNotificationType_Transient_SystemWithUserValue = 2, +}; + +enum EVRNotificationStyle +{ + /** Creates a notification with minimal external styling. */ + EVRNotificationStyle_None = 0, + + /** Used for notifications about overlay-level status. In Steam this is used for events like downloads completing. */ + EVRNotificationStyle_Application = 100, + + /** Used for notifications about contacts that are unknown or not available. In Steam this is used for friend invitations and offline friends. */ + EVRNotificationStyle_Contact_Disabled = 200, + + /** Used for notifications about contacts that are available but inactive. In Steam this is used for friends that are online but not playing a game. */ + EVRNotificationStyle_Contact_Enabled = 201, + + /** Used for notifications about contacts that are available and active. In Steam this is used for friends that are online and currently running a game. */ + EVRNotificationStyle_Contact_Active = 202, +}; + +static const uint32_t k_unNotificationTextMaxSize = 256; + +typedef uint32_t VRNotificationId; + + + +#pragma pack( pop ) + +/** Allows notification sources to interact with the VR system + This current interface is not yet implemented. Do not use yet. */ +class IVRNotifications +{ +public: + /** Create a notification and enqueue it to be shown to the user. + * An overlay handle is required to create a notification, as otherwise it would be impossible for a user to act on it. + * To create a two-line notification, use a line break ('\n') to split the text into two lines. + * The pImage argument may be NULL, in which case the specified overlay's icon will be used instead. */ + virtual EVRNotificationError CreateNotification( VROverlayHandle_t ulOverlayHandle, uint64_t ulUserValue, EVRNotificationType type, const char *pchText, EVRNotificationStyle style, const NotificationBitmap_t *pImage, /* out */ VRNotificationId *pNotificationId ) = 0; + + /** Destroy a notification, hiding it first if it currently shown to the user. */ + virtual EVRNotificationError RemoveNotification( VRNotificationId notificationId ) = 0; + +}; + +static const char * const IVRNotifications_Version = "IVRNotifications_002"; + +} // namespace vr + + + +// ivroverlay.h +namespace vr +{ + + /** The maximum length of an overlay key in bytes, counting the terminating null character. */ + static const uint32_t k_unVROverlayMaxKeyLength = 128; + + /** The maximum length of an overlay name in bytes, counting the terminating null character. */ + static const uint32_t k_unVROverlayMaxNameLength = 128; + + /** The maximum number of overlays that can exist in the system at one time. */ + static const uint32_t k_unMaxOverlayCount = 128; + + /** The maximum number of overlay intersection mask primitives per overlay */ + static const uint32_t k_unMaxOverlayIntersectionMaskPrimitivesCount = 32; + + /** Types of input supported by VR Overlays */ + enum VROverlayInputMethod + { + VROverlayInputMethod_None = 0, // No input events will be generated automatically for this overlay + VROverlayInputMethod_Mouse = 1, // Tracked controllers will get mouse events automatically + // VROverlayInputMethod_DualAnalog = 2, // No longer supported + }; + + /** Allows the caller to figure out which overlay transform getter to call. */ + enum VROverlayTransformType + { + VROverlayTransform_Invalid = -1, + VROverlayTransform_Absolute = 0, + VROverlayTransform_TrackedDeviceRelative = 1, + VROverlayTransform_SystemOverlay = 2, + VROverlayTransform_TrackedComponent = 3, + VROverlayTransform_Cursor = 4, + VROverlayTransform_DashboardTab = 5, + VROverlayTransform_DashboardThumb = 6, + VROverlayTransform_Mountable = 7, + }; + + /** Overlay control settings */ + enum VROverlayFlags + { + // Set this flag on a dashboard overlay to prevent a tab from showing up for that overlay + VROverlayFlags_NoDashboardTab = 1 << 3, + + // When this is set the overlay will receive VREvent_ScrollDiscrete events like a mouse wheel. + // Requires mouse input mode. + VROverlayFlags_SendVRDiscreteScrollEvents = 1 << 6, + + // Indicates that the overlay would like to receive + VROverlayFlags_SendVRTouchpadEvents = 1 << 7, + + // If set this will render a vertical scroll wheel on the primary controller, + // only needed if not using VROverlayFlags_SendVRScrollEvents but you still want to represent a scroll wheel + VROverlayFlags_ShowTouchPadScrollWheel = 1 << 8, + + // If this is set ownership and render access to the overlay are transferred + // to the new scene process on a call to IVRApplications::LaunchInternalProcess + VROverlayFlags_TransferOwnershipToInternalProcess = 1 << 9, + + // If set, renders 50% of the texture in each eye, side by side + VROverlayFlags_SideBySide_Parallel = 1 << 10, // Texture is left/right + VROverlayFlags_SideBySide_Crossed = 1 << 11, // Texture is crossed and right/left + + VROverlayFlags_Panorama = 1 << 12, // Texture is a panorama + VROverlayFlags_StereoPanorama = 1 << 13, // Texture is a stereo panorama + + // If this is set on an overlay owned by the scene application that overlay + // will be sorted with the "Other" overlays on top of all other scene overlays + VROverlayFlags_SortWithNonSceneOverlays = 1 << 14, + + // If set, the overlay will be shown in the dashboard, otherwise it will be hidden. + VROverlayFlags_VisibleInDashboard = 1 << 15, + + // If this is set and the overlay's input method is not none, the system-wide laser mouse + // mode will be activated whenever this overlay is visible. + VROverlayFlags_MakeOverlaysInteractiveIfVisible = 1 << 16, + + // If this is set the overlay will receive smooth VREvent_ScrollSmooth that emulate trackpad scrolling. + // Requires mouse input mode. + VROverlayFlags_SendVRSmoothScrollEvents = 1 << 17, + + // If this is set, the overlay texture will be protected content, preventing unauthorized reads. + VROverlayFlags_ProtectedContent = 1 << 18, + + // If this is set, the laser mouse splat will not be drawn over this overlay. The overlay will + // be responsible for drawing its own "cursor". + VROverlayFlags_HideLaserIntersection = 1 << 19, + + // If this is set, clicking away from the overlay will cause it to receive a VREvent_Modal_Cancel event. + // This is ignored for dashboard overlays. + VROverlayFlags_WantsModalBehavior = 1 << 20, + + // If this is set, alpha composition assumes the texture is pre-multiplied + VROverlayFlags_IsPremultiplied = 1 << 21, + }; + + enum VRMessageOverlayResponse + { + VRMessageOverlayResponse_ButtonPress_0 = 0, + VRMessageOverlayResponse_ButtonPress_1 = 1, + VRMessageOverlayResponse_ButtonPress_2 = 2, + VRMessageOverlayResponse_ButtonPress_3 = 3, + VRMessageOverlayResponse_CouldntFindSystemOverlay = 4, + VRMessageOverlayResponse_CouldntFindOrCreateClientOverlay= 5, + VRMessageOverlayResponse_ApplicationQuit = 6 + }; + + struct VROverlayIntersectionParams_t + { + HmdVector3_t vSource; + HmdVector3_t vDirection; + ETrackingUniverseOrigin eOrigin; + }; + + struct VROverlayIntersectionResults_t + { + HmdVector3_t vPoint; + HmdVector3_t vNormal; + HmdVector2_t vUVs; + float fDistance; + }; + + // Input modes for the Big Picture gamepad text entry + enum EGamepadTextInputMode + { + k_EGamepadTextInputModeNormal = 0, + k_EGamepadTextInputModePassword = 1, + k_EGamepadTextInputModeSubmit = 2, + }; + + // Controls number of allowed lines for the Big Picture gamepad text entry + enum EGamepadTextInputLineMode + { + k_EGamepadTextInputLineModeSingleLine = 0, + k_EGamepadTextInputLineModeMultipleLines = 1 + }; + + enum EVROverlayIntersectionMaskPrimitiveType + { + OverlayIntersectionPrimitiveType_Rectangle, + OverlayIntersectionPrimitiveType_Circle, + }; + + struct IntersectionMaskRectangle_t + { + float m_flTopLeftX; + float m_flTopLeftY; + float m_flWidth; + float m_flHeight; + }; + + struct IntersectionMaskCircle_t + { + float m_flCenterX; + float m_flCenterY; + float m_flRadius; + }; + + /** NOTE!!! If you change this you MUST manually update openvr_interop.cs.py and openvr_api_flat.h.py */ + typedef union + { + IntersectionMaskRectangle_t m_Rectangle; + IntersectionMaskCircle_t m_Circle; + } VROverlayIntersectionMaskPrimitive_Data_t; + + struct VROverlayIntersectionMaskPrimitive_t + { + EVROverlayIntersectionMaskPrimitiveType m_nPrimitiveType; + VROverlayIntersectionMaskPrimitive_Data_t m_Primitive; + }; + + enum EKeyboardFlags + { + KeyboardFlag_Minimal = 1 << 0, // makes the keyboard send key events immediately instead of accumulating a buffer + KeyboardFlag_Modal = 2 << 0, // makes the keyboard take all focus and dismiss when clicking off the panel + }; + + + class IVROverlay + { + public: + + // --------------------------------------------- + // Overlay management methods + // --------------------------------------------- + + /** Finds an existing overlay with the specified key. */ + virtual EVROverlayError FindOverlay( const char *pchOverlayKey, VROverlayHandle_t * pOverlayHandle ) = 0; + + /** Creates a new named overlay. All overlays start hidden and with default settings. */ + virtual EVROverlayError CreateOverlay( const char *pchOverlayKey, const char *pchOverlayName, VROverlayHandle_t * pOverlayHandle ) = 0; + + /** Destroys the specified overlay. When an application calls VR_Shutdown all overlays created by that app are + * automatically destroyed. */ + virtual EVROverlayError DestroyOverlay( VROverlayHandle_t ulOverlayHandle ) = 0; + + /** Fills the provided buffer with the string key of the overlay. Returns the size of buffer required to store the key, including + * the terminating null character. k_unVROverlayMaxKeyLength will be enough bytes to fit the string. */ + virtual uint32_t GetOverlayKey( VROverlayHandle_t ulOverlayHandle, VR_OUT_STRING() char *pchValue, uint32_t unBufferSize, EVROverlayError *pError = 0L ) = 0; + + /** Fills the provided buffer with the friendly name of the overlay. Returns the size of buffer required to store the key, including + * the terminating null character. k_unVROverlayMaxNameLength will be enough bytes to fit the string. */ + virtual uint32_t GetOverlayName( VROverlayHandle_t ulOverlayHandle, VR_OUT_STRING() char *pchValue, uint32_t unBufferSize, EVROverlayError *pError = 0L ) = 0; + + /** set the name to use for this overlay */ + virtual EVROverlayError SetOverlayName( VROverlayHandle_t ulOverlayHandle, const char *pchName ) = 0; + + /** Gets the raw image data from an overlay. Overlay image data is always returned as RGBA data, 4 bytes per pixel. If the buffer is not large enough, width and height + * will be set and VROverlayError_ArrayTooSmall is returned. */ + virtual EVROverlayError GetOverlayImageData( VROverlayHandle_t ulOverlayHandle, void *pvBuffer, uint32_t unBufferSize, uint32_t *punWidth, uint32_t *punHeight ) = 0; + + /** returns a string that corresponds with the specified overlay error. The string will be the name + * of the error enum value for all valid error codes */ + virtual const char *GetOverlayErrorNameFromEnum( EVROverlayError error ) = 0; + + // --------------------------------------------- + // Overlay rendering methods + // --------------------------------------------- + + /** Sets the pid that is allowed to render to this overlay (the creator pid is always allow to render), + * by default this is the pid of the process that made the overlay */ + virtual EVROverlayError SetOverlayRenderingPid( VROverlayHandle_t ulOverlayHandle, uint32_t unPID ) = 0; + + /** Gets the pid that is allowed to render to this overlay */ + virtual uint32_t GetOverlayRenderingPid( VROverlayHandle_t ulOverlayHandle ) = 0; + + /** Specify flag setting for a given overlay */ + virtual EVROverlayError SetOverlayFlag( VROverlayHandle_t ulOverlayHandle, VROverlayFlags eOverlayFlag, bool bEnabled ) = 0; + + /** Sets flag setting for a given overlay */ + virtual EVROverlayError GetOverlayFlag( VROverlayHandle_t ulOverlayHandle, VROverlayFlags eOverlayFlag, bool *pbEnabled ) = 0; + + /** Gets all the flags for a given overlay */ + virtual EVROverlayError GetOverlayFlags( VROverlayHandle_t ulOverlayHandle, uint32_t *pFlags ) = 0; + + /** Sets the color tint of the overlay quad. Use 0.0 to 1.0 per channel. */ + virtual EVROverlayError SetOverlayColor( VROverlayHandle_t ulOverlayHandle, float fRed, float fGreen, float fBlue ) = 0; + + /** Gets the color tint of the overlay quad. */ + virtual EVROverlayError GetOverlayColor( VROverlayHandle_t ulOverlayHandle, float *pfRed, float *pfGreen, float *pfBlue ) = 0; + + /** Sets the alpha of the overlay quad. Use 1.0 for 100 percent opacity to 0.0 for 0 percent opacity. */ + virtual EVROverlayError SetOverlayAlpha( VROverlayHandle_t ulOverlayHandle, float fAlpha ) = 0; + + /** Gets the alpha of the overlay quad. By default overlays are rendering at 100 percent alpha (1.0). */ + virtual EVROverlayError GetOverlayAlpha( VROverlayHandle_t ulOverlayHandle, float *pfAlpha ) = 0; + + /** Sets the aspect ratio of the texels in the overlay. 1.0 means the texels are square. 2.0 means the texels + * are twice as wide as they are tall. Defaults to 1.0. */ + virtual EVROverlayError SetOverlayTexelAspect( VROverlayHandle_t ulOverlayHandle, float fTexelAspect ) = 0; + + /** Gets the aspect ratio of the texels in the overlay. Defaults to 1.0 */ + virtual EVROverlayError GetOverlayTexelAspect( VROverlayHandle_t ulOverlayHandle, float *pfTexelAspect ) = 0; + + /** Sets the rendering sort order for the overlay. Overlays are rendered this order: + * Overlays owned by the scene application + * Overlays owned by some other application + * + * Within a category overlays are rendered lowest sort order to highest sort order. Overlays with the same + * sort order are rendered back to front base on distance from the HMD. + * + * Sort order defaults to 0. */ + virtual EVROverlayError SetOverlaySortOrder( VROverlayHandle_t ulOverlayHandle, uint32_t unSortOrder ) = 0; + + /** Gets the sort order of the overlay. See SetOverlaySortOrder for how this works. */ + virtual EVROverlayError GetOverlaySortOrder( VROverlayHandle_t ulOverlayHandle, uint32_t *punSortOrder ) = 0; + + /** Sets the width of the overlay quad in meters. By default overlays are rendered on a quad that is 1 meter across */ + virtual EVROverlayError SetOverlayWidthInMeters( VROverlayHandle_t ulOverlayHandle, float fWidthInMeters ) = 0; + + /** Returns the width of the overlay quad in meters. By default overlays are rendered on a quad that is 1 meter across */ + virtual EVROverlayError GetOverlayWidthInMeters( VROverlayHandle_t ulOverlayHandle, float *pfWidthInMeters ) = 0; + + /** Use to draw overlay as a curved surface. Curvature is a percentage from (0..1] where 1 is a fully closed cylinder. + * For a specific radius, curvature can be computed as: overlay.width / (2 PI r). */ + virtual EVROverlayError SetOverlayCurvature( VROverlayHandle_t ulOverlayHandle, float fCurvature ) = 0; + + /** Returns the curvature of the overlay as a percentage from (0..1] where 1 is a fully closed cylinder. */ + virtual EVROverlayError GetOverlayCurvature( VROverlayHandle_t ulOverlayHandle, float *pfCurvature ) = 0; + + /** Sets the colorspace the overlay texture's data is in. Defaults to 'auto'. + * If the texture needs to be resolved, you should call SetOverlayTexture with the appropriate colorspace instead. */ + virtual EVROverlayError SetOverlayTextureColorSpace( VROverlayHandle_t ulOverlayHandle, EColorSpace eTextureColorSpace ) = 0; + + /** Gets the overlay's current colorspace setting. */ + virtual EVROverlayError GetOverlayTextureColorSpace( VROverlayHandle_t ulOverlayHandle, EColorSpace *peTextureColorSpace ) = 0; + + /** Sets the part of the texture to use for the overlay. UV Min is the upper left corner and UV Max is the lower right corner. */ + virtual EVROverlayError SetOverlayTextureBounds( VROverlayHandle_t ulOverlayHandle, const VRTextureBounds_t *pOverlayTextureBounds ) = 0; + + /** Gets the part of the texture to use for the overlay. UV Min is the upper left corner and UV Max is the lower right corner. */ + virtual EVROverlayError GetOverlayTextureBounds( VROverlayHandle_t ulOverlayHandle, VRTextureBounds_t *pOverlayTextureBounds ) = 0; + + /** Returns the transform type of this overlay. */ + virtual EVROverlayError GetOverlayTransformType( VROverlayHandle_t ulOverlayHandle, VROverlayTransformType *peTransformType ) = 0; + + /** Sets the transform to absolute tracking origin. */ + virtual EVROverlayError SetOverlayTransformAbsolute( VROverlayHandle_t ulOverlayHandle, ETrackingUniverseOrigin eTrackingOrigin, const HmdMatrix34_t *pmatTrackingOriginToOverlayTransform ) = 0; + + /** Gets the transform if it is absolute. Returns an error if the transform is some other type. */ + virtual EVROverlayError GetOverlayTransformAbsolute( VROverlayHandle_t ulOverlayHandle, ETrackingUniverseOrigin *peTrackingOrigin, HmdMatrix34_t *pmatTrackingOriginToOverlayTransform ) = 0; + + /** Sets the transform to relative to the transform of the specified tracked device. */ + virtual EVROverlayError SetOverlayTransformTrackedDeviceRelative( VROverlayHandle_t ulOverlayHandle, TrackedDeviceIndex_t unTrackedDevice, const HmdMatrix34_t *pmatTrackedDeviceToOverlayTransform ) = 0; + + /** Gets the transform if it is relative to a tracked device. Returns an error if the transform is some other type. */ + virtual EVROverlayError GetOverlayTransformTrackedDeviceRelative( VROverlayHandle_t ulOverlayHandle, TrackedDeviceIndex_t *punTrackedDevice, HmdMatrix34_t *pmatTrackedDeviceToOverlayTransform ) = 0; + + /** Sets the transform to draw the overlay on a rendermodel component mesh instead of a quad. This will only draw when the system is + * drawing the device. Overlays with this transform type cannot receive mouse events. */ + virtual EVROverlayError SetOverlayTransformTrackedDeviceComponent( VROverlayHandle_t ulOverlayHandle, TrackedDeviceIndex_t unDeviceIndex, const char *pchComponentName ) = 0; + + /** Gets the transform information when the overlay is rendering on a component. */ + virtual EVROverlayError GetOverlayTransformTrackedDeviceComponent( VROverlayHandle_t ulOverlayHandle, TrackedDeviceIndex_t *punDeviceIndex, VR_OUT_STRING() char *pchComponentName, uint32_t unComponentNameSize ) = 0; + + /** Gets the transform if it is relative to another overlay. Returns an error if the transform is some other type. */ + virtual vr::EVROverlayError GetOverlayTransformOverlayRelative( VROverlayHandle_t ulOverlayHandle, VROverlayHandle_t *ulOverlayHandleParent, HmdMatrix34_t *pmatParentOverlayToOverlayTransform ) = 0; + + /** Sets the transform to relative to the transform of the specified overlay. This overlays visibility will also track the parents visibility */ + virtual vr::EVROverlayError SetOverlayTransformOverlayRelative( VROverlayHandle_t ulOverlayHandle, VROverlayHandle_t ulOverlayHandleParent, const HmdMatrix34_t *pmatParentOverlayToOverlayTransform ) = 0; + + /** Sets the hotspot for the specified overlay when that overlay is used as a cursor. These are in texture space with 0,0 in the upper left corner of + * the texture and 1,1 in the lower right corner of the texture. */ + virtual EVROverlayError SetOverlayTransformCursor( VROverlayHandle_t ulCursorOverlayHandle, const HmdVector2_t *pvHotspot ) = 0; + + /** Gets cursor hotspot/transform for the specified overlay */ + virtual vr::EVROverlayError GetOverlayTransformCursor( VROverlayHandle_t ulOverlayHandle, HmdVector2_t *pvHotspot ) = 0; + + /** Shows the VR overlay. For dashboard overlays, only the Dashboard Manager is allowed to call this. */ + virtual EVROverlayError ShowOverlay( VROverlayHandle_t ulOverlayHandle ) = 0; + + /** Hides the VR overlay. For dashboard overlays, only the Dashboard Manager is allowed to call this. */ + virtual EVROverlayError HideOverlay( VROverlayHandle_t ulOverlayHandle ) = 0; + + /** Returns true if the overlay is visible. */ + virtual bool IsOverlayVisible( VROverlayHandle_t ulOverlayHandle ) = 0; + + /** Get the transform in 3d space associated with a specific 2d point in the overlay's coordinate space (where 0,0 is the lower left). -Z points out of the overlay */ + virtual EVROverlayError GetTransformForOverlayCoordinates( VROverlayHandle_t ulOverlayHandle, ETrackingUniverseOrigin eTrackingOrigin, HmdVector2_t coordinatesInOverlay, HmdMatrix34_t *pmatTransform ) = 0; + + // --------------------------------------------- + // Overlay input methods + // --------------------------------------------- + + /** Returns true and fills the event with the next event on the overlay's event queue, if there is one. + * If there are no events this method returns false. uncbVREvent should be the size in bytes of the VREvent_t struct */ + virtual bool PollNextOverlayEvent( VROverlayHandle_t ulOverlayHandle, VREvent_t *pEvent, uint32_t uncbVREvent ) = 0; + + /** Returns the current input settings for the specified overlay. */ + virtual EVROverlayError GetOverlayInputMethod( VROverlayHandle_t ulOverlayHandle, VROverlayInputMethod *peInputMethod ) = 0; + + /** Sets the input settings for the specified overlay. */ + virtual EVROverlayError SetOverlayInputMethod( VROverlayHandle_t ulOverlayHandle, VROverlayInputMethod eInputMethod ) = 0; + + /** Gets the mouse scaling factor that is used for mouse events. The actual texture may be a different size, but this is + * typically the size of the underlying UI in pixels. */ + virtual EVROverlayError GetOverlayMouseScale( VROverlayHandle_t ulOverlayHandle, HmdVector2_t *pvecMouseScale ) = 0; + + /** Sets the mouse scaling factor that is used for mouse events. The actual texture may be a different size, but this is + * typically the size of the underlying UI in pixels (not in world space). */ + virtual EVROverlayError SetOverlayMouseScale( VROverlayHandle_t ulOverlayHandle, const HmdVector2_t *pvecMouseScale ) = 0; + + /** Computes the overlay-space pixel coordinates of where the ray intersects the overlay with the + * specified settings. Returns false if there is no intersection. */ + virtual bool ComputeOverlayIntersection( VROverlayHandle_t ulOverlayHandle, const VROverlayIntersectionParams_t *pParams, VROverlayIntersectionResults_t *pResults ) = 0; + + /** Returns true if the specified overlay is the hover target. An overlay is the hover target when it is the last overlay "moused over" + * by the virtual mouse pointer */ + virtual bool IsHoverTargetOverlay( VROverlayHandle_t ulOverlayHandle ) = 0; + + /** Sets a list of primitives to be used for controller ray intersection + * typically the size of the underlying UI in pixels (not in world space). */ + virtual EVROverlayError SetOverlayIntersectionMask( VROverlayHandle_t ulOverlayHandle, VROverlayIntersectionMaskPrimitive_t *pMaskPrimitives, uint32_t unNumMaskPrimitives, uint32_t unPrimitiveSize = sizeof( VROverlayIntersectionMaskPrimitive_t ) ) = 0; + + /** Triggers a haptic event on the laser mouse controller for the specified overlay */ + virtual EVROverlayError TriggerLaserMouseHapticVibration( VROverlayHandle_t ulOverlayHandle, float fDurationSeconds, float fFrequency, float fAmplitude ) = 0; + + /** Sets the cursor to use for the specified overlay. This will be drawn instead of the generic blob when the laser mouse is pointed at the specified overlay */ + virtual EVROverlayError SetOverlayCursor( VROverlayHandle_t ulOverlayHandle, VROverlayHandle_t ulCursorHandle ) = 0; + + /** Sets the override cursor position to use for this overlay in overlay mouse coordinates. This position will be used to draw the cursor + * instead of whatever the laser mouse cursor position is. */ + virtual EVROverlayError SetOverlayCursorPositionOverride( VROverlayHandle_t ulOverlayHandle, const HmdVector2_t *pvCursor ) = 0; + + /** Clears the override cursor position for this overlay */ + virtual EVROverlayError ClearOverlayCursorPositionOverride( VROverlayHandle_t ulOverlayHandle ) = 0; + + // --------------------------------------------- + // Overlay texture methods + // --------------------------------------------- + + /** Texture to draw for the overlay. This function can only be called by the overlay's creator or renderer process (see SetOverlayRenderingPid) . + * + * OpenGL dirty state: + * glBindTexture + */ + virtual EVROverlayError SetOverlayTexture( VROverlayHandle_t ulOverlayHandle, const Texture_t *pTexture ) = 0; + + /** Use this to tell the overlay system to release the texture set for this overlay. */ + virtual EVROverlayError ClearOverlayTexture( VROverlayHandle_t ulOverlayHandle ) = 0; + + /** Separate interface for providing the data as a stream of bytes, but there is an upper bound on data + * that can be sent. This function can only be called by the overlay's renderer process. */ + virtual EVROverlayError SetOverlayRaw( VROverlayHandle_t ulOverlayHandle, void *pvBuffer, uint32_t unWidth, uint32_t unHeight, uint32_t unBytesPerPixel ) = 0; + + /** Separate interface for providing the image through a filename: can be png or jpg, and should not be bigger than 1920x1080. + * This function can only be called by the overlay's renderer process */ + virtual EVROverlayError SetOverlayFromFile( VROverlayHandle_t ulOverlayHandle, const char *pchFilePath ) = 0; + + /** Get the native texture handle/device for an overlay you have created. + * On windows this handle will be a ID3D11ShaderResourceView with a ID3D11Texture2D bound. + * + * The texture will always be sized to match the backing texture you supplied in SetOverlayTexture above. + * + * You MUST call ReleaseNativeOverlayHandle() with pNativeTextureHandle once you are done with this texture. + * + * pNativeTextureHandle is an OUTPUT, it will be a pointer to a ID3D11ShaderResourceView *. + * pNativeTextureRef is an INPUT and should be a ID3D11Resource *. The device used by pNativeTextureRef will be used to bind pNativeTextureHandle. + */ + virtual EVROverlayError GetOverlayTexture( VROverlayHandle_t ulOverlayHandle, void **pNativeTextureHandle, void *pNativeTextureRef, uint32_t *pWidth, uint32_t *pHeight, uint32_t *pNativeFormat, ETextureType *pAPIType, EColorSpace *pColorSpace, VRTextureBounds_t *pTextureBounds ) = 0; + + /** Release the pNativeTextureHandle provided from the GetOverlayTexture call, this allows the system to free the underlying GPU resources for this object, + * so only do it once you stop rendering this texture. + */ + virtual EVROverlayError ReleaseNativeOverlayHandle( VROverlayHandle_t ulOverlayHandle, void *pNativeTextureHandle ) = 0; + + /** Get the size of the overlay texture */ + virtual EVROverlayError GetOverlayTextureSize( VROverlayHandle_t ulOverlayHandle, uint32_t *pWidth, uint32_t *pHeight ) = 0; + + // ---------------------------------------------- + // Dashboard Overlay Methods + // ---------------------------------------------- + + /** Creates a dashboard overlay and returns its handle */ + virtual EVROverlayError CreateDashboardOverlay( const char *pchOverlayKey, const char *pchOverlayFriendlyName, VROverlayHandle_t * pMainHandle, VROverlayHandle_t *pThumbnailHandle ) = 0; + + /** Returns true if the dashboard is visible */ + virtual bool IsDashboardVisible() = 0; + + /** returns true if the dashboard is visible and the specified overlay is the active system Overlay */ + virtual bool IsActiveDashboardOverlay( VROverlayHandle_t ulOverlayHandle ) = 0; + + /** Sets the dashboard overlay to only appear when the specified process ID has scene focus */ + virtual EVROverlayError SetDashboardOverlaySceneProcess( VROverlayHandle_t ulOverlayHandle, uint32_t unProcessId ) = 0; + + /** Gets the process ID that this dashboard overlay requires to have scene focus */ + virtual EVROverlayError GetDashboardOverlaySceneProcess( VROverlayHandle_t ulOverlayHandle, uint32_t *punProcessId ) = 0; + + /** Shows the dashboard. */ + virtual void ShowDashboard( const char *pchOverlayToShow ) = 0; + + /** Returns the tracked device that has the laser pointer in the dashboard */ + virtual vr::TrackedDeviceIndex_t GetPrimaryDashboardDevice() = 0; + + // --------------------------------------------- + // Keyboard methods + // --------------------------------------------- + + /** Show the virtual keyboard to accept input. In most cases, you should pass KeyboardFlag_Modal to enable modal overlay + * behavior on the keyboard itself. See EKeyboardFlags for more. */ + virtual EVROverlayError ShowKeyboard( EGamepadTextInputMode eInputMode, EGamepadTextInputLineMode eLineInputMode, uint32_t unFlags, + const char *pchDescription, uint32_t unCharMax, const char *pchExistingText, uint64_t uUserValue ) = 0; + + /** Show the virtual keyboard to accept input for an overlay. In most cases, you should pass KeyboardFlag_Modal to enable modal + * overlay behavior on the keyboard itself. See EKeyboardFlags for more. */ + virtual EVROverlayError ShowKeyboardForOverlay( VROverlayHandle_t ulOverlayHandle, EGamepadTextInputMode eInputMode, + EGamepadTextInputLineMode eLineInputMode, uint32_t unFlags, const char *pchDescription, uint32_t unCharMax, + const char *pchExistingText, uint64_t uUserValue ) = 0; + + /** Get the text that was entered into the text input **/ + virtual uint32_t GetKeyboardText( VR_OUT_STRING() char *pchText, uint32_t cchText ) = 0; + + /** Hide the virtual keyboard **/ + virtual void HideKeyboard() = 0; + + /** Set the position of the keyboard in world space **/ + virtual void SetKeyboardTransformAbsolute( ETrackingUniverseOrigin eTrackingOrigin, const HmdMatrix34_t *pmatTrackingOriginToKeyboardTransform ) = 0; + + /** Set the position of the keyboard in overlay space by telling it to avoid a rectangle in the overlay. Rectangle coords have (0,0) in the bottom left **/ + virtual void SetKeyboardPositionForOverlay( VROverlayHandle_t ulOverlayHandle, HmdRect2_t avoidRect ) = 0; + + // --------------------------------------------- + // Message box methods + // --------------------------------------------- + + /** Show the message overlay. This will block and return you a result. **/ + virtual VRMessageOverlayResponse ShowMessageOverlay( const char* pchText, const char* pchCaption, const char* pchButton0Text, const char* pchButton1Text = nullptr, const char* pchButton2Text = nullptr, const char* pchButton3Text = nullptr ) = 0; + + /** If the calling process owns the overlay and it's open, this will close it. **/ + virtual void CloseMessageOverlay() = 0; + }; + + static const char * const IVROverlay_Version = "IVROverlay_024"; + +} // namespace vr + +// ivroverlayview.h +namespace vr +{ + struct VROverlayView_t + { + VROverlayHandle_t overlayHandle; + Texture_t texture; + VRTextureBounds_t textureBounds; + }; + + enum EDeviceType + { + DeviceType_Invalid = -1, // Invalid handle + DeviceType_DirectX11 = 0, // Handle is an ID3D11Device + DeviceType_Vulkan = 1, // Handle is a pointer to a VRVulkanDevice_t structure + }; + + struct VRVulkanDevice_t + { + VkInstance_T *m_pInstance; + VkDevice_T *m_pDevice; + VkPhysicalDevice_T *m_pPhysicalDevice; + VkQueue_T *m_pQueue; + uint32_t m_uQueueFamilyIndex; + }; + + struct VRNativeDevice_t + { + void *handle; // See EDeviceType definition above + EDeviceType eType; + }; + + class IVROverlayView + { + public: + /** Acquire an OverlayView_t from an overlay handle + * + * The overlay view can be used to sample the contents directly by a native API. The + * contents of the OverlayView_t will remain unchanged through the lifetime of the + * OverlayView_t. + * + * The caller acquires read privileges over the OverlayView_t, but should not + * write to it. + * + * AcquireOverlayView() may be called on the same ulOverlayHandle multiple times to + * refresh the image contents. In this case the caller is strongly encouraged to re-use + * the same pOverlayView for all re-acquisition calls. + * + * If the producer has not yet queued an image, AcquireOverlayView will return success, + * and the Texture_t will have the expected ETextureType. However, the Texture_t->handle + * will be nullptr. Once the producer generates the first overlay frame, Texture_t->handle + * will become a valid handle. + */ + virtual EVROverlayError AcquireOverlayView(VROverlayHandle_t ulOverlayHandle, VRNativeDevice_t *pNativeDevice, VROverlayView_t *pOverlayView, uint32_t unOverlayViewSize ) = 0; + + /** Release an acquired OverlayView_t + * + * Denotes that pOverlayView will no longer require access to the resources it acquired in + * all previous calls to AcquireOverlayView(). + * + * All OverlayView_t*'s provided to AcquireOverlayView() as pOverlayViews must be + * passed into ReleaseOverlayView() in order for the underlying GPU resources to be freed. + */ + virtual EVROverlayError ReleaseOverlayView(VROverlayView_t *pOverlayView) = 0; + + /** Posts an overlay event */ + virtual void PostOverlayEvent(VROverlayHandle_t ulOverlayHandle, const VREvent_t *pvrEvent) = 0; + + /** Determines whether this process is permitted to view an overlay's content. */ + virtual bool IsViewingPermitted( VROverlayHandle_t ulOverlayHandle ) = 0; + + }; + + static const char * const IVROverlayView_Version = "IVROverlayView_003"; + +} + +// ivrrendermodels.h +namespace vr +{ + +static const char * const k_pch_Controller_Component_GDC2015 = "gdc2015"; // Canonical coordinate system of the gdc 2015 wired controller, provided for backwards compatibility +static const char * const k_pch_Controller_Component_Base = "base"; // For controllers with an unambiguous 'base'. +static const char * const k_pch_Controller_Component_Tip = "tip"; // For controllers with an unambiguous 'tip' (used for 'laser-pointing') +static const char * const k_pch_Controller_Component_HandGrip = "handgrip"; // Neutral, ambidextrous hand-pose when holding controller. On plane between neutrally posed index finger and thumb +static const char * const k_pch_Controller_Component_Status = "status"; // 1:1 aspect ratio status area, with canonical [0,1] uv mapping + +#pragma pack( push, 8 ) + +/** Errors that can occur with the VR compositor */ +enum EVRRenderModelError +{ + VRRenderModelError_None = 0, + VRRenderModelError_Loading = 100, + VRRenderModelError_NotSupported = 200, + VRRenderModelError_InvalidArg = 300, + VRRenderModelError_InvalidModel = 301, + VRRenderModelError_NoShapes = 302, + VRRenderModelError_MultipleShapes = 303, + VRRenderModelError_TooManyVertices = 304, + VRRenderModelError_MultipleTextures = 305, + VRRenderModelError_BufferTooSmall = 306, + VRRenderModelError_NotEnoughNormals = 307, + VRRenderModelError_NotEnoughTexCoords = 308, + + VRRenderModelError_InvalidTexture = 400, +}; + +enum EVRRenderModelTextureFormat +{ + VRRenderModelTextureFormat_RGBA8_SRGB = 0, // RGBA with 8 bits per channel per pixel. Data size is width * height * 4ub + VRRenderModelTextureFormat_BC2, + VRRenderModelTextureFormat_BC4, + VRRenderModelTextureFormat_BC7, + VRRenderModelTextureFormat_BC7_SRGB +}; + +/** A single vertex in a render model */ +struct RenderModel_Vertex_t +{ + HmdVector3_t vPosition; // position in meters in device space + HmdVector3_t vNormal; + float rfTextureCoord[2]; +}; + +/** A texture map for use on a render model */ +#if defined(__linux__) || defined(__APPLE__) +// This structure was originally defined mis-packed on Linux, preserved for +// compatibility. +#pragma pack( push, 4 ) +#endif + +struct RenderModel_TextureMap_t +{ + uint16_t unWidth, unHeight; // width and height of the texture map in pixels + const uint8_t *rubTextureMapData; // Map texture data. + EVRRenderModelTextureFormat format; // Refer to EVRRenderModelTextureFormat +}; +#if defined(__linux__) || defined(__APPLE__) +#pragma pack( pop ) +#endif + +/** Session unique texture identifier. Rendermodels which share the same texture will have the same id. +IDs <0 denote the texture is not present */ + +typedef int32_t TextureID_t; + +const TextureID_t INVALID_TEXTURE_ID = -1; + +#if defined(__linux__) || defined(__APPLE__) +// This structure was originally defined mis-packed on Linux, preserved for +// compatibility. +#pragma pack( push, 4 ) +#endif + +struct RenderModel_t +{ + const RenderModel_Vertex_t *rVertexData; // Vertex data for the mesh + uint32_t unVertexCount; // Number of vertices in the vertex data + const uint16_t *rIndexData; // Indices into the vertex data for each triangle + uint32_t unTriangleCount; // Number of triangles in the mesh. Index count is 3 * TriangleCount + TextureID_t diffuseTextureId; // Session unique texture identifier. Rendermodels which share the same texture will have the same id. <0 == texture not present +}; +#if defined(__linux__) || defined(__APPLE__) +#pragma pack( pop ) +#endif + + +struct RenderModel_ControllerMode_State_t +{ + bool bScrollWheelVisible; // is this controller currently set to be in a scroll wheel mode +}; + +#pragma pack( pop ) + +class IVRRenderModels +{ +public: + + /** Loads and returns a render model for use in the application. pchRenderModelName should be a render model name + * from the Prop_RenderModelName_String property or an absolute path name to a render model on disk. + * + * The resulting render model is valid until VR_Shutdown() is called or until FreeRenderModel() is called. When the + * application is finished with the render model it should call FreeRenderModel() to free the memory associated + * with the model. + * + * The method returns VRRenderModelError_Loading while the render model is still being loaded. + * The method returns VRRenderModelError_None once loaded successfully, otherwise will return an error. */ + virtual EVRRenderModelError LoadRenderModel_Async( const char *pchRenderModelName, RenderModel_t **ppRenderModel ) = 0; + + /** Frees a previously returned render model + * It is safe to call this on a null ptr. */ + virtual void FreeRenderModel( RenderModel_t *pRenderModel ) = 0; + + /** Loads and returns a texture for use in the application. */ + virtual EVRRenderModelError LoadTexture_Async( TextureID_t textureId, RenderModel_TextureMap_t **ppTexture ) = 0; + + /** Frees a previously returned texture + * It is safe to call this on a null ptr. */ + virtual void FreeTexture( RenderModel_TextureMap_t *pTexture ) = 0; + + /** Creates a D3D11 texture and loads data into it. */ + virtual EVRRenderModelError LoadTextureD3D11_Async( TextureID_t textureId, void *pD3D11Device, void **ppD3D11Texture2D ) = 0; + + /** Helper function to copy the bits into an existing texture. */ + virtual EVRRenderModelError LoadIntoTextureD3D11_Async( TextureID_t textureId, void *pDstTexture ) = 0; + + /** Use this to free textures created with LoadTextureD3D11_Async instead of calling Release on them. */ + virtual void FreeTextureD3D11( void *pD3D11Texture2D ) = 0; + + /** Use this to get the names of available render models. Index does not correlate to a tracked device index, but + * is only used for iterating over all available render models. If the index is out of range, this function will return 0. + * Otherwise, it will return the size of the buffer required for the name. */ + virtual uint32_t GetRenderModelName( uint32_t unRenderModelIndex, VR_OUT_STRING() char *pchRenderModelName, uint32_t unRenderModelNameLen ) = 0; + + /** Returns the number of available render models. */ + virtual uint32_t GetRenderModelCount() = 0; + + + /** Returns the number of components of the specified render model. + * Components are useful when client application wish to draw, label, or otherwise interact with components of tracked objects. + * Examples controller components: + * renderable things such as triggers, buttons + * non-renderable things which include coordinate systems such as 'tip', 'base', a neutral controller agnostic hand-pose + * If all controller components are enumerated and rendered, it will be equivalent to drawing the traditional render model + * Returns 0 if components not supported, >0 otherwise */ + virtual uint32_t GetComponentCount( const char *pchRenderModelName ) = 0; + + /** Use this to get the names of available components. Index does not correlate to a tracked device index, but + * is only used for iterating over all available components. If the index is out of range, this function will return 0. + * Otherwise, it will return the size of the buffer required for the name. */ + virtual uint32_t GetComponentName( const char *pchRenderModelName, uint32_t unComponentIndex, VR_OUT_STRING( ) char *pchComponentName, uint32_t unComponentNameLen ) = 0; + + /** Get the button mask for all buttons associated with this component + * If no buttons (or axes) are associated with this component, return 0 + * Note: multiple components may be associated with the same button. Ex: two grip buttons on a single controller. + * Note: A single component may be associated with multiple buttons. Ex: A trackpad which also provides "D-pad" functionality */ + virtual uint64_t GetComponentButtonMask( const char *pchRenderModelName, const char *pchComponentName ) = 0; + + /** Use this to get the render model name for the specified rendermode/component combination, to be passed to LoadRenderModel. + * If the component name is out of range, this function will return 0. + * Otherwise, it will return the size of the buffer required for the name. */ + virtual uint32_t GetComponentRenderModelName( const char *pchRenderModelName, const char *pchComponentName, VR_OUT_STRING( ) char *pchComponentRenderModelName, uint32_t unComponentRenderModelNameLen ) = 0; + + /** Use this to query information about the component, as a function of the controller state. + * + * For dynamic controller components (ex: trigger) values will reflect component motions + * For static components this will return a consistent value independent of the VRControllerState_t + * + * If the pchRenderModelName or pchComponentName is invalid, this will return false (and transforms will be set to identity). + * Otherwise, return true + * Note: For dynamic objects, visibility may be dynamic. (I.e., true/false will be returned based on controller state and controller mode state ) */ + virtual bool GetComponentStateForDevicePath( const char *pchRenderModelName, const char *pchComponentName, vr::VRInputValueHandle_t devicePath, const vr::RenderModel_ControllerMode_State_t *pState, vr::RenderModel_ComponentState_t *pComponentState ) = 0; + + /** This version of GetComponentState takes a controller state block instead of an action origin. This function is deprecated. You should use the new input system and GetComponentStateForDevicePath instead. */ + virtual bool GetComponentState( const char *pchRenderModelName, const char *pchComponentName, const vr::VRControllerState_t *pControllerState, const RenderModel_ControllerMode_State_t *pState, RenderModel_ComponentState_t *pComponentState ) = 0; + + /** Returns true if the render model has a component with the specified name */ + virtual bool RenderModelHasComponent( const char *pchRenderModelName, const char *pchComponentName ) = 0; + + /** Returns the URL of the thumbnail image for this rendermodel */ + virtual uint32_t GetRenderModelThumbnailURL( const char *pchRenderModelName, VR_OUT_STRING() char *pchThumbnailURL, uint32_t unThumbnailURLLen, vr::EVRRenderModelError *peError ) = 0; + + /** Provides a render model path that will load the unskinned model if the model name provided has been replace by the user. If the model + * hasn't been replaced the path value will still be a valid path to load the model. Pass this to LoadRenderModel_Async, etc. to load the + * model. */ + virtual uint32_t GetRenderModelOriginalPath( const char *pchRenderModelName, VR_OUT_STRING() char *pchOriginalPath, uint32_t unOriginalPathLen, vr::EVRRenderModelError *peError ) = 0; + + /** Returns a string for a render model error */ + virtual const char *GetRenderModelErrorNameFromEnum( vr::EVRRenderModelError error ) = 0; +}; + +static const char * const IVRRenderModels_Version = "IVRRenderModels_006"; + +} + + +// ivrextendeddisplay.h +namespace vr +{ + + /** NOTE: Use of this interface is not recommended in production applications. It will not work for displays which use + * direct-to-display mode. Creating our own window is also incompatible with the VR compositor and is not available when the compositor is running. */ + class IVRExtendedDisplay + { + public: + + /** Size and position that the window needs to be on the VR display. */ + virtual void GetWindowBounds( int32_t *pnX, int32_t *pnY, uint32_t *pnWidth, uint32_t *pnHeight ) = 0; + + /** Gets the viewport in the frame buffer to draw the output of the distortion into */ + virtual void GetEyeOutputViewport( EVREye eEye, uint32_t *pnX, uint32_t *pnY, uint32_t *pnWidth, uint32_t *pnHeight ) = 0; + + /** [D3D10/11 Only] + * Returns the adapter index and output index that the user should pass into EnumAdapters and EnumOutputs + * to create the device and swap chain in DX10 and DX11. If an error occurs both indices will be set to -1. + */ + virtual void GetDXGIOutputInfo( int32_t *pnAdapterIndex, int32_t *pnAdapterOutputIndex ) = 0; + + }; + + static const char * const IVRExtendedDisplay_Version = "IVRExtendedDisplay_001"; + +} + + +// ivrtrackedcamera.h +namespace vr +{ + +class IVRTrackedCamera +{ +public: + /** Returns a string for an error */ + virtual const char *GetCameraErrorNameFromEnum( vr::EVRTrackedCameraError eCameraError ) = 0; + + /** For convenience, same as tracked property request Prop_HasCamera_Bool */ + virtual vr::EVRTrackedCameraError HasCamera( vr::TrackedDeviceIndex_t nDeviceIndex, bool *pHasCamera ) = 0; + + /** Gets size of the image frame. */ + virtual vr::EVRTrackedCameraError GetCameraFrameSize( vr::TrackedDeviceIndex_t nDeviceIndex, vr::EVRTrackedCameraFrameType eFrameType, uint32_t *pnWidth, uint32_t *pnHeight, uint32_t *pnFrameBufferSize ) = 0; + + virtual vr::EVRTrackedCameraError GetCameraIntrinsics( vr::TrackedDeviceIndex_t nDeviceIndex, uint32_t nCameraIndex, vr::EVRTrackedCameraFrameType eFrameType, vr::HmdVector2_t *pFocalLength, vr::HmdVector2_t *pCenter ) = 0; + + virtual vr::EVRTrackedCameraError GetCameraProjection( vr::TrackedDeviceIndex_t nDeviceIndex, uint32_t nCameraIndex, vr::EVRTrackedCameraFrameType eFrameType, float flZNear, float flZFar, vr::HmdMatrix44_t *pProjection ) = 0; + + /** Acquiring streaming service permits video streaming for the caller. Releasing hints the system that video services do not need to be maintained for this client. + * If the camera has not already been activated, a one time spin up may incur some auto exposure as well as initial streaming frame delays. + * The camera should be considered a global resource accessible for shared consumption but not exclusive to any caller. + * The camera may go inactive due to lack of active consumers or headset idleness. */ + virtual vr::EVRTrackedCameraError AcquireVideoStreamingService( vr::TrackedDeviceIndex_t nDeviceIndex, vr::TrackedCameraHandle_t *pHandle ) = 0; + virtual vr::EVRTrackedCameraError ReleaseVideoStreamingService( vr::TrackedCameraHandle_t hTrackedCamera ) = 0; + + /** Copies the image frame into a caller's provided buffer. The image data is currently provided as RGBA data, 4 bytes per pixel. + * A caller can provide null for the framebuffer or frameheader if not desired. Requesting the frame header first, followed by the frame buffer allows + * the caller to determine if the frame as advanced per the frame header sequence. + * If there is no frame available yet, due to initial camera spinup or re-activation, the error will be VRTrackedCameraError_NoFrameAvailable. + * Ideally a caller should be polling at ~16ms intervals */ + virtual vr::EVRTrackedCameraError GetVideoStreamFrameBuffer( vr::TrackedCameraHandle_t hTrackedCamera, vr::EVRTrackedCameraFrameType eFrameType, void *pFrameBuffer, uint32_t nFrameBufferSize, vr::CameraVideoStreamFrameHeader_t *pFrameHeader, uint32_t nFrameHeaderSize ) = 0; + + /** Gets size of the image frame. */ + virtual vr::EVRTrackedCameraError GetVideoStreamTextureSize( vr::TrackedDeviceIndex_t nDeviceIndex, vr::EVRTrackedCameraFrameType eFrameType, vr::VRTextureBounds_t *pTextureBounds, uint32_t *pnWidth, uint32_t *pnHeight ) = 0; + + /** Access a shared D3D11 texture for the specified tracked camera stream. + * The camera frame type VRTrackedCameraFrameType_Undistorted is not supported directly as a shared texture. It is an interior subregion of the shared texture VRTrackedCameraFrameType_MaximumUndistorted. + * Instead, use GetVideoStreamTextureSize() with VRTrackedCameraFrameType_Undistorted to determine the proper interior subregion bounds along with GetVideoStreamTextureD3D11() with + * VRTrackedCameraFrameType_MaximumUndistorted to provide the texture. The VRTrackedCameraFrameType_MaximumUndistorted will yield an image where the invalid regions are decoded + * by the alpha channel having a zero component. The valid regions all have a non-zero alpha component. The subregion as described by VRTrackedCameraFrameType_Undistorted + * guarantees a rectangle where all pixels are valid. */ + virtual vr::EVRTrackedCameraError GetVideoStreamTextureD3D11( vr::TrackedCameraHandle_t hTrackedCamera, vr::EVRTrackedCameraFrameType eFrameType, void *pD3D11DeviceOrResource, void **ppD3D11ShaderResourceView, vr::CameraVideoStreamFrameHeader_t *pFrameHeader, uint32_t nFrameHeaderSize ) = 0; + + /** Access a shared GL texture for the specified tracked camera stream */ + virtual vr::EVRTrackedCameraError GetVideoStreamTextureGL( vr::TrackedCameraHandle_t hTrackedCamera, vr::EVRTrackedCameraFrameType eFrameType, vr::glUInt_t *pglTextureId, vr::CameraVideoStreamFrameHeader_t *pFrameHeader, uint32_t nFrameHeaderSize ) = 0; + virtual vr::EVRTrackedCameraError ReleaseVideoStreamTextureGL( vr::TrackedCameraHandle_t hTrackedCamera, vr::glUInt_t glTextureId ) = 0; + virtual void SetCameraTrackingSpace( vr::ETrackingUniverseOrigin eUniverse ) = 0; + virtual vr::ETrackingUniverseOrigin GetCameraTrackingSpace( ) = 0; +}; + +static const char * const IVRTrackedCamera_Version = "IVRTrackedCamera_006"; + +} // namespace vr + + +// ivrscreenshots.h +namespace vr +{ + +/** Errors that can occur with the VR compositor */ +enum EVRScreenshotError +{ + VRScreenshotError_None = 0, + VRScreenshotError_RequestFailed = 1, + VRScreenshotError_IncompatibleVersion = 100, + VRScreenshotError_NotFound = 101, + VRScreenshotError_BufferTooSmall = 102, + VRScreenshotError_ScreenshotAlreadyInProgress = 108, +}; + +/** Allows the application to generate screenshots */ +class IVRScreenshots +{ +public: + /** Request a screenshot of the requested type. + * A request of the VRScreenshotType_Stereo type will always + * work. Other types will depend on the underlying application + * support. + * The first file name is for the preview image and should be a + * regular screenshot (ideally from the left eye). The second + * is the VR screenshot in the correct format. They should be + * in the same aspect ratio. Formats per type: + * VRScreenshotType_Mono: the VR filename is ignored (can be + * nullptr), this is a normal flat single shot. + * VRScreenshotType_Stereo: The VR image should be a + * side-by-side with the left eye image on the left. + * VRScreenshotType_Cubemap: The VR image should be six square + * images composited horizontally. + * VRScreenshotType_StereoPanorama: above/below with left eye + * panorama being the above image. Image is typically square + * with the panorama being 2x horizontal. + * + * Note that the VR dashboard will call this function when + * the user presses the screenshot binding (currently System + * Button + Trigger). If Steam is running, the destination + * file names will be in %TEMP% and will be copied into + * Steam's screenshot library for the running application + * once SubmitScreenshot() is called. + * If Steam is not running, the paths will be in the user's + * documents folder under Documents\SteamVR\Screenshots. + * Other VR applications can call this to initiate a + * screenshot outside of user control. + * The destination file names do not need an extension, + * will be replaced with the correct one for the format + * which is currently .png. */ + virtual vr::EVRScreenshotError RequestScreenshot( vr::ScreenshotHandle_t *pOutScreenshotHandle, vr::EVRScreenshotType type, const char *pchPreviewFilename, const char *pchVRFilename ) = 0; + + /** Called by the running VR application to indicate that it + * wishes to be in charge of screenshots. If the + * application does not call this, the Compositor will only + * support VRScreenshotType_Stereo screenshots that will be + * captured without notification to the running app. + * Once hooked your application will receive a + * VREvent_RequestScreenshot event when the user presses the + * buttons to take a screenshot. */ + virtual vr::EVRScreenshotError HookScreenshot( VR_ARRAY_COUNT( numTypes ) const vr::EVRScreenshotType *pSupportedTypes, int numTypes ) = 0; + + /** When your application receives a + * VREvent_RequestScreenshot event, call these functions to get + * the details of the screenshot request. */ + virtual vr::EVRScreenshotType GetScreenshotPropertyType( vr::ScreenshotHandle_t screenshotHandle, vr::EVRScreenshotError *pError ) = 0; + + /** Get the filename for the preview or vr image (see + * vr::EScreenshotPropertyFilenames). The return value is + * the size of the string. */ + virtual uint32_t GetScreenshotPropertyFilename( vr::ScreenshotHandle_t screenshotHandle, vr::EVRScreenshotPropertyFilenames filenameType, VR_OUT_STRING() char *pchFilename, uint32_t cchFilename, vr::EVRScreenshotError *pError ) = 0; + + /** Call this if the application is taking the screen shot + * will take more than a few ms processing. This will result + * in an overlay being presented that shows a completion + * bar. */ + virtual vr::EVRScreenshotError UpdateScreenshotProgress( vr::ScreenshotHandle_t screenshotHandle, float flProgress ) = 0; + + /** Tells the compositor to take an internal screenshot of + * type VRScreenshotType_Stereo. It will take the current + * submitted scene textures of the running application and + * write them into the preview image and a side-by-side file + * for the VR image. + * This is similar to request screenshot, but doesn't ever + * talk to the application, just takes the shot and submits. */ + virtual vr::EVRScreenshotError TakeStereoScreenshot( vr::ScreenshotHandle_t *pOutScreenshotHandle, const char *pchPreviewFilename, const char *pchVRFilename ) = 0; + + /** Submit the completed screenshot. If Steam is running + * this will call into the Steam client and upload the + * screenshot to the screenshots section of the library for + * the running application. If Steam is not running, this + * function will display a notification to the user that the + * screenshot was taken. The paths should be full paths with + * extensions. + * File paths should be absolute including extensions. + * screenshotHandle can be k_unScreenshotHandleInvalid if this + * was a new shot taking by the app to be saved and not + * initiated by a user (achievement earned or something) */ + virtual vr::EVRScreenshotError SubmitScreenshot( vr::ScreenshotHandle_t screenshotHandle, vr::EVRScreenshotType type, const char *pchSourcePreviewFilename, const char *pchSourceVRFilename ) = 0; +}; + +static const char * const IVRScreenshots_Version = "IVRScreenshots_001"; + +} // namespace vr + + + +// ivrresources.h +namespace vr +{ + +class IVRResources +{ +public: + + // ------------------------------------ + // Shared Resource Methods + // ------------------------------------ + + /** Loads the specified resource into the provided buffer if large enough. + * Returns the size in bytes of the buffer required to hold the specified resource. */ + virtual uint32_t LoadSharedResource( const char *pchResourceName, char *pchBuffer, uint32_t unBufferLen ) = 0; + + /** Provides the full path to the specified resource. Resource names can include named directories for + * drivers and other things, and this resolves all of those and returns the actual physical path. + * pchResourceTypeDirectory is the subdirectory of resources to look in. */ + virtual uint32_t GetResourceFullPath( const char *pchResourceName, const char *pchResourceTypeDirectory, VR_OUT_STRING() char *pchPathBuffer, uint32_t unBufferLen ) = 0; +}; + +static const char * const IVRResources_Version = "IVRResources_001"; + + +} +// ivrdrivermanager.h +namespace vr +{ + +class IVRDriverManager +{ +public: + virtual uint32_t GetDriverCount() const = 0; + + /** Returns the length of the number of bytes necessary to hold this string including the trailing null. */ + virtual uint32_t GetDriverName( vr::DriverId_t nDriver, VR_OUT_STRING() char *pchValue, uint32_t unBufferSize ) = 0; + + virtual DriverHandle_t GetDriverHandle( const char *pchDriverName ) = 0; + + virtual bool IsEnabled( vr::DriverId_t nDriver ) const = 0; +}; + +static const char * const IVRDriverManager_Version = "IVRDriverManager_001"; + +} // namespace vr + + + +// ivrinput.h +namespace vr +{ + // Maximum number of characters in an action name, including the trailing null + static const uint32_t k_unMaxActionNameLength = 64; + + // Maximum number of characters in an action set name, including the trailing null + static const uint32_t k_unMaxActionSetNameLength = 64; + + // Maximum number of origins for an action + static const uint32_t k_unMaxActionOriginCount = 16; + + // Maximum number of characters in a bone name, including the trailing null + static const uint32_t k_unMaxBoneNameLength = 32; + + enum EVRSkeletalTransformSpace + { + VRSkeletalTransformSpace_Model = 0, + VRSkeletalTransformSpace_Parent = 1 + }; + + enum EVRSkeletalReferencePose + { + VRSkeletalReferencePose_BindPose = 0, + VRSkeletalReferencePose_OpenHand, + VRSkeletalReferencePose_Fist, + VRSkeletalReferencePose_GripLimit + }; + + enum EVRFinger + { + VRFinger_Thumb = 0, + VRFinger_Index, + VRFinger_Middle, + VRFinger_Ring, + VRFinger_Pinky, + VRFinger_Count + }; + + enum EVRFingerSplay + { + VRFingerSplay_Thumb_Index = 0, + VRFingerSplay_Index_Middle, + VRFingerSplay_Middle_Ring, + VRFingerSplay_Ring_Pinky, + VRFingerSplay_Count + }; + + enum EVRSummaryType + { + // The skeletal summary data will match the animated bone transforms for the action. + VRSummaryType_FromAnimation = 0, + + // The skeletal summary data will include unprocessed data directly from the device when available. + // This data is generally less latent than the data that is computed from the animations. + VRSummaryType_FromDevice = 1, + }; + + enum EVRInputFilterCancelType + { + VRInputFilterCancel_Timers = 0, + VRInputFilterCancel_Momentum = 1, + }; + + enum EVRInputStringBits + { + VRInputString_Hand = 0x01, + VRInputString_ControllerType = 0x02, + VRInputString_InputSource = 0x04, + + VRInputString_All = 0xFFFFFFFF + }; + + struct InputAnalogActionData_t + { + /** Whether or not this action is currently available to be bound in the active action set */ + bool bActive; + + /** The origin that caused this action's current state */ + VRInputValueHandle_t activeOrigin; + + /** The current state of this action; will be delta updates for mouse actions */ + float x, y, z; + + /** Deltas since the previous call to UpdateActionState() */ + float deltaX, deltaY, deltaZ; + + /** Time relative to now when this event happened. Will be negative to indicate a past time. */ + float fUpdateTime; + }; + + struct InputDigitalActionData_t + { + /** Whether or not this action is currently available to be bound in the active action set */ + bool bActive; + + /** The origin that caused this action's current state */ + VRInputValueHandle_t activeOrigin; + + /** The current state of this action; will be true if currently pressed */ + bool bState; + + /** This is true if the state has changed since the last frame */ + bool bChanged; + + /** Time relative to now when this event happened. Will be negative to indicate a past time. */ + float fUpdateTime; + }; + + struct InputPoseActionData_t + { + /** Whether or not this action is currently available to be bound in the active action set */ + bool bActive; + + /** The origin that caused this action's current state */ + VRInputValueHandle_t activeOrigin; + + /** The current state of this action */ + TrackedDevicePose_t pose; + }; + + struct InputSkeletalActionData_t + { + /** Whether or not this action is currently available to be bound in the active action set */ + bool bActive; + + /** The origin that caused this action's current state */ + VRInputValueHandle_t activeOrigin; + }; + + struct InputOriginInfo_t + { + VRInputValueHandle_t devicePath; + TrackedDeviceIndex_t trackedDeviceIndex; + char rchRenderModelComponentName[128]; + }; + + struct InputBindingInfo_t + { + char rchDevicePathName[128]; + char rchInputPathName[128]; + char rchModeName[128]; + char rchSlotName[128]; + char rchInputSourceType[ 32 ]; + }; + + // * Experimental global action set priority * + // These constants are part of the experimental support in SteamVR for overlay + // apps selectively overriding input in the base scene application. This may be + // useful for overlay applications that need to use part or all of a controller + // without taking away all input to the game. This system must be enabled by the + // "Experimental overlay input overrides" setting in the developer section of + // SteamVR settings. + // + // To use this system, set the nPriority field of an action set to any number in + // this range. + static const int32_t k_nActionSetOverlayGlobalPriorityMin = 0x01000000; + static const int32_t k_nActionSetOverlayGlobalPriorityMax = 0x01FFFFFF; + + static const int32_t k_nActionSetPriorityReservedMin = 0x02000000; + + struct VRActiveActionSet_t + { + /** This is the handle of the action set to activate for this frame. */ + VRActionSetHandle_t ulActionSet; + + /** This is the handle of a device path that this action set should be active for. To + * activate for all devices, set this to k_ulInvalidInputValueHandle. */ + VRInputValueHandle_t ulRestrictedToDevice; + + /** The action set to activate for all devices other than ulRestrictedDevice. If + * ulRestrictedToDevice is set to k_ulInvalidInputValueHandle, this parameter is + * ignored. */ + VRActionSetHandle_t ulSecondaryActionSet; + + // This field is ignored + uint32_t unPadding; + + /** The priority of this action set relative to other action sets. Any inputs + * bound to a source (e.g. trackpad, joystick, trigger) will disable bindings in + * other active action sets with a smaller priority. + * + * Overlay applications (i.e. ApplicationType_Overlay) may set their action set priority + * to a value between k_nActionSetOverlayGlobalPriorityMin and k_nActionSetOverlayGlobalPriorityMax + * to cause any inputs bound to a source used by that action set to be disabled in scene applications. + * + * No action set priority may value may be larger than k_nActionSetPriorityReservedMin + */ + int32_t nPriority; + }; + + /** Contains summary information about the current skeletal pose */ + struct VRSkeletalSummaryData_t + { + /** The amount that each finger is 'curled' inwards towards the palm. In the case of the thumb, + * this represents how much the thumb is wrapped around the fist. + * 0 means straight, 1 means fully curled */ + float flFingerCurl[ VRFinger_Count ]; + + /** The amount that each pair of adjacent fingers are separated. + * 0 means the digits are touching, 1 means they are fully separated. + */ + float flFingerSplay[ VRFingerSplay_Count ]; + }; + + + class IVRInput + { + public: + + // --------------- Handle management --------------- // + + /** Sets the path to the action manifest JSON file that is used by this application. If this information + * was set on the Steam partner site, calls to this function are ignored. If the Steam partner site + * setting and the path provided by this call are different, VRInputError_MismatchedActionManifest is returned. + * This call must be made before the first call to UpdateActionState or IVRSystem::PollNextEvent. */ + virtual EVRInputError SetActionManifestPath( const char *pchActionManifestPath ) = 0; + + /** Returns a handle for an action set. This handle is used for all performance-sensitive calls. */ + virtual EVRInputError GetActionSetHandle( const char *pchActionSetName, VRActionSetHandle_t *pHandle ) = 0; + + /** Returns a handle for an action. This handle is used for all performance-sensitive calls. */ + virtual EVRInputError GetActionHandle( const char *pchActionName, VRActionHandle_t *pHandle ) = 0; + + /** Returns a handle for any path in the input system. E.g. /user/hand/right */ + virtual EVRInputError GetInputSourceHandle( const char *pchInputSourcePath, VRInputValueHandle_t *pHandle ) = 0; + + + + // --------------- Reading action state ------------------- // + + /** Reads the current state into all actions. After this call, the results of Get*Action calls + * will be the same until the next call to UpdateActionState. */ + virtual EVRInputError UpdateActionState( VR_ARRAY_COUNT( unSetCount ) VRActiveActionSet_t *pSets, uint32_t unSizeOfVRSelectedActionSet_t, uint32_t unSetCount ) = 0; + + /** Reads the state of a digital action given its handle. This will return VRInputError_WrongType if the type of + * action is something other than digital */ + virtual EVRInputError GetDigitalActionData( VRActionHandle_t action, InputDigitalActionData_t *pActionData, uint32_t unActionDataSize, VRInputValueHandle_t ulRestrictToDevice ) = 0; + + /** Reads the state of an analog action given its handle. This will return VRInputError_WrongType if the type of + * action is something other than analog */ + virtual EVRInputError GetAnalogActionData( VRActionHandle_t action, InputAnalogActionData_t *pActionData, uint32_t unActionDataSize, VRInputValueHandle_t ulRestrictToDevice ) = 0; + + /** Reads the state of a pose action given its handle for the number of seconds relative to now. This + * will generally be called with negative times from the fUpdateTime fields in other actions. */ + virtual EVRInputError GetPoseActionDataRelativeToNow( VRActionHandle_t action, ETrackingUniverseOrigin eOrigin, float fPredictedSecondsFromNow, InputPoseActionData_t *pActionData, uint32_t unActionDataSize, VRInputValueHandle_t ulRestrictToDevice ) = 0; + + /** Reads the state of a pose action given its handle. The returned values will match the values returned + * by the last call to IVRCompositor::WaitGetPoses(). */ + virtual EVRInputError GetPoseActionDataForNextFrame( VRActionHandle_t action, ETrackingUniverseOrigin eOrigin, InputPoseActionData_t *pActionData, uint32_t unActionDataSize, VRInputValueHandle_t ulRestrictToDevice ) = 0; + + /** Reads the state of a skeletal action given its handle. */ + virtual EVRInputError GetSkeletalActionData( VRActionHandle_t action, InputSkeletalActionData_t *pActionData, uint32_t unActionDataSize ) = 0; + + /** Returns the current dominant hand for the user for this application. This function will only return success for applications + * which include "supports_dominant_hand_setting": true in their action manifests. The dominant hand will only change after + * a call to UpdateActionState, and the action data returned after that point will use the new dominant hand. */ + virtual EVRInputError GetDominantHand( ETrackedControllerRole *peDominantHand ) = 0; + + /** Sets the dominant hand for the user for this application. */ + virtual EVRInputError SetDominantHand( ETrackedControllerRole eDominantHand ) = 0; + + // --------------- Static Skeletal Data ------------------- // + + /** Reads the number of bones in skeleton associated with the given action */ + virtual EVRInputError GetBoneCount( VRActionHandle_t action, uint32_t* pBoneCount ) = 0; + + /** Fills the given array with the index of each bone's parent in the skeleton associated with the given action */ + virtual EVRInputError GetBoneHierarchy( VRActionHandle_t action, VR_ARRAY_COUNT( unIndexArayCount ) BoneIndex_t* pParentIndices, uint32_t unIndexArayCount ) = 0; + + /** Fills the given buffer with the name of the bone at the given index in the skeleton associated with the given action */ + virtual EVRInputError GetBoneName( VRActionHandle_t action, BoneIndex_t nBoneIndex, VR_OUT_STRING() char* pchBoneName, uint32_t unNameBufferSize ) = 0; + + /** Fills the given buffer with the transforms for a specific static skeletal reference pose */ + virtual EVRInputError GetSkeletalReferenceTransforms( VRActionHandle_t action, EVRSkeletalTransformSpace eTransformSpace, EVRSkeletalReferencePose eReferencePose, VR_ARRAY_COUNT( unTransformArrayCount ) VRBoneTransform_t *pTransformArray, uint32_t unTransformArrayCount ) = 0; + + /** Reads the level of accuracy to which the controller is able to track the user to recreate a skeletal pose */ + virtual EVRInputError GetSkeletalTrackingLevel( VRActionHandle_t action, EVRSkeletalTrackingLevel* pSkeletalTrackingLevel ) = 0; + + // --------------- Dynamic Skeletal Data ------------------- // + + /** Reads the state of the skeletal bone data associated with this action and copies it into the given buffer. */ + virtual EVRInputError GetSkeletalBoneData( VRActionHandle_t action, EVRSkeletalTransformSpace eTransformSpace, EVRSkeletalMotionRange eMotionRange, VR_ARRAY_COUNT( unTransformArrayCount ) VRBoneTransform_t *pTransformArray, uint32_t unTransformArrayCount ) = 0; + + /** Reads summary information about the current pose of the skeleton associated with the given action. */ + virtual EVRInputError GetSkeletalSummaryData( VRActionHandle_t action, EVRSummaryType eSummaryType, VRSkeletalSummaryData_t * pSkeletalSummaryData ) = 0; + + /** Reads the state of the skeletal bone data in a compressed form that is suitable for + * sending over the network. The required buffer size will never exceed ( sizeof(VR_BoneTransform_t)*boneCount + 2). + * Usually the size will be much smaller. */ + virtual EVRInputError GetSkeletalBoneDataCompressed( VRActionHandle_t action, EVRSkeletalMotionRange eMotionRange, VR_OUT_BUFFER_COUNT( unCompressedSize ) void *pvCompressedData, uint32_t unCompressedSize, uint32_t *punRequiredCompressedSize ) = 0; + + /** Turns a compressed buffer from GetSkeletalBoneDataCompressed and turns it back into a bone transform array. */ + virtual EVRInputError DecompressSkeletalBoneData( const void *pvCompressedBuffer, uint32_t unCompressedBufferSize, EVRSkeletalTransformSpace eTransformSpace, VR_ARRAY_COUNT( unTransformArrayCount ) VRBoneTransform_t *pTransformArray, uint32_t unTransformArrayCount ) = 0; + + // --------------- Haptics ------------------- // + + /** Triggers a haptic event as described by the specified action */ + virtual EVRInputError TriggerHapticVibrationAction( VRActionHandle_t action, float fStartSecondsFromNow, float fDurationSeconds, float fFrequency, float fAmplitude, VRInputValueHandle_t ulRestrictToDevice ) = 0; + + // --------------- Action Origins ---------------- // + + /** Retrieve origin handles for an action */ + virtual EVRInputError GetActionOrigins( VRActionSetHandle_t actionSetHandle, VRActionHandle_t digitalActionHandle, VR_ARRAY_COUNT( originOutCount ) VRInputValueHandle_t *originsOut, uint32_t originOutCount ) = 0; + + /** Retrieves the name of the origin in the current language. unStringSectionsToInclude is a bitfield of values in EVRInputStringBits that allows the + application to specify which parts of the origin's information it wants a string for. */ + virtual EVRInputError GetOriginLocalizedName( VRInputValueHandle_t origin, VR_OUT_STRING() char *pchNameArray, uint32_t unNameArraySize, int32_t unStringSectionsToInclude ) = 0; + + /** Retrieves useful information for the origin of this action */ + virtual EVRInputError GetOriginTrackedDeviceInfo( VRInputValueHandle_t origin, InputOriginInfo_t *pOriginInfo, uint32_t unOriginInfoSize ) = 0; + + /** Retrieves useful information about the bindings for an action */ + virtual EVRInputError GetActionBindingInfo( VRActionHandle_t action, InputBindingInfo_t *pOriginInfo, uint32_t unBindingInfoSize, uint32_t unBindingInfoCount, uint32_t *punReturnedBindingInfoCount ) = 0; + + /** Shows the current binding for the action in-headset */ + virtual EVRInputError ShowActionOrigins( VRActionSetHandle_t actionSetHandle, VRActionHandle_t ulActionHandle ) = 0; + + /** Shows the current binding all the actions in the specified action sets */ + virtual EVRInputError ShowBindingsForActionSet( VR_ARRAY_COUNT( unSetCount ) VRActiveActionSet_t *pSets, uint32_t unSizeOfVRSelectedActionSet_t, uint32_t unSetCount, VRInputValueHandle_t originToHighlight ) = 0; + + /** Use this to query what action on the component returned by GetOriginTrackedDeviceInfo would trigger this binding. */ + virtual EVRInputError GetComponentStateForBinding( const char *pchRenderModelName, const char *pchComponentName, + const InputBindingInfo_t *pOriginInfo, uint32_t unBindingInfoSize, uint32_t unBindingInfoCount, + vr::RenderModel_ComponentState_t *pComponentState ) = 0; + + + // --------------- Legacy Input ------------------- // + virtual bool IsUsingLegacyInput() = 0; + + + // --------------- Utilities ------------------- // + + /** Opens the binding user interface. If no app key is provided it will use the key from the calling process. + * If no set is provided it will open to the root of the app binding page. */ + virtual EVRInputError OpenBindingUI( const char* pchAppKey, VRActionSetHandle_t ulActionSetHandle, VRInputValueHandle_t ulDeviceHandle, bool bShowOnDesktop ) = 0; + + /** Returns the variant set in the current bindings. If the binding doesn't include a variant setting, this function + * will return an empty string */ + virtual EVRInputError GetBindingVariant( vr::VRInputValueHandle_t ulDevicePath, + VR_OUT_STRING() char *pchVariantArray, uint32_t unVariantArraySize ) = 0; + + }; + + static const char * const IVRInput_Version = "IVRInput_010"; + +} // namespace vr + +// ivriobuffer.h +namespace vr +{ + +typedef uint64_t IOBufferHandle_t; +static const uint64_t k_ulInvalidIOBufferHandle = 0; + + enum EIOBufferError + { + IOBuffer_Success = 0, + IOBuffer_OperationFailed = 100, + IOBuffer_InvalidHandle = 101, + IOBuffer_InvalidArgument = 102, + IOBuffer_PathExists = 103, + IOBuffer_PathDoesNotExist = 104, + IOBuffer_Permission = 105, + }; + + enum EIOBufferMode + { + IOBufferMode_Read = 0x0001, + IOBufferMode_Write = 0x0002, + IOBufferMode_Create = 0x0200, + }; + + // ---------------------------------------------------------------------------------------------- + // Purpose: + // ---------------------------------------------------------------------------------------------- + class IVRIOBuffer + { + public: + /** opens an existing or creates a new IOBuffer of unSize bytes */ + virtual vr::EIOBufferError Open( const char *pchPath, vr::EIOBufferMode mode, uint32_t unElementSize, uint32_t unElements, vr::IOBufferHandle_t *pulBuffer ) = 0; + + /** closes a previously opened or created buffer */ + virtual vr::EIOBufferError Close( vr::IOBufferHandle_t ulBuffer ) = 0; + + /** reads up to unBytes from buffer into *pDst, returning number of bytes read in *punRead */ + virtual vr::EIOBufferError Read( vr::IOBufferHandle_t ulBuffer, void *pDst, uint32_t unBytes, uint32_t *punRead ) = 0; + + /** writes unBytes of data from *pSrc into a buffer. */ + virtual vr::EIOBufferError Write( vr::IOBufferHandle_t ulBuffer, void *pSrc, uint32_t unBytes ) = 0; + + /** retrieves the property container of an buffer. */ + virtual vr::PropertyContainerHandle_t PropertyContainer( vr::IOBufferHandle_t ulBuffer ) = 0; + + /** inexpensively checks for readers to allow writers to fast-fail potentially expensive copies and writes. */ + virtual bool HasReaders( vr::IOBufferHandle_t ulBuffer ) = 0; + }; + + static const char *IVRIOBuffer_Version = "IVRIOBuffer_002"; +} + +// ivrspatialanchors.h +namespace vr +{ + static const SpatialAnchorHandle_t k_ulInvalidSpatialAnchorHandle = 0; + + struct SpatialAnchorPose_t + { + HmdMatrix34_t mAnchorToAbsoluteTracking; + }; + + class IVRSpatialAnchors + { + public: + + /** Returns a handle for an spatial anchor described by "descriptor". On success, pHandle + * will contain a handle valid for this session. Caller can wait for an event or occasionally + * poll GetSpatialAnchorPose() to find the virtual coordinate associated with this anchor. */ + virtual EVRSpatialAnchorError CreateSpatialAnchorFromDescriptor( const char *pchDescriptor, SpatialAnchorHandle_t *pHandleOut ) = 0; + + /** Returns a handle for an new spatial anchor at pPose. On success, pHandle + * will contain a handle valid for this session. Caller can wait for an event or occasionally + * poll GetSpatialAnchorDescriptor() to find the permanent descriptor for this pose. + * The result of GetSpatialAnchorPose() may evolve from this initial position if the driver chooses + * to update it. + * The anchor will be associated with the driver that provides unDeviceIndex, and the driver may use that specific + * device as a hint for how to best create the anchor. + * The eOrigin must match whatever tracking origin you are working in (seated/standing/raw). + * This should be called when the user is close to (and ideally looking at/interacting with) the target physical + * location. At that moment, the driver will have the most information about how to recover that physical point + * in the future, and the quality of the anchor (when the descriptor is re-used) will be highest. + * The caller may decide to apply offsets from this initial pose, but is advised to stay relatively close to the + * original pose location for highest fidelity. */ + virtual EVRSpatialAnchorError CreateSpatialAnchorFromPose( TrackedDeviceIndex_t unDeviceIndex, ETrackingUniverseOrigin eOrigin, SpatialAnchorPose_t *pPose, SpatialAnchorHandle_t *pHandleOut ) = 0; + + /** Get the pose for a given handle. This is intended to be cheap enough to call every frame (or fairly often) + * so that the driver can refine this position when it has more information available. */ + virtual EVRSpatialAnchorError GetSpatialAnchorPose( SpatialAnchorHandle_t unHandle, ETrackingUniverseOrigin eOrigin, SpatialAnchorPose_t *pPoseOut ) = 0; + + /** Get the descriptor for a given handle. This will be empty for handles where the driver has not + * yet built a descriptor. It will be the application-supplied descriptor for previously saved anchors + * that the application is requesting poses for. If the driver has called UpdateSpatialAnchorDescriptor() + * already in this session, it will be the descriptor provided by the driver. + * Returns true if the descriptor fits into the buffer, else false. Buffer size should be at least + * k_unMaxSpatialAnchorDescriptorSize. */ + virtual EVRSpatialAnchorError GetSpatialAnchorDescriptor( SpatialAnchorHandle_t unHandle, VR_OUT_STRING() char *pchDescriptorOut, uint32_t *punDescriptorBufferLenInOut ) = 0; + + }; + + static const char * const IVRSpatialAnchors_Version = "IVRSpatialAnchors_001"; + +} // namespace vr + +// ivrdebug.h +namespace vr +{ + enum EVRDebugError + { + VRDebugError_Success = 0, + VRDebugError_BadParameter + }; + + /** Handle for vr profiler events */ + typedef uint64_t VrProfilerEventHandle_t; + + class IVRDebug + { + public: + + /** Create a vr profiler discrete event (point) + * The event will be associated with the message provided in pchMessage, and the current + * time will be used as the event timestamp. */ + virtual EVRDebugError EmitVrProfilerEvent( const char *pchMessage ) = 0; + + /** Create an vr profiler duration event (line) + * The current time will be used as the timestamp for the start of the line. + * On success, pHandleOut will contain a handle valid for terminating this event. */ + virtual EVRDebugError BeginVrProfilerEvent( VrProfilerEventHandle_t *pHandleOut ) = 0; + + /** Terminate a vr profiler event + * The event associated with hHandle will be considered completed when this method is called. + * The current time will be used assocaited to the termination time of the event, and + * pchMessage will be used as the event title. */ + virtual EVRDebugError FinishVrProfilerEvent( VrProfilerEventHandle_t hHandle, const char *pchMessage ) = 0; + + /** Sends a request to the driver for the specified device and returns the response. The maximum response size is 32k, + * but this method can be called with a smaller buffer. If the response exceeds the size of the buffer, it is truncated. + * The size of the response including its terminating null is returned. */ + virtual uint32_t DriverDebugRequest( vr::TrackedDeviceIndex_t unDeviceIndex, const char *pchRequest, VR_OUT_STRING() char *pchResponseBuffer, uint32_t unResponseBufferSize ) = 0; + + }; + + static const char * const IVRDebug_Version = "IVRDebug_001"; + +} // namespace vr +// End + +#endif // _OPENVR_API + + + +namespace vr +{ +#if !defined( OPENVR_INTERFACE_INTERNAL ) + + /** Finds the active installation of the VR API and initializes it. The provided path must be absolute + * or relative to the current working directory. These are the local install versions of the equivalent + * functions in steamvr.h and will work without a local Steam install. + * + * This path is to the "root" of the VR API install. That's the directory with + * the "drivers" directory and a platform (i.e. "win32") directory in it, not the directory with the DLL itself. + * + * pStartupInfo is reserved for future use. + */ + inline IVRSystem *VR_Init( EVRInitError *peError, EVRApplicationType eApplicationType, const char *pStartupInfo = nullptr ); + + /** unloads vrclient.dll. Any interface pointers from the interface are + * invalid after this point */ + inline void VR_Shutdown(); + + /** Returns true if there is an HMD attached. This check is as lightweight as possible and + * can be called outside of VR_Init/VR_Shutdown. It should be used when an application wants + * to know if initializing VR is a possibility but isn't ready to take that step yet. + */ + VR_INTERFACE bool VR_CALLTYPE VR_IsHmdPresent(); + + /** Returns true if the OpenVR runtime is installed. */ + VR_INTERFACE bool VR_CALLTYPE VR_IsRuntimeInstalled(); + + /** Returns where the OpenVR runtime is installed. */ + VR_INTERFACE bool VR_GetRuntimePath( VR_OUT_STRING() char *pchPathBuffer, uint32_t unBufferSize, uint32_t *punRequiredBufferSize ); + + /** Returns the name of the enum value for an EVRInitError. This function may be called outside of VR_Init()/VR_Shutdown(). */ + VR_INTERFACE const char *VR_CALLTYPE VR_GetVRInitErrorAsSymbol( EVRInitError error ); + + /** Returns an English string for an EVRInitError. Applications should call VR_GetVRInitErrorAsSymbol instead and + * use that as a key to look up their own localized error message. This function may be called outside of VR_Init()/VR_Shutdown(). */ + VR_INTERFACE const char *VR_CALLTYPE VR_GetVRInitErrorAsEnglishDescription( EVRInitError error ); + + /** Returns the interface of the specified version. This method must be called after VR_Init. The + * pointer returned is valid until VR_Shutdown is called. + */ + VR_INTERFACE void *VR_CALLTYPE VR_GetGenericInterface( const char *pchInterfaceVersion, EVRInitError *peError ); + + /** Returns whether the interface of the specified version exists. + */ + VR_INTERFACE bool VR_CALLTYPE VR_IsInterfaceVersionValid( const char *pchInterfaceVersion ); + + /** Returns a token that represents whether the VR interface handles need to be reloaded */ + VR_INTERFACE uint32_t VR_CALLTYPE VR_GetInitToken(); + + // These typedefs allow old enum names from SDK 0.9.11 to be used in applications. + // They will go away in the future. + typedef EVRInitError HmdError; + typedef EVREye Hmd_Eye; + typedef EColorSpace ColorSpace; + typedef ETrackingResult HmdTrackingResult; + typedef ETrackedDeviceClass TrackedDeviceClass; + typedef ETrackingUniverseOrigin TrackingUniverseOrigin; + typedef ETrackedDeviceProperty TrackedDeviceProperty; + typedef ETrackedPropertyError TrackedPropertyError; + typedef EVRSubmitFlags VRSubmitFlags_t; + typedef EVRState VRState_t; + typedef ECollisionBoundsStyle CollisionBoundsStyle_t; + typedef EVROverlayError VROverlayError; + typedef EVRFirmwareError VRFirmwareError; + typedef EVRCompositorError VRCompositorError; + typedef EVRScreenshotError VRScreenshotsError; + + inline uint32_t &VRToken() + { + static uint32_t token; + return token; + } + + class COpenVRContext + { + public: + COpenVRContext() { Clear(); } + void Clear(); + + inline void CheckClear() + { + if ( VRToken() != VR_GetInitToken() ) + { + Clear(); + VRToken() = VR_GetInitToken(); + } + } + + IVRSystem *VRSystem() + { + CheckClear(); + if ( m_pVRSystem == nullptr ) + { + EVRInitError eError; + m_pVRSystem = ( IVRSystem * )VR_GetGenericInterface( IVRSystem_Version, &eError ); + } + return m_pVRSystem; + } + IVRChaperone *VRChaperone() + { + CheckClear(); + if ( m_pVRChaperone == nullptr ) + { + EVRInitError eError; + m_pVRChaperone = ( IVRChaperone * )VR_GetGenericInterface( IVRChaperone_Version, &eError ); + } + return m_pVRChaperone; + } + + IVRChaperoneSetup *VRChaperoneSetup() + { + CheckClear(); + if ( m_pVRChaperoneSetup == nullptr ) + { + EVRInitError eError; + m_pVRChaperoneSetup = ( IVRChaperoneSetup * )VR_GetGenericInterface( IVRChaperoneSetup_Version, &eError ); + } + return m_pVRChaperoneSetup; + } + + IVRCompositor *VRCompositor() + { + CheckClear(); + if ( m_pVRCompositor == nullptr ) + { + EVRInitError eError; + m_pVRCompositor = ( IVRCompositor * )VR_GetGenericInterface( IVRCompositor_Version, &eError ); + } + return m_pVRCompositor; + } + + IVROverlay *VROverlay() + { + CheckClear(); + if ( m_pVROverlay == nullptr ) + { + EVRInitError eError; + m_pVROverlay = ( IVROverlay * )VR_GetGenericInterface( IVROverlay_Version, &eError ); + } + return m_pVROverlay; + } + + IVROverlayView *VROverlayView() + { + CheckClear(); + if ( m_pVROverlayView == nullptr ) + { + EVRInitError eError; + m_pVROverlayView = ( IVROverlayView * ) VR_GetGenericInterface( IVROverlayView_Version, &eError ); + } + return m_pVROverlayView; + } + + IVRHeadsetView *VRHeadsetView() + { + CheckClear(); + if ( m_pVRHeadsetView == nullptr ) + { + EVRInitError eError; + m_pVRHeadsetView = ( IVRHeadsetView * ) VR_GetGenericInterface( IVRHeadsetView_Version, &eError ); + } + return m_pVRHeadsetView; + } + + IVRResources *VRResources() + { + CheckClear(); + if ( m_pVRResources == nullptr ) + { + EVRInitError eError; + m_pVRResources = (IVRResources *)VR_GetGenericInterface( IVRResources_Version, &eError ); + } + return m_pVRResources; + } + + IVRScreenshots *VRScreenshots() + { + CheckClear(); + if ( m_pVRScreenshots == nullptr ) + { + EVRInitError eError; + m_pVRScreenshots = ( IVRScreenshots * )VR_GetGenericInterface( IVRScreenshots_Version, &eError ); + } + return m_pVRScreenshots; + } + + IVRRenderModels *VRRenderModels() + { + CheckClear(); + if ( m_pVRRenderModels == nullptr ) + { + EVRInitError eError; + m_pVRRenderModels = ( IVRRenderModels * )VR_GetGenericInterface( IVRRenderModels_Version, &eError ); + } + return m_pVRRenderModels; + } + + IVRExtendedDisplay *VRExtendedDisplay() + { + CheckClear(); + if ( m_pVRExtendedDisplay == nullptr ) + { + EVRInitError eError; + m_pVRExtendedDisplay = ( IVRExtendedDisplay * )VR_GetGenericInterface( IVRExtendedDisplay_Version, &eError ); + } + return m_pVRExtendedDisplay; + } + + IVRSettings *VRSettings() + { + CheckClear(); + if ( m_pVRSettings == nullptr ) + { + EVRInitError eError; + m_pVRSettings = ( IVRSettings * )VR_GetGenericInterface( IVRSettings_Version, &eError ); + } + return m_pVRSettings; + } + + IVRApplications *VRApplications() + { + CheckClear(); + if ( m_pVRApplications == nullptr ) + { + EVRInitError eError; + m_pVRApplications = ( IVRApplications * )VR_GetGenericInterface( IVRApplications_Version, &eError ); + } + return m_pVRApplications; + } + + IVRTrackedCamera *VRTrackedCamera() + { + CheckClear(); + if ( m_pVRTrackedCamera == nullptr ) + { + EVRInitError eError; + m_pVRTrackedCamera = ( IVRTrackedCamera * )VR_GetGenericInterface( IVRTrackedCamera_Version, &eError ); + } + return m_pVRTrackedCamera; + } + + IVRDriverManager *VRDriverManager() + { + CheckClear(); + if ( !m_pVRDriverManager ) + { + EVRInitError eError; + m_pVRDriverManager = ( IVRDriverManager * )VR_GetGenericInterface( IVRDriverManager_Version, &eError ); + } + return m_pVRDriverManager; + } + + IVRInput *VRInput() + { + CheckClear(); + if ( !m_pVRInput ) + { + EVRInitError eError; + m_pVRInput = (IVRInput *)VR_GetGenericInterface( IVRInput_Version, &eError ); + } + return m_pVRInput; + } + + IVRIOBuffer *VRIOBuffer() + { + if ( !m_pVRIOBuffer ) + { + EVRInitError eError; + m_pVRIOBuffer = ( IVRIOBuffer * )VR_GetGenericInterface( IVRIOBuffer_Version, &eError ); + } + return m_pVRIOBuffer; + } + + IVRSpatialAnchors *VRSpatialAnchors() + { + CheckClear(); + if ( !m_pVRSpatialAnchors ) + { + EVRInitError eError; + m_pVRSpatialAnchors = (IVRSpatialAnchors *)VR_GetGenericInterface( IVRSpatialAnchors_Version, &eError ); + } + return m_pVRSpatialAnchors; + } + + IVRDebug *VRDebug() + { + CheckClear(); + if ( !m_pVRDebug ) + { + EVRInitError eError; + m_pVRDebug = (IVRDebug *)VR_GetGenericInterface( IVRDebug_Version, &eError ); + } + return m_pVRDebug; + } + + IVRNotifications *VRNotifications() + { + CheckClear(); + if ( !m_pVRNotifications ) + { + EVRInitError eError; + m_pVRNotifications = ( IVRNotifications * )VR_GetGenericInterface( IVRNotifications_Version, &eError ); + } + return m_pVRNotifications; + } + + private: + IVRSystem *m_pVRSystem; + IVRChaperone *m_pVRChaperone; + IVRChaperoneSetup *m_pVRChaperoneSetup; + IVRCompositor *m_pVRCompositor; + IVRHeadsetView *m_pVRHeadsetView; + IVROverlay *m_pVROverlay; + IVROverlayView *m_pVROverlayView; + IVRResources *m_pVRResources; + IVRRenderModels *m_pVRRenderModels; + IVRExtendedDisplay *m_pVRExtendedDisplay; + IVRSettings *m_pVRSettings; + IVRApplications *m_pVRApplications; + IVRTrackedCamera *m_pVRTrackedCamera; + IVRScreenshots *m_pVRScreenshots; + IVRDriverManager *m_pVRDriverManager; + IVRInput *m_pVRInput; + IVRIOBuffer *m_pVRIOBuffer; + IVRSpatialAnchors *m_pVRSpatialAnchors; + IVRDebug *m_pVRDebug; + IVRNotifications *m_pVRNotifications; + }; + + inline COpenVRContext &OpenVRInternal_ModuleContext() + { + static void *ctx[ sizeof( COpenVRContext ) / sizeof( void * ) ]; + return *( COpenVRContext * )ctx; // bypass zero-init constructor + } + + inline IVRSystem *VR_CALLTYPE VRSystem() { return OpenVRInternal_ModuleContext().VRSystem(); } + inline IVRChaperone *VR_CALLTYPE VRChaperone() { return OpenVRInternal_ModuleContext().VRChaperone(); } + inline IVRChaperoneSetup *VR_CALLTYPE VRChaperoneSetup() { return OpenVRInternal_ModuleContext().VRChaperoneSetup(); } + inline IVRCompositor *VR_CALLTYPE VRCompositor() { return OpenVRInternal_ModuleContext().VRCompositor(); } + inline IVROverlay *VR_CALLTYPE VROverlay() { return OpenVRInternal_ModuleContext().VROverlay(); } + inline IVROverlayView *VR_CALLTYPE VROverlayView() { return OpenVRInternal_ModuleContext().VROverlayView(); } + inline IVRHeadsetView *VR_CALLTYPE VRHeadsetView() { return OpenVRInternal_ModuleContext().VRHeadsetView(); } + inline IVRScreenshots *VR_CALLTYPE VRScreenshots() { return OpenVRInternal_ModuleContext().VRScreenshots(); } + inline IVRRenderModels *VR_CALLTYPE VRRenderModels() { return OpenVRInternal_ModuleContext().VRRenderModels(); } + inline IVRApplications *VR_CALLTYPE VRApplications() { return OpenVRInternal_ModuleContext().VRApplications(); } + inline IVRSettings *VR_CALLTYPE VRSettings() { return OpenVRInternal_ModuleContext().VRSettings(); } + inline IVRResources *VR_CALLTYPE VRResources() { return OpenVRInternal_ModuleContext().VRResources(); } + inline IVRExtendedDisplay *VR_CALLTYPE VRExtendedDisplay() { return OpenVRInternal_ModuleContext().VRExtendedDisplay(); } + inline IVRTrackedCamera *VR_CALLTYPE VRTrackedCamera() { return OpenVRInternal_ModuleContext().VRTrackedCamera(); } + inline IVRDriverManager *VR_CALLTYPE VRDriverManager() { return OpenVRInternal_ModuleContext().VRDriverManager(); } + inline IVRInput *VR_CALLTYPE VRInput() { return OpenVRInternal_ModuleContext().VRInput(); } + inline IVRIOBuffer *VR_CALLTYPE VRIOBuffer() { return OpenVRInternal_ModuleContext().VRIOBuffer(); } + inline IVRSpatialAnchors *VR_CALLTYPE VRSpatialAnchors() { return OpenVRInternal_ModuleContext().VRSpatialAnchors(); } + inline IVRNotifications *VR_CALLTYPE VRNotifications() { return OpenVRInternal_ModuleContext().VRNotifications(); } + inline IVRDebug *VR_CALLTYPE VRDebug() { return OpenVRInternal_ModuleContext().VRDebug(); } + + inline void COpenVRContext::Clear() + { + m_pVRSystem = nullptr; + m_pVRChaperone = nullptr; + m_pVRChaperoneSetup = nullptr; + m_pVRCompositor = nullptr; + m_pVROverlay = nullptr; + m_pVROverlayView = nullptr; + m_pVRHeadsetView = nullptr; + m_pVRRenderModels = nullptr; + m_pVRExtendedDisplay = nullptr; + m_pVRSettings = nullptr; + m_pVRApplications = nullptr; + m_pVRTrackedCamera = nullptr; + m_pVRResources = nullptr; + m_pVRScreenshots = nullptr; + m_pVRDriverManager = nullptr; + m_pVRInput = nullptr; + m_pVRIOBuffer = nullptr; + m_pVRSpatialAnchors = nullptr; + m_pVRNotifications = nullptr; + m_pVRDebug = nullptr; + } + + VR_INTERFACE uint32_t VR_CALLTYPE VR_InitInternal2( EVRInitError *peError, EVRApplicationType eApplicationType, const char *pStartupInfo ); + VR_INTERFACE void VR_CALLTYPE VR_ShutdownInternal(); + + /** Finds the active installation of vrclient.dll and initializes it */ + inline IVRSystem *VR_Init( EVRInitError *peError, EVRApplicationType eApplicationType, const char *pStartupInfo ) + { + IVRSystem *pVRSystem = nullptr; + + EVRInitError eError; + VRToken() = VR_InitInternal2( &eError, eApplicationType, pStartupInfo ); + COpenVRContext &ctx = OpenVRInternal_ModuleContext(); + ctx.Clear(); + + if ( eError == VRInitError_None ) + { + if ( VR_IsInterfaceVersionValid( IVRSystem_Version ) ) + { + pVRSystem = VRSystem(); + } + else + { + VR_ShutdownInternal(); + eError = VRInitError_Init_InterfaceNotFound; + } + } + + if ( peError ) + *peError = eError; + return pVRSystem; + } + + /** unloads vrclient.dll. Any interface pointers from the interface are + * invalid after this point */ + inline void VR_Shutdown() + { + VR_ShutdownInternal(); + } + +#endif // OPENVR_INTERFACE_INTERNAL +} diff --git a/gfx/vr/service/openvr/moz.build b/gfx/vr/service/openvr/moz.build new file mode 100644 index 0000000000..5ad6d0b9b4 --- /dev/null +++ b/gfx/vr/service/openvr/moz.build @@ -0,0 +1,62 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +FINAL_LIBRARY = 'xul' + +DEFINES['VR_API_PUBLIC'] = True + +# Windows.h wrappers conflict with internal methods in openvr +DEFINES['MOZ_DISABLE_WINDOWS_WRAPPER'] = True + +if CONFIG['OS_ARCH'] == 'WINNT': + if CONFIG['HAVE_64BIT_BUILD']: + DEFINES['WIN64'] = True + else: + DEFINES['WIN32'] = True + +if CONFIG['OS_ARCH'] == 'Darwin': + DEFINES['POSIX'] = True + DEFINES['OSX'] = True + if CONFIG['CC_TYPE'] in ('clang', 'gcc'): + CXXFLAGS += ['-xobjective-c++'] + +if CONFIG['OS_ARCH'] == 'Linux': + DEFINES['POSIX'] = True + DEFINES['LINUX'] = True + if CONFIG['HAVE_64BIT_BUILD']: + DEFINES['LINUX64'] = True + else: + DEFINES['LINUX32'] = True + +LOCAL_INCLUDES += [ + '/toolkit/components/jsoncpp/include', +] + +USE_LIBS += [ + 'jsoncpp', +] + +EXPORTS += [ + 'headers/openvr.h', +] + +SOURCES += [ + 'src/dirtools_public.cpp', + 'src/envvartools_public.cpp', + 'src/hmderrors_public.cpp', + 'src/openvr_api_public.cpp', + 'src/pathtools_public.cpp', + 'src/sharedlibtools_public.cpp', + 'src/strtools_public.cpp', + 'src/vrpathregistry_public.cpp', +] + +if CONFIG['CC_TYPE'] in ('clang', 'gcc'): + # Harmless warnings in 3rd party code + CXXFLAGS += [ + '-Wno-error=parentheses', + '-Wno-error=unused-variable', + ] diff --git a/gfx/vr/service/openvr/src/README b/gfx/vr/service/openvr/src/README new file mode 100644 index 0000000000..826c58da79 --- /dev/null +++ b/gfx/vr/service/openvr/src/README @@ -0,0 +1,39 @@ +This is the source code for the OpenVR API client binding library which connects +OpenVR applications to the SteamVR runtime, taking into account the version +of the OpenVR interface they were compiled against. + +The client binding library - openvr_api.dll on Windows, openvr_api.so on +Linux, and openvr_api.dylib or OpenVR.framework on macOS - knows how to find +and read the SteamVR runtime installation information which allows it to +find and dynamically connect to the installed runtime. In combination with the +interface version identifiers from /include/openvr.h which are baked +into applications at the time they are built, the OpenVR API client +binding library captures and conveys to the SteamVR runtime the version +of the OpenVR API interface behavior that the application expects. + +Applications carry with them a private/local copy of the client binding +library when they ship, and they should install it locally to their +application. Applications should not install the client binding library +globally or attempt to link to a globally installed client binding library. +Doing so negates at least part of the ability for the client binding library +to accurately reflect the version of the OpenVR API that the application +was built against, and so hinders compatibility support in the face of +API changes. + +Most applications should simply link to and redistribute with their application +the pre-built client binding library found in the /bin directory of this +repository. Some small number of applications which have specific requirements +around redistributing only binaries they build themselves should build +the client library from this source and either statically link it into +their application or redistribute the binary they build. + +This is a cmake project, to build it use the version of cmake appropriate +for your platform. For example, to build on a POSIX system simply perform + + cd src; mkdir _build; cd _build; cmake ..; make + +and you will end up with the static library /src/bin/<arch>/libopenvr_api.a + +To build a shared library, pass -DBUILD_SHARED=1 to cmake. +To build as a framework on apple platforms, pass -DBUILD_FRAMEWORK=1 to cmake. +To see a complete list of configurable build options, use `cmake -LAH` diff --git a/gfx/vr/service/openvr/src/dirtools_public.cpp b/gfx/vr/service/openvr/src/dirtools_public.cpp new file mode 100644 index 0000000000..e5cfc02e0c --- /dev/null +++ b/gfx/vr/service/openvr/src/dirtools_public.cpp @@ -0,0 +1,101 @@ +//========= Copyright Valve Corporation ============// +#include "dirtools_public.h" +#include "strtools_public.h" +#include "pathtools_public.h" + +#include <errno.h> +#include <string.h> + +#ifdef _WIN32 +#include "windows.h" +#else +#include <stdlib.h> +#include <stdio.h> +#include <sys/stat.h> +#include <sys/types.h> +#endif + +#if defined( OSX ) +#include <sys/syslimits.h> +#endif + + +//----------------------------------------------------------------------------- +// Purpose: utility function to create dirs & subdirs +//----------------------------------------------------------------------------- +bool BCreateDirectoryRecursive( const char *pchPath ) +{ + // Does it already exist? + if ( Path_IsDirectory( pchPath ) ) + return true; + + // copy the path into something we can munge + int len = (int)strlen( pchPath ); + char *path = (char *)malloc( len + 1 ); + strcpy( path, pchPath ); + + // Walk backwards to first non-existing dir that we find + char *s = path + len - 1; + + const char slash = Path_GetSlash(); + while ( s > path ) + { + if ( *s == slash ) + { + *s = '\0'; + bool bExists = Path_IsDirectory( path ); + *s = slash; + + if ( bExists ) + { + ++s; + break; + } + } + --s; + } + + // and then move forwards from there + + while ( *s ) + { + if ( *s == slash ) + { + *s = '\0'; + BCreateDirectory( path ); + *s = slash; + } + s++; + } + + bool bRetVal = BCreateDirectory( path ); + free( path ); + return bRetVal; +} + + +//----------------------------------------------------------------------------- +// Purpose: Creates the directory, returning true if it is created, or if it already existed +//----------------------------------------------------------------------------- +bool BCreateDirectory( const char *pchPath ) +{ +#ifdef WIN32 + std::wstring wPath = UTF8to16( pchPath ); + if ( ::CreateDirectoryW( wPath.c_str(), NULL ) ) + return true; + + if ( ::GetLastError() == ERROR_ALREADY_EXISTS ) + return true; + + return false; +#else + int i = mkdir( pchPath, S_IRWXU | S_IRWXG | S_IRWXO ); + if ( i == 0 ) + return true; + if ( errno == EEXIST ) + return true; + + return false; +#endif +} + diff --git a/gfx/vr/service/openvr/src/dirtools_public.h b/gfx/vr/service/openvr/src/dirtools_public.h new file mode 100644 index 0000000000..b048b41b78 --- /dev/null +++ b/gfx/vr/service/openvr/src/dirtools_public.h @@ -0,0 +1,17 @@ +//========= Copyright Valve Corporation ============// +#pragma once + +#include <stdint.h> +#include <string> + + +#if !defined(_WIN32) +#include <sys/types.h> +#include <sys/stat.h> +#endif + + +extern bool BCreateDirectoryRecursive( const char *pchPath ); +extern bool BCreateDirectory( const char *pchPath ); + + diff --git a/gfx/vr/service/openvr/src/envvartools_public.cpp b/gfx/vr/service/openvr/src/envvartools_public.cpp new file mode 100644 index 0000000000..4fb4817927 --- /dev/null +++ b/gfx/vr/service/openvr/src/envvartools_public.cpp @@ -0,0 +1,88 @@ +//========= Copyright Valve Corporation ============// +#include "envvartools_public.h" +#include "strtools_public.h" +#include <stdlib.h> +#include <string> +#include <cctype> + +#if defined(_WIN32) +#include <windows.h> + +#undef GetEnvironmentVariable +#undef SetEnvironmentVariable +#endif + + +std::string GetEnvironmentVariable( const char *pchVarName ) +{ +#if defined(_WIN32) + char rchValue[32767]; // max size for an env var on Windows + DWORD cChars = GetEnvironmentVariableA( pchVarName, rchValue, sizeof( rchValue ) ); + if( cChars == 0 ) + return ""; + else + return rchValue; +#elif defined(POSIX) + char *pchValue = getenv( pchVarName ); + if( pchValue ) + return pchValue; + else + return ""; +#else +#error "Unsupported Platform" +#endif +} + +bool GetEnvironmentVariableAsBool( const char *pchVarName, bool bDefault ) +{ + std::string sValue = GetEnvironmentVariable( pchVarName ); + + if ( sValue.empty() ) + { + return bDefault; + } + + sValue = StringToLower( sValue ); + std::string sYesValues[] = { "y", "yes", "true" }; + std::string sNoValues[] = { "n", "no", "false" }; + + for ( std::string &sMatch : sYesValues ) + { + if ( sMatch == sValue ) + { + return true; + } + } + + for ( std::string &sMatch : sNoValues ) + { + if ( sMatch == sValue ) + { + return false; + } + } + + if ( std::isdigit( sValue.at(0) ) ) + { + return atoi( sValue.c_str() ) != 0; + } + + fprintf( stderr, + "GetEnvironmentVariableAsBool(%s): Unable to parse value '%s', using default %d\n", + pchVarName, sValue.c_str(), bDefault ); + return bDefault; +} + +bool SetEnvironmentVariable( const char *pchVarName, const char *pchVarValue ) +{ +#if defined(_WIN32) + return 0 != SetEnvironmentVariableA( pchVarName, pchVarValue ); +#elif defined(POSIX) + if( pchVarValue == NULL ) + return 0 == unsetenv( pchVarName ); + else + return 0 == setenv( pchVarName, pchVarValue, 1 ); +#else +#error "Unsupported Platform" +#endif +} diff --git a/gfx/vr/service/openvr/src/envvartools_public.h b/gfx/vr/service/openvr/src/envvartools_public.h new file mode 100644 index 0000000000..7cd4c208e6 --- /dev/null +++ b/gfx/vr/service/openvr/src/envvartools_public.h @@ -0,0 +1,8 @@ +//========= Copyright Valve Corporation ============// +#pragma once + +#include <string> + +std::string GetEnvironmentVariable( const char *pchVarName ); +bool GetEnvironmentVariableAsBool( const char *pchVarName, bool bDefault ); +bool SetEnvironmentVariable( const char *pchVarName, const char *pchVarValue ); diff --git a/gfx/vr/service/openvr/src/hmderrors_public.cpp b/gfx/vr/service/openvr/src/hmderrors_public.cpp new file mode 100644 index 0000000000..e02c370cb1 --- /dev/null +++ b/gfx/vr/service/openvr/src/hmderrors_public.cpp @@ -0,0 +1,326 @@ +//========= Copyright Valve Corporation ============// +#include "openvr.h" +#include "hmderrors_public.h" +#include <stdio.h> +#include <algorithm> + +using namespace vr; + +#define RETURN_ENUM_AS_STRING(enumValue) case enumValue: return #enumValue; + + +const char *GetEnglishStringForHmdError( vr::EVRInitError eError ) +{ + switch( eError ) + { + case VRInitError_None: return "No Error (0)"; + + case VRInitError_Init_InstallationNotFound: return "Installation Not Found (100)"; + case VRInitError_Init_InstallationCorrupt: return "Installation Corrupt (101)"; + case VRInitError_Init_VRClientDLLNotFound: return "vrclient Shared Lib Not Found (102)"; + case VRInitError_Init_FileNotFound: return "File Not Found (103)"; + case VRInitError_Init_FactoryNotFound: return "Factory Function Not Found (104)"; + case VRInitError_Init_InterfaceNotFound: return "Interface Not Found (105)"; + case VRInitError_Init_InvalidInterface: return "Invalid Interface (106)"; + case VRInitError_Init_UserConfigDirectoryInvalid: return "User Config Directory Invalid (107)"; + case VRInitError_Init_HmdNotFound: return "Hmd Not Found (108)"; + case VRInitError_Init_NotInitialized: return "Not Initialized (109)"; + case VRInitError_Init_PathRegistryNotFound: return "Installation path could not be located (110)"; + case VRInitError_Init_NoConfigPath: return "Config path could not be located (111)"; + case VRInitError_Init_NoLogPath: return "Log path could not be located (112)"; + case VRInitError_Init_PathRegistryNotWritable: return "Unable to write path registry (113)"; + case VRInitError_Init_AppInfoInitFailed: return "App info manager init failed (114)"; + case VRInitError_Init_Retry: return "Internal Retry (115)"; + case VRInitError_Init_InitCanceledByUser: return "User Canceled Init (116)"; + case VRInitError_Init_AnotherAppLaunching: return "Another app was already launching (117)"; + case VRInitError_Init_SettingsInitFailed: return "Settings manager init failed (118)"; + case VRInitError_Init_ShuttingDown: return "VR system shutting down (119)"; + case VRInitError_Init_TooManyObjects: return "Too many tracked objects (120)"; + case VRInitError_Init_NoServerForBackgroundApp: return "Not starting vrserver for background app (121)"; + case VRInitError_Init_NotSupportedWithCompositor: return "The requested interface is incompatible with the compositor and the compositor is running (122)"; + case VRInitError_Init_NotAvailableToUtilityApps: return "This interface is not available to utility applications (123)"; + case VRInitError_Init_Internal: return "vrserver internal error (124)"; + case VRInitError_Init_HmdDriverIdIsNone: return "Hmd DriverId is invalid (125)"; + case VRInitError_Init_HmdNotFoundPresenceFailed: return "Hmd Not Found Presence Failed (126)"; + case VRInitError_Init_VRMonitorNotFound: return "VR Monitor Not Found (127)"; + case VRInitError_Init_VRMonitorStartupFailed: return "VR Monitor startup failed (128)"; + case VRInitError_Init_LowPowerWatchdogNotSupported: return "Low Power Watchdog Not Supported (129)"; + case VRInitError_Init_InvalidApplicationType: return "Invalid Application Type (130)"; + case VRInitError_Init_NotAvailableToWatchdogApps: return "Not available to watchdog apps (131)"; + case VRInitError_Init_WatchdogDisabledInSettings: return "Watchdog disabled in settings (132)"; + case VRInitError_Init_VRDashboardNotFound: return "VR Dashboard Not Found (133)"; + case VRInitError_Init_VRDashboardStartupFailed: return "VR Dashboard startup failed (134)"; + case VRInitError_Init_VRHomeNotFound: return "VR Home Not Found (135)"; + case VRInitError_Init_VRHomeStartupFailed: return "VR home startup failed (136)"; + case VRInitError_Init_RebootingBusy: return "Rebooting In Progress (137)"; + case VRInitError_Init_FirmwareUpdateBusy: return "Firmware Update In Progress (138)"; + case VRInitError_Init_FirmwareRecoveryBusy: return "Firmware Recovery In Progress (139)"; + case VRInitError_Init_USBServiceBusy: return "USB Service Busy (140)"; + + case VRInitError_Driver_Failed: return "Driver Failed (200)"; + case VRInitError_Driver_Unknown: return "Driver Not Known (201)"; + case VRInitError_Driver_HmdUnknown: return "HMD Not Known (202)"; + case VRInitError_Driver_NotLoaded: return "Driver Not Loaded (203)"; + case VRInitError_Driver_RuntimeOutOfDate: return "Driver runtime is out of date (204)"; + case VRInitError_Driver_HmdInUse: return "HMD already in use by another application (205)"; + case VRInitError_Driver_NotCalibrated: return "Device is not calibrated (206)"; + case VRInitError_Driver_CalibrationInvalid: return "Device Calibration is invalid (207)"; + case VRInitError_Driver_HmdDisplayNotFound: return "HMD detected over USB, but Monitor not found (208)"; + case VRInitError_Driver_TrackedDeviceInterfaceUnknown: return "Driver Tracked Device Interface unknown (209)"; + // case VRInitError_Driver_HmdDisplayNotFoundAfterFix: return "HMD detected over USB, but Monitor not found after attempt to fix (210)"; // taken out upon Ben's request: He thinks that there is no need to separate that error from 208 + case VRInitError_Driver_HmdDriverIdOutOfBounds: return "Hmd DriverId is our of bounds (211)"; + case VRInitError_Driver_HmdDisplayMirrored: return "HMD detected over USB, but Monitor may be mirrored instead of extended (212)"; + case VRInitError_Driver_HmdDisplayNotFoundLaptop: return "On laptop, HMD detected over USB, but Monitor not found (213)"; + + case VRInitError_IPC_ServerInitFailed: return "VR Server Init Failed (300)"; + case VRInitError_IPC_ConnectFailed: return "Connect to VR Server Failed (301)"; + case VRInitError_IPC_SharedStateInitFailed: return "Shared IPC State Init Failed (302)"; + case VRInitError_IPC_CompositorInitFailed: return "Shared IPC Compositor Init Failed (303)"; + case VRInitError_IPC_MutexInitFailed: return "Shared IPC Mutex Init Failed (304)"; + case VRInitError_IPC_Failed: return "Shared IPC Failed (305)"; + case VRInitError_IPC_CompositorConnectFailed: return "Shared IPC Compositor Connect Failed (306)"; + case VRInitError_IPC_CompositorInvalidConnectResponse: return "Shared IPC Compositor Invalid Connect Response (307)"; + case VRInitError_IPC_ConnectFailedAfterMultipleAttempts: return "Shared IPC Connect Failed After Multiple Attempts (308)"; + case VRInitError_IPC_ConnectFailedAfterTargetExited: return "Shared IPC Connect Failed After Target Exited (309)"; + case VRInitError_IPC_NamespaceUnavailable: return "Shared IPC Namespace Unavailable (310)"; + + case VRInitError_Compositor_Failed: return "Compositor failed to initialize (400)"; + case VRInitError_Compositor_D3D11HardwareRequired: return "Compositor failed to find DX11 hardware (401)"; + case VRInitError_Compositor_FirmwareRequiresUpdate: return "Compositor requires mandatory firmware update (402)"; + case VRInitError_Compositor_OverlayInitFailed: return "Compositor initialization succeeded, but overlay init failed (403)"; + case VRInitError_Compositor_ScreenshotsInitFailed: return "Compositor initialization succeeded, but screenshot init failed (404)"; + case VRInitError_Compositor_UnableToCreateDevice: return "Compositor unable to create graphics device (405)"; + + // Oculus + case VRInitError_VendorSpecific_UnableToConnectToOculusRuntime: return "Unable to connect to Oculus Runtime (1000)"; + case VRInitError_VendorSpecific_OculusRuntimeBadInstall: return "Unable to connect to Oculus Runtime, possible bad install (1114)"; + + // Lighthouse + case VRInitError_VendorSpecific_HmdFound_CantOpenDevice: return "HMD found, but can not open device (1101)"; + case VRInitError_VendorSpecific_HmdFound_UnableToRequestConfigStart: return "HMD found, but unable to request config (1102)"; + case VRInitError_VendorSpecific_HmdFound_NoStoredConfig: return "HMD found, but no stored config (1103)"; + case VRInitError_VendorSpecific_HmdFound_ConfigFailedSanityCheck: return "HMD found, but failed configuration check (1113)"; + case VRInitError_VendorSpecific_HmdFound_ConfigTooBig: return "HMD found, but config too big (1104)"; + case VRInitError_VendorSpecific_HmdFound_ConfigTooSmall: return "HMD found, but config too small (1105)"; + case VRInitError_VendorSpecific_HmdFound_UnableToInitZLib: return "HMD found, but unable to init ZLib (1106)"; + case VRInitError_VendorSpecific_HmdFound_CantReadFirmwareVersion: return "HMD found, but problems with the data (1107)"; + case VRInitError_VendorSpecific_HmdFound_UnableToSendUserDataStart: return "HMD found, but problems with the data (1108)"; + case VRInitError_VendorSpecific_HmdFound_UnableToGetUserDataStart: return "HMD found, but problems with the data (1109)"; + case VRInitError_VendorSpecific_HmdFound_UnableToGetUserDataNext: return "HMD found, but problems with the data (1110)"; + case VRInitError_VendorSpecific_HmdFound_UserDataAddressRange: return "HMD found, but problems with the data (1111)"; + case VRInitError_VendorSpecific_HmdFound_UserDataError: return "HMD found, but problems with the data (1112)"; + + case VRInitError_Steam_SteamInstallationNotFound: return "Unable to find Steam installation (2000)"; + + default: + return GetIDForVRInitError( eError ); + } + +} + + +const char *GetIDForVRInitError( vr::EVRInitError eError ) +{ + switch( eError ) + { + RETURN_ENUM_AS_STRING( VRInitError_None ); + RETURN_ENUM_AS_STRING( VRInitError_Unknown ); + + RETURN_ENUM_AS_STRING( VRInitError_Init_InstallationNotFound ); + RETURN_ENUM_AS_STRING( VRInitError_Init_InstallationCorrupt ); + RETURN_ENUM_AS_STRING( VRInitError_Init_VRClientDLLNotFound ); + RETURN_ENUM_AS_STRING( VRInitError_Init_FileNotFound ); + RETURN_ENUM_AS_STRING( VRInitError_Init_FactoryNotFound ); + RETURN_ENUM_AS_STRING( VRInitError_Init_InterfaceNotFound ); + RETURN_ENUM_AS_STRING( VRInitError_Init_InvalidInterface ); + RETURN_ENUM_AS_STRING( VRInitError_Init_UserConfigDirectoryInvalid ); + RETURN_ENUM_AS_STRING( VRInitError_Init_HmdNotFound ); + RETURN_ENUM_AS_STRING( VRInitError_Init_NotInitialized ); + RETURN_ENUM_AS_STRING( VRInitError_Init_PathRegistryNotFound ); + RETURN_ENUM_AS_STRING( VRInitError_Init_NoConfigPath ); + RETURN_ENUM_AS_STRING( VRInitError_Init_NoLogPath ); + RETURN_ENUM_AS_STRING( VRInitError_Init_PathRegistryNotWritable ); + RETURN_ENUM_AS_STRING( VRInitError_Init_AppInfoInitFailed ); + RETURN_ENUM_AS_STRING( VRInitError_Init_Retry ); + RETURN_ENUM_AS_STRING( VRInitError_Init_InitCanceledByUser ); + RETURN_ENUM_AS_STRING( VRInitError_Init_AnotherAppLaunching ); + RETURN_ENUM_AS_STRING( VRInitError_Init_SettingsInitFailed ); + RETURN_ENUM_AS_STRING( VRInitError_Init_ShuttingDown ); + RETURN_ENUM_AS_STRING( VRInitError_Init_TooManyObjects ); + RETURN_ENUM_AS_STRING( VRInitError_Init_NoServerForBackgroundApp ); + RETURN_ENUM_AS_STRING( VRInitError_Init_NotSupportedWithCompositor ); + RETURN_ENUM_AS_STRING( VRInitError_Init_NotAvailableToUtilityApps ); + RETURN_ENUM_AS_STRING( VRInitError_Init_Internal ); + RETURN_ENUM_AS_STRING( VRInitError_Init_HmdDriverIdIsNone ); + RETURN_ENUM_AS_STRING( VRInitError_Init_HmdNotFoundPresenceFailed ); + RETURN_ENUM_AS_STRING( VRInitError_Init_VRMonitorNotFound ); + RETURN_ENUM_AS_STRING( VRInitError_Init_VRMonitorStartupFailed ); + RETURN_ENUM_AS_STRING( VRInitError_Init_LowPowerWatchdogNotSupported ); + RETURN_ENUM_AS_STRING( VRInitError_Init_InvalidApplicationType ); + RETURN_ENUM_AS_STRING( VRInitError_Init_NotAvailableToWatchdogApps ); + RETURN_ENUM_AS_STRING( VRInitError_Init_WatchdogDisabledInSettings ); + RETURN_ENUM_AS_STRING( VRInitError_Init_VRDashboardNotFound ); + RETURN_ENUM_AS_STRING( VRInitError_Init_VRDashboardStartupFailed ); + RETURN_ENUM_AS_STRING( VRInitError_Init_VRHomeNotFound ); + RETURN_ENUM_AS_STRING( VRInitError_Init_VRHomeStartupFailed ); + RETURN_ENUM_AS_STRING( VRInitError_Init_RebootingBusy ); + RETURN_ENUM_AS_STRING( VRInitError_Init_FirmwareUpdateBusy ); + RETURN_ENUM_AS_STRING( VRInitError_Init_FirmwareRecoveryBusy ); + RETURN_ENUM_AS_STRING( VRInitError_Init_USBServiceBusy ); + RETURN_ENUM_AS_STRING( VRInitError_Init_VRWebHelperStartupFailed ); + RETURN_ENUM_AS_STRING( VRInitError_Init_TrackerManagerInitFailed ); + RETURN_ENUM_AS_STRING( VRInitError_Init_AlreadyRunning ); + RETURN_ENUM_AS_STRING( VRInitError_Init_FailedForVrMonitor); + RETURN_ENUM_AS_STRING( VRInitError_Init_PropertyManagerInitFailed ); + RETURN_ENUM_AS_STRING( VRInitError_Init_WebServerFailed ); + + RETURN_ENUM_AS_STRING( VRInitError_Driver_Failed ); + RETURN_ENUM_AS_STRING( VRInitError_Driver_Unknown ); + RETURN_ENUM_AS_STRING( VRInitError_Driver_HmdUnknown); + RETURN_ENUM_AS_STRING( VRInitError_Driver_NotLoaded); + RETURN_ENUM_AS_STRING( VRInitError_Driver_RuntimeOutOfDate); + RETURN_ENUM_AS_STRING( VRInitError_Driver_HmdInUse); + RETURN_ENUM_AS_STRING( VRInitError_Driver_NotCalibrated); + RETURN_ENUM_AS_STRING( VRInitError_Driver_CalibrationInvalid); + RETURN_ENUM_AS_STRING( VRInitError_Driver_HmdDisplayNotFound); + RETURN_ENUM_AS_STRING( VRInitError_Driver_TrackedDeviceInterfaceUnknown ); + // RETURN_ENUM_AS_STRING( VRInitError_Driver_HmdDisplayNotFoundAfterFix ); + RETURN_ENUM_AS_STRING( VRInitError_Driver_HmdDriverIdOutOfBounds ); + RETURN_ENUM_AS_STRING( VRInitError_Driver_HmdDisplayMirrored ); + RETURN_ENUM_AS_STRING( VRInitError_Driver_HmdDisplayNotFoundLaptop ); + + RETURN_ENUM_AS_STRING( VRInitError_IPC_ServerInitFailed); + RETURN_ENUM_AS_STRING( VRInitError_IPC_ConnectFailed); + RETURN_ENUM_AS_STRING( VRInitError_IPC_SharedStateInitFailed); + RETURN_ENUM_AS_STRING( VRInitError_IPC_CompositorInitFailed); + RETURN_ENUM_AS_STRING( VRInitError_IPC_MutexInitFailed); + RETURN_ENUM_AS_STRING( VRInitError_IPC_Failed); + RETURN_ENUM_AS_STRING( VRInitError_IPC_CompositorConnectFailed); + RETURN_ENUM_AS_STRING( VRInitError_IPC_CompositorInvalidConnectResponse); + RETURN_ENUM_AS_STRING( VRInitError_IPC_ConnectFailedAfterMultipleAttempts ); + RETURN_ENUM_AS_STRING( VRInitError_IPC_ConnectFailedAfterTargetExited ); + RETURN_ENUM_AS_STRING( VRInitError_IPC_NamespaceUnavailable ); + + RETURN_ENUM_AS_STRING( VRInitError_Compositor_Failed ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_D3D11HardwareRequired ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_FirmwareRequiresUpdate ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_OverlayInitFailed ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_ScreenshotsInitFailed ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_UnableToCreateDevice ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_SharedStateIsNull ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_NotificationManagerIsNull ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_ResourceManagerClientIsNull ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_MessageOverlaySharedStateInitFailure ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_PropertiesInterfaceIsNull ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_CreateFullscreenWindowFailed ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_SettingsInterfaceIsNull ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_FailedToShowWindow ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_DistortInterfaceIsNull ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_DisplayFrequencyFailure ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_RendererInitializationFailed ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_DXGIFactoryInterfaceIsNull ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_DXGIFactoryCreateFailed ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_DXGIFactoryQueryFailed ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_InvalidAdapterDesktop ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_InvalidHmdAttachment ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_InvalidOutputDesktop ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_InvalidDeviceProvided ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_D3D11RendererInitializationFailed ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_FailedToFindDisplayMode ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_FailedToCreateSwapChain ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_FailedToGetBackBuffer ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_FailedToCreateRenderTarget ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_FailedToCreateDXGI2SwapChain ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_FailedtoGetDXGI2BackBuffer ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_FailedToCreateDXGI2RenderTarget ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_FailedToGetDXGIDeviceInterface ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_SelectDisplayMode ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_FailedToCreateNvAPIRenderTargets ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_NvAPISetDisplayMode ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_FailedToCreateDirectModeDisplay ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_InvalidHmdPropertyContainer ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_UpdateDisplayFrequency ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_CreateRasterizerState ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_CreateWireframeRasterizerState ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_CreateSamplerState ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_CreateClampToBorderSamplerState ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_CreateAnisoSamplerState ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_CreateOverlaySamplerState ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_CreatePanoramaSamplerState ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_CreateFontSamplerState ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_CreateNoBlendState ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_CreateBlendState ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_CreateAlphaBlendState ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_CreateBlendStateMaskR ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_CreateBlendStateMaskG ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_CreateBlendStateMaskB ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_CreateDepthStencilState ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_CreateDepthStencilStateNoWrite ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_CreateDepthStencilStateNoDepth ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_CreateFlushTexture ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_CreateDistortionSurfaces ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_CreateConstantBuffer ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_CreateHmdPoseConstantBuffer ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_CreateHmdPoseStagingConstantBuffer ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_CreateSharedFrameInfoConstantBuffer ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_CreateOverlayConstantBuffer ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_CreateSceneTextureIndexConstantBuffer ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_CreateReadableSceneTextureIndexConstantBuffer ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_CreateLayerGraphicsTextureIndexConstantBuffer ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_CreateLayerComputeTextureIndexConstantBuffer ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_CreateLayerComputeSceneTextureIndexConstantBuffer ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_CreateComputeHmdPoseConstantBuffer ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_CreateGeomConstantBuffer ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_CreatePanelMaskConstantBuffer ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_CreatePixelSimUBO ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_CreateMSAARenderTextures ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_CreateResolveRenderTextures ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_CreateComputeResolveRenderTextures ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_CreateDriverDirectModeResolveTextures ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_OpenDriverDirectModeResolveTextures ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_CreateFallbackSyncTexture ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_ShareFallbackSyncTexture ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_CreateOverlayIndexBuffer ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_CreateOverlayVertexBuffer ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_CreateTextVertexBuffer ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_CreateTextIndexBuffer ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_CreateMirrorTextures ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_CreateLastFrameRenderTexture ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_CreateMirrorOverlay ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_FailedToCreateVirtualDisplayBackbuffer ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_DisplayModeNotSupported ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_CreateOverlayInvalidCall ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_CreateOverlayAlreadyInitialized ); + RETURN_ENUM_AS_STRING( VRInitError_Compositor_FailedToCreateMailbox ); + + // Vendor-specific errors + RETURN_ENUM_AS_STRING( VRInitError_VendorSpecific_UnableToConnectToOculusRuntime); + RETURN_ENUM_AS_STRING( VRInitError_VendorSpecific_WindowsNotInDevMode ); + RETURN_ENUM_AS_STRING( VRInitError_VendorSpecific_OculusRuntimeBadInstall ); + + // Lighthouse + RETURN_ENUM_AS_STRING( VRInitError_VendorSpecific_HmdFound_CantOpenDevice); + RETURN_ENUM_AS_STRING( VRInitError_VendorSpecific_HmdFound_UnableToRequestConfigStart); + RETURN_ENUM_AS_STRING( VRInitError_VendorSpecific_HmdFound_NoStoredConfig); + RETURN_ENUM_AS_STRING( VRInitError_VendorSpecific_HmdFound_ConfigFailedSanityCheck ); + RETURN_ENUM_AS_STRING( VRInitError_VendorSpecific_HmdFound_ConfigTooBig ); + RETURN_ENUM_AS_STRING( VRInitError_VendorSpecific_HmdFound_ConfigTooSmall ); + RETURN_ENUM_AS_STRING( VRInitError_VendorSpecific_HmdFound_UnableToInitZLib ); + RETURN_ENUM_AS_STRING( VRInitError_VendorSpecific_HmdFound_CantReadFirmwareVersion ); + RETURN_ENUM_AS_STRING( VRInitError_VendorSpecific_HmdFound_UnableToSendUserDataStart ); + RETURN_ENUM_AS_STRING( VRInitError_VendorSpecific_HmdFound_UnableToGetUserDataStart ); + RETURN_ENUM_AS_STRING( VRInitError_VendorSpecific_HmdFound_UnableToGetUserDataNext ); + RETURN_ENUM_AS_STRING( VRInitError_VendorSpecific_HmdFound_UserDataAddressRange ); + RETURN_ENUM_AS_STRING( VRInitError_VendorSpecific_HmdFound_UserDataError ); + + RETURN_ENUM_AS_STRING( VRInitError_Steam_SteamInstallationNotFound ); + + default: + { + static char buf[128]; + sprintf( buf, "Unknown error (%d)", eError ); + return buf; + } + } +} + diff --git a/gfx/vr/service/openvr/src/hmderrors_public.h b/gfx/vr/service/openvr/src/hmderrors_public.h new file mode 100644 index 0000000000..ccd6c8a96c --- /dev/null +++ b/gfx/vr/service/openvr/src/hmderrors_public.h @@ -0,0 +1,6 @@ +//========= Copyright Valve Corporation ============// +#pragma once + +const char *GetEnglishStringForHmdError( vr::EVRInitError eError ); +const char *GetIDForVRInitError( vr::EVRInitError eError ); + diff --git a/gfx/vr/service/openvr/src/ivrclientcore.h b/gfx/vr/service/openvr/src/ivrclientcore.h new file mode 100644 index 0000000000..6884e7fbc8 --- /dev/null +++ b/gfx/vr/service/openvr/src/ivrclientcore.h @@ -0,0 +1,35 @@ +//========= Copyright Valve Corporation ============// +#pragma once + +namespace vr +{ + +class IVRClientCore +{ +public: + /** Initializes the system */ + virtual EVRInitError Init( vr::EVRApplicationType eApplicationType, const char *pStartupInfo ) = 0; + + /** cleans up everything in vrclient.dll and prepares the DLL to be unloaded */ + virtual void Cleanup() = 0; + + /** checks to see if the specified interface/version is supported in this vrclient.dll */ + virtual EVRInitError IsInterfaceVersionValid( const char *pchInterfaceVersion ) = 0; + + /** Retrieves any interface from vrclient.dll */ + virtual void *GetGenericInterface( const char *pchNameAndVersion, EVRInitError *peError ) = 0; + + /** Returns true if any driver has an HMD attached. Can be called outside of Init/Cleanup */ + virtual bool BIsHmdPresent() = 0; + + /** Returns an English error string from inside vrclient.dll which might be newer than the API DLL */ + virtual const char *GetEnglishStringForHmdError( vr::EVRInitError eError ) = 0; + + /** Returns an error symbol from inside vrclient.dll which might be newer than the API DLL */ + virtual const char *GetIDForVRInitError( vr::EVRInitError eError ) = 0; +}; + +static const char * const IVRClientCore_Version = "IVRClientCore_003"; + + +} diff --git a/gfx/vr/service/openvr/src/openvr_api_public.cpp b/gfx/vr/service/openvr/src/openvr_api_public.cpp new file mode 100644 index 0000000000..54aa555955 --- /dev/null +++ b/gfx/vr/service/openvr/src/openvr_api_public.cpp @@ -0,0 +1,352 @@ +//========= Copyright Valve Corporation ============// +#define VR_API_EXPORT 1 +#include "openvr.h" +#include "ivrclientcore.h" +#include "pathtools_public.h" +#include "sharedlibtools_public.h" +#include "envvartools_public.h" +#include "hmderrors_public.h" +#include "strtools_public.h" +#include "vrpathregistry_public.h" +#include <mutex> + +using vr::EVRInitError; +using vr::IVRSystem; +using vr::IVRClientCore; +using vr::VRInitError_None; + +// figure out how to import from the VR API dll +#if defined(_WIN32) + +#if !defined(OPENVR_BUILD_STATIC) +#define VR_EXPORT_INTERFACE extern "C" __declspec( dllexport ) +#else +#define VR_EXPORT_INTERFACE extern "C" +#endif + +#elif defined(__GNUC__) || defined(COMPILER_GCC) || defined(__APPLE__) + +#define VR_EXPORT_INTERFACE extern "C" __attribute__((visibility("default"))) + +#else +#error "Unsupported Platform." +#endif + +namespace vr +{ + +static void *g_pVRModule = NULL; +static IVRClientCore *g_pHmdSystem = NULL; +static std::recursive_mutex g_mutexSystem; + + +typedef void* (*VRClientCoreFactoryFn)(const char *pInterfaceName, int *pReturnCode); + +static uint32_t g_nVRToken = 0; + +uint32_t VR_GetInitToken() +{ + return g_nVRToken; +} + +EVRInitError VR_LoadHmdSystemInternal(); +void CleanupInternalInterfaces(); + + +uint32_t VR_InitInternal2( EVRInitError *peError, vr::EVRApplicationType eApplicationType, const char *pStartupInfo ) +{ + std::lock_guard<std::recursive_mutex> lock( g_mutexSystem ); + + EVRInitError err = VR_LoadHmdSystemInternal(); + if ( err == vr::VRInitError_None ) + { + err = g_pHmdSystem->Init( eApplicationType, pStartupInfo ); + } + + if ( peError ) + *peError = err; + + if ( err != VRInitError_None ) + { + SharedLib_Unload( g_pVRModule ); + g_pHmdSystem = NULL; + g_pVRModule = NULL; + + return 0; + } + + return ++g_nVRToken; +} + +VR_INTERFACE uint32_t VR_CALLTYPE VR_InitInternal( EVRInitError *peError, EVRApplicationType eApplicationType ); + +uint32_t VR_InitInternal( EVRInitError *peError, vr::EVRApplicationType eApplicationType ) +{ + return VR_InitInternal2( peError, eApplicationType, nullptr ); +} + +void VR_ShutdownInternal() +{ + std::lock_guard<std::recursive_mutex> lock( g_mutexSystem ); + + if ( g_pHmdSystem ) + { + g_pHmdSystem->Cleanup(); + g_pHmdSystem = NULL; + } + if ( g_pVRModule ) + { + SharedLib_Unload( g_pVRModule ); + g_pVRModule = NULL; + } + +#if !defined( VR_API_PUBLIC ) + CleanupInternalInterfaces(); +#endif + + ++g_nVRToken; +} + +EVRInitError VR_LoadHmdSystemInternal() +{ + std::string sRuntimePath, sConfigPath, sLogPath; + + bool bReadPathRegistry = CVRPathRegistry_Public::GetPaths( &sRuntimePath, &sConfigPath, &sLogPath, NULL, NULL ); + if( !bReadPathRegistry ) + { + return vr::VRInitError_Init_PathRegistryNotFound; + } + + // figure out where we're going to look for vrclient.dll + // see if the specified path actually exists. + if( !Path_IsDirectory( sRuntimePath ) ) + { + return vr::VRInitError_Init_InstallationNotFound; + } + + // Because we don't have a way to select debug vs. release yet we'll just + // use debug if it's there +#if defined( LINUX64 ) || defined( LINUXARM64 ) + std::string sTestPath = Path_Join( sRuntimePath, "bin", PLATSUBDIR ); +#else + std::string sTestPath = Path_Join( sRuntimePath, "bin" ); +#endif + if( !Path_IsDirectory( sTestPath ) ) + { + return vr::VRInitError_Init_InstallationCorrupt; + } + +#if defined( WIN64 ) + std::string sDLLPath = Path_Join( sTestPath, "vrclient_x64" DYNAMIC_LIB_EXT ); +#else + std::string sDLLPath = Path_Join( sTestPath, "vrclient" DYNAMIC_LIB_EXT ); +#endif + + // only look in the override + void *pMod = SharedLib_Load( sDLLPath.c_str() ); + // nothing more to do if we can't load the DLL + if( !pMod ) + { + return vr::VRInitError_Init_VRClientDLLNotFound; + } + + VRClientCoreFactoryFn fnFactory = ( VRClientCoreFactoryFn )( SharedLib_GetFunction( pMod, "VRClientCoreFactory" ) ); + if( !fnFactory ) + { + SharedLib_Unload( pMod ); + return vr::VRInitError_Init_FactoryNotFound; + } + + int nReturnCode = 0; + g_pHmdSystem = static_cast< IVRClientCore * > ( fnFactory( vr::IVRClientCore_Version, &nReturnCode ) ); + if( !g_pHmdSystem ) + { + SharedLib_Unload( pMod ); + return vr::VRInitError_Init_InterfaceNotFound; + } + + g_pVRModule = pMod; + return VRInitError_None; +} + + +void *VR_GetGenericInterface(const char *pchInterfaceVersion, EVRInitError *peError) +{ + std::lock_guard<std::recursive_mutex> lock( g_mutexSystem ); + + if (!g_pHmdSystem) + { + if (peError) + *peError = vr::VRInitError_Init_NotInitialized; + return NULL; + } + + return g_pHmdSystem->GetGenericInterface(pchInterfaceVersion, peError); +} + +bool VR_IsInterfaceVersionValid(const char *pchInterfaceVersion) +{ + std::lock_guard<std::recursive_mutex> lock( g_mutexSystem ); + + if (!g_pHmdSystem) + { + return false; + } + + return g_pHmdSystem->IsInterfaceVersionValid(pchInterfaceVersion) == VRInitError_None; +} + +bool VR_IsHmdPresent() +{ + std::lock_guard<std::recursive_mutex> lock( g_mutexSystem ); + + if( g_pHmdSystem ) + { + // if we're already initialized, just call through + return g_pHmdSystem->BIsHmdPresent(); + } + else + { + // otherwise we need to do a bit more work + EVRInitError err = VR_LoadHmdSystemInternal(); + if( err != VRInitError_None ) + return false; + + bool bHasHmd = g_pHmdSystem->BIsHmdPresent(); + + g_pHmdSystem = NULL; + SharedLib_Unload( g_pVRModule ); + g_pVRModule = NULL; + + return bHasHmd; + } +} + +/** Returns true if the OpenVR runtime is installed. */ +bool VR_IsRuntimeInstalled() +{ + std::lock_guard<std::recursive_mutex> lock( g_mutexSystem ); + + if( g_pHmdSystem ) + { + // if we're already initialized, OpenVR is obviously installed + return true; + } + else + { + // otherwise we need to do a bit more work + std::string sRuntimePath, sConfigPath, sLogPath; + + bool bReadPathRegistry = CVRPathRegistry_Public::GetPaths( &sRuntimePath, &sConfigPath, &sLogPath, NULL, NULL ); + if( !bReadPathRegistry ) + { + return false; + } + + // figure out where we're going to look for vrclient.dll + // see if the specified path actually exists. + if( !Path_IsDirectory( sRuntimePath ) ) + { + return false; + } + + // the installation may be corrupt in some way, but it certainly looks installed + return true; + } +} + + +// ------------------------------------------------------------------------------- +// Purpose: This is the old Runtime Path interface that is no longer exported in the +// latest header. We still want to export it from the DLL, though, so updating +// to a new DLL doesn't break old compiled code. This version was not thread +// safe and could change the buffer pointer to by a previous result on a +// subsequent call +// ------------------------------------------------------------------------------- +VR_EXPORT_INTERFACE const char *VR_CALLTYPE VR_RuntimePath(); + +/** Returns where OpenVR runtime is installed. */ +const char *VR_RuntimePath() +{ + static char rchBuffer[1024]; + uint32_t unRequiredSize; + if ( VR_GetRuntimePath( rchBuffer, sizeof( rchBuffer ), &unRequiredSize ) && unRequiredSize < sizeof( rchBuffer ) ) + { + return rchBuffer; + } + else + { + return nullptr; + } +} + + +/** Returns where OpenVR runtime is installed. */ +bool VR_GetRuntimePath( char *pchPathBuffer, uint32_t unBufferSize, uint32_t *punRequiredBufferSize ) +{ + // otherwise we need to do a bit more work + std::string sRuntimePath; + + *punRequiredBufferSize = 0; + + bool bReadPathRegistry = CVRPathRegistry_Public::GetPaths( &sRuntimePath, nullptr, nullptr, nullptr, nullptr ); + if ( !bReadPathRegistry ) + { + return false; + } + + // figure out where we're going to look for vrclient.dll + // see if the specified path actually exists. + if ( !Path_IsDirectory( sRuntimePath ) ) + { + return false; + } + + *punRequiredBufferSize = (uint32_t)sRuntimePath.size() + 1; + if ( sRuntimePath.size() >= unBufferSize ) + { + *pchPathBuffer = '\0'; + } + else + { + strcpy_safe( pchPathBuffer, unBufferSize, sRuntimePath.c_str() ); + } + + return true; +} + + +/** Returns the symbol version of an HMD error. */ +const char *VR_GetVRInitErrorAsSymbol( EVRInitError error ) +{ + std::lock_guard<std::recursive_mutex> lock( g_mutexSystem ); + + if( g_pHmdSystem ) + return g_pHmdSystem->GetIDForVRInitError( error ); + else + return GetIDForVRInitError( error ); +} + + +/** Returns the english string version of an HMD error. */ +const char *VR_GetVRInitErrorAsEnglishDescription( EVRInitError error ) +{ + std::lock_guard<std::recursive_mutex> lock( g_mutexSystem ); + + if ( g_pHmdSystem ) + return g_pHmdSystem->GetEnglishStringForHmdError( error ); + else + return GetEnglishStringForHmdError( error ); +} + + +VR_INTERFACE const char *VR_CALLTYPE VR_GetStringForHmdError( vr::EVRInitError error ); + +/** Returns the english string version of an HMD error. */ +const char *VR_GetStringForHmdError( EVRInitError error ) +{ + return VR_GetVRInitErrorAsEnglishDescription( error ); +} + +} + diff --git a/gfx/vr/service/openvr/src/pathtools_public.cpp b/gfx/vr/service/openvr/src/pathtools_public.cpp new file mode 100644 index 0000000000..e7f6d6ca1b --- /dev/null +++ b/gfx/vr/service/openvr/src/pathtools_public.cpp @@ -0,0 +1,901 @@ +//========= Copyright Valve Corporation ============// +#include "strtools_public.h" +#include "pathtools_public.h" + +#if defined( _WIN32) +#include <windows.h> +#include <direct.h> +#include <shobjidl.h> +#include <knownfolders.h> +#include <shlobj.h> +#include <share.h> + +#undef GetEnvironmentVariable +#else +#include <dlfcn.h> +#include <stdio.h> +#include <unistd.h> +#include <stdlib.h> +#include <alloca.h> +#endif + +#if defined OSX +#include <Foundation/Foundation.h> +#include <AppKit/AppKit.h> +#include <mach-o/dyld.h> +#define _S_IFDIR S_IFDIR // really from tier0/platform.h which we dont have yet +#endif + +#include <sys/stat.h> + +#include <algorithm> + +/** Returns the path (including filename) to the current executable */ +std::string Path_GetExecutablePath() +{ +#if defined( _WIN32 ) + wchar_t *pwchPath = new wchar_t[MAX_UNICODE_PATH]; + char *pchPath = new char[MAX_UNICODE_PATH_IN_UTF8]; + ::GetModuleFileNameW( NULL, pwchPath, MAX_UNICODE_PATH ); + WideCharToMultiByte( CP_UTF8, 0, pwchPath, -1, pchPath, MAX_UNICODE_PATH_IN_UTF8, NULL, NULL ); + delete[] pwchPath; + + std::string sPath = pchPath; + delete[] pchPath; + return sPath; +#elif defined( OSX ) + char rchPath[1024]; + uint32_t nBuff = sizeof( rchPath ); + bool bSuccess = _NSGetExecutablePath(rchPath, &nBuff) == 0; + rchPath[nBuff-1] = '\0'; + if( bSuccess ) + return rchPath; + else + return ""; +#elif defined LINUX + char rchPath[1024]; + size_t nBuff = sizeof( rchPath ); + ssize_t nRead = readlink("/proc/self/exe", rchPath, nBuff-1 ); + if ( nRead != -1 ) + { + rchPath[ nRead ] = 0; + return rchPath; + } + else + { + return ""; + } +#else + AssertMsg( false, "Implement Plat_GetExecutablePath" ); + return ""; +#endif + +} + +/** Returns the path of the current working directory */ +std::string Path_GetWorkingDirectory() +{ + std::string sPath; +#if defined( _WIN32 ) + wchar_t buf[MAX_UNICODE_PATH]; + sPath = UTF16to8( _wgetcwd( buf, MAX_UNICODE_PATH ) ); +#else + char buf[ 1024 ]; + sPath = getcwd( buf, sizeof( buf ) ); +#endif + return sPath; +} + +/** Sets the path of the current working directory. Returns true if this was successful. */ +bool Path_SetWorkingDirectory( const std::string & sPath ) +{ + bool bSuccess; +#if defined( _WIN32 ) + std::wstring wsPath = UTF8to16( sPath.c_str() ); + bSuccess = 0 == _wchdir( wsPath.c_str() ); +#else + bSuccess = 0 == chdir( sPath.c_str() ); +#endif + return bSuccess; +} + +/** Gets the path to a temporary directory. */ +std::string Path_GetTemporaryDirectory() +{ +#if defined( _WIN32 ) + wchar_t buf[MAX_UNICODE_PATH]; + if ( GetTempPathW( MAX_UNICODE_PATH, buf ) == 0 ) + return Path_GetWorkingDirectory(); + return UTF16to8( buf ); +#else + const char *pchTmpDir = getenv( "TMPDIR" ); + if ( pchTmpDir == NULL ) + { + return ""; + } + return pchTmpDir; +#endif +} + +/** Returns the specified path without its filename */ +std::string Path_StripFilename( const std::string & sPath, char slash ) +{ + if( slash == 0 ) + slash = Path_GetSlash(); + + std::string::size_type n = sPath.find_last_of( slash ); + if( n == std::string::npos ) + return sPath; + else + return std::string( sPath.begin(), sPath.begin() + n ); +} + +/** returns just the filename from the provided full or relative path. */ +std::string Path_StripDirectory( const std::string & sPath, char slash ) +{ + if( slash == 0 ) + slash = Path_GetSlash(); + + std::string::size_type n = sPath.find_last_of( slash ); + if( n == std::string::npos ) + return sPath; + else + return std::string( sPath.begin() + n + 1, sPath.end() ); +} + +/** returns just the filename with no extension of the provided filename. +* If there is a path the path is left intact. */ +std::string Path_StripExtension( const std::string & sPath ) +{ + for( std::string::const_reverse_iterator i = sPath.rbegin(); i != sPath.rend(); i++ ) + { + if( *i == '.' ) + { + return std::string( sPath.begin(), i.base() - 1 ); + } + + // if we find a slash there is no extension + if( *i == '\\' || *i == '/' ) + break; + } + + // we didn't find an extension + return sPath; +} + +/** returns just extension of the provided filename (if any). */ +std::string Path_GetExtension( const std::string & sPath ) +{ + for ( std::string::const_reverse_iterator i = sPath.rbegin(); i != sPath.rend(); i++ ) + { + if ( *i == '.' ) + { + return std::string( i.base(), sPath.end() ); + } + + // if we find a slash there is no extension + if ( *i == '\\' || *i == '/' ) + break; + } + + // we didn't find an extension + return ""; +} + +bool Path_IsAbsolute( const std::string & sPath ) +{ + if( sPath.empty() ) + return false; + +#if defined( WIN32 ) + if ( sPath.size() < 3 ) // must be c:\x or \\x at least + return false; + + if ( sPath[1] == ':' ) // drive letter plus slash, but must test both slash cases + { + if ( sPath[2] == '\\' || sPath[2] == '/' ) + return true; + } + else if ( sPath[0] == '\\' && sPath[1] == '\\' ) // UNC path + return true; +#else + if( sPath[0] == '\\' || sPath[0] == '/' ) // any leading slash + return true; +#endif + + return false; +} + + +/** Makes an absolute path from a relative path and a base path */ +std::string Path_MakeAbsolute( const std::string & sRelativePath, const std::string & sBasePath ) +{ + if( Path_IsAbsolute( sRelativePath ) ) + return Path_Compact( sRelativePath ); + else + { + if( !Path_IsAbsolute( sBasePath ) ) + return ""; + + std::string sCompacted = Path_Compact( Path_Join( sBasePath, sRelativePath ) ); + if( Path_IsAbsolute( sCompacted ) ) + return sCompacted; + else + return ""; + } +} + + +/** Fixes the directory separators for the current platform */ +std::string Path_FixSlashes( const std::string & sPath, char slash ) +{ + if( slash == 0 ) + slash = Path_GetSlash(); + + std::string sFixed = sPath; + for( std::string::iterator i = sFixed.begin(); i != sFixed.end(); i++ ) + { + if( *i == '/' || *i == '\\' ) + *i = slash; + } + + return sFixed; +} + + +char Path_GetSlash() +{ +#if defined(_WIN32) + return '\\'; +#else + return '/'; +#endif +} + +/** Jams two paths together with the right kind of slash */ +std::string Path_Join( const std::string & first, const std::string & second, char slash ) +{ + if( slash == 0 ) + slash = Path_GetSlash(); + + // only insert a slash if we don't already have one + std::string::size_type nLen = first.length(); + if( !nLen ) + return second; +#if defined(_WIN32) + if( first.back() == '\\' || first.back() == '/' ) + nLen--; +#else + char last_char = first[first.length()-1]; + if (last_char == '\\' || last_char == '/') + nLen--; +#endif + + return first.substr( 0, nLen ) + std::string( 1, slash ) + second; +} + + +std::string Path_Join( const std::string & first, const std::string & second, const std::string & third, char slash ) +{ + return Path_Join( Path_Join( first, second, slash ), third, slash ); +} + +std::string Path_Join( const std::string & first, const std::string & second, const std::string & third, const std::string &fourth, char slash ) +{ + return Path_Join( Path_Join( Path_Join( first, second, slash ), third, slash ), fourth, slash ); +} + +std::string Path_Join( + const std::string & first, + const std::string & second, + const std::string & third, + const std::string & fourth, + const std::string & fifth, + char slash ) +{ + return Path_Join( Path_Join( Path_Join( Path_Join( first, second, slash ), third, slash ), fourth, slash ), fifth, slash ); +} + + +std::string Path_RemoveTrailingSlash( const std::string & sRawPath, char slash ) +{ + if ( slash == 0 ) + slash = Path_GetSlash(); + + std::string sPath = sRawPath; + std::string::size_type nCurrent = sRawPath.length(); + if ( nCurrent == 0 ) + return sPath; + + int nLastFound = -1; + nCurrent--; + while( nCurrent != 0 ) + { + if ( sRawPath[ nCurrent ] == slash ) + { + nLastFound = (int)nCurrent; + nCurrent--; + } + else + { + break; + } + } + + if ( nLastFound >= 0 ) + { + sPath.erase( nLastFound, std::string::npos ); + } + + return sPath; +} + + +/** Removes redundant <dir>/.. elements in the path. Returns an empty path if the +* specified path has a broken number of directories for its number of ..s */ +std::string Path_Compact( const std::string & sRawPath, char slash ) +{ + if( slash == 0 ) + slash = Path_GetSlash(); + + std::string sPath = Path_FixSlashes( sRawPath, slash ); + std::string sSlashString( 1, slash ); + + // strip out all /./ + for( std::string::size_type i = 0; (i + 3) < sPath.length(); ) + { + if( sPath[ i ] == slash && sPath[ i+1 ] == '.' && sPath[ i+2 ] == slash ) + { + sPath.replace( i, 3, sSlashString ); + } + else + { + ++i; + } + } + + + // get rid of trailing /. but leave the path separator + if( sPath.length() > 2 ) + { + std::string::size_type len = sPath.length(); + if( sPath[ len-1 ] == '.' && sPath[ len-2 ] == slash ) + { + sPath.pop_back(); + //Not sure why the following line of code was used for a while. It causes problems with strlen. + //sPath[len-1] = 0; // for now, at least + } + } + + // get rid of leading ./ + if( sPath.length() > 2 ) + { + if( sPath[ 0 ] == '.' && sPath[ 1 ] == slash ) + { + sPath.replace( 0, 2, "" ); + } + } + + // each time we encounter .. back up until we've found the previous directory name + // then get rid of both + std::string::size_type i = 0; + while( i < sPath.length() ) + { + if( i > 0 && sPath.length() - i >= 2 + && sPath[i] == '.' + && sPath[i+1] == '.' + && ( i + 2 == sPath.length() || sPath[ i+2 ] == slash ) + && sPath[ i-1 ] == slash ) + { + // check if we've hit the start of the string and have a bogus path + if( i == 1 ) + return ""; + + // find the separator before i-1 + std::string::size_type iDirStart = i-2; + while( iDirStart > 0 && sPath[ iDirStart - 1 ] != slash ) + --iDirStart; + + // remove everything from iDirStart to i+2 + sPath.replace( iDirStart, (i - iDirStart) + 3, "" ); + + // start over + i = 0; + } + else + { + ++i; + } + } + + return sPath; +} + + +/** Returns true if these two paths are the same without respect for internal . or .. +* sequences, slash type, or case (on case-insensitive platforms). */ +bool Path_IsSamePath( const std::string & sPath1, const std::string & sPath2 ) +{ + std::string sCompact1 = Path_Compact( sPath1 ); + std::string sCompact2 = Path_Compact( sPath2 ); +#if defined(WIN32) + return !stricmp( sCompact1.c_str(), sCompact2.c_str() ); +#else + return !strcmp( sCompact1.c_str(), sCompact2.c_str() ); +#endif +} + + +/** Returns the path to the current DLL or exe */ +std::string Path_GetThisModulePath() +{ + // gets the path of vrclient.dll itself +#ifdef WIN32 + HMODULE hmodule = NULL; + + ::GetModuleHandleEx( GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, reinterpret_cast<LPCTSTR>(Path_GetThisModulePath), &hmodule ); + + wchar_t *pwchPath = new wchar_t[MAX_UNICODE_PATH]; + char *pchPath = new char[ MAX_UNICODE_PATH_IN_UTF8 ]; + ::GetModuleFileNameW( hmodule, pwchPath, MAX_UNICODE_PATH ); + WideCharToMultiByte( CP_UTF8, 0, pwchPath, -1, pchPath, MAX_UNICODE_PATH_IN_UTF8, NULL, NULL ); + delete[] pwchPath; + + std::string sPath = pchPath; + delete [] pchPath; + return sPath; + +#elif defined( OSX ) || defined( LINUX ) + // get the addr of a function in vrclient.so and then ask the dlopen system about it + Dl_info info; + dladdr( (void *)Path_GetThisModulePath, &info ); + return info.dli_fname; +#endif + +} + + +/** returns true if the specified path exists and is a directory */ +bool Path_IsDirectory( const std::string & sPath ) +{ + std::string sFixedPath = Path_FixSlashes( sPath ); + if( sFixedPath.empty() ) + return false; + char cLast = sFixedPath[ sFixedPath.length() - 1 ]; + if( cLast == '/' || cLast == '\\' ) + sFixedPath.erase( sFixedPath.end() - 1, sFixedPath.end() ); + + // see if the specified path actually exists. + +#if defined(POSIX) + struct stat buf; + if ( stat( sFixedPath.c_str(), &buf ) == -1 ) + { + return false; + } + +#if defined( LINUX ) || defined( OSX ) + return S_ISDIR( buf.st_mode ); +#else + return (buf.st_mode & _S_IFDIR) != 0; +#endif + +#else + struct _stat buf; + std::wstring wsFixedPath = UTF8to16( sFixedPath.c_str() ); + if ( _wstat( wsFixedPath.c_str(), &buf ) == -1 ) + { + return false; + } + + return (buf.st_mode & _S_IFDIR) != 0; +#endif +} + +/** returns true if the specified path represents an app bundle */ +bool Path_IsAppBundle( const std::string & sPath ) +{ +#if defined(OSX) + @autoreleasepool { + NSBundle *bundle = [ NSBundle bundleWithPath: [ NSString stringWithUTF8String:sPath.c_str() ] ]; + bool bisAppBundle = ( nullptr != bundle ); + return bisAppBundle; + } +#else + return false; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: returns true if the the path exists +//----------------------------------------------------------------------------- +bool Path_Exists( const std::string & sPath ) +{ + std::string sFixedPath = Path_FixSlashes( sPath ); + if( sFixedPath.empty() ) + return false; + +#if defined( WIN32 ) + struct _stat buf; + std::wstring wsFixedPath = UTF8to16( sFixedPath.c_str() ); + if ( _wstat( wsFixedPath.c_str(), &buf ) == -1 ) + { + return false; + } +#else + struct stat buf; + if ( stat ( sFixedPath.c_str(), &buf ) == -1) + { + return false; + } +#endif + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: helper to find a directory upstream from a given path +//----------------------------------------------------------------------------- +std::string Path_FindParentDirectoryRecursively( const std::string &strStartDirectory, const std::string &strDirectoryName ) +{ + std::string strFoundPath = ""; + std::string strCurrentPath = Path_FixSlashes( strStartDirectory ); + if ( strCurrentPath.length() == 0 ) + return ""; + + bool bExists = Path_Exists( strCurrentPath ); + std::string strCurrentDirectoryName = Path_StripDirectory( strCurrentPath ); + if ( bExists && stricmp( strCurrentDirectoryName.c_str(), strDirectoryName.c_str() ) == 0 ) + return strCurrentPath; + + while( bExists && strCurrentPath.length() != 0 ) + { + strCurrentPath = Path_StripFilename( strCurrentPath ); + strCurrentDirectoryName = Path_StripDirectory( strCurrentPath ); + bExists = Path_Exists( strCurrentPath ); + if ( bExists && stricmp( strCurrentDirectoryName.c_str(), strDirectoryName.c_str() ) == 0 ) + return strCurrentPath; + } + + return ""; +} + + +//----------------------------------------------------------------------------- +// Purpose: helper to find a subdirectory upstream from a given path +//----------------------------------------------------------------------------- +std::string Path_FindParentSubDirectoryRecursively( const std::string &strStartDirectory, const std::string &strDirectoryName ) +{ + std::string strFoundPath = ""; + std::string strCurrentPath = Path_FixSlashes( strStartDirectory ); + if ( strCurrentPath.length() == 0 ) + return ""; + + bool bExists = Path_Exists( strCurrentPath ); + while( bExists && strCurrentPath.length() != 0 ) + { + strCurrentPath = Path_StripFilename( strCurrentPath ); + bExists = Path_Exists( strCurrentPath ); + + if( Path_Exists( Path_Join( strCurrentPath, strDirectoryName ) ) ) + { + strFoundPath = Path_Join( strCurrentPath, strDirectoryName ); + break; + } + } + return strFoundPath; +} + + +//----------------------------------------------------------------------------- +// Purpose: reading and writing files in the vortex directory +//----------------------------------------------------------------------------- +unsigned char * Path_ReadBinaryFile( const std::string &strFilename, int *pSize ) +{ + FILE *f; +#if defined( POSIX ) + f = fopen( strFilename.c_str(), "rb" ); +#else + std::wstring wstrFilename = UTF8to16( strFilename.c_str() ); + // the open operation needs to be sharable, therefore use of _wfsopen instead of _wfopen_s + f = _wfsopen( wstrFilename.c_str(), L"rb", _SH_DENYNO ); +#endif + + unsigned char* buf = NULL; + + if ( f != NULL ) + { + fseek(f, 0, SEEK_END); + int size = ftell(f); + fseek(f, 0, SEEK_SET); + + buf = new unsigned char[size]; + if (buf && fread(buf, size, 1, f) == 1) + { + if (pSize) + *pSize = size; + } + else + { + delete[] buf; + buf = 0; + } + + fclose(f); + } + + return buf; +} + +uint32_t Path_ReadBinaryFile( const std::string &strFilename, unsigned char *pBuffer, uint32_t unSize ) +{ + FILE *f; +#if defined( POSIX ) + f = fopen( strFilename.c_str(), "rb" ); +#else + std::wstring wstrFilename = UTF8to16( strFilename.c_str() ); + errno_t err = _wfopen_s( &f, wstrFilename.c_str(), L"rb" ); + if ( err != 0 ) + { + f = NULL; + } +#endif + + uint32_t unSizeToReturn = 0; + + if ( f != NULL ) + { + fseek( f, 0, SEEK_END ); + uint32_t size = (uint32_t)ftell( f ); + fseek( f, 0, SEEK_SET ); + + if ( size > unSize || !pBuffer ) + { + unSizeToReturn = (uint32_t)size; + } + else + { + if ( fread( pBuffer, size, 1, f ) == 1 ) + { + unSizeToReturn = (uint32_t)size; + } + } + + fclose( f ); + } + + return unSizeToReturn; +} + +bool Path_WriteBinaryFile(const std::string &strFilename, unsigned char *pData, unsigned nSize) +{ + FILE *f; +#if defined( POSIX ) + f = fopen(strFilename.c_str(), "wb"); +#else + std::wstring wstrFilename = UTF8to16( strFilename.c_str() ); + errno_t err = _wfopen_s( &f, wstrFilename.c_str(), L"wb" ); + if (err != 0) + { + f = NULL; + } +#endif + + size_t written = 0; + if (f != NULL) { + written = fwrite(pData, sizeof(unsigned char), nSize, f); + fclose(f); + } + + return written == nSize ? true : false; +} + +std::string Path_ReadTextFile( const std::string &strFilename ) +{ + // doing it this way seems backwards, but I don't + // see an easy way to do this with C/C++ style IO + // that isn't worse... + int size; + unsigned char* buf = Path_ReadBinaryFile( strFilename, &size ); + if (!buf) + return ""; + + // convert CRLF -> LF + size_t outsize = 1; + for (int i=1; i < size; i++) + { + if (buf[i] == '\n' && buf[i-1] == '\r') // CRLF + buf[outsize-1] = '\n'; // ->LF + else + buf[outsize++] = buf[i]; // just copy + } + + std::string ret((char *)buf, outsize); + delete[] buf; + return ret; +} + + +bool Path_MakeWritable( const std::string &strFilename ) +{ +#if defined ( _WIN32 ) + std::wstring wstrFilename = UTF8to16( strFilename.c_str() ); + + DWORD dwAttrs = GetFileAttributesW( wstrFilename.c_str() ); + if ( dwAttrs != INVALID_FILE_ATTRIBUTES && ( dwAttrs & FILE_ATTRIBUTE_READONLY ) ) + { + return SetFileAttributesW( wstrFilename.c_str(), dwAttrs & ~FILE_ATTRIBUTE_READONLY ); + } +#else + struct stat sb; + + if ( stat( strFilename.c_str(), &sb ) == 0 && !( sb.st_mode & S_IWUSR ) ) + { + return ( chmod( strFilename.c_str(), sb.st_mode | S_IWUSR ) == 0 ); + } +#endif + + return true; +} + +bool Path_WriteStringToTextFile( const std::string &strFilename, const char *pchData ) +{ + FILE *f; +#if defined( POSIX ) + f = fopen( strFilename.c_str(), "w" ); +#else + std::wstring wstrFilename = UTF8to16( strFilename.c_str() ); + errno_t err = _wfopen_s( &f, wstrFilename.c_str(), L"w" ); + if ( err != 0 ) + { + f = NULL; + } +#endif + + bool ok = false; + + if ( f != NULL ) + { + ok = fputs( pchData, f) >= 0; + fclose(f); + } + + return ok; +} + +bool Path_WriteStringToTextFileAtomic( const std::string &strFilename, const char *pchData ) +{ + std::string strTmpFilename = strFilename + ".tmp"; + + if ( !Path_WriteStringToTextFile( strTmpFilename, pchData ) ) + return false; + + // Platform specific atomic file replacement +#if defined( _WIN32 ) + std::wstring wsFilename = UTF8to16( strFilename.c_str() ); + std::wstring wsTmpFilename = UTF8to16( strTmpFilename.c_str() ); + if ( !::ReplaceFileW( wsFilename.c_str(), wsTmpFilename.c_str(), nullptr, 0, 0, 0 ) ) + { + // if we couldn't ReplaceFile, try a non-atomic write as a fallback + if ( !Path_WriteStringToTextFile( strFilename, pchData ) ) + return false; + } +#elif defined( POSIX ) + if ( rename( strTmpFilename.c_str(), strFilename.c_str() ) == -1 ) + return false; +#else +#error Do not know how to write atomic file +#endif + + return true; +} + + +#if defined(WIN32) +#define FILE_URL_PREFIX "file:///" +#else +#define FILE_URL_PREFIX "file://" +#endif + +// Mozilla: see README.mozilla for more details +// ---------------------------------------------------------------------------------------------------------------------------- +// Purpose: Turns a path to a file on disk into a URL (or just returns the value if it's already a URL) +// ---------------------------------------------------------------------------------------------------------------------------- +// std::string Path_FilePathToUrl( const std::string & sRelativePath, const std::string & sBasePath ) +// { +// if ( StringHasPrefix( sRelativePath, "http://" ) +// || StringHasPrefix( sRelativePath, "https://" ) +// || StringHasPrefix( sRelativePath, "vr-input-workshop://" ) +// || StringHasPrefix( sRelativePath, "file://" ) +// ) +// { +// return sRelativePath; +// } +// else +// { +// std::string sAbsolute = Path_MakeAbsolute( sRelativePath, sBasePath ); +// if ( sAbsolute.empty() ) +// return sAbsolute; +// sAbsolute = Path_FixSlashes( sAbsolute, '/' ); + +// size_t unBufferSize = sAbsolute.length() * 3; +// char *pchBuffer = (char *)alloca( unBufferSize ); +// V_URLEncodeFullPath( pchBuffer, (int)unBufferSize, sAbsolute.c_str(), (int)sAbsolute.length() ); + +// return std::string( FILE_URL_PREFIX ) + pchBuffer; +// } +// } + +// Mozilla: see README.mozilla for more details +// ----------------------------------------------------------------------------------------------------- +// Purpose: Strips off file:// off a URL and returns the path. For other kinds of URLs an empty string is returned +// ----------------------------------------------------------------------------------------------------- +// std::string Path_UrlToFilePath( const std::string & sFileUrl ) +// { +// if ( !strnicmp( sFileUrl.c_str(), FILE_URL_PREFIX, strlen( FILE_URL_PREFIX ) ) ) +// { +// char *pchBuffer = (char *)alloca( sFileUrl.length() ); +// V_URLDecodeNoPlusForSpace( pchBuffer, (int)sFileUrl.length(), +// sFileUrl.c_str() + strlen( FILE_URL_PREFIX ), (int)( sFileUrl.length() - strlen( FILE_URL_PREFIX ) ) ); + +// return Path_FixSlashes( pchBuffer ); +// } +// else +// { +// return ""; +// } +// } + + +// ----------------------------------------------------------------------------------------------------- +// Purpose: Returns the root of the directory the system wants us to store user documents in +// ----------------------------------------------------------------------------------------------------- +std::string GetUserDocumentsPath() +{ +#if defined( WIN32 ) + WCHAR rwchPath[MAX_PATH]; + + if ( !SUCCEEDED( SHGetFolderPathW( NULL, CSIDL_MYDOCUMENTS | CSIDL_FLAG_CREATE, NULL, 0, rwchPath ) ) ) + { + return ""; + } + + // Convert the path to UTF-8 and store in the output + std::string sUserPath = UTF16to8( rwchPath ); + + return sUserPath; +#elif defined( OSX ) + @autoreleasepool { + NSArray *paths = NSSearchPathForDirectoriesInDomains( NSDocumentDirectory, NSUserDomainMask, YES ); + if ( [paths count] == 0 ) + { + return ""; + } + + return [[paths objectAtIndex:0] UTF8String]; + } +#elif defined( LINUX ) + // @todo: not solved/changed as part of OSX - still not real - just removed old class based steam cut and paste + const char *pchHome = getenv( "HOME" ); + if ( pchHome == NULL ) + { + return ""; + } + return pchHome; +#endif +} + + +// ----------------------------------------------------------------------------------------------------- +// Purpose: deletes / unlinks a single file +// ----------------------------------------------------------------------------------------------------- +bool Path_UnlinkFile( const std::string &strFilename ) +{ +#if defined( WIN32 ) + std::wstring wsFilename = UTF8to16( strFilename.c_str() ); + return ( 0 != DeleteFileW( wsFilename.c_str() ) ); +#else + return ( 0 == unlink( strFilename.c_str() ) ); +#endif +} diff --git a/gfx/vr/service/openvr/src/pathtools_public.h b/gfx/vr/service/openvr/src/pathtools_public.h new file mode 100644 index 0000000000..33688fba2f --- /dev/null +++ b/gfx/vr/service/openvr/src/pathtools_public.h @@ -0,0 +1,150 @@ +//========= Copyright Valve Corporation ============// +#pragma once + +#include <string> +#include <stdint.h> + +/** Returns the path (including filename) to the current executable */ +std::string Path_GetExecutablePath(); + +/** Returns the path of the current working directory */ +std::string Path_GetWorkingDirectory(); + +/** Sets the path of the current working directory. Returns true if this was successful. */ +bool Path_SetWorkingDirectory( const std::string & sPath ); + +/** Gets the path to a temporary directory. */ +std::string Path_GetTemporaryDirectory(); + +/** returns the path (including filename) of the current shared lib or DLL */ +std::string Path_GetThisModulePath(); + +/** Returns the specified path without its filename. +* If slash is unspecified the native path separator of the current platform +* will be used. */ +std::string Path_StripFilename( const std::string & sPath, char slash = 0 ); + +/** returns just the filename from the provided full or relative path. */ +std::string Path_StripDirectory( const std::string & sPath, char slash = 0 ); + +/** returns just the filename with no extension of the provided filename. +* If there is a path the path is left intact. */ +std::string Path_StripExtension( const std::string & sPath ); + +/** returns just extension of the provided filename (if any). */ +std::string Path_GetExtension( const std::string & sPath ); + +/** Returns true if the path is absolute */ +bool Path_IsAbsolute( const std::string & sPath ); + +/** Makes an absolute path from a relative path and a base path */ +std::string Path_MakeAbsolute( const std::string & sRelativePath, const std::string & sBasePath ); + +/** Fixes the directory separators for the current platform. +* If slash is unspecified the native path separator of the current platform +* will be used. */ +std::string Path_FixSlashes( const std::string & sPath, char slash = 0 ); + +/** Returns the path separator for the current platform */ +char Path_GetSlash(); + +/** Jams two paths together with the right kind of slash */ +std::string Path_Join( const std::string & first, const std::string & second, char slash = 0 ); +std::string Path_Join( const std::string & first, const std::string & second, const std::string & third, char slash = 0 ); +std::string Path_Join( const std::string & first, const std::string & second, const std::string & third, const std::string &fourth, char slash = 0 ); +std::string Path_Join( + const std::string & first, + const std::string & second, + const std::string & third, + const std::string & fourth, + const std::string & fifth, + char slash = 0 ); + + +/** Removes redundant <dir>/.. elements in the path. Returns an empty path if the +* specified path has a broken number of directories for its number of ..s. +* If slash is unspecified the native path separator of the current platform +* will be used. */ +std::string Path_Compact( const std::string & sRawPath, char slash = 0 ); + +/** Returns true if these two paths are the same without respect for internal . or .. +* sequences, slash type, or case (on case-insensitive platforms). */ +bool Path_IsSamePath( const std::string & sPath1, const std::string & sPath2 ); + +//** Removed trailing slashes */ +std::string Path_RemoveTrailingSlash( const std::string & sRawPath, char slash = 0 ); + +/** returns true if the specified path exists and is a directory */ +bool Path_IsDirectory( const std::string & sPath ); + +/** returns true if the specified path represents an app bundle */ +bool Path_IsAppBundle( const std::string & sPath ); + +/** returns true if the the path exists */ +bool Path_Exists( const std::string & sPath ); + +/** Helper functions to find parent directories or subdirectories of parent directories */ +std::string Path_FindParentDirectoryRecursively( const std::string &strStartDirectory, const std::string &strDirectoryName ); +std::string Path_FindParentSubDirectoryRecursively( const std::string &strStartDirectory, const std::string &strDirectoryName ); + +/** Make a text file writable. */ +bool Path_MakeWritable( const std::string &strFilename ); + +/** Path operations to read or write text/binary files */ +unsigned char * Path_ReadBinaryFile( const std::string &strFilename, int *pSize ); +uint32_t Path_ReadBinaryFile( const std::string &strFilename, unsigned char *pBuffer, uint32_t unSize ); +bool Path_WriteBinaryFile( const std::string &strFilename, unsigned char *pData, unsigned nSize ); +std::string Path_ReadTextFile( const std::string &strFilename ); +bool Path_WriteStringToTextFile( const std::string &strFilename, const char *pchData ); +bool Path_WriteStringToTextFileAtomic( const std::string &strFilename, const char *pchData ); + +// Mozilla: see README.mozilla for more details +/** Returns a file:// url for paths, or an http or https url if that's what was provided */ +// std::string Path_FilePathToUrl( const std::string & sRelativePath, const std::string & sBasePath ); + +/** Strips off file:// off a URL and returns the path. For other kinds of URLs an empty string is returned */ +std::string Path_UrlToFilePath( const std::string & sFileUrl ); + +/** Returns the root of the directory the system wants us to store user documents in */ +std::string GetUserDocumentsPath(); + +/** deletes / unlinks a single file */ +bool Path_UnlinkFile( const std::string &strFilename ); + +#ifndef MAX_UNICODE_PATH + #define MAX_UNICODE_PATH 32767 +#endif + +#ifndef MAX_UNICODE_PATH_IN_UTF8 + #define MAX_UNICODE_PATH_IN_UTF8 (MAX_UNICODE_PATH * 4) +#endif + +//----------------------------------------------------------------------------- +#if defined(WIN32) +#define DYNAMIC_LIB_EXT ".dll" +#define PROGRAM_EXT ".exe" +#ifdef _WIN64 +#define PLATSUBDIR "win64" +#else +#define PLATSUBDIR "win32" +#endif +#elif defined(OSX) +#define DYNAMIC_LIB_EXT ".dylib" +#define PLATSUBDIR "osx32" +#define PROGRAM_EXT "" +#elif defined(LINUX) +#define DYNAMIC_LIB_EXT ".so" +#define PROGRAM_EXT "" +#if defined( LINUX32 ) +#define PLATSUBDIR "linux32" +#elif defined( ANDROIDARM64 ) +#define PLATSUBDIR "androidarm64" +#elif defined( LINUXARM64 ) +#define PLATSUBDIR "linuxarm64" +#else +#define PLATSUBDIR "linux64" +#endif +#else +#warning "Unknown platform for PLATSUBDIR" +#define PLATSUBDIR "unknown_platform" +#endif diff --git a/gfx/vr/service/openvr/src/sharedlibtools_public.cpp b/gfx/vr/service/openvr/src/sharedlibtools_public.cpp new file mode 100644 index 0000000000..048512a1de --- /dev/null +++ b/gfx/vr/service/openvr/src/sharedlibtools_public.cpp @@ -0,0 +1,43 @@ +//========= Copyright Valve Corporation ============// +#include "sharedlibtools_public.h" +#include <string.h> + +#if defined(_WIN32) +#include <windows.h> +#endif + +#if defined(POSIX) +#include <dlfcn.h> +#endif + +SharedLibHandle SharedLib_Load( const char *pchPath ) +{ +#if defined( _WIN32) + return (SharedLibHandle)LoadLibraryEx( pchPath, NULL, LOAD_WITH_ALTERED_SEARCH_PATH ); +#elif defined(POSIX) + return (SharedLibHandle)dlopen(pchPath, RTLD_LOCAL|RTLD_NOW); +#endif +} + +void *SharedLib_GetFunction( SharedLibHandle lib, const char *pchFunctionName) +{ +#if defined( _WIN32) + return (void*)GetProcAddress( (HMODULE)lib, pchFunctionName ); +#elif defined(POSIX) + return dlsym( lib, pchFunctionName ); +#endif +} + + +void SharedLib_Unload( SharedLibHandle lib ) +{ + if ( !lib ) + return; +#if defined( _WIN32) + FreeLibrary( (HMODULE)lib ); +#elif defined(POSIX) + dlclose( lib ); +#endif +} + + diff --git a/gfx/vr/service/openvr/src/sharedlibtools_public.h b/gfx/vr/service/openvr/src/sharedlibtools_public.h new file mode 100644 index 0000000000..10163dbfab --- /dev/null +++ b/gfx/vr/service/openvr/src/sharedlibtools_public.h @@ -0,0 +1,10 @@ +//========= Copyright Valve Corporation ============// +#pragma once + +typedef void *SharedLibHandle; + +SharedLibHandle SharedLib_Load( const char *pchPath ); +void *SharedLib_GetFunction( SharedLibHandle lib, const char *pchFunctionName); +void SharedLib_Unload( SharedLibHandle lib ); + + diff --git a/gfx/vr/service/openvr/src/strtools_public.cpp b/gfx/vr/service/openvr/src/strtools_public.cpp new file mode 100644 index 0000000000..f52f8e9004 --- /dev/null +++ b/gfx/vr/service/openvr/src/strtools_public.cpp @@ -0,0 +1,571 @@ +//========= Copyright Valve Corporation ============// +#include "strtools_public.h" +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <sstream> +// Mozilla: see README.mozilla for more details +// #include <codecvt> +#include <iostream> +#include <functional> +#include <locale> +// #include <codecvt> + +#if defined( _WIN32 ) +#include <windows.h> +#endif + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool StringHasPrefix( const std::string & sString, const std::string & sPrefix ) +{ + return 0 == strnicmp( sString.c_str(), sPrefix.c_str(), sPrefix.length() ); +} + +bool StringHasPrefixCaseSensitive( const std::string & sString, const std::string & sPrefix ) +{ + return 0 == strncmp( sString.c_str(), sPrefix.c_str(), sPrefix.length() ); +} + + +bool StringHasSuffix( const std::string &sString, const std::string &sSuffix ) +{ + size_t cStrLen = sString.length(); + size_t cSuffixLen = sSuffix.length(); + + if ( cSuffixLen > cStrLen ) + return false; + + std::string sStringSuffix = sString.substr( cStrLen - cSuffixLen, cSuffixLen ); + + return 0 == stricmp( sStringSuffix.c_str(), sSuffix.c_str() ); +} + +bool StringHasSuffixCaseSensitive( const std::string &sString, const std::string &sSuffix ) +{ + size_t cStrLen = sString.length(); + size_t cSuffixLen = sSuffix.length(); + + if ( cSuffixLen > cStrLen ) + return false; + + std::string sStringSuffix = sString.substr( cStrLen - cSuffixLen, cSuffixLen ); + + return 0 == strncmp( sStringSuffix.c_str(), sSuffix.c_str(),cSuffixLen ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +// Mozilla: see README.mozilla for more details +//typedef std::codecvt_utf8< wchar_t > convert_type; + +// Mozilla: see README.mozilla for more details +#if defined( _WIN32 ) +std::string UTF16to8(const wchar_t * in) +{ + int retLength = ::WideCharToMultiByte(CP_UTF8, 0, in, -1, nullptr, 0, nullptr, nullptr); + if (retLength == 0) + { + return std::string(); + } + + char* retString = new char[retLength]; + ::WideCharToMultiByte(CP_UTF8, 0, in, -1, retString, retLength, nullptr, nullptr); + + std::string retStringValue(retString); + + delete[] retString; + + return retStringValue; + + // static std::wstring_convert< convert_type, wchar_t > s_converter; // construction of this can be expensive (or even serialized) depending on locale + + // try + // { + // return s_converter.to_bytes( in ); + // } + // catch ( ... ) + // { + // return std::string(); + // } +} + +std::string UTF16to8( const std::wstring & in ) { return UTF16to8( in.c_str() ); } + +// Mozilla: see README.mozilla for more details +std::wstring UTF8to16(const char * in) +{ + int retLength = ::MultiByteToWideChar(CP_UTF8, 0, in, -1, nullptr, 0); + if (retLength == 0) + { + return std::wstring(); + } + + wchar_t* retString = new wchar_t[retLength]; + ::MultiByteToWideChar(CP_UTF8, 0, in, -1, retString, retLength); + + std::wstring retStringValue(retString); + + delete[] retString; + + return retStringValue; + + //static std::wstring_convert< convert_type, wchar_t > s_converter; // construction of this can be expensive (or even serialized) depending on locale + + //try + //{ + // return s_converter.from_bytes( in ); + //} + //catch ( ... ) + //{ + // return std::wstring(); + //} +} + +std::wstring UTF8to16( const std::string & in ) { return UTF8to16( in.c_str() ); } +#endif + + +#if defined( _WIN32 ) +//----------------------------------------------------------------------------- +// Purpose: Convert LPSTR in the default CodePage to UTF8 +//----------------------------------------------------------------------------- +std::string DefaultACPtoUTF8( const char *pszStr ) +{ + if ( GetACP() == CP_UTF8 ) + { + return pszStr; + } + else + { + std::vector<wchar_t> vecBuf( strlen( pszStr ) + 1 ); // should be guaranteed to be enough + MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, pszStr, -1, vecBuf.data(), (int) vecBuf.size() ); + return UTF16to8( vecBuf.data() ); + } +} +#endif + +// -------------------------------------------------------------------- +// Purpose: +// -------------------------------------------------------------------- +void strcpy_safe( char *pchBuffer, size_t unBufferSizeBytes, const char *pchSource ) +{ + strncpy( pchBuffer, pchSource, unBufferSizeBytes - 1 ); + pchBuffer[unBufferSizeBytes - 1] = '\0'; +} + +// -------------------------------------------------------------------- +// Purpose: converts a string to upper case +// -------------------------------------------------------------------- +std::string StringToUpper( const std::string & sString ) +{ + std::string sOut; + sOut.reserve( sString.size() + 1 ); + for( std::string::const_iterator i = sString.begin(); i != sString.end(); i++ ) + { + sOut.push_back( (char)toupper( *i ) ); + } + + return sOut; +} + + +// -------------------------------------------------------------------- +// Purpose: converts a string to lower case +// -------------------------------------------------------------------- +std::string StringToLower( const std::string & sString ) +{ + std::string sOut; + sOut.reserve( sString.size() + 1 ); + for( std::string::const_iterator i = sString.begin(); i != sString.end(); i++ ) + { + sOut.push_back( (char)tolower( *i ) ); + } + + return sOut; +} + + +uint32_t ReturnStdString( const std::string & sValue, char *pchBuffer, uint32_t unBufferLen ) +{ + uint32_t unLen = (uint32_t)sValue.length() + 1; + if( !pchBuffer || !unBufferLen ) + return unLen; + + if( unBufferLen < unLen ) + { + pchBuffer[0] = '\0'; + } + else + { + memcpy( pchBuffer, sValue.c_str(), unLen ); + } + + return unLen; +} + + +/** Returns a std::string from a uint64_t */ +// Mozilla: see README.mozilla for more details +// std::string Uint64ToString( uint64_t ulValue ) +// { +// char buf[ 22 ]; +// #if defined( _WIN32 ) +// sprintf_s( buf, "%llu", ulValue ); +// #else +// snprintf( buf, sizeof( buf ), "%llu", (long long unsigned int ) ulValue ); +// #endif +// return buf; +// } + + +/** returns a uint64_t from a string */ +uint64_t StringToUint64( const std::string & sValue ) +{ + return strtoull( sValue.c_str(), NULL, 0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: Helper for converting a numeric value to a hex digit, value should be 0-15. +//----------------------------------------------------------------------------- +char cIntToHexDigit( int nValue ) +{ + //Assert( nValue >= 0 && nValue <= 15 ); + return "0123456789ABCDEF"[ nValue & 15 ]; +} + +//----------------------------------------------------------------------------- +// Purpose: Helper for converting a hex char value to numeric, return -1 if the char +// is not a valid hex digit. +//----------------------------------------------------------------------------- +int iHexCharToInt( char cValue ) +{ + int32_t iValue = cValue; + if ( (uint32_t)( iValue - '0' ) < 10 ) + return iValue - '0'; + + iValue |= 0x20; + if ( (uint32_t)( iValue - 'a' ) < 6 ) + return iValue - 'a' + 10; + + return -1; +} + + +//----------------------------------------------------------------------------- +// Purpose: These define the set of characters to filter for components (which +// need all the escaping we can muster) vs. paths (which don't want +// / and : escaped so we don't break less compliant URL handling code. +//----------------------------------------------------------------------------- +static bool CharNeedsEscape_Component( const char c ) +{ + return (!(c >= 'a' && c <= 'z') && !(c >= 'A' && c <= 'Z') && !(c >= '0' && c <= '9') + && c != '-' && c != '_' && c != '.'); +} +static bool CharNeedsEscape_FullPath( const char c ) +{ + return (!(c >= 'a' && c <= 'z') && !(c >= 'A' && c <= 'Z') && !(c >= '0' && c <= '9') + && c != '-' && c != '_' && c != '.' && c != '/' && c != ':' ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Internal implementation of encode, works in the strict RFC manner, or +// with spaces turned to + like HTML form encoding. +//----------------------------------------------------------------------------- +void V_URLEncodeInternal( char *pchDest, int nDestLen, const char *pchSource, int nSourceLen, + bool bUsePlusForSpace, std::function< bool(const char)> fnNeedsEscape ) +{ + //AssertMsg( nDestLen > 3*nSourceLen, "Target buffer for V_URLEncode should be 3x source length, plus one for terminating null\n" ); + + int iDestPos = 0; + for ( int i=0; i < nSourceLen; ++i ) + { + // worst case we need 3 additional chars + if( (iDestPos+3) > nDestLen ) + { + pchDest[0] = '\0'; +// AssertMsg( false, "Target buffer too short\n" ); + return; + } + + // We allow only a-z, A-Z, 0-9, period, underscore, and hyphen to pass through unescaped. + // These are the characters allowed by both the original RFC 1738 and the latest RFC 3986. + // Current specs also allow '~', but that is forbidden under original RFC 1738. + if ( fnNeedsEscape( pchSource[i] ) ) + { + if ( bUsePlusForSpace && pchSource[i] == ' ' ) + { + pchDest[iDestPos++] = '+'; + } + else + { + pchDest[iDestPos++] = '%'; + uint8_t iValue = pchSource[i]; + if ( iValue == 0 ) + { + pchDest[iDestPos++] = '0'; + pchDest[iDestPos++] = '0'; + } + else + { + char cHexDigit1 = cIntToHexDigit( iValue % 16 ); + iValue /= 16; + char cHexDigit2 = cIntToHexDigit( iValue ); + pchDest[iDestPos++] = cHexDigit2; + pchDest[iDestPos++] = cHexDigit1; + } + } + } + else + { + pchDest[iDestPos++] = pchSource[i]; + } + } + + if( (iDestPos+1) > nDestLen ) + { + pchDest[0] = '\0'; + //AssertMsg( false, "Target buffer too short to terminate\n" ); + return; + } + + // Null terminate + pchDest[iDestPos++] = 0; +} + + +//----------------------------------------------------------------------------- +// Purpose: Internal implementation of decode, works in the strict RFC manner, or +// with spaces turned to + like HTML form encoding. +// +// Returns the amount of space used in the output buffer. +//----------------------------------------------------------------------------- +size_t V_URLDecodeInternal( char *pchDecodeDest, int nDecodeDestLen, const char *pchEncodedSource, int nEncodedSourceLen, bool bUsePlusForSpace ) +{ + if ( nDecodeDestLen < nEncodedSourceLen ) + { + //AssertMsg( false, "V_URLDecode needs a dest buffer at least as large as the source" ); + return 0; + } + + int iDestPos = 0; + for( int i=0; i < nEncodedSourceLen; ++i ) + { + if ( bUsePlusForSpace && pchEncodedSource[i] == '+' ) + { + pchDecodeDest[ iDestPos++ ] = ' '; + } + else if ( pchEncodedSource[i] == '%' ) + { + // Percent signifies an encoded value, look ahead for the hex code, convert to numeric, and use that + + // First make sure we have 2 more chars + if ( i < nEncodedSourceLen - 2 ) + { + char cHexDigit1 = pchEncodedSource[i+1]; + char cHexDigit2 = pchEncodedSource[i+2]; + + // Turn the chars into a hex value, if they are not valid, then we'll + // just place the % and the following two chars direct into the string, + // even though this really shouldn't happen, who knows what bad clients + // may do with encoding. + bool bValid = false; + int iValue = iHexCharToInt( cHexDigit1 ); + if ( iValue != -1 ) + { + iValue *= 16; + int iValue2 = iHexCharToInt( cHexDigit2 ); + if ( iValue2 != -1 ) + { + iValue += iValue2; + pchDecodeDest[ iDestPos++ ] = (char)iValue; + bValid = true; + } + } + + if ( !bValid ) + { + pchDecodeDest[ iDestPos++ ] = '%'; + pchDecodeDest[ iDestPos++ ] = cHexDigit1; + pchDecodeDest[ iDestPos++ ] = cHexDigit2; + } + } + + // Skip ahead + i += 2; + } + else + { + pchDecodeDest[ iDestPos++ ] = pchEncodedSource[i]; + } + } + + // We may not have extra room to NULL terminate, since this can be used on raw data, but if we do + // go ahead and do it as this can avoid bugs. + if ( iDestPos < nDecodeDestLen ) + { + pchDecodeDest[iDestPos] = 0; + } + + return (size_t)iDestPos; +} + +//----------------------------------------------------------------------------- +// Purpose: Encodes a string (or binary data) from URL encoding format, see rfc1738 section 2.2. +// This version of the call isn't a strict RFC implementation, but uses + for space as is +// the standard in HTML form encoding, despite it not being part of the RFC. +// +// Dest buffer should be at least as large as source buffer to guarantee room for decode. +//----------------------------------------------------------------------------- +void V_URLEncode( char *pchDest, int nDestLen, const char *pchSource, int nSourceLen ) +{ + return V_URLEncodeInternal( pchDest, nDestLen, pchSource, nSourceLen, true, CharNeedsEscape_Component ); +} + + +void V_URLEncodeNoPlusForSpace( char *pchDest, int nDestLen, const char *pchSource, int nSourceLen ) +{ + return V_URLEncodeInternal( pchDest, nDestLen, pchSource, nSourceLen, false, CharNeedsEscape_Component ); +} + +void V_URLEncodeFullPath( char *pchDest, int nDestLen, const char *pchSource, int nSourceLen ) +{ + return V_URLEncodeInternal( pchDest, nDestLen, pchSource, nSourceLen, false, CharNeedsEscape_FullPath ); +} + +//----------------------------------------------------------------------------- +// Purpose: Decodes a string (or binary data) from URL encoding format, see rfc1738 section 2.2. +// This version of the call isn't a strict RFC implementation, but uses + for space as is +// the standard in HTML form encoding, despite it not being part of the RFC. +// +// Dest buffer should be at least as large as source buffer to guarantee room for decode. +// Dest buffer being the same as the source buffer (decode in-place) is explicitly allowed. +//----------------------------------------------------------------------------- +size_t V_URLDecode( char *pchDecodeDest, int nDecodeDestLen, const char *pchEncodedSource, int nEncodedSourceLen ) +{ + return V_URLDecodeInternal( pchDecodeDest, nDecodeDestLen, pchEncodedSource, nEncodedSourceLen, true ); +} + +size_t V_URLDecodeNoPlusForSpace( char *pchDecodeDest, int nDecodeDestLen, const char *pchEncodedSource, int nEncodedSourceLen ) +{ + return V_URLDecodeInternal( pchDecodeDest, nDecodeDestLen, pchEncodedSource, nEncodedSourceLen, false ); +} + +//----------------------------------------------------------------------------- +void V_StripExtension( std::string &in ) +{ + // Find the last dot. If it's followed by a dot or a slash, then it's part of a + // directory specifier like ../../somedir/./blah. + std::string::size_type test = in.rfind( '.' ); + if ( test != std::string::npos ) + { + // This handles things like ".\blah" or "c:\my@email.com\abc\def\geh" + // Which would otherwise wind up with "" and "c:\my@email", respectively. + if ( in.rfind( '\\' ) < test && in.rfind( '/' ) < test ) + { + in.resize( test ); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Tokenizes a string into a vector of strings +//----------------------------------------------------------------------------- +std::vector<std::string> TokenizeString( const std::string & sString, char cToken ) +{ + std::vector<std::string> vecStrings; + std::istringstream stream( sString ); + std::string s; + while ( std::getline( stream, s, cToken ) ) + { + vecStrings.push_back( s ); + } + return vecStrings; +} + +// Mozilla: see README.mozilla for more details +//----------------------------------------------------------------------------- +// Purpose: Repairs a should-be-UTF-8 string to a for-sure-is-UTF-8 string, plus return boolean if we subbed in '?' somewhere +//----------------------------------------------------------------------------- +// bool RepairUTF8( const char *pbegin, const char *pend, std::string & sOutputUtf8 ) +// { +// typedef std::codecvt_utf8<char32_t> facet_type; +// facet_type myfacet; + +// std::mbstate_t mystate = std::mbstate_t(); + +// sOutputUtf8.clear(); +// sOutputUtf8.reserve( pend - pbegin ); +// bool bSqueakyClean = true; + +// const char *pmid = pbegin; +// while ( pmid != pend ) +// { +// bool bHasError = false; +// bool bHasValidData = false; + +// char32_t out = 0xdeadbeef, *pout; +// pbegin = pmid; +// switch ( myfacet.in( mystate, pbegin, pend, pmid, &out, &out + 1, pout ) ) +// { +// case facet_type::ok: +// bHasValidData = true; +// break; + +// case facet_type::noconv: +// // unexpected! always converting type +// bSqueakyClean = false; +// break; + +// case facet_type::partial: +// bHasError = pbegin == pmid; +// if ( bHasError ) +// { +// bSqueakyClean = false; +// } +// else +// { +// bHasValidData = true; +// } +// break; + +// case facet_type::error: +// bHasError = true; +// bSqueakyClean = false; +// break; +// } + +// if ( bHasValidData ) +// { +// // could convert back, but no need +// for ( const char *p = pbegin; p != pmid; ++p ) +// { +// sOutputUtf8 += *p; +// } +// } + +// if ( bHasError ) +// { +// sOutputUtf8 += '?'; +// } + +// if ( pmid == pbegin ) +// { +// pmid++; +// } +// } + +// return bSqueakyClean; +// } + +// //----------------------------------------------------------------------------- +// // Purpose: Repairs a should-be-UTF-8 string to a for-sure-is-UTF-8 string, plus return boolean if we subbed in '?' somewhere +// //----------------------------------------------------------------------------- +// bool RepairUTF8( const std::string & sInputUtf8, std::string & sOutputUtf8 ) +// { +// return RepairUTF8( sInputUtf8.data(), sInputUtf8.data() + sInputUtf8.size(), sOutputUtf8 ); +// } diff --git a/gfx/vr/service/openvr/src/strtools_public.h b/gfx/vr/service/openvr/src/strtools_public.h new file mode 100644 index 0000000000..067bbe1b1b --- /dev/null +++ b/gfx/vr/service/openvr/src/strtools_public.h @@ -0,0 +1,156 @@ +//========= Copyright Valve Corporation ============// +#pragma once + +#include <string> +#include <stdint.h> +#include <sys/types.h> +#include <vector> + +/** returns true if the string has the prefix */ +bool StringHasPrefix( const std::string & sString, const std::string & sPrefix ); +bool StringHasPrefixCaseSensitive( const std::string & sString, const std::string & sPrefix ); + +/** returns if the string has the suffix */ +bool StringHasSuffix( const std::string &sString, const std::string &sSuffix ); +bool StringHasSuffixCaseSensitive( const std::string &sString, const std::string &sSuffix ); + +// Mozilla: see README.mozilla for more details +#if defined( _WIN32 ) +/** converts a UTF-16 string to a UTF-8 string */ +std::string UTF16to8( const wchar_t * in ); +std::string UTF16to8( const std::wstring & in ); + +/** converts a UTF-8 string to a UTF-16 string */ +std::wstring UTF8to16(const char * in); +std::wstring UTF8to16( const std::string & in ); +#define Utf16FromUtf8 UTF8to16 +#endif + +#if defined( _WIN32 ) +std::string DefaultACPtoUTF8( const char *pszStr ); +#endif + +/** Repairs a should-be-UTF-8 string to a for-sure-is-UTF-8 string, plus return boolean if we subbed in '?' somewhere */ +bool RepairUTF8( const char *begin, const char *end, std::string & sOutputUtf8 ); +bool RepairUTF8( const std::string & sInputUtf8, std::string & sOutputUtf8 ); + +/** safely copy a string into a buffer */ +void strcpy_safe( char *pchBuffer, size_t unBufferSizeBytes, const char *pchSource ); +template< size_t bufferSize > +void strcpy_safe( char (& buffer) [ bufferSize ], const char *pchSource ) +{ + strcpy_safe( buffer, bufferSize, pchSource ); +} + + +/** converts a string to upper case */ +std::string StringToUpper( const std::string & sString ); + +/** converts a string to lower case */ +std::string StringToLower( const std::string & sString ); + +// we stricmp (from WIN) but it isn't POSIX - OSX/LINUX have strcasecmp so just inline bridge to it +#if defined( OSX ) || defined( LINUX ) +#include <strings.h> +inline int stricmp(const char *pStr1, const char *pStr2) { return strcasecmp(pStr1,pStr2); } +#ifndef _stricmp +#define _stricmp stricmp +#endif +inline int strnicmp( const char *pStr1, const char *pStr2, size_t unBufferLen ) { return strncasecmp( pStr1,pStr2, unBufferLen ); } +#ifndef _strnicmp +#define _strnicmp strnicmp +#endif + +#ifndef _vsnprintf_s +#define _vsnprintf_s vsnprintf +#endif + +#define _TRUNCATE ((size_t)-1) + +#endif + +#if defined( OSX ) +// behaviors ensure NULL-termination at least as well as _TRUNCATE does, but +// wcsncpy_s/strncpy_s can non-NULL-terminate, wcslcpy/strlcpy can not. +// inline errno_t wcsncpy_s(wchar_t *strDest, size_t numberOfElements, const wchar_t *strSource, size_t count) +// { +// return wcslcpy(strDest, strSource, numberOfElements); +// } + +// inline errno_t strncpy_s(char *strDest, size_t numberOfElements, const char *strSource, size_t count) +// { +// return strlcpy(strDest, strSource, numberOfElements); +// } + +#endif + +#if defined( LINUX ) +// this implementation does not return whether or not the destination was +// truncated, but that is straightforward to fix if anybody actually needs the +// return code. +#include "string.h" +inline void wcsncpy_s(wchar_t *strDest, size_t numberOfElements, const wchar_t *strSource, size_t count) +{ + wcsncpy(strDest, strSource, numberOfElements); + strDest[numberOfElements-1] = '\0'; +} + +inline void strncpy_s(char *strDest, size_t numberOfElements, const char *strSource, size_t count) +{ + strncpy(strDest, strSource, numberOfElements); + strDest[numberOfElements-1] = '\0'; +} + +#endif + +#if defined( _WIN32 ) && _MSC_VER < 1800 +inline uint64_t strtoull(const char *str, char **endptr, int base) { return _strtoui64( str, endptr, base ); } +#endif + +/* Handles copying a std::string into a buffer as would be provided in an API */ +uint32_t ReturnStdString( const std::string & sValue, char *pchBuffer, uint32_t unBufferLen ); + +/** Returns a std::string from a uint64_t */ +// Mozilla: see README.mozilla for more details +//std::string Uint64ToString( uint64_t ulValue ); + +/** returns a uint64_t from a string */ +uint64_t StringToUint64( const std::string & sValue ); + +//----------------------------------------------------------------------------- +// Purpose: Encodes a string (or binary data) from URL encoding format, see rfc1738 section 2.2. +// This version of the call isn't a strict RFC implementation, but uses + for space as is +// the standard in HTML form encoding, despite it not being part of the RFC. +// +// Dest buffer should be at least as large as source buffer to guarantee room for decode. +//----------------------------------------------------------------------------- +void V_URLEncode( char *pchDest, int nDestLen, const char *pchSource, int nSourceLen ); + +/** Same as V_URLEncode, but without plus for space. */ +void V_URLEncodeNoPlusForSpace( char *pchDest, int nDestLen, const char *pchSource, int nSourceLen ); + +/** Same as V_URLEncodeNoPlusForSpace, but without escaping / and : */ +void V_URLEncodeFullPath( char *pchDest, int nDestLen, const char *pchSource, int nSourceLen ); + + +//----------------------------------------------------------------------------- +// Purpose: Decodes a string (or binary data) from URL encoding format, see rfc1738 section 2.2. +// This version of the call isn't a strict RFC implementation, but uses + for space as is +// the standard in HTML form encoding, despite it not being part of the RFC. +// +// Dest buffer should be at least as large as source buffer to guarantee room for decode. +// Dest buffer being the same as the source buffer (decode in-place) is explicitly allowed. +//----------------------------------------------------------------------------- +size_t V_URLDecode( char *pchDecodeDest, int nDecodeDestLen, const char *pchEncodedSource, int nEncodedSourceLen ); + +/** Same as V_URLDecode, but without plus for space. */ +size_t V_URLDecodeNoPlusForSpace( char *pchDecodeDest, int nDecodeDestLen, const char *pchEncodedSource, int nEncodedSourceLen ); + +//----------------------------------------------------------------------------- +// Purpose: strip extension from a path +//----------------------------------------------------------------------------- +void V_StripExtension( std::string &in ); + + +/** Tokenizes a string into a vector of strings */ +std::vector<std::string> TokenizeString( const std::string & sString, char cToken ); diff --git a/gfx/vr/service/openvr/src/vrpathregistry_public.cpp b/gfx/vr/service/openvr/src/vrpathregistry_public.cpp new file mode 100644 index 0000000000..f40fc1cda1 --- /dev/null +++ b/gfx/vr/service/openvr/src/vrpathregistry_public.cpp @@ -0,0 +1,442 @@ +//========= Copyright Valve Corporation ============// + +#include "vrpathregistry_public.h" +#include "json/json.h" +#include "pathtools_public.h" +#include "envvartools_public.h" +#include "strtools_public.h" +#include "dirtools_public.h" + +#if defined( WIN32 ) +#include <windows.h> +#include <shlobj.h> + +#undef GetEnvironmentVariable +#elif defined OSX +#include <Foundation/Foundation.h> +#include <AppKit/AppKit.h> +#elif defined(LINUX) +#include <dlfcn.h> +#include <stdio.h> +#endif + +#include <algorithm> + +#ifndef VRLog + #if defined( __MINGW32__ ) + #define VRLog(args...) fprintf(stderr, args) + #elif defined( WIN32 ) + #define VRLog(fmt, ...) fprintf(stderr, fmt, __VA_ARGS__) + #else + #define VRLog(args...) fprintf(stderr, args) + #endif +#endif + +/** Returns the root of the directory the system wants us to store user config data in */ +static std::string GetAppSettingsPath() +{ +#if defined( WIN32 ) + WCHAR rwchPath[MAX_PATH]; + + if( !SUCCEEDED( SHGetFolderPathW( NULL, CSIDL_LOCAL_APPDATA | CSIDL_FLAG_CREATE, NULL, 0, rwchPath ) ) ) + { + return ""; + } + + // Convert the path to UTF-8 and store in the output + std::string sUserPath = UTF16to8( rwchPath ); + + return sUserPath; +#elif defined( OSX ) + std::string sSettingsDir; + @autoreleasepool { + // Search for the path + NSArray *paths = NSSearchPathForDirectoriesInDomains( NSApplicationSupportDirectory, NSUserDomainMask, YES ); + if ( [paths count] == 0 ) + { + return ""; + } + + NSString *resolvedPath = [paths objectAtIndex:0]; + resolvedPath = [resolvedPath stringByAppendingPathComponent: @"OpenVR"]; + + if ( ![[NSFileManager defaultManager] createDirectoryAtPath: resolvedPath withIntermediateDirectories:YES attributes:nil error:nil] ) + { + return ""; + } + + sSettingsDir.assign( [resolvedPath UTF8String] ); + } + return sSettingsDir; +#elif defined( LINUX ) + + // As defined by XDG Base Directory Specification + // https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html + + const char *pchHome = getenv("XDG_CONFIG_HOME"); + if ( ( pchHome != NULL) && ( pchHome[0] != '\0' ) ) + { + return pchHome; + } + + // + // XDG_CONFIG_HOME is not defined, use ~/.config instead + // + pchHome = getenv( "HOME" ); + if ( pchHome == NULL ) + { + return ""; + } + + std::string sUserPath( pchHome ); + sUserPath = Path_Join( sUserPath, ".config" ); + return sUserPath; +#else + #warning "Unsupported platform" +#endif +} + + +// --------------------------------------------------------------------------- +// Purpose: Constructor +// --------------------------------------------------------------------------- +CVRPathRegistry_Public::CVRPathRegistry_Public() +{ + +} + +// --------------------------------------------------------------------------- +// Purpose: Computes the registry filename +// --------------------------------------------------------------------------- +std::string CVRPathRegistry_Public::GetOpenVRConfigPath() +{ + std::string sConfigPath = GetAppSettingsPath(); + if( sConfigPath.empty() ) + return ""; + +#if defined( _WIN32 ) || defined( LINUX ) + sConfigPath = Path_Join( sConfigPath, "openvr" ); +#elif defined ( OSX ) + sConfigPath = Path_Join( sConfigPath, ".openvr" ); +#else + #warning "Unsupported platform" +#endif + sConfigPath = Path_FixSlashes( sConfigPath ); + return sConfigPath; +} + + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +std::string CVRPathRegistry_Public::GetVRPathRegistryFilename() +{ + std::string sOverridePath = GetEnvironmentVariable( "VR_PATHREG_OVERRIDE" ); + if ( !sOverridePath.empty() ) + return sOverridePath; + + std::string sPath = GetOpenVRConfigPath(); + if ( sPath.empty() ) + return ""; + +#if defined( _WIN32 ) + sPath = Path_Join( sPath, "openvrpaths.vrpath" ); +#elif defined ( POSIX ) + sPath = Path_Join( sPath, "openvrpaths.vrpath" ); +#else + #error "Unsupported platform" +#endif + sPath = Path_FixSlashes( sPath ); + return sPath; +} + + +// --------------------------------------------------------------------------- +// Purpose: Converts JSON to a history array +// --------------------------------------------------------------------------- +static void ParseStringListFromJson( std::vector< std::string > *pvecHistory, const Json::Value & root, const char *pchArrayName ) +{ + if( !root.isMember( pchArrayName ) ) + return; + + const Json::Value & arrayNode = root[ pchArrayName ]; + if( !arrayNode ) + { + VRLog( "VR Path Registry node %s is not an array\n", pchArrayName ); + return; + } + + pvecHistory->clear(); + pvecHistory->reserve( arrayNode.size() ); + for( uint32_t unIndex = 0; unIndex < arrayNode.size(); unIndex++ ) + { + std::string sPath( arrayNode[ unIndex ].asString() ); + pvecHistory->push_back( sPath ); + } +} + + +// --------------------------------------------------------------------------- +// Purpose: Converts a history array to JSON +// --------------------------------------------------------------------------- +static void StringListToJson( const std::vector< std::string > & vecHistory, Json::Value & root, const char *pchArrayName ) +{ + Json::Value & arrayNode = root[ pchArrayName ]; + for( auto i = vecHistory.begin(); i != vecHistory.end(); i++ ) + { + arrayNode.append( *i ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +bool CVRPathRegistry_Public::ToJsonString( std::string &sJsonString ) +{ + std::string sRegPath = GetVRPathRegistryFilename(); + if( sRegPath.empty() ) + return false; + + std::string sRegistryContents = Path_ReadTextFile( sRegPath ); + if( sRegistryContents.empty() ) + return false; + + sJsonString = sRegistryContents; + + return true; +} + +// Mozilla: see README.mozilla for more details +// --------------------------------------------------------------------------- +// Purpose: Loads the config file from its well known location +// --------------------------------------------------------------------------- +bool CVRPathRegistry_Public::BLoadFromFile( std::string *psLoadError ) +{ + std::string sRegPath = GetVRPathRegistryFilename(); + if( sRegPath.empty() ) + { + if ( psLoadError ) + { + *psLoadError = "Unable to determine VR Path Registry filename"; + } + return false; + } + + std::string sRegistryContents = Path_ReadTextFile( sRegPath ); + if( sRegistryContents.empty() ) + { + if ( psLoadError ) + { + *psLoadError = "Unable to read VR Path Registry from " + sRegPath; + } + return false; + } + + Json::Value root; + Json::CharReaderBuilder builder; + std::istringstream istream( sRegistryContents ); + std::string sErrors; + +// try + { + if ( !parseFromStream( builder, istream, &root, &sErrors ) ) + { + if ( psLoadError ) + { + *psLoadError = "Unable to parse " + sRegPath + ": " + sErrors; + } + return false; + } + + ParseStringListFromJson( &m_vecRuntimePath, root, "runtime" ); + ParseStringListFromJson( &m_vecConfigPath, root, "config" ); + ParseStringListFromJson( &m_vecLogPath, root, "log" ); + if ( root.isMember( "external_drivers" ) && root["external_drivers"].isArray() ) + { + ParseStringListFromJson( &m_vecExternalDrivers, root, "external_drivers" ); + } + } +// catch ( ... ) +// { +// if ( psLoadError ) +// { +// *psLoadError = "Unable to parse " + sRegPath + ": exception thrown in JSON library"; +// } +// return false; +// } + + return true; +} + + +// --------------------------------------------------------------------------- +// Purpose: Saves the config file to its well known location +// --------------------------------------------------------------------------- +bool CVRPathRegistry_Public::BSaveToFile() const +{ + std::string sRegPath = GetVRPathRegistryFilename(); + if( sRegPath.empty() ) + return false; + + Json::Value root; + + root[ "version" ] = 1; + root[ "jsonid" ] = "vrpathreg"; + + StringListToJson( m_vecRuntimePath, root, "runtime" ); + StringListToJson( m_vecConfigPath, root, "config" ); + StringListToJson( m_vecLogPath, root, "log" ); + StringListToJson( m_vecExternalDrivers, root, "external_drivers" ); + + Json::StreamWriterBuilder builder; + std::string sRegistryContents = Json::writeString( builder, root ); + + // make sure the directory we're writing into actually exists + std::string sRegDirectory = Path_StripFilename( sRegPath ); + if( !BCreateDirectoryRecursive( sRegDirectory.c_str() ) ) + { + VRLog( "Unable to create path registry directory %s\n", sRegDirectory.c_str() ); + return false; + } + + if( !Path_WriteStringToTextFile( sRegPath, sRegistryContents.c_str() ) ) + { + VRLog( "Unable to write VR path registry to %s\n", sRegPath.c_str() ); + return false; + } + + return true; +} + + +// --------------------------------------------------------------------------- +// Purpose: Returns the current runtime path or NULL if no path is configured. +// --------------------------------------------------------------------------- +std::string CVRPathRegistry_Public::GetRuntimePath() const +{ + if( m_vecRuntimePath.empty() ) + return ""; + else + return m_vecRuntimePath.front().c_str(); +} + + +// --------------------------------------------------------------------------- +// Purpose: Returns the current config path or NULL if no path is configured. +// --------------------------------------------------------------------------- +std::string CVRPathRegistry_Public::GetConfigPath() const +{ + if( m_vecConfigPath.empty() ) + return ""; + else + return m_vecConfigPath.front().c_str(); +} + + +// --------------------------------------------------------------------------- +// Purpose: Returns the current log path or NULL if no path is configured. +// --------------------------------------------------------------------------- +std::string CVRPathRegistry_Public::GetLogPath() const +{ + if( m_vecLogPath.empty() ) + return ""; + else + return m_vecLogPath.front().c_str(); +} + + + +// --------------------------------------------------------------------------- +// Purpose: Returns paths using the path registry and the provided override +// values. Pass NULL for any paths you don't care about. +// --------------------------------------------------------------------------- +bool CVRPathRegistry_Public::GetPaths( std::string *psRuntimePath, std::string *psConfigPath, std::string *psLogPath, const char *pchConfigPathOverride, const char *pchLogPathOverride, std::vector<std::string> *pvecExternalDrivers ) +{ + std::string sLoadError; + CVRPathRegistry_Public pathReg; + bool bLoadedRegistry = pathReg.BLoadFromFile( &sLoadError ); + int nCountEnvironmentVariables = 0; + int nRequestedPaths = 0; + + if( psRuntimePath ) + { + nRequestedPaths++; + if ( GetEnvironmentVariable( k_pchRuntimeOverrideVar ).length() != 0 ) + { + *psRuntimePath = GetEnvironmentVariable( k_pchRuntimeOverrideVar ); + nCountEnvironmentVariables++; + } + else if( !pathReg.GetRuntimePath().empty() ) + { + *psRuntimePath = pathReg.GetRuntimePath(); + } + else + { + *psRuntimePath = ""; + } + } + + if( psConfigPath ) + { + nRequestedPaths++; + if ( GetEnvironmentVariable( k_pchConfigOverrideVar ).length() != 0 ) + { + *psConfigPath = GetEnvironmentVariable( k_pchConfigOverrideVar ); + nCountEnvironmentVariables++; + } + else if( pchConfigPathOverride ) + { + *psConfigPath = pchConfigPathOverride; + } + else if( !pathReg.GetConfigPath().empty() ) + { + *psConfigPath = pathReg.GetConfigPath(); + } + else + { + *psConfigPath = ""; + } + } + + if( psLogPath ) + { + nRequestedPaths++; + if ( GetEnvironmentVariable( k_pchLogOverrideVar ).length() != 0 ) + { + *psLogPath = GetEnvironmentVariable( k_pchLogOverrideVar ); + nCountEnvironmentVariables++; + } + else if( pchLogPathOverride ) + { + *psLogPath = pchLogPathOverride; + } + else if( !pathReg.GetLogPath().empty() ) + { + *psLogPath = pathReg.GetLogPath(); + } + else + { + *psLogPath = ""; + } + } + + if ( pvecExternalDrivers ) + { + *pvecExternalDrivers = pathReg.m_vecExternalDrivers; + } + + if ( nCountEnvironmentVariables == nRequestedPaths ) + { + // all three environment variables were set, so we don't need the physical file + return true; + } + else if( !bLoadedRegistry ) + { + VRLog( "%s\n", sLoadError.c_str() ); + } + + return bLoadedRegistry; +} + diff --git a/gfx/vr/service/openvr/src/vrpathregistry_public.h b/gfx/vr/service/openvr/src/vrpathregistry_public.h new file mode 100644 index 0000000000..776935a011 --- /dev/null +++ b/gfx/vr/service/openvr/src/vrpathregistry_public.h @@ -0,0 +1,45 @@ +//========= Copyright Valve Corporation ============// +#pragma once + +#include <string> +#include <vector> +#include <stdint.h> + +static const char *k_pchRuntimeOverrideVar = "VR_OVERRIDE"; +static const char *k_pchConfigOverrideVar = "VR_CONFIG_PATH"; +static const char *k_pchLogOverrideVar = "VR_LOG_PATH"; + +class CVRPathRegistry_Public +{ +public: + static std::string GetVRPathRegistryFilename(); + static std::string GetOpenVRConfigPath(); + +public: + CVRPathRegistry_Public(); + + /** Returns paths using the path registry and the provided override values. Pass NULL for any paths you don't care about. + * Returns false if the path registry could not be read. Valid paths might still be returned based on environment variables. */ + static bool GetPaths( std::string *psRuntimePath, std::string *psConfigPath, std::string *psLogPath, const char *pchConfigPathOverride, const char *pchLogPathOverride, std::vector<std::string> *pvecExternalDrivers = NULL ); + + bool BLoadFromFile( std::string *psError = nullptr ); + bool BSaveToFile() const; + + bool ToJsonString( std::string &sJsonString ); + + // methods to get the current values + std::string GetRuntimePath() const; + std::string GetConfigPath() const; + std::string GetLogPath() const; + +protected: + typedef std::vector< std::string > StringVector_t; + + // index 0 is the current setting + StringVector_t m_vecRuntimePath; + StringVector_t m_vecLogPath; + StringVector_t m_vecConfigPath; + + // full list of external drivers + StringVector_t m_vecExternalDrivers; +}; diff --git a/gfx/vr/service/osvr/ClientKit/ClientKitC.h b/gfx/vr/service/osvr/ClientKit/ClientKitC.h new file mode 100644 index 0000000000..8309e890d3 --- /dev/null +++ b/gfx/vr/service/osvr/ClientKit/ClientKitC.h @@ -0,0 +1,37 @@ +/** @file + @brief Header + + Must be c-safe! + + @date 2014 + + @author + Sensics, Inc. + <http://sensics.com/osvr> +*/ + +/* +// Copyright 2014 Sensics, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +*/ + +#ifndef INCLUDED_ClientKitC_h_GUID_8D7DF104_892D_4CB5_2302_7C6BB5BC985C +#define INCLUDED_ClientKitC_h_GUID_8D7DF104_892D_4CB5_2302_7C6BB5BC985C + +#include <osvr/ClientKit/ContextC.h> +#include <osvr/ClientKit/InterfaceC.h> +#include <osvr/ClientKit/InterfaceCallbackC.h> +#include <osvr/ClientKit/SystemCallbackC.h> + +#endif diff --git a/gfx/vr/service/osvr/ClientKit/ContextC.h b/gfx/vr/service/osvr/ClientKit/ContextC.h new file mode 100644 index 0000000000..e07e1b4a77 --- /dev/null +++ b/gfx/vr/service/osvr/ClientKit/ContextC.h @@ -0,0 +1,96 @@ +/** @file + @brief Header + + Must be c-safe! + + @todo Apply annotation macros + + @date 2014 + + @author + Sensics, Inc. + <http://sensics.com/osvr> +*/ + +/* +// Copyright 2014 Sensics, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +*/ + +#ifndef INCLUDED_ContextC_h_GUID_3790F330_2425_4486_4C9F_20C300D7DED3 +#define INCLUDED_ContextC_h_GUID_3790F330_2425_4486_4C9F_20C300D7DED3 + +/* Internal Includes */ +#include <osvr/ClientKit/Export.h> +#include <osvr/Util/APIBaseC.h> +#include <osvr/Util/ReturnCodesC.h> +#include <osvr/Util/AnnotationMacrosC.h> +#include <osvr/Util/StdInt.h> +#include <osvr/Util/ClientOpaqueTypesC.h> + +/* Library/third-party includes */ +/* none */ + +/* Standard includes */ +/* none */ + +OSVR_EXTERN_C_BEGIN + +/** @addtogroup ClientKit + @{ +*/ + +/** @brief Initialize the library. + + @param applicationIdentifier A null terminated string identifying your + application. Reverse DNS format strongly suggested. + @param flags initialization options (reserved) - pass 0 for now. + + @returns Client context - will be needed for subsequent calls +*/ +OSVR_CLIENTKIT_EXPORT OSVR_ClientContext osvrClientInit( + const char applicationIdentifier[], uint32_t flags OSVR_CPP_ONLY(= 0)); + +/** @brief Updates the state of the context - call regularly in your mainloop. + + @param ctx Client context +*/ +OSVR_CLIENTKIT_EXPORT OSVR_ReturnCode osvrClientUpdate(OSVR_ClientContext ctx); + +/** @brief Checks to see if the client context is fully started up and connected + properly to a server. + + If this reports that the client context is not OK, there may not be a server + running, or you may just have to call osvrClientUpdate() a few times to + permit startup to finish. The return value of this call will not change from + failure to success without calling osvrClientUpdate(). + + @param ctx Client context + + @return OSVR_RETURN_FAILURE if not yet fully connected/initialized, or if + some other error (null context) occurs. +*/ +OSVR_CLIENTKIT_EXPORT OSVR_ReturnCode +osvrClientCheckStatus(OSVR_ClientContext ctx); + +/** @brief Shutdown the library. + @param ctx Client context +*/ +OSVR_CLIENTKIT_EXPORT OSVR_ReturnCode +osvrClientShutdown(OSVR_ClientContext ctx); + +/** @} */ +OSVR_EXTERN_C_END + +#endif diff --git a/gfx/vr/service/osvr/ClientKit/DisplayC.h b/gfx/vr/service/osvr/ClientKit/DisplayC.h new file mode 100644 index 0000000000..fb11ca6b53 --- /dev/null +++ b/gfx/vr/service/osvr/ClientKit/DisplayC.h @@ -0,0 +1,506 @@ +/** @file + @brief Header + + Must be c-safe! + + @date 2015 + + @author + Sensics, Inc. + <http://sensics.com/osvr> +*/ + +/* +// Copyright 2015 Sensics, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +*/ + +#ifndef INCLUDED_DisplayC_h_GUID_8658EDC9_32A2_49A2_5F5C_10F67852AE74 +#define INCLUDED_DisplayC_h_GUID_8658EDC9_32A2_49A2_5F5C_10F67852AE74 + +/* Internal Includes */ +#include <osvr/ClientKit/Export.h> +#include <osvr/Util/APIBaseC.h> +#include <osvr/Util/ReturnCodesC.h> +#include <osvr/Util/ClientOpaqueTypesC.h> +#include <osvr/Util/RenderingTypesC.h> +#include <osvr/Util/MatrixConventionsC.h> +#include <osvr/Util/Pose3C.h> +#include <osvr/Util/BoolC.h> +#include <osvr/Util/RadialDistortionParametersC.h> + +/* Library/third-party includes */ +/* none */ + +/* Standard includes */ +/* none */ + +OSVR_EXTERN_C_BEGIN +/** @addtogroup ClientKit + @{ + @name Display API + @{ +*/ + +/** @brief Opaque type of a display configuration. */ +typedef struct OSVR_DisplayConfigObject* OSVR_DisplayConfig; + +/** @brief Allocates a display configuration object populated with data from the + OSVR system. + + Before this call will succeed, your application will need to be correctly + and fully connected to an OSVR server. You may consider putting this call in + a loop alternating with osvrClientUpdate() until this call succeeds. + + Data provided by a display configuration object: + + - The logical display topology (number and relationship of viewers, eyes, + and surfaces), which remains constant throughout the life of the + configuration object. (A method of notification of change here is TBD). + - Pose data for viewers (not required for rendering) and pose/view data for + eyes (used for rendering) which is based on tracker data: if used, these + should be queried every frame. + - Projection matrix data for surfaces, which while in current practice may + be relatively unchanging, we are not guaranteeing them to be constant: + these should be queried every frame. + - Video-input-relative viewport size/location for a surface: would like this + to be variable, but probably not feasible. If you have input, please + comment on the dev mailing list. + - Per-surface distortion strategy priorities/availabilities: constant. Note + the following, though... + - Per-surface distortion strategy parameters: variable, request each frame. + (Could make constant with a notification if needed?) + + Important note: While most of this data is immediately available if you are + successful in getting a display config object, the pose-based data (viewer + pose, eye pose, eye view matrix) needs tracker state, so at least one (and in + practice, typically more) osvrClientUpdate() must be performed before a new + tracker report is available to populate that state. See + osvrClientCheckDisplayStartup() to query if all startup data is available. + + @todo Decide if relative viewport should be constant in a display config, + and update docs accordingly. + + @todo Decide if distortion params should be constant in a display config, + and update docs accordingly. + + @return OSVR_RETURN_FAILURE if invalid parameters were passed or some other + error occurred, in which case the output argument is unmodified. +*/ +OSVR_CLIENTKIT_EXPORT OSVR_ReturnCode +osvrClientGetDisplay(OSVR_ClientContext ctx, OSVR_DisplayConfig* disp); + +/** @brief Frees a display configuration object. The corresponding context must + still be open. + + If you fail to call this, it will be automatically called as part of + clean-up when the corresponding context is closed. + + @return OSVR_RETURN_FAILURE if a null config was passed, or if the given + display object was already freed. +*/ +OSVR_CLIENTKIT_EXPORT OSVR_ReturnCode +osvrClientFreeDisplay(OSVR_DisplayConfig disp); + +/** @brief Checks to see if a display is fully configured and ready, including + having received its first pose update. + + Once this first succeeds, it will continue to succeed for the lifetime of + the display config object, so it is not necessary to keep calling once you + get a successful result. + + @return OSVR_RETURN_FAILURE if a null config was passed, or if the given + display config object was otherwise not ready for full use. +*/ +OSVR_CLIENTKIT_EXPORT OSVR_ReturnCode +osvrClientCheckDisplayStartup(OSVR_DisplayConfig disp); + +/** @brief A display config can have one or more display inputs to pass pixels + over (HDMI/DVI connections, etc): retrieve the number of display inputs in + the current configuration. + + @param disp Display config object. + @param[out] numDisplayInputs Number of display inputs in the logical display + topology, **constant** throughout the active, valid lifetime of a display + config object. + + @sa OSVR_DisplayInputCount + + @return OSVR_RETURN_FAILURE if invalid parameters were passed, in + which case the output argument is unmodified. +*/ +OSVR_CLIENTKIT_EXPORT OSVR_ReturnCode osvrClientGetNumDisplayInputs( + OSVR_DisplayConfig disp, OSVR_DisplayInputCount* numDisplayInputs); + +/** @brief Retrieve the pixel dimensions of a given display input for a display + config + + @param disp Display config object. + @param displayInputIndex The zero-based index of the display input. + @param[out] width Width (in pixels) of the display input. + @param[out] height Height (in pixels) of the display input. + + The out parameters are **constant** throughout the active, valid lifetime of + a display config object. + + @sa OSVR_DisplayDimension + + @return OSVR_RETURN_FAILURE if invalid parameters were passed, in + which case the output arguments are unmodified. +*/ +OSVR_CLIENTKIT_EXPORT OSVR_ReturnCode osvrClientGetDisplayDimensions( + OSVR_DisplayConfig disp, OSVR_DisplayInputCount displayInputIndex, + OSVR_DisplayDimension* width, OSVR_DisplayDimension* height); + +/** @brief A display config can have one (or theoretically more) viewers: + retrieve the viewer count. + + @param disp Display config object. + @param[out] viewers Number of viewers in the logical display topology, + **constant** throughout the active, valid lifetime of a display config + object. + + @sa OSVR_ViewerCount + + @return OSVR_RETURN_FAILURE if invalid parameters were passed, in which case + the output argument is unmodified. +*/ +OSVR_CLIENTKIT_EXPORT OSVR_ReturnCode +osvrClientGetNumViewers(OSVR_DisplayConfig disp, OSVR_ViewerCount* viewers); + +/** @brief Get the pose of a viewer in a display config. + + Note that there may not necessarily be any surfaces rendered from this pose + (it's the unused "center" eye in a stereo configuration, for instance) so + only use this if it makes integration into your engine or existing + applications (not originally designed for stereo) easier. + + Will only succeed if osvrClientCheckDisplayStartup() succeeds. + + @return OSVR_RETURN_FAILURE if invalid parameters were passed or no pose was + yet available, in which case the pose argument is unmodified. +*/ +OSVR_CLIENTKIT_EXPORT OSVR_ReturnCode osvrClientGetViewerPose( + OSVR_DisplayConfig disp, OSVR_ViewerCount viewer, OSVR_Pose3* pose); + +/** @brief Each viewer in a display config can have one or more "eyes" which + have a substantially similar pose: get the count. + + @param disp Display config object. + @param viewer Viewer ID + @param[out] eyes Number of eyes for this viewer in the logical display + topology, **constant** throughout the active, valid lifetime of a display + config object + + @sa OSVR_EyeCount + + @return OSVR_RETURN_FAILURE if invalid parameters were passed, in which case + the output argument is unmodified. +*/ +OSVR_CLIENTKIT_EXPORT OSVR_ReturnCode osvrClientGetNumEyesForViewer( + OSVR_DisplayConfig disp, OSVR_ViewerCount viewer, OSVR_EyeCount* eyes); + +/** @brief Get the "viewpoint" for the given eye of a viewer in a display + config. + + Will only succeed if osvrClientCheckDisplayStartup() succeeds. + + @param disp Display config object + @param viewer Viewer ID + @param eye Eye ID + @param[out] pose Room-space pose (not relative to pose of the viewer) + + @return OSVR_RETURN_FAILURE if invalid parameters were passed or no pose was + yet available, in which case the pose argument is unmodified. +*/ +OSVR_CLIENTKIT_EXPORT OSVR_ReturnCode +osvrClientGetViewerEyePose(OSVR_DisplayConfig disp, OSVR_ViewerCount viewer, + OSVR_EyeCount eye, OSVR_Pose3* pose); + +/** @brief Get the view matrix (inverse of pose) for the given eye of a + viewer in a display config - matrix of **doubles**. + + Will only succeed if osvrClientCheckDisplayStartup() succeeds. + + @param disp Display config object + @param viewer Viewer ID + @param eye Eye ID + @param flags Bitwise OR of matrix convention flags (see @ref MatrixFlags) + @param[out] mat Pass a double[::OSVR_MATRIX_SIZE] to get the transformation + matrix from room space to eye space (not relative to pose of the viewer) + + @return OSVR_RETURN_FAILURE if invalid parameters were passed or no pose was + yet available, in which case the output argument is unmodified. +*/ +OSVR_CLIENTKIT_EXPORT OSVR_ReturnCode osvrClientGetViewerEyeViewMatrixd( + OSVR_DisplayConfig disp, OSVR_ViewerCount viewer, OSVR_EyeCount eye, + OSVR_MatrixConventions flags, double* mat); + +/** @brief Get the view matrix (inverse of pose) for the given eye of a + viewer in a display config - matrix of **floats**. + + Will only succeed if osvrClientCheckDisplayStartup() succeeds. + + @param disp Display config object + @param viewer Viewer ID + @param eye Eye ID + @param flags Bitwise OR of matrix convention flags (see @ref MatrixFlags) + @param[out] mat Pass a float[::OSVR_MATRIX_SIZE] to get the transformation + matrix from room space to eye space (not relative to pose of the viewer) + + @return OSVR_RETURN_FAILURE if invalid parameters were passed or no pose was + yet available, in which case the output argument is unmodified. +*/ +OSVR_CLIENTKIT_EXPORT OSVR_ReturnCode osvrClientGetViewerEyeViewMatrixf( + OSVR_DisplayConfig disp, OSVR_ViewerCount viewer, OSVR_EyeCount eye, + OSVR_MatrixConventions flags, float* mat); + +/** @brief Each eye of each viewer in a display config has one or more surfaces + (aka "screens") on which content should be rendered. + + @param disp Display config object + @param viewer Viewer ID + @param eye Eye ID + @param[out] surfaces Number of surfaces (numbered [0, surfaces - 1]) for the + given viewer and eye. **Constant** throughout the active, valid lifetime of + a display config object. + + @sa OSVR_SurfaceCount + + @return OSVR_RETURN_FAILURE if invalid parameters were passed, in which case + the output argument is unmodified. +*/ +OSVR_CLIENTKIT_EXPORT OSVR_ReturnCode osvrClientGetNumSurfacesForViewerEye( + OSVR_DisplayConfig disp, OSVR_ViewerCount viewer, OSVR_EyeCount eye, + OSVR_SurfaceCount* surfaces); + +/** @brief Get the dimensions/location of the viewport **within the display + input** for a surface seen by an eye of a viewer in a display config. (This + does not include other video inputs that may be on a single virtual desktop, + etc. or explicitly account for display configurations that use multiple + video inputs. It does not necessarily indicate that a viewport in the sense + of glViewport must be created with these parameters, though the parameter + order matches for convenience.) + + @param disp Display config object + @param viewer Viewer ID + @param eye Eye ID + @param surface Surface ID + @param[out] left Output: Distance in pixels from the left of the video input + to the left of the viewport. + @param[out] bottom Output: Distance in pixels from the bottom of the video + input to the bottom of the viewport. + @param[out] width Output: Width of viewport in pixels. + @param[out] height Output: Height of viewport in pixels. + + + @return OSVR_RETURN_FAILURE if invalid parameters were passed, in which case + the output arguments are unmodified. +*/ +OSVR_CLIENTKIT_EXPORT OSVR_ReturnCode +osvrClientGetRelativeViewportForViewerEyeSurface( + OSVR_DisplayConfig disp, OSVR_ViewerCount viewer, OSVR_EyeCount eye, + OSVR_SurfaceCount surface, OSVR_ViewportDimension* left, + OSVR_ViewportDimension* bottom, OSVR_ViewportDimension* width, + OSVR_ViewportDimension* height); + +/** @brief Get the index of the display input for a surface seen by an eye of a + viewer in a display config. + + This is the OSVR-assigned display input: it may not (and in practice, + usually will not) match any platform-specific display indices. This function + exists to associate surfaces with video inputs as enumerated by + osvrClientGetNumDisplayInputs(). + + @param disp Display config object + @param viewer Viewer ID + @param eye Eye ID + @param surface Surface ID + @param[out] displayInput Zero-based index of the display input pixels for + this surface are tranmitted over. + + This association is **constant** throughout the active, valid lifetime of a + display config object. + + @sa osvrClientGetNumDisplayInputs(), + osvrClientGetRelativeViewportForViewerEyeSurface() + + @return OSVR_RETURN_FAILURE if invalid parameters were passed, in which + case the output argument is unmodified. + */ +OSVR_CLIENTKIT_EXPORT OSVR_ReturnCode +osvrClientGetViewerEyeSurfaceDisplayInputIndex( + OSVR_DisplayConfig disp, OSVR_ViewerCount viewer, OSVR_EyeCount eye, + OSVR_SurfaceCount surface, OSVR_DisplayInputCount* displayInput); + +/** @brief Get the projection matrix for a surface seen by an eye of a viewer + in a display config. (double version) + + @param disp Display config object + @param viewer Viewer ID + @param eye Eye ID + @param surface Surface ID + @param near Distance from viewpoint to near clipping plane - must be + positive. + @param far Distance from viewpoint to far clipping plane - must be positive + and not equal to near, typically greater than near. + @param flags Bitwise OR of matrix convention flags (see @ref MatrixFlags) + @param[out] matrix Output projection matrix: supply an array of 16 + (::OSVR_MATRIX_SIZE) doubles. + + @return OSVR_RETURN_FAILURE if invalid parameters were passed, in which case + the output argument is unmodified. +*/ +OSVR_CLIENTKIT_EXPORT OSVR_ReturnCode +osvrClientGetViewerEyeSurfaceProjectionMatrixd( + OSVR_DisplayConfig disp, OSVR_ViewerCount viewer, OSVR_EyeCount eye, + OSVR_SurfaceCount surface, double near, double far, + OSVR_MatrixConventions flags, double* matrix); + +/** @brief Get the projection matrix for a surface seen by an eye of a viewer + in a display config. (float version) + + @param disp Display config object + @param viewer Viewer ID + @param eye Eye ID + @param surface Surface ID + @param near Distance to near clipping plane - must be nonzero, typically + positive. + @param far Distance to far clipping plane - must be nonzero, typically + positive and greater than near. + @param flags Bitwise OR of matrix convention flags (see @ref MatrixFlags) + @param[out] matrix Output projection matrix: supply an array of 16 + (::OSVR_MATRIX_SIZE) floats. + + @return OSVR_RETURN_FAILURE if invalid parameters were passed, in which case + the output argument is unmodified. +*/ +OSVR_CLIENTKIT_EXPORT OSVR_ReturnCode +osvrClientGetViewerEyeSurfaceProjectionMatrixf( + OSVR_DisplayConfig disp, OSVR_ViewerCount viewer, OSVR_EyeCount eye, + OSVR_SurfaceCount surface, float near, float far, + OSVR_MatrixConventions flags, float* matrix); + +/** @brief Get the clipping planes (positions at unit distance) for a surface + seen by an eye of a viewer + in a display config. + + This is only for use in integrations that cannot accept a fully-formulated + projection matrix as returned by + osvrClientGetViewerEyeSurfaceProjectionMatrixf() or + osvrClientGetViewerEyeSurfaceProjectionMatrixd(), and may not necessarily + provide the same optimizations. + + As all the planes are given at unit (1) distance, before passing these + planes to a consuming function in your application/engine, you will typically + divide them by your near clipping plane distance. + + @param disp Display config object + @param viewer Viewer ID + @param eye Eye ID + @param surface Surface ID + @param[out] left Distance to left clipping plane + @param[out] right Distance to right clipping plane + @param[out] bottom Distance to bottom clipping plane + @param[out] top Distance to top clipping plane + + @return OSVR_RETURN_FAILURE if invalid parameters were passed, in which case + the output arguments are unmodified. +*/ +OSVR_CLIENTKIT_EXPORT OSVR_ReturnCode +osvrClientGetViewerEyeSurfaceProjectionClippingPlanes( + OSVR_DisplayConfig disp, OSVR_ViewerCount viewer, OSVR_EyeCount eye, + OSVR_SurfaceCount surface, double* left, double* right, double* bottom, + double* top); + +/** @brief Determines if a surface seen by an eye of a viewer in a display + config requests some distortion to be performed. + + This simply reports true or false, and does not specify which kind of + distortion implementations have been parameterized for this display. For + each distortion implementation your application supports, you'll want to + call the corresponding priority function to find out if it is available. + + @param disp Display config object + @param viewer Viewer ID + @param eye Eye ID + @param surface Surface ID + @param[out] distortionRequested Output parameter: whether distortion is + requested. **Constant** throughout the active, valid lifetime of a display + config object. + + @return OSVR_RETURN_FAILURE if invalid parameters were passed, in which case + the output argument is unmodified. +*/ +OSVR_CLIENTKIT_EXPORT OSVR_ReturnCode +osvrClientDoesViewerEyeSurfaceWantDistortion(OSVR_DisplayConfig disp, + OSVR_ViewerCount viewer, + OSVR_EyeCount eye, + OSVR_SurfaceCount surface, + OSVR_CBool* distortionRequested); + +/** @brief Returns the priority/availability of radial distortion parameters for + a surface seen by an eye of a viewer in a display config. + + If osvrClientDoesViewerEyeSurfaceWantDistortion() reports false, then the + display does not request distortion of any sort, and thus neither this nor + any other distortion strategy priority function will report an "available" + priority. + + @param disp Display config object + @param viewer Viewer ID + @param eye Eye ID + @param surface Surface ID + @param[out] priority Output: the priority level. Negative values + (canonically OSVR_DISTORTION_PRIORITY_UNAVAILABLE) indicate this technique + not available, higher values indicate higher preference for the given + technique based on the device's description. **Constant** throughout the + active, valid lifetime of a display config object. + + @return OSVR_RETURN_FAILURE if invalid parameters were passed, in which case + the output argument is unmodified. +*/ +OSVR_CLIENTKIT_EXPORT OSVR_ReturnCode +osvrClientGetViewerEyeSurfaceRadialDistortionPriority( + OSVR_DisplayConfig disp, OSVR_ViewerCount viewer, OSVR_EyeCount eye, + OSVR_SurfaceCount surface, OSVR_DistortionPriority* priority); + +/** @brief Returns the radial distortion parameters, if known/requested, for a + surface seen by an eye of a viewer in a display config. + + Will only succeed if osvrClientGetViewerEyeSurfaceRadialDistortionPriority() + reports a non-negative priority. + + @param disp Display config object + @param viewer Viewer ID + @param eye Eye ID + @param surface Surface ID + @param[out] params Output: the parameters for radial distortion + + @return OSVR_RETURN_FAILURE if this surface does not have these parameters + described, or if invalid parameters were passed, in which case the output + argument is unmodified. +*/ +OSVR_CLIENTKIT_EXPORT OSVR_ReturnCode +osvrClientGetViewerEyeSurfaceRadialDistortion( + OSVR_DisplayConfig disp, OSVR_ViewerCount viewer, OSVR_EyeCount eye, + OSVR_SurfaceCount surface, OSVR_RadialDistortionParameters* params); + +/** @} + @} +*/ + +OSVR_EXTERN_C_END + +#endif diff --git a/gfx/vr/service/osvr/ClientKit/Export.h b/gfx/vr/service/osvr/ClientKit/Export.h new file mode 100644 index 0000000000..eec95c2737 --- /dev/null +++ b/gfx/vr/service/osvr/ClientKit/Export.h @@ -0,0 +1,140 @@ +/** @file + @brief Automatically-generated export header - do not edit! + + @date 2016 + + @author + Sensics, Inc. + <http://sensics.com/osvr> +*/ + +// Copyright 2016 Sensics, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef OSVR_CLIENTKIT_EXPORT_H +#define OSVR_CLIENTKIT_EXPORT_H + +#ifdef OSVR_CLIENTKIT_STATIC_DEFINE +# define OSVR_CLIENTKIT_EXPORT +# define OSVR_CLIENTKIT_NO_EXPORT +#endif + +/* Per-compiler advance preventative definition */ +#if defined(__BORLANDC__) || defined(__CODEGEARC__) || defined(__HP_aCC) || \ + defined(__PGI) || defined(__WATCOMC__) +/* Compilers that don't support deprecated, according to CMake. */ +# ifndef OSVR_CLIENTKIT_DEPRECATED +# define OSVR_CLIENTKIT_DEPRECATED +# endif +#endif + +/* Check for attribute support */ +#if defined(__INTEL_COMPILER) +/* Checking before GNUC because Intel implements GNU extensions, + * so it chooses to define __GNUC__ as well. */ +# if __INTEL_COMPILER >= 1200 +/* Intel compiler 12.0 or newer can handle these attributes per CMake */ +# define OSVR_CLIENTKIT_EXPORT_HEADER_SUPPORTS_ATTRIBUTES +# endif + +#elif defined(__GNUC__) +# if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 2)) +/* GCC 4.2+ */ +# define OSVR_CLIENTKIT_EXPORT_HEADER_SUPPORTS_ATTRIBUTES +# endif +#endif + +/* Per-platform defines */ +#if defined(_MSC_VER) +/* MSVC on Windows */ + +# ifndef OSVR_CLIENTKIT_EXPORT +# ifdef osvrClientKit_EXPORTS +/* We are building this library */ +# define OSVR_CLIENTKIT_EXPORT __declspec(dllexport) +# else +/* We are using this library */ +# define OSVR_CLIENTKIT_EXPORT __declspec(dllimport) +# endif +# endif + +# ifndef OSVR_CLIENTKIT_DEPRECATED +# define OSVR_CLIENTKIT_DEPRECATED __declspec(deprecated) +# endif + +#elif defined(_WIN32) && defined(__GNUC__) +/* GCC-compatible on Windows */ + +# ifndef OSVR_CLIENTKIT_EXPORT +# ifdef osvrClientKit_EXPORTS +/* We are building this library */ +# define OSVR_CLIENTKIT_EXPORT __attribute__((dllexport)) +# else +/* We are using this library */ +# define OSVR_CLIENTKIT_EXPORT __attribute__((dllimport)) +# endif +# endif + +# ifndef OSVR_CLIENTKIT_DEPRECATED +# define OSVR_CLIENTKIT_DEPRECATED __attribute__((__deprecated__)) +# endif + +#elif defined(OSVR_CLIENTKIT_EXPORT_HEADER_SUPPORTS_ATTRIBUTES) || \ + (defined(__APPLE__) && defined(__MACH__)) +/* GCC4.2+ compatible (assuming something *nix-like) and Mac OS X */ +/* (The first macro is defined at the top of the file, if applicable) */ +/* see https://gcc.gnu.org/wiki/Visibility */ + +# ifndef OSVR_CLIENTKIT_EXPORT +/* We are building/using this library */ +# define OSVR_CLIENTKIT_EXPORT __attribute__((visibility("default"))) +# endif + +# ifndef OSVR_CLIENTKIT_NO_EXPORT +# define OSVR_CLIENTKIT_NO_EXPORT __attribute__((visibility("hidden"))) +# endif + +# ifndef OSVR_CLIENTKIT_DEPRECATED +# define OSVR_CLIENTKIT_DEPRECATED __attribute__((__deprecated__)) +# endif + +#endif +/* End of platform ifdefs */ + +/* fallback def */ +#ifndef OSVR_CLIENTKIT_EXPORT +# define OSVR_CLIENTKIT_EXPORT +#endif + +/* fallback def */ +#ifndef OSVR_CLIENTKIT_NO_EXPORT +# define OSVR_CLIENTKIT_NO_EXPORT +#endif + +/* fallback def */ +#ifndef OSVR_CLIENTKIT_DEPRECATED_EXPORT +# define OSVR_CLIENTKIT_DEPRECATED_EXPORT \ + OSVR_CLIENTKIT_EXPORT OSVR_CLIENTKIT_DEPRECATED +#endif + +/* fallback def */ +#ifndef OSVR_CLIENTKIT_DEPRECATED_NO_EXPORT +# define OSVR_CLIENTKIT_DEPRECATED_NO_EXPORT \ + OSVR_CLIENTKIT_NO_EXPORT OSVR_CLIENTKIT_DEPRECATED +#endif + +/* Clean up after ourselves */ +#undef OSVR_CLIENTKIT_EXPORT_HEADER_SUPPORTS_ATTRIBUTES + +#endif diff --git a/gfx/vr/service/osvr/ClientKit/InterfaceC.h b/gfx/vr/service/osvr/ClientKit/InterfaceC.h new file mode 100644 index 0000000000..a8523b1733 --- /dev/null +++ b/gfx/vr/service/osvr/ClientKit/InterfaceC.h @@ -0,0 +1,74 @@ +/** @file + @brief Header + + Must be c-safe! + + @date 2014 + + @author + Sensics, Inc. + <http://sensics.com/osvr> +*/ + +/* +// Copyright 2014 Sensics, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +*/ + +#ifndef INCLUDED_InterfaceC_h_GUID_D90BBAA6_AD62_499D_C023_2F6ED8987C17 +#define INCLUDED_InterfaceC_h_GUID_D90BBAA6_AD62_499D_C023_2F6ED8987C17 + +/* Internal Includes */ +#include <osvr/ClientKit/Export.h> +#include <osvr/Util/APIBaseC.h> +#include <osvr/Util/ReturnCodesC.h> +#include <osvr/Util/AnnotationMacrosC.h> +#include <osvr/Util/ClientOpaqueTypesC.h> + +/* Library/third-party includes */ +/* none */ + +/* Standard includes */ +/* none */ + +OSVR_EXTERN_C_BEGIN +/** @addtogroup ClientKit +@{ +*/ + +/** @brief Get the interface associated with the given path. + @param ctx Client context + @param path A resource path (null-terminated string) + @param[out] iface The interface object. May be freed when no longer needed, + otherwise it will be freed when the context is closed. +*/ +OSVR_CLIENTKIT_EXPORT OSVR_ReturnCode osvrClientGetInterface( + OSVR_ClientContext ctx, const char path[], OSVR_ClientInterface* iface); + +/** @brief Free an interface object before context closure. + + @param ctx Client context + @param iface The interface object + + @returns OSVR_RETURN_SUCCESS unless a null context or interface was passed + or the given interface was not found in the context (i.e. had already been + freed) +*/ +OSVR_CLIENTKIT_EXPORT OSVR_ReturnCode +osvrClientFreeInterface(OSVR_ClientContext ctx, OSVR_ClientInterface iface); + +/** @} */ +OSVR_EXTERN_C_END + +#endif diff --git a/gfx/vr/service/osvr/ClientKit/InterfaceCallbackC.h b/gfx/vr/service/osvr/ClientKit/InterfaceCallbackC.h new file mode 100644 index 0000000000..18e2c135a7 --- /dev/null +++ b/gfx/vr/service/osvr/ClientKit/InterfaceCallbackC.h @@ -0,0 +1,77 @@ +/** @file + @brief Header + + Must be c-safe! + + @date 2014 + + @author + Sensics, Inc. + <http://sensics.com/osvr> +*/ + +/* +// Copyright 2014 Sensics, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +*/ + +#ifndef INCLUDED_InterfaceCallbacksC_h_GUID_8F16E6CB_F998_4ABC_5B6B_4FC1E4B71BC9 +#define INCLUDED_InterfaceCallbacksC_h_GUID_8F16E6CB_F998_4ABC_5B6B_4FC1E4B71BC9 + +/* Internal Includes */ +#include <osvr/ClientKit/Export.h> +#include <osvr/Util/APIBaseC.h> +#include <osvr/Util/ReturnCodesC.h> +#include <osvr/Util/AnnotationMacrosC.h> +#include <osvr/Util/ClientOpaqueTypesC.h> +#include <osvr/Util/ClientCallbackTypesC.h> + +/* Library/third-party includes */ +/* none */ + +/* Standard includes */ +/* none */ + +OSVR_EXTERN_C_BEGIN + +#define OSVR_INTERFACE_CALLBACK_METHOD(TYPE) \ + /** @brief Register a callback for TYPE reports on an interface */ \ + OSVR_CLIENTKIT_EXPORT OSVR_ReturnCode osvrRegister##TYPE##Callback( \ + OSVR_ClientInterface iface, OSVR_##TYPE##Callback cb, void* userdata); + +OSVR_INTERFACE_CALLBACK_METHOD(Pose) +OSVR_INTERFACE_CALLBACK_METHOD(Position) +OSVR_INTERFACE_CALLBACK_METHOD(Orientation) +OSVR_INTERFACE_CALLBACK_METHOD(Velocity) +OSVR_INTERFACE_CALLBACK_METHOD(LinearVelocity) +OSVR_INTERFACE_CALLBACK_METHOD(AngularVelocity) +OSVR_INTERFACE_CALLBACK_METHOD(Acceleration) +OSVR_INTERFACE_CALLBACK_METHOD(LinearAcceleration) +OSVR_INTERFACE_CALLBACK_METHOD(AngularAcceleration) +OSVR_INTERFACE_CALLBACK_METHOD(Button) +OSVR_INTERFACE_CALLBACK_METHOD(Analog) +OSVR_INTERFACE_CALLBACK_METHOD(Imaging) +OSVR_INTERFACE_CALLBACK_METHOD(Location2D) +OSVR_INTERFACE_CALLBACK_METHOD(Direction) +OSVR_INTERFACE_CALLBACK_METHOD(EyeTracker2D) +OSVR_INTERFACE_CALLBACK_METHOD(EyeTracker3D) +OSVR_INTERFACE_CALLBACK_METHOD(EyeTrackerBlink) +OSVR_INTERFACE_CALLBACK_METHOD(NaviVelocity) +OSVR_INTERFACE_CALLBACK_METHOD(NaviPosition) + +#undef OSVR_INTERFACE_CALLBACK_METHOD + +OSVR_EXTERN_C_END + +#endif diff --git a/gfx/vr/service/osvr/ClientKit/InterfaceStateC.h b/gfx/vr/service/osvr/ClientKit/InterfaceStateC.h new file mode 100644 index 0000000000..46d0b60393 --- /dev/null +++ b/gfx/vr/service/osvr/ClientKit/InterfaceStateC.h @@ -0,0 +1,79 @@ +/** @file + @brief Header + + Must be c-safe! + + @date 2014 + + @author + Sensics, Inc. + <http://sensics.com/osvr> +*/ + +/* +// Copyright 2014 Sensics, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +*/ + +#ifndef INCLUDED_InterfaceStateC_h_GUID_8F85D178_74B9_4AA9_4E9E_243089411408 +#define INCLUDED_InterfaceStateC_h_GUID_8F85D178_74B9_4AA9_4E9E_243089411408 + +/* Internal Includes */ +#include <osvr/ClientKit/Export.h> +#include <osvr/Util/APIBaseC.h> +#include <osvr/Util/ReturnCodesC.h> +#include <osvr/Util/AnnotationMacrosC.h> +#include <osvr/Util/ClientOpaqueTypesC.h> +#include <osvr/Util/ClientReportTypesC.h> +#include <osvr/Util/TimeValueC.h> + +/* Library/third-party includes */ +/* none */ + +/* Standard includes */ +/* none */ + +OSVR_EXTERN_C_BEGIN + +#define OSVR_CALLBACK_METHODS(TYPE) \ + /** @brief Get TYPE state from an interface, returning failure if none \ + * exists */ \ + OSVR_CLIENTKIT_EXPORT OSVR_ReturnCode osvrGet##TYPE##State( \ + OSVR_ClientInterface iface, struct OSVR_TimeValue* timestamp, \ + OSVR_##TYPE##State* state); + +OSVR_CALLBACK_METHODS(Pose) +OSVR_CALLBACK_METHODS(Position) +OSVR_CALLBACK_METHODS(Orientation) +OSVR_CALLBACK_METHODS(Velocity) +OSVR_CALLBACK_METHODS(LinearVelocity) +OSVR_CALLBACK_METHODS(AngularVelocity) +OSVR_CALLBACK_METHODS(Acceleration) +OSVR_CALLBACK_METHODS(LinearAcceleration) +OSVR_CALLBACK_METHODS(AngularAcceleration) +OSVR_CALLBACK_METHODS(Button) +OSVR_CALLBACK_METHODS(Analog) +OSVR_CALLBACK_METHODS(Location2D) +OSVR_CALLBACK_METHODS(Direction) +OSVR_CALLBACK_METHODS(EyeTracker2D) +OSVR_CALLBACK_METHODS(EyeTracker3D) +OSVR_CALLBACK_METHODS(EyeTrackerBlink) +OSVR_CALLBACK_METHODS(NaviVelocity) +OSVR_CALLBACK_METHODS(NaviPosition) + +#undef OSVR_CALLBACK_METHODS + +OSVR_EXTERN_C_END + +#endif diff --git a/gfx/vr/service/osvr/ClientKit/SystemCallbackC.h b/gfx/vr/service/osvr/ClientKit/SystemCallbackC.h new file mode 100644 index 0000000000..2476d5f21c --- /dev/null +++ b/gfx/vr/service/osvr/ClientKit/SystemCallbackC.h @@ -0,0 +1,47 @@ +/** @file + @brief Header + + Must be c-safe! + + @date 2014 + + @author + Sensics, Inc. + <http://sensics.com/osvr> +*/ + +/* +// Copyright 2014 Sensics, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +*/ + +#ifndef INCLUDED_SystemCallbackC_h_GUID_543F3F04_343E_4389_08A0_DEA988EC23F7 +#define INCLUDED_SystemCallbackC_h_GUID_543F3F04_343E_4389_08A0_DEA988EC23F7 + +/* Internal Includes */ +#include <osvr/ClientKit/Export.h> +#include <osvr/Util/APIBaseC.h> +#include <osvr/Util/ReturnCodesC.h> +#include <osvr/Util/AnnotationMacrosC.h> + +/* Library/third-party includes */ +/* none */ + +/* Standard includes */ +/* none */ + +OSVR_EXTERN_C_BEGIN +OSVR_EXTERN_C_END + +#endif diff --git a/gfx/vr/service/osvr/ClientKit/TransformsC.h b/gfx/vr/service/osvr/ClientKit/TransformsC.h new file mode 100644 index 0000000000..183497dfd8 --- /dev/null +++ b/gfx/vr/service/osvr/ClientKit/TransformsC.h @@ -0,0 +1,75 @@ +/** @file + @brief Header controlling the OSVR transformation hierarchy + + Must be c-safe! + + @date 2015 + + @author + Sensics, Inc. + <http://sensics.com/osvr> +*/ + +/* +// Copyright 2015 Sensics, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +*/ + +#ifndef INCLUDED_TransformsC_h_GUID_5B5B7438_42D4_4095_E48A_90E2CC13498E +#define INCLUDED_TransformsC_h_GUID_5B5B7438_42D4_4095_E48A_90E2CC13498E + +/* Internal Includes */ +#include <osvr/ClientKit/Export.h> +#include <osvr/Util/APIBaseC.h> +#include <osvr/Util/ReturnCodesC.h> +#include <osvr/Util/ClientOpaqueTypesC.h> + +/* Library/third-party includes */ +/* none */ + +/* Standard includes */ +/* none */ + +OSVR_EXTERN_C_BEGIN + +/** @addtogroup ClientKit + @{ +*/ + +/** @brief Updates the internal "room to world" transformation (applied to all + tracker data for this client context instance) based on the user's head + orientation, so that the direction the user is facing becomes -Z to your + application. Only rotates about the Y axis (yaw). + + Note that this method internally calls osvrClientUpdate() to get a head pose + so your callbacks may be called during its execution! + + @param ctx Client context +*/ +OSVR_CLIENTKIT_EXPORT OSVR_ReturnCode +osvrClientSetRoomRotationUsingHead(OSVR_ClientContext ctx); + +/** @brief Clears/resets the internal "room to world" transformation back to an + identity transformation - that is, clears the effect of any other + manipulation of the room to world transform. + + @param ctx Client context +*/ +OSVR_CLIENTKIT_EXPORT OSVR_ReturnCode +osvrClientClearRoomToWorldTransform(OSVR_ClientContext ctx); + +/** @} */ +OSVR_EXTERN_C_END + +#endif diff --git a/gfx/vr/service/osvr/Util/APIBaseC.h b/gfx/vr/service/osvr/Util/APIBaseC.h new file mode 100644 index 0000000000..5d133f5c52 --- /dev/null +++ b/gfx/vr/service/osvr/Util/APIBaseC.h @@ -0,0 +1,50 @@ +/** @file + @brief Header providing basic C macros for defining API headers. + + Must be c-safe! + + @date 2014 + + @author + Sensics, Inc. + <http://sensics.com/osvr> +*/ + +/* +// Copyright 2014 Sensics, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +*/ + +#ifndef INCLUDED_APIBaseC_h_GUID_C5A2E769_2ADC_429E_D250_DF0883E6E5DB +#define INCLUDED_APIBaseC_h_GUID_C5A2E769_2ADC_429E_D250_DF0883E6E5DB + +#ifdef __cplusplus +# define OSVR_C_ONLY(X) +# define OSVR_CPP_ONLY(X) X +# define OSVR_EXTERN_C_BEGIN extern "C" { +# define OSVR_EXTERN_C_END } +# define OSVR_INLINE inline +#else +# define OSVR_C_ONLY(X) X +# define OSVR_CPP_ONLY(X) +# define OSVR_EXTERN_C_BEGIN +# define OSVR_EXTERN_C_END +# ifdef _MSC_VER +# define OSVR_INLINE static __inline +# else +# define OSVR_INLINE static inline +# endif +#endif + +#endif diff --git a/gfx/vr/service/osvr/Util/AnnotationMacrosC.h b/gfx/vr/service/osvr/Util/AnnotationMacrosC.h new file mode 100644 index 0000000000..6ce645840c --- /dev/null +++ b/gfx/vr/service/osvr/Util/AnnotationMacrosC.h @@ -0,0 +1,232 @@ +/** @file + @brief Header containing macros for source-level annotation. + + In theory, supporting MSVC SAL, as well as compatible GCC and + Clang attributes. In practice, expanded as time allows and requires. + + Must be c-safe! + + @date 2014 + + @author + Sensics, Inc. + <http://sensics.com/osvr> +*/ + +/* +// Copyright 2014 Sensics, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +*/ + +#ifndef INCLUDED_AnnotationMacrosC_h_GUID_48538D9B_35E3_4E9A_D2B0_D83D51DD5900 +#define INCLUDED_AnnotationMacrosC_h_GUID_48538D9B_35E3_4E9A_D2B0_D83D51DD5900 + +#ifndef OSVR_DISABLE_ANALYSIS + +# if defined(_MSC_VER) && (_MSC_VER >= 1700) +/* Visual C++ (2012 and newer) */ +/* Using SAL attribute format: + * http://msdn.microsoft.com/en-us/library/ms182032(v=vs.120).aspx */ + +# include <sal.h> + +# define OSVR_IN _In_ +# define OSVR_IN_PTR _In_ +# define OSVR_IN_OPT _In_opt_ +# define OSVR_IN_STRZ _In_z_ +# define OSVR_IN_READS(NUM_ELEMENTS) _In_reads_(NUM_ELEMENTS) + +# define OSVR_OUT _Out_ +# define OSVR_OUT_PTR _Outptr_ +# define OSVR_OUT_OPT _Out_opt_ + +# define OSVR_INOUT _Inout_ +# define OSVR_INOUT_PTR _Inout_ + +# define OSVR_RETURN_WARN_UNUSED _Must_inspect_result_ +# define OSVR_RETURN_SUCCESS_CONDITION(X) _Return_type_success_(X) + +/* end of msvc section */ +# elif defined(__GNUC__) && (__GNUC__ >= 4) +/* section for GCC and GCC-alikes */ + +# if defined(__clang__) +/* clang-specific section */ +# endif + +# define OSVR_FUNC_NONNULL(X) __attribute__((__nonnull__ X)) +# define OSVR_RETURN_WARN_UNUSED __attribute__((warn_unused_result)) + +/* end of gcc section and compiler detection */ +# endif + +/* end of ndef disable analysis */ +#endif + +/* Fallback declarations */ +/** +@defgroup annotation_macros Static analysis annotation macros +@brief Wrappers for Microsoft's SAL annotations and others +@ingroup Util + +Use of these is optional, but recommended particularly for C APIs, +as well as any methods handling a buffer with a length. +@{ +*/ +/** @name Parameter annotations + + These indicate the role and valid values for parameters to functions. + + At most one of these should be placed before a parameter's type name in the + function parameter list, in both the declaration and definition. (They must + match!) + @{ +*/ +/** @def OSVR_IN + @brief Indicates a required function parameter that serves only as input. +*/ +#ifndef OSVR_IN +# define OSVR_IN +#endif + +/** @def OSVR_IN_PTR + @brief Indicates a required pointer (non-null) function parameter that + serves only as input. +*/ +#ifndef OSVR_IN_PTR +# define OSVR_IN_PTR +#endif + +/** @def OSVR_IN_OPT + @brief Indicates a function parameter (pointer) that serves only as input, + but is optional and might be NULL. +*/ +#ifndef OSVR_IN_OPT +# define OSVR_IN_OPT +#endif + +/** @def OSVR_IN_STRZ + @brief Indicates a null-terminated string function parameter that serves + only as input. +*/ +#ifndef OSVR_IN_STRZ +# define OSVR_IN_STRZ +#endif + +/** @def OSVR_IN_READS(NUM_ELEMENTS) + @brief Indicates a buffer containing input with the specified number of + elements. + + The specified number of elements is typically the name of another parameter. +*/ +#ifndef OSVR_IN_READS +# define OSVR_IN_READS(NUM_ELEMENTS) +#endif + +/** @def OSVR_OUT + @brief Indicates a required function parameter that serves only as output. + In C code, since this usually means "pointer", you probably want + OSVR_OUT_PTR instead. +*/ +#ifndef OSVR_OUT +# define OSVR_OUT +#endif + +/** @def OSVR_OUT_PTR + @brief Indicates a required pointer (non-null) function parameter that + serves only as output. +*/ +#ifndef OSVR_OUT_PTR +# define OSVR_OUT_PTR +#endif + +/** @def OSVR_OUT_OPT + @brief Indicates a function parameter (pointer) that serves only as output, + but is optional and might be NULL +*/ +#ifndef OSVR_OUT_OPT +# define OSVR_OUT_OPT +#endif + +/** @def OSVR_INOUT + @brief Indicates a required function parameter that is both read and written + to. + + In C code, since this usually means "pointer", you probably want + OSVR_INOUT_PTR instead. +*/ +#ifndef OSVR_INOUT +# define OSVR_INOUT +#endif + +/** @def OSVR_INOUT_PTR + @brief Indicates a required pointer (non-null) function parameter that is + both read and written to. +*/ +#ifndef OSVR_INOUT_PTR +# define OSVR_INOUT_PTR +#endif + +/* End of parameter annotations. */ +/** @} */ + +/** @name Function annotations + + These indicate particular relevant aspects about a function. Some + duplicate the effective meaning of parameter annotations: applying both + allows the fullest extent of static analysis tools to analyze the code, + and in some compilers, generate warnings. + + @{ +*/ +/** @def OSVR_FUNC_NONNULL(X) + @brief Indicates the parameter(s) that must be non-null. + + @param X A parenthesized list of parameters by number (1-based index) + + Should be placed after a function declaration (but before the + semicolon). Repeating in the definition is not needed. +*/ +#ifndef OSVR_FUNC_NONNULL +# define OSVR_FUNC_NONNULL(X) +#endif + +/** @def OSVR_RETURN_WARN_UNUSED + @brief Indicates the function has a return value that must be used (either a + security problem or an obvious bug if not). + + Should be placed before the return value (and virtual keyword, if + applicable) in both declaration and definition. +*/ +#ifndef OSVR_RETURN_WARN_UNUSED +# define OSVR_RETURN_WARN_UNUSED +#endif +/* End of function annotations. */ +/** @} */ + +/** @def OSVR_RETURN_SUCCESS_CONDITION + @brief Applied to a typedef, indicates the condition for `return` under + which a function returning it should be considered to have succeeded (thus + holding certain specifications). + + Should be placed before the typename in a typedef, with the parameter + including the keyword `return` to substitute for the return value. +*/ +#ifndef OSVR_RETURN_SUCCESS_CONDITION +# define OSVR_RETURN_SUCCESS_CONDITION(X) +#endif + +/* End of annotation group. */ +/** @} */ +#endif diff --git a/gfx/vr/service/osvr/Util/BoolC.h b/gfx/vr/service/osvr/Util/BoolC.h new file mode 100644 index 0000000000..b50ec7cfd7 --- /dev/null +++ b/gfx/vr/service/osvr/Util/BoolC.h @@ -0,0 +1,59 @@ +/** @file + @brief Header providing a C-safe "bool" type, because we can't depend on + Visual Studio providing proper C99 support in external-facing APIs. + + Must be c-safe! + + @date 2015 + + @author + Sensics, Inc. + <http://sensics.com/osvr> +*/ + +/* +// Copyright 2015 Sensics, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +*/ + +#ifndef INCLUDED_BoolC_h_GUID_4F97BE90_2758_4BA5_B0FC_0CA92DEBA210 +#define INCLUDED_BoolC_h_GUID_4F97BE90_2758_4BA5_B0FC_0CA92DEBA210 + +/* Internal Includes */ +#include <osvr/Util/APIBaseC.h> +#include <osvr/Util/StdInt.h> + +/* Library/third-party includes */ +/* none */ + +/* Standard includes */ +/* none */ + +OSVR_EXTERN_C_BEGIN +/** @addtogroup Util +@{ +*/ + +/** @brief A pre-C99-safe bool type. Canonical values for true and false are + * provided. Interpretation of other values is not defined. */ +typedef uint8_t OSVR_CBool; +/** @brief Canonical "true" value for OSVR_CBool */ +#define OSVR_TRUE (1) +/** @brief Canonical "false" value for OSVR_CBool */ +#define OSVR_FALSE (0) + +/** @} */ +OSVR_EXTERN_C_END + +#endif diff --git a/gfx/vr/service/osvr/Util/ChannelCountC.h b/gfx/vr/service/osvr/Util/ChannelCountC.h new file mode 100644 index 0000000000..dc49b3b171 --- /dev/null +++ b/gfx/vr/service/osvr/Util/ChannelCountC.h @@ -0,0 +1,57 @@ +/** @file + @brief Header + + Must be c-safe! + + @date 2015 + + @author + Sensics, Inc. + <http://sensics.com/osvr> +*/ + +/* +// Copyright 2015 Sensics, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +*/ + +#ifndef INCLUDED_ChannelCountC_h_GUID_CF7E5EE7_28B0_4B99_E823_DD701904B5D1 +#define INCLUDED_ChannelCountC_h_GUID_CF7E5EE7_28B0_4B99_E823_DD701904B5D1 + +/* Internal Includes */ +#include <osvr/Util/StdInt.h> +#include <osvr/Util/APIBaseC.h> + +/* Library/third-party includes */ +/* none */ + +/* Standard includes */ +/* none */ + +OSVR_EXTERN_C_BEGIN + +/** @addtogroup PluginKit +@{ +*/ + +/** @brief The integer type specifying a number of channels/sensors or a +channel/sensor index. +*/ +typedef uint32_t OSVR_ChannelCount; + +/** @} */ + +OSVR_EXTERN_C_END + +#endif diff --git a/gfx/vr/service/osvr/Util/ClientCallbackTypesC.h b/gfx/vr/service/osvr/Util/ClientCallbackTypesC.h new file mode 100644 index 0000000000..4a3c53e822 --- /dev/null +++ b/gfx/vr/service/osvr/Util/ClientCallbackTypesC.h @@ -0,0 +1,140 @@ +/** @file + @brief Header + + Must be c-safe! + + GENERATED - do not edit by hand! + + @date 2014 + + @author + Sensics, Inc. + <http://sensics.com/osvr> +*/ + +/* +// Copyright 2014 Sensics, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +*/ + +#ifndef INCLUDED_ClientCallbackTypesC_h_GUID_4D43A675_C8A4_4BBF_516F_59E6C785E4EF +#define INCLUDED_ClientCallbackTypesC_h_GUID_4D43A675_C8A4_4BBF_516F_59E6C785E4EF + +/* Internal Includes */ +#include <osvr/Util/ClientReportTypesC.h> +#include <osvr/Util/ImagingReportTypesC.h> +#include <osvr/Util/ReturnCodesC.h> +#include <osvr/Util/TimeValueC.h> + +/* Library/third-party includes */ +/* none */ + +/* Standard includes */ +/* none */ + +OSVR_EXTERN_C_BEGIN + +/** @addtogroup ClientKit + @{ +*/ + +/** @name Report callback types + @{ +*/ + +/* generated file - do not edit! */ +/** @brief C function type for a Pose callback */ +typedef void (*OSVR_PoseCallback)(void* userdata, + const struct OSVR_TimeValue* timestamp, + const struct OSVR_PoseReport* report); +/** @brief C function type for a Position callback */ +typedef void (*OSVR_PositionCallback)(void* userdata, + const struct OSVR_TimeValue* timestamp, + const struct OSVR_PositionReport* report); +/** @brief C function type for a Orientation callback */ +typedef void (*OSVR_OrientationCallback)( + void* userdata, const struct OSVR_TimeValue* timestamp, + const struct OSVR_OrientationReport* report); +/** @brief C function type for a Velocity callback */ +typedef void (*OSVR_VelocityCallback)(void* userdata, + const struct OSVR_TimeValue* timestamp, + const struct OSVR_VelocityReport* report); +/** @brief C function type for a LinearVelocity callback */ +typedef void (*OSVR_LinearVelocityCallback)( + void* userdata, const struct OSVR_TimeValue* timestamp, + const struct OSVR_LinearVelocityReport* report); +/** @brief C function type for a AngularVelocity callback */ +typedef void (*OSVR_AngularVelocityCallback)( + void* userdata, const struct OSVR_TimeValue* timestamp, + const struct OSVR_AngularVelocityReport* report); +/** @brief C function type for a Acceleration callback */ +typedef void (*OSVR_AccelerationCallback)( + void* userdata, const struct OSVR_TimeValue* timestamp, + const struct OSVR_AccelerationReport* report); +/** @brief C function type for a LinearAcceleration callback */ +typedef void (*OSVR_LinearAccelerationCallback)( + void* userdata, const struct OSVR_TimeValue* timestamp, + const struct OSVR_LinearAccelerationReport* report); +/** @brief C function type for a AngularAcceleration callback */ +typedef void (*OSVR_AngularAccelerationCallback)( + void* userdata, const struct OSVR_TimeValue* timestamp, + const struct OSVR_AngularAccelerationReport* report); +/** @brief C function type for a Button callback */ +typedef void (*OSVR_ButtonCallback)(void* userdata, + const struct OSVR_TimeValue* timestamp, + const struct OSVR_ButtonReport* report); +/** @brief C function type for a Analog callback */ +typedef void (*OSVR_AnalogCallback)(void* userdata, + const struct OSVR_TimeValue* timestamp, + const struct OSVR_AnalogReport* report); +/** @brief C function type for a Imaging callback */ +typedef void (*OSVR_ImagingCallback)(void* userdata, + const struct OSVR_TimeValue* timestamp, + const struct OSVR_ImagingReport* report); +/** @brief C function type for a Location2D callback */ +typedef void (*OSVR_Location2DCallback)( + void* userdata, const struct OSVR_TimeValue* timestamp, + const struct OSVR_Location2DReport* report); +/** @brief C function type for a Direction callback */ +typedef void (*OSVR_DirectionCallback)( + void* userdata, const struct OSVR_TimeValue* timestamp, + const struct OSVR_DirectionReport* report); +/** @brief C function type for a EyeTracker2D callback */ +typedef void (*OSVR_EyeTracker2DCallback)( + void* userdata, const struct OSVR_TimeValue* timestamp, + const struct OSVR_EyeTracker2DReport* report); +/** @brief C function type for a EyeTracker3D callback */ +typedef void (*OSVR_EyeTracker3DCallback)( + void* userdata, const struct OSVR_TimeValue* timestamp, + const struct OSVR_EyeTracker3DReport* report); +/** @brief C function type for a EyeTrackerBlink callback */ +typedef void (*OSVR_EyeTrackerBlinkCallback)( + void* userdata, const struct OSVR_TimeValue* timestamp, + const struct OSVR_EyeTrackerBlinkReport* report); +/** @brief C function type for a NaviVelocity callback */ +typedef void (*OSVR_NaviVelocityCallback)( + void* userdata, const struct OSVR_TimeValue* timestamp, + const struct OSVR_NaviVelocityReport* report); +/** @brief C function type for a NaviPosition callback */ +typedef void (*OSVR_NaviPositionCallback)( + void* userdata, const struct OSVR_TimeValue* timestamp, + const struct OSVR_NaviPositionReport* report); + +/** @} */ + +/** @} */ + +OSVR_EXTERN_C_END + +#endif diff --git a/gfx/vr/service/osvr/Util/ClientOpaqueTypesC.h b/gfx/vr/service/osvr/Util/ClientOpaqueTypesC.h new file mode 100644 index 0000000000..84203bb0d4 --- /dev/null +++ b/gfx/vr/service/osvr/Util/ClientOpaqueTypesC.h @@ -0,0 +1,69 @@ +/** @file + @brief Header declaring opaque types used by @ref Client and @ref ClientKit + + Must be c-safe! + + @date 2014 + + @author + Sensics, Inc. + <http://sensics.com/osvr> +*/ + +/* +// Copyright 2014 Sensics, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +*/ + +#ifndef INCLUDED_ClientOpaqueTypesC_h_GUID_24B79ED2_5751_4BA2_1690_BBD250EBC0C1 +#define INCLUDED_ClientOpaqueTypesC_h_GUID_24B79ED2_5751_4BA2_1690_BBD250EBC0C1 + +/* Internal Includes */ +#include <osvr/Util/APIBaseC.h> + +/* Library/third-party includes */ +/* none */ + +/* Standard includes */ +/* none */ + +OSVR_EXTERN_C_BEGIN + +/** @addtogroup ClientKit + @{ +*/ +/** @brief Opaque handle that should be retained by your application. You need + only and exactly one. + + Created by osvrClientInit() at application start. + + You are required to clean up this handle with osvrClientShutdown(). +*/ +typedef struct OSVR_ClientContextObject* OSVR_ClientContext; + +/** @brief Opaque handle to an interface used for registering callbacks and + getting status. + + You are not required to clean up this handle (it will be automatically + cleaned up when the context is), but you can if you are no longer using it, + using osvrClientFreeInterface() to inform the context that you no longer need + this interface. +*/ +typedef struct OSVR_ClientInterfaceObject* OSVR_ClientInterface; + +/** @} */ + +OSVR_EXTERN_C_END + +#endif diff --git a/gfx/vr/service/osvr/Util/ClientReportTypesC.h b/gfx/vr/service/osvr/Util/ClientReportTypesC.h new file mode 100644 index 0000000000..09769d2462 --- /dev/null +++ b/gfx/vr/service/osvr/Util/ClientReportTypesC.h @@ -0,0 +1,348 @@ +/** @file + @brief Header + + Must be c-safe! + + @date 2014 + + @author + Sensics, Inc. + <http://sensics.com/osvr> +*/ + +/* +// Copyright 2014 Sensics, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +*/ + +#ifndef INCLUDED_ClientReportTypesC_h_GUID_E79DAB07_78B7_4795_1EB9_CA6EEB274AEE +#define INCLUDED_ClientReportTypesC_h_GUID_E79DAB07_78B7_4795_1EB9_CA6EEB274AEE + +/* Internal Includes */ +#include <osvr/Util/APIBaseC.h> +#include <osvr/Util/Pose3C.h> +#include <osvr/Util/StdInt.h> + +#include <osvr/Util/Vec2C.h> +#include <osvr/Util/Vec3C.h> +#include <osvr/Util/ChannelCountC.h> +#include <osvr/Util/BoolC.h> + +/* Library/third-party includes */ +/* none */ + +/* Standard includes */ +/* none */ + +OSVR_EXTERN_C_BEGIN + +/** @addtogroup ClientKit + @{ +*/ + +/** @name State types +@{ +*/ +/** @brief Type of position state */ +typedef OSVR_Vec3 OSVR_PositionState; + +/** @brief Type of orientation state */ +typedef OSVR_Quaternion OSVR_OrientationState; + +/** @brief Type of pose state */ +typedef OSVR_Pose3 OSVR_PoseState; + +/** @brief Type of linear velocity state */ +typedef OSVR_Vec3 OSVR_LinearVelocityState; + +/** @brief The quaternion represents the incremental rotation taking place over + a period of dt seconds. Use of dt (which does not necessarily + have to be 1, as other velocity/acceleration representations imply) and an + incremental quaternion allows device reports to be scaled to avoid aliasing +*/ +typedef struct OSVR_IncrementalQuaternion { + OSVR_Quaternion incrementalRotation; + double dt; +} OSVR_IncrementalQuaternion; + +/** @brief Type of angular velocity state: an incremental quaternion, providing + the incremental rotation taking place due to velocity over a period of dt + seconds. +*/ +typedef OSVR_IncrementalQuaternion OSVR_AngularVelocityState; + +/** @brief Struct for combined velocity state */ +typedef struct OSVR_VelocityState { + OSVR_LinearVelocityState linearVelocity; + /** @brief Whether the data source reports valid data for + #OSVR_VelocityState::linearVelocity */ + OSVR_CBool linearVelocityValid; + + OSVR_AngularVelocityState angularVelocity; + /** @brief Whether the data source reports valid data for + #OSVR_VelocityState::angularVelocity */ + OSVR_CBool angularVelocityValid; +} OSVR_VelocityState; + +/** @brief Type of linear acceleration state */ +typedef OSVR_Vec3 OSVR_LinearAccelerationState; + +/** @brief Type of angular acceleration state + */ +typedef OSVR_IncrementalQuaternion OSVR_AngularAccelerationState; + +/** @brief Struct for combined acceleration state */ +typedef struct OSVR_AccelerationState { + OSVR_LinearAccelerationState linearAcceleration; + /** @brief Whether the data source reports valid data for + #OSVR_AccelerationState::linearAcceleration */ + OSVR_CBool linearAccelerationValid; + + OSVR_AngularAccelerationState angularAcceleration; + /** @brief Whether the data source reports valid data for + #OSVR_AccelerationState::angularAcceleration */ + OSVR_CBool angularAccelerationValid; +} OSVR_AccelerationState; + +/** @brief Type of button state */ +typedef uint8_t OSVR_ButtonState; + +/** @brief OSVR_ButtonState value indicating "button down" */ +#define OSVR_BUTTON_PRESSED (1) + +/** @brief OSVR_ButtonState value indicating "button up" */ +#define OSVR_BUTTON_NOT_PRESSED (0) + +/** @brief Type of analog channel state */ +typedef double OSVR_AnalogState; + +/** @} */ + +/** @name Report types + @{ +*/ +/** @brief Report type for a position callback on a tracker interface */ +typedef struct OSVR_PositionReport { + /** @brief Identifies the sensor that the report comes from */ + int32_t sensor; + /** @brief The position vector */ + OSVR_PositionState xyz; +} OSVR_PositionReport; + +/** @brief Report type for an orientation callback on a tracker interface */ +typedef struct OSVR_OrientationReport { + /** @brief Identifies the sensor that the report comes from */ + int32_t sensor; + /** @brief The rotation unit quaternion */ + OSVR_OrientationState rotation; +} OSVR_OrientationReport; + +/** @brief Report type for a pose (position and orientation) callback on a + tracker interface +*/ +typedef struct OSVR_PoseReport { + /** @brief Identifies the sensor that the report comes from */ + int32_t sensor; + /** @brief The pose structure, containing a position vector and a rotation + quaternion + */ + OSVR_PoseState pose; +} OSVR_PoseReport; + +/** @brief Report type for a velocity (linear and angular) callback on a + tracker interface +*/ +typedef struct OSVR_VelocityReport { + /** @brief Identifies the sensor that the report comes from */ + int32_t sensor; + /** @brief The data state - note that not all fields are neccesarily valid, + use the `Valid` members to check the status of the other fields. + */ + OSVR_VelocityState state; +} OSVR_VelocityReport; + +/** @brief Report type for a linear velocity callback on a tracker interface + */ +typedef struct OSVR_LinearVelocityReport { + /** @brief Identifies the sensor that the report comes from */ + int32_t sensor; + /** @brief The state itself */ + OSVR_LinearVelocityState state; +} OSVR_LinearVelocityReport; + +/** @brief Report type for an angular velocity callback on a tracker interface + */ +typedef struct OSVR_AngularVelocityReport { + /** @brief Identifies the sensor that the report comes from */ + int32_t sensor; + /** @brief The state itself */ + OSVR_AngularVelocityState state; +} OSVR_AngularVelocityReport; + +/** @brief Report type for an acceleration (linear and angular) callback on a + tracker interface +*/ +typedef struct OSVR_AccelerationReport { + /** @brief Identifies the sensor that the report comes from */ + int32_t sensor; + /** @brief The data state - note that not all fields are neccesarily valid, + use the `Valid` members to check the status of the other fields. + */ + OSVR_AccelerationState state; +} OSVR_AccelerationReport; + +/** @brief Report type for a linear acceleration callback on a tracker interface + */ +typedef struct OSVR_LinearAccelerationReport { + /** @brief Identifies the sensor that the report comes from */ + int32_t sensor; + /** @brief The state itself */ + OSVR_LinearAccelerationState state; +} OSVR_LinearAccelerationReport; + +/** @brief Report type for an angular acceleration callback on a tracker + interface +*/ +typedef struct OSVR_AngularAccelerationReport { + /** @brief Identifies the sensor that the report comes from */ + int32_t sensor; + /** @brief The state itself */ + OSVR_AngularAccelerationState state; +} OSVR_AngularAccelerationReport; + +/** @brief Report type for a callback on a button interface */ +typedef struct OSVR_ButtonReport { + /** @brief Identifies the sensor that the report comes from */ + int32_t sensor; + /** @brief The button state: 1 is pressed, 0 is not pressed. */ + OSVR_ButtonState state; +} OSVR_ButtonReport; + +/** @brief Report type for a callback on an analog interface */ +typedef struct OSVR_AnalogReport { + /** @brief Identifies the sensor/channel that the report comes from */ + int32_t sensor; + /** @brief The analog state. */ + OSVR_AnalogState state; +} OSVR_AnalogReport; + +/** @brief Type of location within a 2D region/surface, in normalized + coordinates (in range [0, 1] in standard OSVR coordinate system) +*/ +typedef OSVR_Vec2 OSVR_Location2DState; + +/** @brief Report type for 2D location */ +typedef struct OSVR_Location2DReport { + OSVR_ChannelCount sensor; + OSVR_Location2DState location; +} OSVR_Location2DReport; + +/** @brief Type of unit directional vector in 3D with no particular origin */ +typedef OSVR_Vec3 OSVR_DirectionState; + +/** @brief Report type for 3D Direction vector */ +typedef struct OSVR_DirectionReport { + OSVR_ChannelCount sensor; + OSVR_DirectionState direction; +} OSVR_DirectionReport; + +/** @brief Type of eye gaze direction in 3D which contains 3D vector (position) + containing gaze base point of the user's respective eye in 3D device + coordinates. +*/ +typedef OSVR_PositionState OSVR_EyeGazeBasePoint3DState; + +/** @brief Type of eye gaze position in 2D which contains users's gaze/point of + regard in normalized display coordinates (in range [0, 1] in standard OSVR + coordinate system) +*/ +typedef OSVR_Location2DState OSVR_EyeGazePosition2DState; + +// typedef OSVR_DirectionState OSVR_EyeGazeBasePoint3DState; + +/** @brief Type of 3D vector (direction vector) containing the normalized gaze + direction of user's respective eye */ +typedef OSVR_DirectionState OSVR_EyeGazeDirectionState; + +/** @brief State for 3D gaze report */ +typedef struct OSVR_EyeTracker3DState { + OSVR_CBool directionValid; + OSVR_DirectionState direction; + OSVR_CBool basePointValid; + OSVR_PositionState basePoint; +} OSVR_EyeTracker3DState; + +/** @brief Report type for 3D gaze report */ +typedef struct OSVR_EyeTracker3DReport { + OSVR_ChannelCount sensor; + OSVR_EyeTracker3DState state; +} OSVR_EyeTracker3DReport; + +/** @brief State for 2D location report */ +typedef OSVR_Location2DState OSVR_EyeTracker2DState; + +/** @brief Report type for 2D location report */ +typedef struct OSVR_EyeTracker2DReport { + OSVR_ChannelCount sensor; + OSVR_EyeTracker2DState state; +} OSVR_EyeTracker2DReport; + +/** @brief State for a blink event */ +typedef OSVR_ButtonState OSVR_EyeTrackerBlinkState; + +/** @brief OSVR_EyeTrackerBlinkState value indicating an eyes blink had occurred + */ +#define OSVR_EYE_BLINK (1) + +/** @brief OSVR_EyeTrackerBlinkState value indicating eyes are not blinking */ +#define OSVR_EYE_NO_BLINK (0) + +/** @brief Report type for a blink event */ +typedef struct OSVR_EyeTrackerBlinkReport { + OSVR_ChannelCount sensor; + OSVR_EyeTrackerBlinkState state; +} OSVR_EyeTrackerBlinkReport; + +/** @brief Report type for an Imaging callback (forward declaration) */ +struct OSVR_ImagingReport; + +/** @brief Type of Navigation Velocity state */ +typedef OSVR_Vec2 OSVR_NaviVelocityState; + +/** @brief Type of Navigation Position state */ +typedef OSVR_Vec2 OSVR_NaviPositionState; + +/** @brief Report type for an navigation velocity callback on a tracker + * interface */ +typedef struct OSVR_NaviVelocityReport { + OSVR_ChannelCount sensor; + /** @brief The 2D vector in world coordinate system, in meters/second */ + OSVR_NaviVelocityState state; +} OSVR_NaviVelocityReport; + +/** @brief Report type for an navigation position callback on a tracker + * interface */ +typedef struct OSVR_NaviPositionReport { + OSVR_ChannelCount sensor; + /** @brief The 2D vector in world coordinate system, in meters, relative to + * starting position */ + OSVR_NaviPositionState state; +} OSVR_NaviPositionReport; + +/** @} */ + +/** @} */ +OSVR_EXTERN_C_END + +#endif diff --git a/gfx/vr/service/osvr/Util/Export.h b/gfx/vr/service/osvr/Util/Export.h new file mode 100644 index 0000000000..24e8d388df --- /dev/null +++ b/gfx/vr/service/osvr/Util/Export.h @@ -0,0 +1,139 @@ +/** @file + @brief Automatically-generated export header - do not edit! + + @date 2016 + + @author + Sensics, Inc. + <http://sensics.com/osvr> +*/ + +// Copyright 2016 Sensics, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef OSVR_UTIL_EXPORT_H +#define OSVR_UTIL_EXPORT_H + +#ifdef OSVR_UTIL_STATIC_DEFINE +# define OSVR_UTIL_EXPORT +# define OSVR_UTIL_NO_EXPORT +#endif + +/* Per-compiler advance preventative definition */ +#if defined(__BORLANDC__) || defined(__CODEGEARC__) || defined(__HP_aCC) || \ + defined(__PGI) || defined(__WATCOMC__) +/* Compilers that don't support deprecated, according to CMake. */ +# ifndef OSVR_UTIL_DEPRECATED +# define OSVR_UTIL_DEPRECATED +# endif +#endif + +/* Check for attribute support */ +#if defined(__INTEL_COMPILER) +/* Checking before GNUC because Intel implements GNU extensions, + * so it chooses to define __GNUC__ as well. */ +# if __INTEL_COMPILER >= 1200 +/* Intel compiler 12.0 or newer can handle these attributes per CMake */ +# define OSVR_UTIL_EXPORT_HEADER_SUPPORTS_ATTRIBUTES +# endif + +#elif defined(__GNUC__) +# if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 2)) +/* GCC 4.2+ */ +# define OSVR_UTIL_EXPORT_HEADER_SUPPORTS_ATTRIBUTES +# endif +#endif + +/* Per-platform defines */ +#if defined(_MSC_VER) +/* MSVC on Windows */ + +# ifndef OSVR_UTIL_EXPORT +# ifdef osvrUtil_EXPORTS +/* We are building this library */ +# define OSVR_UTIL_EXPORT __declspec(dllexport) +# else +/* We are using this library */ +# define OSVR_UTIL_EXPORT __declspec(dllimport) +# endif +# endif + +# ifndef OSVR_UTIL_DEPRECATED +# define OSVR_UTIL_DEPRECATED __declspec(deprecated) +# endif + +#elif defined(_WIN32) && defined(__GNUC__) +/* GCC-compatible on Windows */ + +# ifndef OSVR_UTIL_EXPORT +# ifdef osvrUtil_EXPORTS +/* We are building this library */ +# define OSVR_UTIL_EXPORT __attribute__((dllexport)) +# else +/* We are using this library */ +# define OSVR_UTIL_EXPORT __attribute__((dllimport)) +# endif +# endif + +# ifndef OSVR_UTIL_DEPRECATED +# define OSVR_UTIL_DEPRECATED __attribute__((__deprecated__)) +# endif + +#elif defined(OSVR_UTIL_EXPORT_HEADER_SUPPORTS_ATTRIBUTES) || \ + (defined(__APPLE__) && defined(__MACH__)) +/* GCC4.2+ compatible (assuming something *nix-like) and Mac OS X */ +/* (The first macro is defined at the top of the file, if applicable) */ +/* see https://gcc.gnu.org/wiki/Visibility */ + +# ifndef OSVR_UTIL_EXPORT +/* We are building/using this library */ +# define OSVR_UTIL_EXPORT __attribute__((visibility("default"))) +# endif + +# ifndef OSVR_UTIL_NO_EXPORT +# define OSVR_UTIL_NO_EXPORT __attribute__((visibility("hidden"))) +# endif + +# ifndef OSVR_UTIL_DEPRECATED +# define OSVR_UTIL_DEPRECATED __attribute__((__deprecated__)) +# endif + +#endif +/* End of platform ifdefs */ + +/* fallback def */ +#ifndef OSVR_UTIL_EXPORT +# define OSVR_UTIL_EXPORT +#endif + +/* fallback def */ +#ifndef OSVR_UTIL_NO_EXPORT +# define OSVR_UTIL_NO_EXPORT +#endif + +/* fallback def */ +#ifndef OSVR_UTIL_DEPRECATED_EXPORT +# define OSVR_UTIL_DEPRECATED_EXPORT OSVR_UTIL_EXPORT OSVR_UTIL_DEPRECATED +#endif + +/* fallback def */ +#ifndef OSVR_UTIL_DEPRECATED_NO_EXPORT +# define OSVR_UTIL_DEPRECATED_NO_EXPORT \ + OSVR_UTIL_NO_EXPORT OSVR_UTIL_DEPRECATED +#endif + +/* Clean up after ourselves */ +#undef OSVR_UTIL_EXPORT_HEADER_SUPPORTS_ATTRIBUTES + +#endif diff --git a/gfx/vr/service/osvr/Util/ImagingReportTypesC.h b/gfx/vr/service/osvr/Util/ImagingReportTypesC.h new file mode 100644 index 0000000000..c3d1a1dee8 --- /dev/null +++ b/gfx/vr/service/osvr/Util/ImagingReportTypesC.h @@ -0,0 +1,91 @@ +/** @file + @brief Header + + Must be c-safe! + + @date 2015 + + @author + Sensics, Inc. + <http://sensics.com/osvr> +*/ + +/* +// Copyright 2015 Sensics, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +*/ + +#ifndef INCLUDED_ImagingReportTypesC_h_GUID_746A7BF8_B92D_4585_CA72_DC5391DEDF24 +#define INCLUDED_ImagingReportTypesC_h_GUID_746A7BF8_B92D_4585_CA72_DC5391DEDF24 + +/* Internal Includes */ +#include <osvr/Util/APIBaseC.h> +#include <osvr/Util/StdInt.h> +#include <osvr/Util/ChannelCountC.h> + +/* Library/third-party includes */ +/* none */ + +/* Standard includes */ +/* none */ + +OSVR_EXTERN_C_BEGIN + +/** @addtogroup ClientKit + @{ +*/ +typedef uint32_t OSVR_ImageDimension; +typedef uint8_t OSVR_ImageChannels; +typedef uint8_t OSVR_ImageDepth; + +/** @brief Type for raw buffer access to image data */ +typedef unsigned char OSVR_ImageBufferElement; + +typedef enum OSVR_ImagingValueType { + OSVR_IVT_UNSIGNED_INT = 0, + OSVR_IVT_SIGNED_INT = 1, + OSVR_IVT_FLOATING_POINT = 2 +} OSVR_ImagingValueType; + +typedef struct OSVR_ImagingMetadata { + /** @brief height in pixels */ + OSVR_ImageDimension height; + /** @brief width in pixels */ + OSVR_ImageDimension width; + /** @brief number of channels of data for each pixel */ + OSVR_ImageChannels channels; + /** @brief the depth (size) in bytes of each channel - valid values are 1, + * 2, 4, and 8 */ + OSVR_ImageDepth depth; + /** @brief Whether values are unsigned ints, signed ints, or floating point + */ + OSVR_ImagingValueType type; + +} OSVR_ImagingMetadata; + +typedef struct OSVR_ImagingState { + OSVR_ImagingMetadata metadata; + OSVR_ImageBufferElement* data; +} OSVR_ImagingState; + +typedef struct OSVR_ImagingReport { + OSVR_ChannelCount sensor; + OSVR_ImagingState state; +} OSVR_ImagingReport; + +/** @} */ + +OSVR_EXTERN_C_END + +#endif diff --git a/gfx/vr/service/osvr/Util/MatrixConventionsC.h b/gfx/vr/service/osvr/Util/MatrixConventionsC.h new file mode 100644 index 0000000000..d9d31ef857 --- /dev/null +++ b/gfx/vr/service/osvr/Util/MatrixConventionsC.h @@ -0,0 +1,190 @@ +/** @file + @brief Header + + Must be c-safe! + + @date 2015 + + @author + Sensics, Inc. + <http://sensics.com/osvr> +*/ + +/* +// Copyright 2015 Sensics, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +*/ + +#ifndef INCLUDED_MatrixConventionsC_h_GUID_6FC7A4C6_E6C5_4A96_1C28_C3D21B909681 +#define INCLUDED_MatrixConventionsC_h_GUID_6FC7A4C6_E6C5_4A96_1C28_C3D21B909681 + +/* Internal Includes */ +#include <osvr/Util/Export.h> +#include <osvr/Util/APIBaseC.h> +#include <osvr/Util/StdInt.h> +#include <osvr/Util/Pose3C.h> +#include <osvr/Util/ReturnCodesC.h> + +/* Library/third-party includes */ +/* none */ + +/* Standard includes */ +/* none */ + +OSVR_EXTERN_C_BEGIN + +/** @defgroup MatrixConvention Matrix conventions and bit flags + @ingroup UtilMath +*/ + +/** @brief Type for passing matrix convention flags. + @ingroup MatrixConvention +*/ +typedef uint16_t OSVR_MatrixConventions; + +#ifndef OSVR_DOXYGEN_EXTERNAL +/** @brief Bitmasks for testing matrix conventions. + @ingroup MatrixConvention +*/ +typedef enum OSVR_MatrixMasks { + OSVR_MATRIX_MASK_ROWMAJOR = 0x1, + OSVR_MATRIX_MASK_ROWVECTORS = 0x2, + OSVR_MATRIX_MASK_LHINPUT = 0x4, + OSVR_MATRIX_MASK_UNSIGNEDZ = 0x8 +} OSVR_MatrixMasks; +#endif + +/** @defgroup MatrixFlags Matrix flags + @ingroup MatrixConvention + + Bit flags for specifying matrix options. Only one option may be specified + per enum, with all the specified options combined with bitwise-or `|`. + + Most methods that take matrix flags only obey ::OSVR_MatrixOrderingFlags and + ::OSVR_MatrixVectorFlags - the flags that affect memory order. The remaining + flags are for use with projection matrix generation API methods. + + @{ +*/ +/** @brief Flag bit controlling output memory order */ +typedef enum OSVR_MatrixOrderingFlags { + /** @brief Column-major memory order (default) */ + OSVR_MATRIX_COLMAJOR = 0x0, + /** @brief Row-major memory order */ + OSVR_MATRIX_ROWMAJOR = OSVR_MATRIX_MASK_ROWMAJOR +} OSVR_MatrixOrderingFlags; + +/** @brief Flag bit controlling expected input to matrices. + (Related to ::OSVR_MatrixOrderingFlags - setting one to non-default results + in an output change, but setting both to non-default results in effectively + no change in the output. If this blows your mind, just ignore this aside and + carry on.) +*/ +typedef enum OSVR_MatrixVectorFlags { + /** @brief Matrix transforms column vectors (default) */ + OSVR_MATRIX_COLVECTORS = 0x0, + /** @brief Matrix transforms row vectors */ + OSVR_MATRIX_ROWVECTORS = OSVR_MATRIX_MASK_ROWVECTORS +} OSVR_MatrixVectorFlags; + +/** @brief Flag bit to indicate coordinate system input to projection matrix */ +typedef enum OSVR_ProjectionMatrixInputFlags { + /** @brief Matrix takes vectors from a right-handed coordinate system + (default) */ + OSVR_MATRIX_RHINPUT = 0x0, + /** @brief Matrix takes vectors from a left-handed coordinate system */ + OSVR_MATRIX_LHINPUT = OSVR_MATRIX_MASK_LHINPUT + +} OSVR_ProjectionMatrixInputFlags; + +/** @brief Flag bit to indicate the desired post-projection Z value convention + */ +typedef enum OSVR_ProjectionMatrixZFlags { + /** @brief Matrix maps the near and far planes to signed Z values (in the + range [-1, 1]) (default)*/ + OSVR_MATRIX_SIGNEDZ = 0x0, + /** @brief Matrix maps the near and far planes to unsigned Z values (in the + range [0, 1]) */ + OSVR_MATRIX_UNSIGNEDZ = OSVR_MATRIX_MASK_UNSIGNEDZ +} OSVR_ProjectionMatrixZFlags; +/** @} */ /* end of matrix flags group */ + +enum { + /** @brief Constant for the number of elements in the matrices we use - 4x4. + @ingroup MatrixConvention + */ + OSVR_MATRIX_SIZE = 16 +}; + +/** @addtogroup UtilMath + @{ +*/ +/** @brief Set a matrix of doubles based on a Pose3. + @param pose The Pose3 to convert + @param flags Memory ordering flag - see @ref MatrixFlags + @param[out] mat an array of 16 doubles +*/ +OSVR_UTIL_EXPORT OSVR_ReturnCode osvrPose3ToMatrixd( + OSVR_Pose3 const* pose, OSVR_MatrixConventions flags, double* mat); + +/** @brief Set a matrix of floats based on a Pose3. + @param pose The Pose3 to convert + @param flags Memory ordering flag - see @ref MatrixFlags + @param[out] mat an array of 16 floats +*/ +OSVR_UTIL_EXPORT OSVR_ReturnCode osvrPose3ToMatrixf( + OSVR_Pose3 const* pose, OSVR_MatrixConventions flags, float* mat); +/** @} */ + +OSVR_EXTERN_C_END + +#ifdef __cplusplus +/** @brief Set a matrix based on a Pose3. (C++-only overload - detecting scalar + * type) */ +inline OSVR_ReturnCode osvrPose3ToMatrix(OSVR_Pose3 const* pose, + OSVR_MatrixConventions flags, + double* mat) { + return osvrPose3ToMatrixd(pose, flags, mat); +} + +/** @brief Set a matrix based on a Pose3. (C++-only overload - detecting scalar + * type) */ +inline OSVR_ReturnCode osvrPose3ToMatrix(OSVR_Pose3 const* pose, + OSVR_MatrixConventions flags, + float* mat) { + return osvrPose3ToMatrixf(pose, flags, mat); +} + +/** @brief Set a matrix based on a Pose3. (C++-only overload - detects scalar + * and takes array rather than pointer) */ +template <typename Scalar> +inline OSVR_ReturnCode osvrPose3ToMatrix(OSVR_Pose3 const* pose, + OSVR_MatrixConventions flags, + Scalar mat[OSVR_MATRIX_SIZE]) { + return osvrPose3ToMatrix(pose, flags, &(mat[0])); +} +/** @brief Set a matrix based on a Pose3. (C++-only overload - detects scalar, + * takes array, takes pose by reference) */ +template <typename Scalar> +inline OSVR_ReturnCode osvrPose3ToMatrix(OSVR_Pose3 const& pose, + OSVR_MatrixConventions flags, + Scalar mat[OSVR_MATRIX_SIZE]) { + return osvrPose3ToMatrix(&pose, flags, &(mat[0])); +} + +#endif + +/** @} */ + +#endif diff --git a/gfx/vr/service/osvr/Util/PlatformConfig.h b/gfx/vr/service/osvr/Util/PlatformConfig.h new file mode 100644 index 0000000000..081712d3a3 --- /dev/null +++ b/gfx/vr/service/osvr/Util/PlatformConfig.h @@ -0,0 +1,87 @@ +/** @file + @brief Auto-configured header + + If this filename ends in `.h`, don't edit it: your edits will + be lost next time this file is regenerated! + + Must be c-safe! + + @date 2014 + + @author + Sensics, Inc. + <http://sensics.com/osvr> +*/ + +/* +// Copyright 2014 Sensics, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +*/ + +#ifndef INCLUDED_PlatformConfig_h_GUID_0D10E644_8114_4294_A839_699F39E1F0E0 +#define INCLUDED_PlatformConfig_h_GUID_0D10E644_8114_4294_A839_699F39E1F0E0 + +/** @def OSVR_HAVE_STRUCT_TIMEVAL_IN_WINSOCK2_H + @brief Does the system have struct timeval in <winsock2.h>? +*/ +#define OSVR_HAVE_STRUCT_TIMEVAL_IN_WINSOCK2_H + +/** @def OSVR_HAVE_STRUCT_TIMEVAL_IN_SYS_TIME_H + @brief Does the system have struct timeval in <sys/time.h>? +*/ + +/* + MinGW and similar environments have both winsock and sys/time.h, so + we hide this define for disambiguation at the top level. +*/ +#ifndef OSVR_HAVE_STRUCT_TIMEVAL_IN_WINSOCK2_H +/* #undef OSVR_HAVE_STRUCT_TIMEVAL_IN_SYS_TIME_H */ +#endif + +#if defined(OSVR_HAVE_STRUCT_TIMEVAL_IN_SYS_TIME_H) || \ + defined(OSVR_HAVE_STRUCT_TIMEVAL_IN_WINSOCK2_H) +# define OSVR_HAVE_STRUCT_TIMEVAL +#endif + +/** + * Platform-specific variables. + * + * Prefer testing for specific compiler or platform features instead of relying + * on these variables. + * + */ +//@{ +/* #undef OSVR_AIX */ +/* #undef OSVR_ANDROID */ +/* #undef OSVR_BSDOS */ +/* #undef OSVR_FREEBSD */ +/* #undef OSVR_HPUX */ +/* #undef OSVR_IRIX */ +/* #undef OSVR_LINUX */ +/* #undef OSVR_KFREEBSD */ +/* #undef OSVR_NETBSD */ +/* #undef OSVR_OPENBSD */ +/* #undef OSVR_OFS1 */ +/* #undef OSVR_SCO_SV */ +/* #undef OSVR_UNIXWARE */ +/* #undef OSVR_XENIX */ +/* #undef OSVR_SUNOS */ +/* #undef OSVR_TRU64 */ +/* #undef OSVR_ULTRIX */ +/* #undef OSVR_CYGWIN */ +/* #undef OSVR_MACOSX */ +#define OSVR_WINDOWS +//@} + +#endif diff --git a/gfx/vr/service/osvr/Util/Pose3C.h b/gfx/vr/service/osvr/Util/Pose3C.h new file mode 100644 index 0000000000..cf56e7cc53 --- /dev/null +++ b/gfx/vr/service/osvr/Util/Pose3C.h @@ -0,0 +1,70 @@ +/** @file + @brief Header + + Must be c-safe! + + @date 2014 + + @author + Sensics, Inc. + <http://sensics.com/osvr> +*/ + +/* +// Copyright 2014 Sensics, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +*/ + +#ifndef INCLUDED_Pose3C_h_GUID_066CFCE2_229C_4194_5D2B_2602CCD5C439 +#define INCLUDED_Pose3C_h_GUID_066CFCE2_229C_4194_5D2B_2602CCD5C439 + +/* Internal Includes */ + +/* Internal Includes */ +#include <osvr/Util/APIBaseC.h> +#include <osvr/Util/Vec3C.h> +#include <osvr/Util/QuaternionC.h> + +/* Library/third-party includes */ +/* none */ + +/* Standard includes */ +/* none */ + +OSVR_EXTERN_C_BEGIN + +/** @addtogroup UtilMath + @{ +*/ + +/** @brief A structure defining a 3D (6DOF) rigid body pose: translation and + rotation. +*/ +typedef struct OSVR_Pose3 { + /** @brief Position vector */ + OSVR_Vec3 translation; + /** @brief Orientation as a unit quaternion */ + OSVR_Quaternion rotation; +} OSVR_Pose3; + +/** @brief Set a pose to identity */ +OSVR_INLINE void osvrPose3SetIdentity(OSVR_Pose3* pose) { + osvrQuatSetIdentity(&(pose->rotation)); + osvrVec3Zero(&(pose->translation)); +} +/** @} */ + +OSVR_EXTERN_C_END + +#endif diff --git a/gfx/vr/service/osvr/Util/QuaternionC.h b/gfx/vr/service/osvr/Util/QuaternionC.h new file mode 100644 index 0000000000..065c68bd4e --- /dev/null +++ b/gfx/vr/service/osvr/Util/QuaternionC.h @@ -0,0 +1,92 @@ +/** @file + @brief Header + + Must be c-safe! + + @date 2014 + + @author + Sensics, Inc. + <http://sensics.com/osvr> +*/ + +/* +// Copyright 2014 Sensics, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +*/ + +#ifndef INCLUDED_QuaternionC_h_GUID_1470A5FE_8209_41A6_C19E_46077FDF9C66 +#define INCLUDED_QuaternionC_h_GUID_1470A5FE_8209_41A6_C19E_46077FDF9C66 + +/* Internal Includes */ +#include <osvr/Util/APIBaseC.h> + +/* Library/third-party includes */ +/* none */ + +/* Standard includes */ +/* none */ + +OSVR_EXTERN_C_BEGIN + +/** @addtogroup UtilMath + @{ +*/ +/** @brief A structure defining a quaternion, often a unit quaternion + * representing 3D rotation. + */ +typedef struct OSVR_Quaternion { + /** @brief Internal data - direct access not recommended */ + double data[4]; +} OSVR_Quaternion; + +#define OSVR_QUAT_MEMBER(COMPONENT, INDEX) \ + /** @brief Accessor for quaternion component COMPONENT */ \ + OSVR_INLINE double osvrQuatGet##COMPONENT(OSVR_Quaternion const* q) { \ + return q->data[INDEX]; \ + } \ + /** @brief Setter for quaternion component COMPONENT */ \ + OSVR_INLINE void osvrQuatSet##COMPONENT(OSVR_Quaternion* q, double val) { \ + q->data[INDEX] = val; \ + } + +OSVR_QUAT_MEMBER(W, 0) +OSVR_QUAT_MEMBER(X, 1) +OSVR_QUAT_MEMBER(Y, 2) +OSVR_QUAT_MEMBER(Z, 3) + +#undef OSVR_QUAT_MEMBER + +/** @brief Set a quaternion to the identity rotation */ +OSVR_INLINE void osvrQuatSetIdentity(OSVR_Quaternion* q) { + osvrQuatSetW(q, 1); + osvrQuatSetX(q, 0); + osvrQuatSetY(q, 0); + osvrQuatSetZ(q, 0); +} + +/** @} */ + +OSVR_EXTERN_C_END + +#ifdef __cplusplus +template <typename StreamType> +inline StreamType& operator<<(StreamType& os, OSVR_Quaternion const& quat) { + os << "(" << osvrQuatGetW(&quat) << ", (" << osvrQuatGetX(&quat) << ", " + << osvrQuatGetY(&quat) << ", " << osvrQuatGetZ(&quat) << "))"; + return os; +} +#endif + +#endif diff --git a/gfx/vr/service/osvr/Util/QuatlibInteropC.h b/gfx/vr/service/osvr/Util/QuatlibInteropC.h new file mode 100644 index 0000000000..1519f02c10 --- /dev/null +++ b/gfx/vr/service/osvr/Util/QuatlibInteropC.h @@ -0,0 +1,84 @@ +/** @file + @brief Header + + Must be c-safe! + + @date 2014 + + @author + Sensics, Inc. + <http://sensics.com/osvr> +*/ + +/* +// Copyright 2014 Sensics, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +*/ + +#ifndef INCLUDED_QuatlibInteropC_h_GUID_85D92019_F0CC_419C_5F6D_F5A3134AA5D4 +#define INCLUDED_QuatlibInteropC_h_GUID_85D92019_F0CC_419C_5F6D_F5A3134AA5D4 + +/* Internal Includes */ +#include <osvr/Util/APIBaseC.h> +#include <osvr/Util/Pose3C.h> + +/* Library/third-party includes */ +#include <quat.h> + +/* Standard includes */ +#include <string.h> + +OSVR_EXTERN_C_BEGIN + +/** @addtogroup UtilMath + @{ +*/ +OSVR_INLINE void osvrQuatToQuatlib(q_type dest, OSVR_Quaternion const* src) { + dest[Q_W] = osvrQuatGetW(src); + dest[Q_X] = osvrQuatGetX(src); + dest[Q_Y] = osvrQuatGetY(src); + dest[Q_Z] = osvrQuatGetZ(src); +} + +OSVR_INLINE void osvrQuatFromQuatlib(OSVR_Quaternion* dest, q_type const src) { + osvrQuatSetW(dest, src[Q_W]); + osvrQuatSetX(dest, src[Q_X]); + osvrQuatSetY(dest, src[Q_Y]); + osvrQuatSetZ(dest, src[Q_Z]); +} + +OSVR_INLINE void osvrVec3ToQuatlib(q_vec_type dest, OSVR_Vec3 const* src) { + memcpy((void*)(dest), (void const*)(src->data), sizeof(double) * 3); +} + +OSVR_INLINE void osvrVec3FromQuatlib(OSVR_Vec3* dest, q_vec_type const src) { + memcpy((void*)(dest->data), (void const*)(src), sizeof(double) * 3); +} + +OSVR_INLINE void osvrPose3ToQuatlib(q_xyz_quat_type* dest, + OSVR_Pose3 const* src) { + osvrVec3ToQuatlib(dest->xyz, &(src->translation)); + osvrQuatToQuatlib(dest->quat, &(src->rotation)); +} + +OSVR_INLINE void osvrPose3FromQuatlib(OSVR_Pose3* dest, + q_xyz_quat_type const* src) { + osvrVec3FromQuatlib(&(dest->translation), src->xyz); + osvrQuatFromQuatlib(&(dest->rotation), src->quat); +} + +/** @} */ + +OSVR_EXTERN_C_END +#endif diff --git a/gfx/vr/service/osvr/Util/RadialDistortionParametersC.h b/gfx/vr/service/osvr/Util/RadialDistortionParametersC.h new file mode 100644 index 0000000000..b3fea12625 --- /dev/null +++ b/gfx/vr/service/osvr/Util/RadialDistortionParametersC.h @@ -0,0 +1,62 @@ +/** @file + @brief Header + + Must be c-safe! + + @date 2015 + + @author + Sensics, Inc. + <http://sensics.com/osvr> +*/ + +/* +// Copyright 2015 Sensics, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +*/ + +#ifndef INCLUDED_RadialDistortionParametersC_h_GUID_925BCEB1_BACA_4DA7_5133_FFF560C72EBD +#define INCLUDED_RadialDistortionParametersC_h_GUID_925BCEB1_BACA_4DA7_5133_FFF560C72EBD + +/* Internal Includes */ +#include <osvr/Util/APIBaseC.h> +#include <osvr/Util/Vec2C.h> +#include <osvr/Util/Vec3C.h> + +/* Library/third-party includes */ +/* none */ + +/* Standard includes */ +/* none */ + +OSVR_EXTERN_C_BEGIN + +/** @addtogroup UtilMath +@{ +*/ + +/** @brief Parameters for a per-color-component radial distortion shader + */ +typedef struct OSVR_RadialDistortionParameters { + /** @brief Vector of K1 coefficients for the R, G, B channels*/ + OSVR_Vec3 k1; + /** @brief Center of projection for the radial distortion, relative to the + bounds of this surface. + */ + OSVR_Vec2 centerOfProjection; +} OSVR_RadialDistortionParameters; + +OSVR_EXTERN_C_END + +#endif diff --git a/gfx/vr/service/osvr/Util/RenderingTypesC.h b/gfx/vr/service/osvr/Util/RenderingTypesC.h new file mode 100644 index 0000000000..103d0396d9 --- /dev/null +++ b/gfx/vr/service/osvr/Util/RenderingTypesC.h @@ -0,0 +1,134 @@ +/** @file + @brief Header with integer types for Viewer, Eye, and Surface + counts/indices, as well as viewport information. + + Must be c-safe! + + @date 2015 + + @author + Sensics, Inc. + <http://sensics.com/osvr> +*/ + +/* +// Copyright 2015 Sensics, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +*/ + +#ifndef INCLUDED_RenderingTypesC_h_GUID_6689A6CA_76AC_48AC_A0D0_2902BC95AC35 +#define INCLUDED_RenderingTypesC_h_GUID_6689A6CA_76AC_48AC_A0D0_2902BC95AC35 + +/* Internal Includes */ +#include <osvr/Util/StdInt.h> +#include <osvr/Util/APIBaseC.h> + +/* Library/third-party includes */ +/* none */ + +/* Standard includes */ +/* none */ + +OSVR_EXTERN_C_BEGIN + +/** @addtogroup PluginKit +@{ +*/ + +/** @brief A count or index for a display input in a display config. + */ +typedef uint8_t OSVR_DisplayInputCount; + +/** @brief The integer type used in specification of size or location of a + display input, in pixels. +*/ +typedef int32_t OSVR_DisplayDimension; + +/** @brief The integer type specifying a number of viewers in a system. + + A "head" is a viewer (though not all viewers are necessarily heads). + + The count is output from osvrClientGetNumViewers(). + + When used as an ID/index, it is zero-based, so values range from 0 to (count + - 1) inclusive. + + The most frequent count is 1, though higher values are theoretically + possible. If you do not handle higher values, do still check and alert the + user if their system reports a higher number, as your application may not + behave as the user expects. +*/ +typedef uint32_t OSVR_ViewerCount; + +/** @brief The integer type specifying the number of eyes (viewpoints) of a + viewer. + + The count for a given viewer is output from osvrClientGetNumEyesForViewer(). + + When used as an ID/index, it is zero-based,so values range from 0 to (count + - 1) inclusive, for a given viewer. + + Use as an ID/index is not meaningful except in conjunction with the ID of + the corresponding viewer. (that is, there is no overall "eye 0", but "viewer + 0, eye 0" is meaningful.) + + In practice, the most frequent counts are 1 (e.g. mono) and 2 (e.g. stereo), + and for example the latter results in eyes with ID 0 and 1 for the viewer. + There is no innate or consistent semantics/meaning ("left" or "right") to + indices guaranteed at this time, and applications should not try to infer + any. +*/ +typedef uint8_t OSVR_EyeCount; + +/** @brief The integer type specifying the number of surfaces seen by a viewer's + eye. + + The count for a given viewer and eye is output from + osvrClientGetNumSurfacesForViewerEye(). Note that the count is not + necessarily equal between eyes of a viewer. + + When used as an ID/index, it is zero-based, so values range from 0 to (count + - 1) inclusive, for a given viewer and eye. + + Use as an ID/index is not meaningful except in conjunction with the IDs of + the corresponding viewer and eye. (that is, there is no overall "surface 0", + but "viewer 0, eye 0, surface 0" is meaningful.) +*/ +typedef uint32_t OSVR_SurfaceCount; + +/** @brief The integer type used in specification of size or location of a + viewport. +*/ +typedef int32_t OSVR_ViewportDimension; + +/** @brief The integer type used to indicate relative priorities of a display + distortion strategy. Negative values are defined to mean that strategy is + unavailable. + + @sa OSVR_DISTORTION_PRIORITY_UNAVAILABLE +*/ +typedef int32_t OSVR_DistortionPriority; + +/** @brief The constant to return as an OSVR_DistortionPriority if a given + strategy is not available for a surface. + + @sa OSVR_DistortionPriority +*/ +#define OSVR_DISTORTION_PRIORITY_UNAVAILABLE (-1) + +/** @} */ + +OSVR_EXTERN_C_END + +#endif diff --git a/gfx/vr/service/osvr/Util/ReturnCodesC.h b/gfx/vr/service/osvr/Util/ReturnCodesC.h new file mode 100644 index 0000000000..971798ea41 --- /dev/null +++ b/gfx/vr/service/osvr/Util/ReturnCodesC.h @@ -0,0 +1,57 @@ +/** @file + @brief Header declaring a type and values for simple C return codes. + + Must be c-safe! + + @date 2014 + + @author + Sensics, Inc. + <http://sensics.com/osvr> +*/ + +/* +// Copyright 2014 Sensics, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +*/ + +#ifndef INCLUDED_ReturnCodesC_h_GUID_C81A2FDE_E5BB_4AAA_70A4_C616DD7C141A +#define INCLUDED_ReturnCodesC_h_GUID_C81A2FDE_E5BB_4AAA_70A4_C616DD7C141A + +/* Internal Includes */ +#include <osvr/Util/APIBaseC.h> +#include <osvr/Util/AnnotationMacrosC.h> + +OSVR_EXTERN_C_BEGIN + +/** @addtogroup PluginKit + @{ +*/ +/** @name Return Codes + @{ +*/ +/** @brief The "success" value for an OSVR_ReturnCode */ +#define OSVR_RETURN_SUCCESS (0) +/** @brief The "failure" value for an OSVR_ReturnCode */ +#define OSVR_RETURN_FAILURE (1) +/** @brief Return type from C API OSVR functions. */ +typedef OSVR_RETURN_SUCCESS_CONDITION( + return == OSVR_RETURN_SUCCESS) char OSVR_ReturnCode; +/** @} */ + +/** @} */ /* end of group */ + +OSVR_EXTERN_C_END + +#endif diff --git a/gfx/vr/service/osvr/Util/StdInt.h b/gfx/vr/service/osvr/Util/StdInt.h new file mode 100644 index 0000000000..4fe7117cb5 --- /dev/null +++ b/gfx/vr/service/osvr/Util/StdInt.h @@ -0,0 +1,42 @@ +/** @file + @brief Header wrapping the C99 standard `stdint` header. + + Must be c-safe! + + @date 2014 + + @author + Sensics, Inc. + <http://sensics.com/osvr> +*/ + +/* +// Copyright 2014 Sensics, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +*/ + +#ifndef INCLUDED_StdInt_h_GUID_C1AAF35C_C704_4DB7_14AC_615730C4619B +#define INCLUDED_StdInt_h_GUID_C1AAF35C_C704_4DB7_14AC_615730C4619B + +/* IWYU pragma: begin_exports */ + +#if !defined(_MSC_VER) || (defined(_MSC_VER) && _MSC_VER >= 1600) +# include <stdint.h> +#else +# include "MSStdIntC.h" +#endif + +/* IWYU pragma: end_exports */ + +#endif diff --git a/gfx/vr/service/osvr/Util/TimeValueC.h b/gfx/vr/service/osvr/Util/TimeValueC.h new file mode 100644 index 0000000000..eb6a4f7cec --- /dev/null +++ b/gfx/vr/service/osvr/Util/TimeValueC.h @@ -0,0 +1,267 @@ +/** @file + @brief Header defining a dependency-free, cross-platform substitute for + struct timeval + + Must be c-safe! + + @date 2014 + + @author + Sensics, Inc. + <http://sensics.com/osvr> +*/ + +/* +// Copyright 2014 Sensics, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +*/ + +#ifndef INCLUDED_TimeValueC_h_GUID_A02C6917_124D_4CB3_E63E_07F2DA7144E9 +#define INCLUDED_TimeValueC_h_GUID_A02C6917_124D_4CB3_E63E_07F2DA7144E9 + +/* Internal Includes */ +#include <osvr/Util/Export.h> +#include <osvr/Util/APIBaseC.h> +#include <osvr/Util/AnnotationMacrosC.h> +#include <osvr/Util/PlatformConfig.h> +#include <osvr/Util/StdInt.h> +#include <osvr/Util/BoolC.h> + +/* Library/third-party includes */ +/* none */ + +/* Standard includes */ +/* none */ + +OSVR_EXTERN_C_BEGIN + +/** @defgroup UtilTime Timestamp interaction + @ingroup Util + + This provides a level of interoperability with struct timeval on systems + with that facility. It provides a neutral representation with sufficiently + large types. + + For C++ code, use of std::chrono or boost::chrono instead is recommended. + + Note that these time values may not necessarily correlate between processes + so should not be used to estimate or measure latency, etc. + + @{ +*/ + +/** @brief The signed integer type storing the seconds in a struct + OSVR_TimeValue */ +typedef int64_t OSVR_TimeValue_Seconds; +/** @brief The signed integer type storing the microseconds in a struct + OSVR_TimeValue */ +typedef int32_t OSVR_TimeValue_Microseconds; + +/** @brief Standardized, portable parallel to struct timeval for representing + both absolute times and time intervals. + + Where interpreted as an absolute time, its meaning is to be considered the + same as that of the POSIX struct timeval: + time since 00:00 Coordinated Universal Time (UTC), January 1, 1970. + + For best results, please keep normalized. Output of all functions here + is normalized. + */ +typedef struct OSVR_TimeValue { + /** @brief Seconds portion of the time value. */ + OSVR_TimeValue_Seconds seconds; + /** @brief Microseconds portion of the time value. */ + OSVR_TimeValue_Microseconds microseconds; +} OSVR_TimeValue; + +#ifdef OSVR_HAVE_STRUCT_TIMEVAL +/** @brief Gets the current time in the TimeValue. Parallel to gettimeofday. */ +OSVR_UTIL_EXPORT void osvrTimeValueGetNow(OSVR_OUT OSVR_TimeValue* dest) + OSVR_FUNC_NONNULL((1)); + +struct timeval; /* forward declaration */ + +/** @brief Converts from a TimeValue struct to your system's struct timeval. + + @param dest Pointer to an empty struct timeval for your platform. + @param src A pointer to an OSVR_TimeValue you'd like to convert from. + + If either parameter is NULL, the function will return without doing + anything. +*/ +OSVR_UTIL_EXPORT void osvrTimeValueToStructTimeval( + OSVR_OUT struct timeval* dest, OSVR_IN_PTR const OSVR_TimeValue* src) + OSVR_FUNC_NONNULL((1, 2)); + +/** @brief Converts from a TimeValue struct to your system's struct timeval. + @param dest An OSVR_TimeValue destination pointer. + @param src Pointer to a struct timeval you'd like to convert from. + + The result is normalized. + + If either parameter is NULL, the function will return without doing + anything. +*/ +OSVR_UTIL_EXPORT void osvrStructTimevalToTimeValue( + OSVR_OUT OSVR_TimeValue* dest, OSVR_IN_PTR const struct timeval* src) + OSVR_FUNC_NONNULL((1, 2)); +#endif + +/** @brief "Normalizes" a time value so that the absolute number of microseconds + is less than 1,000,000, and that the sign of both components is the same. + + @param tv Address of a struct TimeValue to normalize in place. + + If the given pointer is NULL, this function returns without doing anything. +*/ +OSVR_UTIL_EXPORT void osvrTimeValueNormalize(OSVR_INOUT_PTR OSVR_TimeValue* tv) + OSVR_FUNC_NONNULL((1)); + +/** @brief Sums two time values, replacing the first with the result. + + @param tvA Destination and first source. + @param tvB second source + + If a given pointer is NULL, this function returns without doing anything. + + Both parameters are expected to be in normalized form. +*/ +OSVR_UTIL_EXPORT void osvrTimeValueSum(OSVR_INOUT_PTR OSVR_TimeValue* tvA, + OSVR_IN_PTR const OSVR_TimeValue* tvB) + OSVR_FUNC_NONNULL((1, 2)); + +/** @brief Computes the difference between two time values, replacing the first + with the result. + + Effectively, `*tvA = *tvA - *tvB` + + @param tvA Destination and first source. + @param tvB second source + + If a given pointer is NULL, this function returns without doing anything. + + Both parameters are expected to be in normalized form. +*/ +OSVR_UTIL_EXPORT void osvrTimeValueDifference( + OSVR_INOUT_PTR OSVR_TimeValue* tvA, OSVR_IN_PTR const OSVR_TimeValue* tvB) + OSVR_FUNC_NONNULL((1, 2)); + +/** @brief Compares two time values (assumed to be normalized), returning + the same values as strcmp + + @return <0 if A is earlier than B, 0 if they are the same, and >0 if A + is later than B. +*/ +OSVR_UTIL_EXPORT int osvrTimeValueCmp(OSVR_IN_PTR const OSVR_TimeValue* tvA, + OSVR_IN_PTR const OSVR_TimeValue* tvB) + OSVR_FUNC_NONNULL((1, 2)); + +OSVR_EXTERN_C_END + +/** @brief Compute the difference between the two time values, returning the + duration as a double-precision floating-point number of seconds. + + Effectively, `ret = *tvA - *tvB` + + @param tvA first source. + @param tvB second source + @return Duration of timespan in seconds (floating-point) +*/ +OSVR_INLINE double osvrTimeValueDurationSeconds( + OSVR_IN_PTR const OSVR_TimeValue* tvA, + OSVR_IN_PTR const OSVR_TimeValue* tvB) { + OSVR_TimeValue A = *tvA; + osvrTimeValueDifference(&A, tvB); + double dt = A.seconds + A.microseconds / 1000000.0; + return dt; +} + +/** @brief True if A is later than B */ +OSVR_INLINE OSVR_CBool +osvrTimeValueGreater(OSVR_IN_PTR const OSVR_TimeValue* tvA, + OSVR_IN_PTR const OSVR_TimeValue* tvB) { + if (!tvA || !tvB) { + return OSVR_FALSE; + } + return ((tvA->seconds > tvB->seconds) || + (tvA->seconds == tvB->seconds && + tvA->microseconds > tvB->microseconds)) + ? OSVR_TRUE + : OSVR_FALSE; +} + +#ifdef __cplusplus + +# include <cmath> +# include <cassert> + +/// Returns true if the time value is normalized. Typically used in assertions. +inline bool osvrTimeValueIsNormalized(const OSVR_TimeValue& tv) { +# ifdef __APPLE__ + // apparently standard library used on mac only has floating-point abs? + return std::abs(double(tv.microseconds)) < 1000000 && +# else + return std::abs(tv.microseconds) < 1000000 && +# endif + ((tv.seconds > 0) == (tv.microseconds > 0)); +} + +/// True if A is later than B +inline bool osvrTimeValueGreater(const OSVR_TimeValue& tvA, + const OSVR_TimeValue& tvB) { + assert(osvrTimeValueIsNormalized(tvA) && + "First timevalue argument to comparison was not normalized!"); + assert(osvrTimeValueIsNormalized(tvB) && + "Second timevalue argument to comparison was not normalized!"); + return (tvA.seconds > tvB.seconds) || + (tvA.seconds == tvB.seconds && tvA.microseconds > tvB.microseconds); +} + +/// Operator > overload for time values +inline bool operator>(const OSVR_TimeValue& tvA, const OSVR_TimeValue& tvB) { + return osvrTimeValueGreater(tvA, tvB); +} + +/// Operator < overload for time values +inline bool operator<(const OSVR_TimeValue& tvA, const OSVR_TimeValue& tvB) { + // Change the order of arguments before forwarding. + return osvrTimeValueGreater(tvB, tvA); +} + +/// Operator == overload for time values +inline bool operator==(const OSVR_TimeValue& tvA, const OSVR_TimeValue& tvB) { + assert(osvrTimeValueIsNormalized(tvA) && + "First timevalue argument to equality comparison was not normalized!"); + assert( + osvrTimeValueIsNormalized(tvB) && + "Second timevalue argument to equality comparison was not normalized!"); + return (tvA.seconds == tvB.seconds) && (tvA.microseconds == tvB.microseconds); +} +/// Operator == overload for time values +inline bool operator!=(const OSVR_TimeValue& tvA, const OSVR_TimeValue& tvB) { + assert(osvrTimeValueIsNormalized(tvA) && + "First timevalue argument to " + "inequality comparison was not " + "normalized!"); + assert(osvrTimeValueIsNormalized(tvB) && + "Second timevalue argument to " + "inequality comparison was not " + "normalized!"); + return (tvA.seconds != tvB.seconds) || (tvA.microseconds != tvB.microseconds); +} +#endif + +/** @} */ + +#endif diff --git a/gfx/vr/service/osvr/Util/Vec2C.h b/gfx/vr/service/osvr/Util/Vec2C.h new file mode 100644 index 0000000000..0641270c1f --- /dev/null +++ b/gfx/vr/service/osvr/Util/Vec2C.h @@ -0,0 +1,86 @@ +/** @file + @brief Header + + Must be c-safe! + + @date 2014 + + @author + Sensics, Inc. + <http://sensics.com/osvr> +*/ + +/* +// Copyright 2014 Sensics, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +*/ + +#ifndef INCLUDED_Vec2C_h_GUID_F9715DE4_2649_4182_0F4C_D62121235D5F +#define INCLUDED_Vec2C_h_GUID_F9715DE4_2649_4182_0F4C_D62121235D5F + +/* Internal Includes */ +#include <osvr/Util/APIBaseC.h> + +/* Library/third-party includes */ +/* none */ + +/* Standard includes */ +/* none */ + +OSVR_EXTERN_C_BEGIN + +/** @addtogroup UtilMath + @{ +*/ +/** @brief A structure defining a 2D vector, which represents position + */ +typedef struct OSVR_Vec2 { + /** @brief Internal array data. */ + double data[2]; +} OSVR_Vec2; + +#define OSVR_VEC_MEMBER(COMPONENT, INDEX) \ + /** @brief Accessor for Vec2 component COMPONENT */ \ + OSVR_INLINE double osvrVec2Get##COMPONENT(OSVR_Vec2 const* v) { \ + return v->data[INDEX]; \ + } \ + /** @brief Setter for Vec2 component COMPONENT */ \ + OSVR_INLINE void osvrVec2Set##COMPONENT(OSVR_Vec2* v, double val) { \ + v->data[INDEX] = val; \ + } + +OSVR_VEC_MEMBER(X, 0) +OSVR_VEC_MEMBER(Y, 1) + +#undef OSVR_VEC_MEMBER + +/** @brief Set a Vec2 to the zero vector */ +OSVR_INLINE void osvrVec2Zero(OSVR_Vec2* v) { + osvrVec2SetX(v, 0); + osvrVec2SetY(v, 0); +} + +/** @} */ + +OSVR_EXTERN_C_END + +#ifdef __cplusplus +template <typename StreamType> +inline StreamType& operator<<(StreamType& os, OSVR_Vec2 const& vec) { + os << "(" << vec.data[0] << ", " << vec.data[1] << ")"; + return os; +} +#endif + +#endif // INCLUDED_Vec2C_h_GUID_F9715DE4_2649_4182_0F4C_D62121235D5F diff --git a/gfx/vr/service/osvr/Util/Vec3C.h b/gfx/vr/service/osvr/Util/Vec3C.h new file mode 100644 index 0000000000..f72327c05b --- /dev/null +++ b/gfx/vr/service/osvr/Util/Vec3C.h @@ -0,0 +1,88 @@ +/** @file + @brief Header + + Must be c-safe! + + @date 2014 + + @author + Sensics, Inc. + <http://sensics.com/osvr> +*/ + +/* +// Copyright 2014 Sensics, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +*/ + +#ifndef INCLUDED_Vec3C_h_GUID_BF4E98ED_74CF_4785_DB61_109A00BA74DE +#define INCLUDED_Vec3C_h_GUID_BF4E98ED_74CF_4785_DB61_109A00BA74DE + +/* Internal Includes */ +#include <osvr/Util/APIBaseC.h> + +/* Library/third-party includes */ +/* none */ + +/* Standard includes */ +/* none */ + +OSVR_EXTERN_C_BEGIN + +/** @addtogroup UtilMath + @{ +*/ +/** @brief A structure defining a 3D vector, often a position/translation. + */ +typedef struct OSVR_Vec3 { + /** @brief Internal array data. */ + double data[3]; +} OSVR_Vec3; + +#define OSVR_VEC_MEMBER(COMPONENT, INDEX) \ + /** @brief Accessor for Vec3 component COMPONENT */ \ + OSVR_INLINE double osvrVec3Get##COMPONENT(OSVR_Vec3 const* v) { \ + return v->data[INDEX]; \ + } \ + /** @brief Setter for Vec3 component COMPONENT */ \ + OSVR_INLINE void osvrVec3Set##COMPONENT(OSVR_Vec3* v, double val) { \ + v->data[INDEX] = val; \ + } + +OSVR_VEC_MEMBER(X, 0) +OSVR_VEC_MEMBER(Y, 1) +OSVR_VEC_MEMBER(Z, 2) + +#undef OSVR_VEC_MEMBER + +/** @brief Set a Vec3 to the zero vector */ +OSVR_INLINE void osvrVec3Zero(OSVR_Vec3* v) { + osvrVec3SetX(v, 0); + osvrVec3SetY(v, 0); + osvrVec3SetZ(v, 0); +} + +/** @} */ + +OSVR_EXTERN_C_END + +#ifdef __cplusplus +template <typename StreamType> +inline StreamType& operator<<(StreamType& os, OSVR_Vec3 const& vec) { + os << "(" << vec.data[0] << ", " << vec.data[1] << ", " << vec.data[2] << ")"; + return os; +} +#endif + +#endif diff --git a/gfx/vr/vrhost/moz.build b/gfx/vr/vrhost/moz.build new file mode 100644 index 0000000000..1fa6e77f03 --- /dev/null +++ b/gfx/vr/vrhost/moz.build @@ -0,0 +1,40 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +SOURCES += ["/gfx/vr/service/VRSession.cpp", "/gfx/vr/VRShMem.cpp", "vrhostapi.cpp"] + +# Since .def files do not support preprocessing, switch which file is used +# to declare exports. See comments in the files for more info. +if CONFIG["NIGHTLY_BUILD"]: + DEFFILE = "vrhostnightly.def" + SOURCES += ["vrhosttest.cpp"] +else: + DEFFILE = "vrhost.def" + +LOCAL_INCLUDES += [ + "/gfx/vr", + "/gfx/vr/external_api", + "/gfx/vr/service", + "/ipc/chromium/src", +] + +EXPORTS.vrhost = ["vrhostex.h"] + +DIRS += ["testhost"] + +# this is Windows-only for now +DEFINES["XP_WIN"] = True +# fixes "lld-link: error: undefined symbol: __imp_moz_xmalloc" +DEFINES["MOZ_NO_MOZALLOC"] = True +# fixes "STL code can only be used with infallible ::operator new()" +DisableStlWrapping() + +# Define UNICODE for default support in this dll +DEFINES["UNICODE"] = True +DEFINES["_UNICODE"] = True + +# Use SharedLibrary to generate the dll +SharedLibrary("vrhost") diff --git a/gfx/vr/vrhost/testhost/moz.build b/gfx/vr/vrhost/testhost/moz.build new file mode 100644 index 0000000000..5b0d3b16e1 --- /dev/null +++ b/gfx/vr/vrhost/testhost/moz.build @@ -0,0 +1,12 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +SOURCES += ["testhost.cpp"] + +USE_LIBS += ["vrhost"] + +# Use Progam to generate the executable +Program("vrtesthost") diff --git a/gfx/vr/vrhost/testhost/testhost.cpp b/gfx/vr/vrhost/testhost/testhost.cpp new file mode 100644 index 0000000000..ae667aaee4 --- /dev/null +++ b/gfx/vr/vrhost/testhost/testhost.cpp @@ -0,0 +1,43 @@ +/* -*- 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 <windows.h> +#include "vrhost/vrhostex.h" + +int main(int argc, char* argv[], char* envp[]) { + HINSTANCE hVR = ::LoadLibraryW(L"vrhost.dll"); + if (hVR != nullptr) { + // Sometimes LoadLibrary can set an error, but, if it returns a handle, + // then it succeeded. Clear the last error so that subsequent calls to + // GetLastError don't improperly attribute failure to another API. + ::SetLastError(0); + + PFN_SAMPLE lpfnSample = (PFN_SAMPLE)GetProcAddress(hVR, "SampleExport"); + if (lpfnSample != nullptr) { + lpfnSample(); + } + + if (strcmp(argv[1], "-testsvc") == 0) { + PFN_SAMPLE lpfnService = + (PFN_SAMPLE)GetProcAddress(hVR, "TestTheService"); + + lpfnService(); + } else if (strcmp(argv[1], "-testmgr") == 0) { + PFN_SAMPLE lpfnManager = + (PFN_SAMPLE)GetProcAddress(hVR, "TestTheManager"); + lpfnManager(); + } else if (strcmp(argv[1], "-testvrwin") == 0) { + PFN_SAMPLE lpfnManager = + (PFN_SAMPLE)GetProcAddress(hVR, "TestCreateVRWindow"); + lpfnManager(); + } + + ::FreeLibrary(hVR); + hVR = nullptr; + } + + return 0; +} diff --git a/gfx/vr/vrhost/vrhost.def b/gfx/vr/vrhost/vrhost.def new file mode 100644 index 0000000000..7a80c3b247 --- /dev/null +++ b/gfx/vr/vrhost/vrhost.def @@ -0,0 +1,15 @@ +;+# 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/.
+
+LIBRARY vrhost.dll
+
+;+= Note: this file defines exports that are only available in Beta and
+;+= Release builds so that test exports are not exposed.
+;+= Please ensure that these exports are also declared in vrhostnightly.def.
+EXPORTS
+ CreateVRWindow PRIVATE
+ CloseVRWindow PRIVATE
+ SendUIMessageToVRWindow PRIVATE
+ WaitForVREvent PRIVATE
+ SendVRTelemetry PRIVATE
\ No newline at end of file diff --git a/gfx/vr/vrhost/vrhostapi.cpp b/gfx/vr/vrhost/vrhostapi.cpp new file mode 100644 index 0000000000..508e379a5f --- /dev/null +++ b/gfx/vr/vrhost/vrhostapi.cpp @@ -0,0 +1,374 @@ +/* -*- 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/. */ + +// vrhostapi.cpp +// Definition of functions that are exported from this dll + +#include "vrhostex.h" +#include "VRShMem.h" + +#include <stdio.h> +#include <string.h> +#include <random> +#include <queue> + +#include "windows.h" + +class VRShmemInstance { + public: + VRShmemInstance() = delete; + VRShmemInstance(const VRShmemInstance& aRHS) = delete; + + static mozilla::gfx::VRShMem& GetInstance() { + static mozilla::gfx::VRShMem shmem(nullptr, true /*aRequiresMutex*/); + return shmem; + } +}; + +// VRWindowManager adds a level of indirection so that system HWND isn't exposed +// outside of these APIs +class VRWindowManager { + public: + HWND GetHWND(uint32_t nId) { + if (nId == nWindow) { + return hWindow; + } else { + return nullptr; + } + } + + uint32_t GetId(HWND hwnd) { + if (hwnd == hWindow) { + return nWindow; + } else { + return 0; + } + } + + HANDLE GetProc(uint32_t nId) { + if (nId == nWindow) { + return hProc; + } else { + return nullptr; + } + } + + HANDLE GetEvent() { return hEvent; } + + uint32_t SetHWND(HWND hwnd, HANDLE hproc, HANDLE hevent) { + if (hWindow == nullptr) { + MOZ_ASSERT(hwnd != nullptr && hproc != nullptr); + hWindow = hwnd; + hProc = hproc; + hEvent = hevent; + nWindow = GetRandomUInt(); +#if defined(DEBUG) && defined(NIGHTLY_BUILD) + printf("VRWindowManager: Storing HWND: 0x%p as ID: 0x%X\n", hWindow, + nWindow); +#endif + return nWindow; + } else { + return -1; + } + } + + uint32_t GetRandomUInt() { return randomGenerator(); } + + static VRWindowManager* GetManager() { + if (Instance == nullptr) { + Instance = new VRWindowManager(); + } + return Instance; + } + + private: + static VRWindowManager* Instance; + + // For now, simply store the ID and HWND, and expand + // to a map when multiple windows/instances are supported. + uint32_t nWindow = 0; + HWND hWindow = nullptr; + HANDLE hProc = nullptr; + HANDLE hEvent = nullptr; + std::random_device randomGenerator; +}; +VRWindowManager* VRWindowManager::Instance = nullptr; + +class VRTelemetryManager { + public: + void SendTelemetry(uint32_t aTelemetryId, uint32_t aValue) { + if (!aTelemetryId) { + return; + } + + mozilla::gfx::VRTelemetryState telemetryState = {0}; + VRShmemInstance::GetInstance().PullTelemetryState(telemetryState); + + if (telemetryState.uid == 0) { + telemetryState.uid = sUid; + } + + switch (mozilla::gfx::VRTelemetryId(aTelemetryId)) { + case mozilla::gfx::VRTelemetryId::INSTALLED_FROM: + MOZ_ASSERT(aValue <= 0x07, + "VRTelemetryId::INSTALLED_FROM only allows 3 bits."); + telemetryState.installedFrom = true; + telemetryState.installedFromValue = aValue; + break; + case mozilla::gfx::VRTelemetryId::ENTRY_METHOD: + MOZ_ASSERT(aValue <= 0x07, + "VRTelemetryId::ENTRY_METHOD only allows 3 bits."); + telemetryState.entryMethod = true; + telemetryState.entryMethodValue = aValue; + break; + case mozilla::gfx::VRTelemetryId::FIRST_RUN: + MOZ_ASSERT(aValue <= 0x01, + "VRTelemetryId::FIRST_RUN only allows 1 bit."); + telemetryState.firstRun = true; + telemetryState.firstRunValue = aValue; + break; + default: + MOZ_CRASH("Undefined VR telemetry type."); + break; + } + VRShmemInstance::GetInstance().PushTelemetryState(telemetryState); + ++sUid; + } + + static VRTelemetryManager* GetManager() { + if (Instance == nullptr) { + Instance = new VRTelemetryManager(); + } + return Instance; + } + + private: + static VRTelemetryManager* Instance; + static uint32_t sUid; // It starts from 1, Zero means the data is read yet + // from VRManager. +}; +uint32_t VRTelemetryManager::sUid = 1; +VRTelemetryManager* VRTelemetryManager::Instance = nullptr; + +// Struct to send params to StartFirefoxThreadProc +struct StartFirefoxParams { + char* firefoxFolder; + char* firefoxProfileFolder; + HANDLE hProcessFx; +}; + +// Helper threadproc function for CreateVRWindow +DWORD StartFirefoxThreadProc(_In_ LPVOID lpParameter) { + wchar_t cmd[] = L"%Sfirefox.exe -wait-for-browser -profile %S --fxr"; + + StartFirefoxParams* params = static_cast<StartFirefoxParams*>(lpParameter); + wchar_t cmdWithPath[MAX_PATH + MAX_PATH] = {0}; + int err = swprintf_s(cmdWithPath, ARRAYSIZE(cmdWithPath), cmd, + params->firefoxFolder, params->firefoxProfileFolder); + + if (err != -1) { + PROCESS_INFORMATION procFx = {0}; + STARTUPINFO startupInfoFx = {0}; + +#if defined(DEBUG) && defined(NIGHTLY_BUILD) + printf("Starting Firefox via: %S\n", cmdWithPath); +#endif + + // Start Firefox + bool fCreateContentProc = ::CreateProcess(nullptr, // lpApplicationName, + cmdWithPath, + nullptr, // lpProcessAttributes, + nullptr, // lpThreadAttributes, + TRUE, // bInheritHandles, + 0, // dwCreationFlags, + nullptr, // lpEnvironment, + nullptr, // lpCurrentDirectory, + &startupInfoFx, &procFx); + + if (!fCreateContentProc) { + printf("Failed to create Firefox process"); + } + + params->hProcessFx = procFx.hProcess; + } + + return 0; +} + +// This export is responsible for starting up a new VR window in Firefox and +// returning data related to its creation back to the caller. +// See nsFxrCommandLineHandler::Handle for more details about the bootstrapping +// process with Firefox. +void CreateVRWindow(char* firefoxFolderPath, char* firefoxProfilePath, + uint32_t dxgiAdapterID, uint32_t widthHost, + uint32_t heightHost, uint32_t* windowId, void** hTex, + uint32_t* width, uint32_t* height) { + mozilla::gfx::VRWindowState windowState = {0}; + + int err = sprintf_s(windowState.signalName, ARRAYSIZE(windowState.signalName), + "fxr::CreateVRWindow::%X", + VRWindowManager::GetManager()->GetRandomUInt()); + + if (err > 0) { + HANDLE hEvent = ::CreateEventA(nullptr, // attributes + FALSE, // bManualReset + FALSE, // bInitialState + windowState.signalName); + + if (hEvent != nullptr) { + // Create Shmem and push state + VRShmemInstance::GetInstance().CreateShMem( + true /*aCreateOnSharedMemory*/); + VRShmemInstance::GetInstance().PushWindowState(windowState); + + // Start Firefox in another thread so that this thread can wait for the + // window state to be updated during Firefox startup + StartFirefoxParams fxParams = {0}; + fxParams.firefoxFolder = firefoxFolderPath; + fxParams.firefoxProfileFolder = firefoxProfilePath; + DWORD dwTid = 0; + HANDLE hThreadFx = CreateThread(nullptr, 0, StartFirefoxThreadProc, + &fxParams, 0, &dwTid); + if (hThreadFx != nullptr) { + // Wait for Firefox to populate rest of window state + ::WaitForSingleObject(hEvent, INFINITE); + + // Update local WindowState with data from Firefox + VRShmemInstance::GetInstance().PullWindowState(windowState); + + (*hTex) = windowState.textureFx; + (*windowId) = VRWindowManager::GetManager()->SetHWND( + (HWND)windowState.hwndFx, fxParams.hProcessFx, hEvent); + (*width) = windowState.widthFx; + (*height) = windowState.heightFx; + } else { + // How do I failfast? + } + } + } +} + +// Keep track of when WaitForVREvent is running to manage shutdown of +// this vrhost. See CloseVRWindow for more details. +volatile bool s_WaitingForVREvent = false; + +// Blocks until a new event is set on the shmem. +// Note: this function can be called from any thread. +void WaitForVREvent(uint32_t& nVRWindowID, uint32_t& eventType, + uint32_t& eventData1, uint32_t& eventData2) { + MOZ_ASSERT(!s_WaitingForVREvent); + s_WaitingForVREvent = true; + + // Initialize all of the out params + nVRWindowID = 0; + eventType = 0; + eventData1 = 0; + eventData2 = 0; + + if (VRShmemInstance::GetInstance().HasExternalShmem()) { + HANDLE evt = VRWindowManager::GetManager()->GetEvent(); + const DWORD waitResult = ::WaitForSingleObject(evt, INFINITE); + if (waitResult != WAIT_OBJECT_0) { + MOZ_ASSERT(false && "Error WaitForVREvent().\n"); + return; + } + mozilla::gfx::VRWindowState windowState = {0}; + VRShmemInstance::GetInstance().PullWindowState(windowState); + + nVRWindowID = + VRWindowManager::GetManager()->GetId((HWND)windowState.hwndFx); + if (nVRWindowID != 0) { + eventType = (uint32_t)windowState.eventType; + mozilla::gfx::VRFxEventType fxEvent = + mozilla::gfx::VRFxEventType(eventType); + + switch (fxEvent) { + case mozilla::gfx::VRFxEventType::IME: + eventData1 = (uint32_t)windowState.eventState; + break; + case mozilla::gfx::VRFxEventType::FULLSCREEN: + eventData1 = (uint32_t)windowState.eventState; + break; + case mozilla::gfx::VRFxEventType::SHUTDOWN: + VRShmemInstance::GetInstance().CloseShMem(); + break; + default: + MOZ_ASSERT(false && "Undefined VR Fx event."); + break; + } + } + } + s_WaitingForVREvent = false; +} + +// Sends a message to the VR window to close. +void CloseVRWindow(uint32_t nVRWindowID, bool waitForTerminate) { + HWND hwnd = VRWindowManager::GetManager()->GetHWND(nVRWindowID); + if (hwnd != nullptr) { + ::SendMessage(hwnd, WM_CLOSE, 0, 0); + + if (waitForTerminate) { + // Wait for Firefox main process to exit + ::WaitForSingleObject(VRWindowManager::GetManager()->GetProc(nVRWindowID), + INFINITE); + } + } + + // If a thread is currently blocked on WaitForVREvent, then defer closing the + // shmem to that thread by sending an async event. + // If WaitForVREvent is not running, it is safe to close the shmem now. + // Subsequent calls to WaitForVREvent will return early because it does not + // have an external shmem. + if (s_WaitingForVREvent) { + VRShmemInstance::GetInstance().SendShutdowmState(nVRWindowID); + } else { + VRShmemInstance::GetInstance().CloseShMem(); + } +} + +// Forwards Win32 UI window messages to the Firefox widget/window associated +// with nVRWindowID. Note that not all message type is supported (only those +// allowed in the switch block below). +void SendUIMessageToVRWindow(uint32_t nVRWindowID, uint32_t msg, + uint64_t wparam, uint64_t lparam) { + HWND hwnd = VRWindowManager::GetManager()->GetHWND(nVRWindowID); + if (hwnd != nullptr) { + switch (msg) { + case WM_MOUSEWHEEL: + // For MOUSEWHEEL, the coordinates are supposed to be at Screen origin + // rather than window client origin. + // Make the conversion to screen coordinates before posting the message + // to the Fx window. + POINT pt; + POINTSTOPOINT(pt, MAKEPOINTS(lparam)); + if (!::ClientToScreen(hwnd, &pt)) { + break; + } + // otherwise, fallthrough + lparam = POINTTOPOINTS(pt); + case WM_MOUSEMOVE: + case WM_LBUTTONDOWN: + case WM_LBUTTONUP: + case WM_CHAR: + case WM_KEYDOWN: + case WM_KEYUP: + ::PostMessage(hwnd, msg, wparam, lparam); + break; + + default: + break; + } + } +} + +void SendVRTelemetry(uint32_t nVRWindowID, uint32_t telemetryId, + uint32_t value) { + HWND hwnd = VRWindowManager::GetManager()->GetHWND(nVRWindowID); + if (hwnd == nullptr) { + return; + } + VRTelemetryManager::GetManager()->SendTelemetry(telemetryId, value); +}
\ No newline at end of file diff --git a/gfx/vr/vrhost/vrhostex.h b/gfx/vr/vrhost/vrhostex.h new file mode 100644 index 0000000000..70f4f98fba --- /dev/null +++ b/gfx/vr/vrhost/vrhostex.h @@ -0,0 +1,33 @@ +/* -*- 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/. */ + +// vrhostex.h +// This file declares the functions and their typedefs for functions exported +// by vrhost.dll + +#pragma once +#include <stdint.h> + +// void SampleExport(); +typedef void (*PFN_SAMPLE)(); + +typedef void (*PFN_CREATEVRWINDOW)(char* firefoxFolderPath, + char* firefoxProfilePath, + uint32_t dxgiAdapterID, uint32_t widthHost, + uint32_t heightHost, uint32_t* windowId, + void** hTex, uint32_t* width, + uint32_t* height); + +typedef void (*PFN_CLOSEVRWINDOW)(uint32_t nVRWindowID, bool waitForTerminate); + +typedef void (*PFN_SENDUIMSG)(uint32_t nVRWindowID, uint32_t msg, + uint64_t wparam, uint64_t lparam); + +typedef void (*PFN_WAITFORVREVENT)(uint32_t& nVRWindowID, uint32_t& eventType, + uint32_t& eventData1, uint32_t& eventData2); + +typedef void (*PFN_SENDVRTELEMETRY)(uint32_t nVRWindowID, uint32_t telemetryId, + uint32_t value); diff --git a/gfx/vr/vrhost/vrhostnightly.def b/gfx/vr/vrhost/vrhostnightly.def new file mode 100644 index 0000000000..0a849e37ce --- /dev/null +++ b/gfx/vr/vrhost/vrhostnightly.def @@ -0,0 +1,23 @@ +;+# 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/.
+
+LIBRARY vrhost.dll
+
+;+= Note: this file defines exports that are only available in Nightly builds
+;+= so that test exports are not exposed in Beta and Release builds.
+;+= Please ensure that the Public Export APIs in the first section below are
+;+= also declared in vrhost.def.
+EXPORTS
+;+= Public Export APIs for vrhost
+ CreateVRWindow PRIVATE
+ CloseVRWindow PRIVATE
+ SendUIMessageToVRWindow PRIVATE
+ WaitForVREvent PRIVATE
+ SendVRTelemetry PRIVATE
+
+;+= Exports only available in Nightlies for testing
+ SampleExport PRIVATE
+ TestTheManager PRIVATE
+ TestTheService PRIVATE
+ TestCreateVRWindow PRIVATE
\ No newline at end of file diff --git a/gfx/vr/vrhost/vrhosttest.cpp b/gfx/vr/vrhost/vrhosttest.cpp new file mode 100644 index 0000000000..84c11c10d6 --- /dev/null +++ b/gfx/vr/vrhost/vrhosttest.cpp @@ -0,0 +1,370 @@ +/* -*- 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/. */ + +// vrhosttest.cpp +// Definition of testing and validation functions that are exported from this +// dll + +#include "vrhostex.h" +#include "VRShMem.h" + +#include <stdio.h> +#include "windows.h" + +static const char s_pszSharedEvent[] = "vrhost_test_event_signal"; +static const DWORD s_dwWFSO_WAIT = 20000; + +void SampleExport() { printf("vrhost.cpp hello world\n"); } + +// For testing ShMem as Manager and Service: +// The two processes should output the steps, synchronously, to validate +// 2-way communication via VRShMem as follows +// 01 mgr: create mgr +// 02 mgr: wait for signal +// 03 svc: create svc +// 04 svc: send signal +// 05 svc: wait for signal +// 06 mgr: push browser +// 07 mgr: send signal +// 08 mgr: wait for signal +// 09 svc: pull browser +// 10 svc: verify data +// 11 svc: push system +// 12 svc: send signal +// 13 svc: wait for signal +// 14 mgr: pull system +// 15 mgr: verify data +// 16 mgr: push window +// 17 mgr: send signal +// 18 mgr: wait for signal +// 19 svc: pull window +// 20 svc: verify data +// 21 svc: push window +// 22 svc: send signal +// 23 mgr: pull window +// 24 mgr: verify data +// 25 return +// These tests can be run with two instances of vrtesthost.exe, one first +// running with -testmgr and the second running with -testsvc. +// TODO: Bug 1563235 - Convert vrtesthost.exe tests into unit tests + +// For testing VRShMem as the Manager (i.e., the one who creates the +// shmem). The sequence of how it tests with the service is listed above. +void TestTheManager() { + printf("TestTheManager Start\n"); + + MOZ_ASSERT(GetLastError() == 0, + "TestTheManager should start with no OS errors"); + HANDLE hEvent = ::CreateEventA(nullptr, // lpEventAttributes + FALSE, // bManualReset + FALSE, // bInitialState + s_pszSharedEvent // lpName + ); + + printf("\n01 mgr: create mgr\n"); + mozilla::gfx::VRShMem shmem(nullptr, true /*aRequiresMutex*/); + shmem.CreateShMem(true /*aCreateOnSharedMemory*/); + + printf("02 mgr: wait for signal\n"); + ::WaitForSingleObject(hEvent, s_dwWFSO_WAIT); + + // Set some state to verify on the other side + mozilla::gfx::VRBrowserState browserState = {0}; + browserState.presentationActive = true; + browserState.layerState[0].type = + mozilla::gfx::VRLayerType::LayerType_2D_Content; + browserState.hapticState[0].controllerIndex = 987; + + printf("06 mgr: push browser\n"); + shmem.PushBrowserState(browserState, true); + + printf("07 mgr: send signal\n"); + ::SetEvent(hEvent); + + printf("08 mgr: wait for signal\n"); + ::WaitForSingleObject(hEvent, s_dwWFSO_WAIT); + + printf("14 mgr: pull system\n"); + mozilla::gfx::VRSystemState state; + shmem.PullSystemState(state.displayState, state.sensorState, + state.controllerState, state.enumerationCompleted, + nullptr); + + printf( + "15 mgr: verify data\n" + "\tstate.enumerationCompleted = %d\n" + "\tstate.displayState.displayName = \"%s\"\n" + "\tstate.controllerState[1].hand = %hhu\n" + "\tstate.sensorState.inputFrameID = %llu\n", + state.enumerationCompleted, state.displayState.displayName, + state.controllerState[1].hand, state.sensorState.inputFrameID); + + // Test the WindowState functions as the host + mozilla::gfx::VRWindowState windowState = {0}; + strcpy(windowState.signalName, "randomsignalstring"); + windowState.dxgiAdapterHost = 99; + windowState.heightHost = 42; + windowState.widthHost = 24; + + printf("16 mgr: push window\n"); + shmem.PushWindowState(windowState); + + printf("17 mgr: send signal\n"); + ::SetEvent(hEvent); + + printf("18 mgr: wait for signal\n"); + ::WaitForSingleObject(hEvent, s_dwWFSO_WAIT); + + printf("23 mgr: pull window\n"); + shmem.PullWindowState(windowState); + + printf( + "24 svc: verify data\n" + "\tstate.hwndFx = 0x%llX\n" + "\tstate.heightFx = %d\n" + "\tstate.widthFx = %d\n" + "\tstate.textureHandle = %p\n", + windowState.hwndFx, windowState.heightFx, windowState.widthFx, + windowState.textureFx); + + shmem.CloseShMem(); + + printf("TestTheManager complete"); + fflush(nullptr); +} + +// For testing VRShMem as the Service (i.e., the one who consumes the +// shmem). The sequence of how it tests with the service is listed above. +void TestTheService() { + printf("TestTheService Start\n"); + + MOZ_ASSERT(GetLastError() == 0, + "TestTheService should start with no OS errors"); + // Handle created by TestTheManager above. + HANDLE hEvent = ::OpenEventA(EVENT_ALL_ACCESS, // dwDesiredAccess + FALSE, // bInheritHandle + s_pszSharedEvent // lpName + ); + + printf("\n03 svc: create svc\n"); + mozilla::gfx::VRShMem shmem(nullptr, true /*aRequiresMutex*/); + shmem.JoinShMem(); + + printf("04 svc: send signal\n"); + ::SetEvent(hEvent); + + printf("05 svc: wait for signal\n"); + ::WaitForSingleObject(hEvent, s_dwWFSO_WAIT); + + printf("09 svc: pull browser\n"); + mozilla::gfx::VRBrowserState state; + shmem.PullBrowserState(state); + + printf( + "10 svc: verify data\n" + "\tstate.presentationActive = %d\n" + "\tstate.layerState[0].type = %hu\n" + "\tstate.hapticState[0].controllerIndex = %d\n", + state.presentationActive, state.layerState[0].type, + state.hapticState[0].controllerIndex); + + // Set some state to verify on the other side + mozilla::gfx::VRSystemState systemState; + systemState.enumerationCompleted = true; + strncpy(systemState.displayState.displayName, "test from vrservice shmem", + mozilla::gfx::kVRDisplayNameMaxLen); + systemState.controllerState[1].hand = mozilla::gfx::ControllerHand::Left; + systemState.sensorState.inputFrameID = 1234567; + + printf("11 svc: push system\n"); + shmem.PushSystemState(systemState); + + printf("12 svc: send signal\n"); + ::SetEvent(hEvent); + + printf("13 svc: wait for signal\n"); + ::WaitForSingleObject(hEvent, s_dwWFSO_WAIT); + + // Test the WindowState functions as Firefox + printf("19 svc: pull window\n"); + mozilla::gfx::VRWindowState windowState; + shmem.PullWindowState(windowState); + + printf( + "20 svc: verify data\n" + "\tstate.signalName = \"%s\"\n" + "\tstate.dxgiAdapterHost = %d\n" + "\tstate.heightHost = %d\n" + "\tstate.widthHost = %d\n", + windowState.signalName, windowState.dxgiAdapterHost, + windowState.heightHost, windowState.widthHost); + + windowState.hwndFx = 0x1234; + windowState.heightFx = 1234; + windowState.widthFx = 4321; + windowState.textureFx = (HANDLE)0x77777; + + printf("21 svc: push window\n"); + shmem.PushWindowState(windowState); + + printf("22 svc: send signal\n"); + ::SetEvent(hEvent); + + shmem.LeaveShMem(); + + printf("TestTheService complete"); + fflush(nullptr); +} + +DWORD TestWaitForVREventThreadProc(_In_ LPVOID lpParameter) { + // WaitForVREvent + printf("\nStarting TestWaitForVREventThreadProc\n"); + + PFN_WAITFORVREVENT fnWaitForVRMsg = (PFN_WAITFORVREVENT)lpParameter; + + uint32_t nVRWindowID = 0; + uint32_t eventType = 0; + uint32_t eventData1 = 0; + uint32_t eventData2 = 0; + + while (eventType != 2) { // FxEvent_SHUTDOWN + fnWaitForVRMsg(nVRWindowID, eventType, eventData1, eventData2); + printf( + "\nWaitForVRMessage:\n\tvrWindowID: %d\n\teventType: %d\n\teventData1: " + "%d\n\teventData2: %d\n", + nVRWindowID, eventType, eventData1, eventData2); + } + + printf("\nReturning from TestWaitForVREventThreadProc\n"); + + return 0; +} + +// This function tests the export CreateVRWindow by outputting the return values +// from the call to the console, as well as testing CloseVRWindow after the data +// is retrieved. +void TestCreateVRWindow() { + printf("TestCreateVRWindow Start\n"); + + // Cache function calls to test real-world export and usage + HMODULE hVRHost = ::GetModuleHandleA("vrhost.dll"); + PFN_CREATEVRWINDOW fnCreate = + (PFN_CREATEVRWINDOW)::GetProcAddress(hVRHost, "CreateVRWindow"); + PFN_CLOSEVRWINDOW fnClose = + (PFN_CLOSEVRWINDOW)::GetProcAddress(hVRHost, "CloseVRWindow"); + PFN_SENDUIMSG fnSendMsg = + (PFN_SENDUIMSG)::GetProcAddress(hVRHost, "SendUIMessageToVRWindow"); + PFN_WAITFORVREVENT fnWaitForVRMsg = + (PFN_WAITFORVREVENT)::GetProcAddress(hVRHost, "WaitForVREvent"); + + // Create the VR Window and store data from creation + char currentDir[MAX_PATH] = {0}; + char currentDirProfile[MAX_PATH] = {0}; + DWORD currentDirLength = + ::GetCurrentDirectoryA(ARRAYSIZE(currentDir), currentDir); + currentDir[currentDirLength] = '\\'; + + int err = sprintf_s(currentDirProfile, ARRAYSIZE(currentDirProfile), + "%svrhosttest-profile", currentDir); + if (err > 0) { + printf("Starting Firefox from %s\n", currentDir); + + UINT windowId; + HANDLE hTex; + UINT width; + UINT height; + fnCreate(currentDir, currentDirProfile, 0, 100, 200, &windowId, &hTex, + &width, &height); + + // Now that the Fx window is created, start a new thread to wait for VR + // events to be sent from it + DWORD dwTid; + HANDLE hThreadWait = CreateThread(nullptr, 0, TestWaitForVREventThreadProc, + (void*)fnWaitForVRMsg, 0, &dwTid); + + // Wait for Fx to finish launch +#ifdef DEBUG + ::Sleep(5000); +#else + ::Sleep(2000); +#endif + + printf( + "1. Simulating a click on the Home button, which should look " + "pressed\n"); + POINT pt; + pt.x = 180; + pt.y = 700; + fnSendMsg(windowId, WM_LBUTTONDOWN, 0, POINTTOPOINTS(pt)); + ::Sleep(3000); + fnSendMsg(windowId, WM_LBUTTONUP, 0, POINTTOPOINTS(pt)); + + printf( + "2. Simulating hovering across the URL bar, which should turn " + "blue\n"); + pt.x = 600; + for (int i = 0; i < 100; ++i) { + pt.x++; + fnSendMsg(windowId, WM_MOUSEMOVE, 0, POINTTOPOINTS(pt)); + ::Sleep(5); + } + + printf( + "3. Simulating clicking inside the URL bar, which should " + "highlight the text\n"); + pt.x = 700; + pt.y = 700; + fnSendMsg(windowId, WM_LBUTTONDOWN, 0, POINTTOPOINTS(pt)); + fnSendMsg(windowId, WM_LBUTTONUP, 0, POINTTOPOINTS(pt)); + + ::Sleep(3000); + + printf("4. Type some UTF16 characters in the URL bar\n"); + fnSendMsg(windowId, WM_CHAR, 0x4E64, 0); + fnSendMsg(windowId, WM_CHAR, 0x312D, 0); + fnSendMsg(windowId, WM_CHAR, 0x0BB9, 0); + fnSendMsg(windowId, WM_CHAR, 0x2745, 0); + + printf( + "5. Simulating clicking outside the URL bar, which should " + "send a keyboard blur event\n"); + pt.x = 20; + pt.y = 20; + fnSendMsg(windowId, WM_LBUTTONDOWN, 0, POINTTOPOINTS(pt)); + fnSendMsg(windowId, WM_LBUTTONUP, 0, POINTTOPOINTS(pt)); + + ::Sleep(1000); + + printf("6. Simulating scrolling the web content down and then up\n"); + + pt.x = 100; + pt.y = 100; + for (int i = -20; i < 10; ++i) { + fnSendMsg(windowId, WM_MOUSEWHEEL, + MAKELONG(0, (i < 0) ? -WHEEL_DELTA : WHEEL_DELTA), + POINTTOPOINTS(pt)); + ::Sleep(100); + } + + ::Sleep(5000); + + // Close the Firefox VR Window + fnClose(windowId, true); + + // Wait for the VR event thread to return + ::WaitForSingleObject(hThreadWait, INFINITE); + + // Print output from CreateVRWindow + printf( + "\n\nTestCreateVRWindow End:\n" + "\twindowId = 0x%X\n" + "\thTex = 0x%p\n" + "\twidth = %d\n" + "\theight = %d\n", + windowId, hTex, width, height); + printf("\n***Note: profile folder created at %s***\n", currentDirProfile); + } +} |