diff options
Diffstat (limited to 'gfx/vr/vrhost/vrhostapi.cpp')
-rw-r--r-- | gfx/vr/vrhost/vrhostapi.cpp | 374 |
1 files changed, 374 insertions, 0 deletions
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 |