summaryrefslogtreecommitdiffstats
path: root/gfx/vr/vrhost/vrhostapi.cpp
blob: 508e379a5fcd03f53ae7ed97fb0dc6560f1d89bb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
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);
}