Bug 1570125 - Create VR window via vrhost r=kip,mossop

This change creates the new export CreateVRWindow from vrhost.dll. This API
results in spawning a new Firefox window with the Firefox Reality 2D UI and
returns data needed for the host to interact with it. VRShMem is used to pass
data across process boundaries during this bootstrap process.

Additional tests are added to vrhost to be later converted to unittests.

Differential Revision: https://phabricator.services.mozilla.com/D40236

--HG--
rename : gfx/vr/vrhost/vrhost.cpp => gfx/vr/vrhost/vrhosttest.cpp
extra : moz-landing-system : lando
This commit is contained in:
thomasmo 2019-08-02 20:55:48 +00:00
Родитель 26c2894a62
Коммит 94cc4efcb3
10 изменённых файлов: 453 добавлений и 21 удалений

Просмотреть файл

@ -30,11 +30,12 @@ using namespace mozilla::gfx;
// 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.2"
#ifdef XP_WIN
static const char* kShmemName = "moz.gecko.vr_ext.0.0.1";
static LPCTSTR kMutexName = TEXT("mozilla::vr::ShmemMutex");
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.0.0.1";
static const char* kShmemName = "/moz.gecko.vr_ext." SHMEM_VERSION;
#endif // XP_WIN
#if !defined(MOZ_WIDGET_ANDROID)
@ -571,3 +572,34 @@ void VRShMem::PullSystemState(
} // 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)
}

Просмотреть файл

@ -43,6 +43,9 @@ class VRShMem final {
bool& aEnumerationCompleted,
const std::function<bool()>& aWaitCondition = nullptr);
void PushWindowState(VRWindowState& aState);
void PullWindowState(VRWindowState& aState);
bool HasExternalShmem() const { return mExternalShmem != nullptr; }
volatile VRExternalShmem* GetExternalShmem() const;
bool IsDisplayStateShutdown() const;

Просмотреть файл

@ -430,6 +430,23 @@ struct VRSystemState {
VRControllerState controllerState[kVRControllerMaxCount];
};
// 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;
// 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];
};
struct VRExternalShmem {
int32_t version;
int32_t size;
@ -455,6 +472,9 @@ struct VRExternalShmem {
int64_t geckoGenerationB;
int64_t servoGenerationB;
#endif // !defined(__ANDROID__)
#if defined(XP_WIN)
VRWindowState windowState;
#endif
#ifdef MOZILLA_INTERNAL_API
void Clear() volatile {
/**

Просмотреть файл

@ -13,6 +13,9 @@
#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"
@ -20,6 +23,8 @@
#include "windows.h"
#include "VRShMem.h"
NS_IMPL_ISUPPORTS(nsFxrCommandLineHandler, nsICommandLineHandler)
NS_IMETHODIMP
@ -43,6 +48,39 @@ nsFxrCommandLineHandler::Handle(nsICommandLine* aCmdLine) {
getter_AddRefs(newWindow));
MOZ_ASSERT(result == NS_OK);
// Send the window's HWND to vrhost through VRShMem
mozilla::gfx::VRShMem shmem(nullptr, true, false);
if (shmem.JoinShMem()) {
mozilla::gfx::VRWindowState windowState = {0};
shmem.PullWindowState(windowState);
nsCOMPtr<nsIWidget> newWidget =
mozilla::widget::WidgetUtils::DOMWindowToWidget(
nsPIDOMWindowOuter::From(newWindow));
HWND hwndWidget = (HWND)newWidget->GetNativeData(NS_NATIVE_WINDOW);
// The CLH should have populated this first
MOZ_ASSERT(windowState.hwndFx == 0);
MOZ_ASSERT(windowState.textureFx == nullptr);
windowState.hwndFx = (uint64_t)hwndWidget;
shmem.PushWindowState(windowState);
// Notify the waiting process that the data is now available
HANDLE hSignal = ::OpenEventA(EVENT_ALL_ACCESS, // dwDesiredAccess
FALSE, // bInheritHandle
windowState.signalName // lpName
);
::SetEvent(hSignal);
shmem.LeaveShMem();
::CloseHandle(hSignal);
} else {
MOZ_CRASH("failed to start with --fxr");
}
}
return NS_OK;

Просмотреть файл

@ -6,9 +6,14 @@
SOURCES += [
'/gfx/vr/VRShMem.cpp',
'vrhost.cpp'
'vrhostapi.cpp'
]
if CONFIG['NIGHTLY_BUILD']:
SOURCES += [
'vrhosttest.cpp'
]
LOCAL_INCLUDES += [
'/gfx/vr',
'/gfx/vr/external_api',

Просмотреть файл

@ -10,6 +10,11 @@
int main(int argc, char* argv[], char* envp[]) {
HINSTANCE hVR = ::LoadLibrary("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();
@ -18,11 +23,16 @@ int main(int argc, char* argv[], char* envp[]) {
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);
@ -30,4 +40,4 @@ int main(int argc, char* argv[], char* envp[]) {
}
return 0;
}
}

Просмотреть файл

@ -4,6 +4,10 @@
LIBRARY vrhost.dll
EXPORTS SampleExport PRIVATE
TestTheManager PRIVATE
TestTheService PRIVATE
EXPORTS SampleExport PRIVATE
TestTheManager PRIVATE
TestTheService PRIVATE
TestCreateVRWindow PRIVATE
CreateVRWindow PRIVATE
CloseVRWindow PRIVATE

188
gfx/vr/vrhost/vrhostapi.cpp Normal file
Просмотреть файл

@ -0,0 +1,188 @@
/* -*- 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 "windows.h"
// 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;
}
}
HANDLE GetProc(uint32_t nId) {
if (nId == nWindow) {
return hProc;
} else {
return nullptr;
}
}
uint32_t SetHWND(HWND hwnd, HANDLE hproc) {
if (hWindow == nullptr) {
MOZ_ASSERT(hwnd != nullptr && hproc != nullptr);
hWindow = hwnd;
hProc = hproc;
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;
std::random_device randomGenerator;
};
VRWindowManager* VRWindowManager::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) {
char cmd[] = "%sfirefox.exe -wait-for-browser -profile %s --fxr";
StartFirefoxParams* params = static_cast<StartFirefoxParams*>(lpParameter);
char cmdWithPath[MAX_PATH + MAX_PATH] = {0};
int err = sprintf_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.
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
TRUE, // bManualReset
FALSE, // bInitialState
windowState.signalName);
if (hEvent != nullptr) {
// Create Shmem and push state
mozilla::gfx::VRShMem shmem(nullptr, true, false);
shmem.CreateShMem();
shmem.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
shmem.PullWindowState(windowState);
(*hTex) = windowState.textureFx;
(*windowId) = VRWindowManager::GetManager()->SetHWND(
(HWND)windowState.hwndFx, fxParams.hProcessFx);
(*width) = windowState.widthFx;
(*height) = windowState.heightFx;
// Neither the Shmem nor its window state are needed anymore
windowState = {0};
shmem.PushWindowState(windowState);
} else {
// How do I failfast?
}
shmem.CloseShMem();
}
}
}
// Sends a message to the VR window to close.
void CloseVRWindow(uint32_t nVRWindowID, bool waitForTerminate) {
::SendMessage(VRWindowManager::GetManager()->GetHWND(nVRWindowID), WM_CLOSE,
0, 0);
if (waitForTerminate) {
// Wait for Firefox main process to exit
::WaitForSingleObject(VRWindowManager::GetManager()->GetProc(nVRWindowID),
INFINITE);
}
}

Просмотреть файл

@ -9,6 +9,16 @@
// by vrhost.dll
#pragma once
#include <stdint.h>
// void SampleExport();
typedef void (*PFN_SAMPLE)();
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);

Просмотреть файл

@ -4,8 +4,9 @@
* 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/. */
// vrhost.cpp
// Definition of functions that are exported from this dll
// vrhosttest.cpp
// Definition of testing and validation functions that are exported from this
// dll
#include "vrhostex.h"
#include "VRShMem.h"
@ -16,7 +17,7 @@
static const char s_pszSharedEvent[] = "vrhost_test_event_signal";
static const DWORD s_dwWFSO_WAIT = 20000;
void SampleExport() { printf("vrhost.cpp hello world"); }
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
@ -33,9 +34,19 @@ void SampleExport() { printf("vrhost.cpp hello world"); }
// 10 svc: verify data
// 11 svc: push system
// 12 svc: send signal
// 13 mgr: pull system
// 14 mgr: verify data
// 15 return
// 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
@ -43,6 +54,10 @@ void SampleExport() { printf("vrhost.cpp hello world"); }
// 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
@ -72,14 +87,14 @@ void TestTheManager() {
printf("08 mgr: wait for signal\n");
::WaitForSingleObject(hEvent, s_dwWFSO_WAIT);
printf("13 mgr: pull system\n");
printf("14 mgr: pull system\n");
mozilla::gfx::VRSystemState state;
shmem.PullSystemState(state.displayState, state.sensorState,
state.controllerState, state.enumerationCompleted,
nullptr);
printf(
"14 mgr: verify data\n"
"15 mgr: verify data\n"
"\tstate.enumerationCompleted = %d\n"
"\tstate.displayState.displayName = \"%s\"\n"
"\tstate.controllerState[1].hand = %hhu\n"
@ -87,16 +102,48 @@ void TestTheManager() {
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("mgr complete");
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() {
// Handle created by BeTheManager above.
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
@ -138,8 +185,83 @@ void TestTheService() {
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("svc complete");
printf("TestTheService complete");
fflush(nullptr);
}
}
// 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");
// Create the VR Window and store data from creation
char currentDir[MAX_PATH] = {0};
char currentDirProfile[MAX_PATH] = {0};
DWORD currentDirLength =
::GetCurrentDirectory(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);
// Close the Firefox VR Window
fnClose(windowId, true);
// 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);
}
}