1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "VRPuppetCommandBuffer.h"
8 #include "prthread.h"
9 #include "mozilla/ClearOnShutdown.h"
10 
11 namespace mozilla::gfx {
12 
13 static StaticRefPtr<VRPuppetCommandBuffer> sVRPuppetCommandBufferSingleton;
14 
15 /* static */
Get()16 VRPuppetCommandBuffer& VRPuppetCommandBuffer::Get() {
17   if (sVRPuppetCommandBufferSingleton == nullptr) {
18     sVRPuppetCommandBufferSingleton = new VRPuppetCommandBuffer();
19     ClearOnShutdown(&sVRPuppetCommandBufferSingleton);
20   }
21   return *sVRPuppetCommandBufferSingleton;
22 }
23 
24 /* static */
IsCreated()25 bool VRPuppetCommandBuffer::IsCreated() {
26   return sVRPuppetCommandBufferSingleton != nullptr;
27 }
28 
VRPuppetCommandBuffer()29 VRPuppetCommandBuffer::VRPuppetCommandBuffer()
30     : mMutex("VRPuppetCommandBuffer::mMutex") {
31   MOZ_COUNT_CTOR(VRPuppetCommandBuffer);
32   MOZ_ASSERT(sVRPuppetCommandBufferSingleton == nullptr);
33   Reset();
34 }
35 
~VRPuppetCommandBuffer()36 VRPuppetCommandBuffer::~VRPuppetCommandBuffer() {
37   MOZ_COUNT_DTOR(VRPuppetCommandBuffer);
38 }
39 
Submit(const nsTArray<uint64_t> & aBuffer)40 void VRPuppetCommandBuffer::Submit(const nsTArray<uint64_t>& aBuffer) {
41   MutexAutoLock lock(mMutex);
42   mBuffer.AppendElements(aBuffer);
43   mEnded = false;
44   mEndedWithTimeout = false;
45 }
46 
HasEnded()47 bool VRPuppetCommandBuffer::HasEnded() {
48   MutexAutoLock lock(mMutex);
49   return mEnded;
50 }
51 
Reset()52 void VRPuppetCommandBuffer::Reset() {
53   MutexAutoLock lock(mMutex);
54   memset(&mPendingState, 0, sizeof(VRSystemState));
55   memset(&mCommittedState, 0, sizeof(VRSystemState));
56   for (int iControllerIdx = 0; iControllerIdx < kVRControllerMaxCount;
57        iControllerIdx++) {
58     for (int iHaptic = 0; iHaptic < kNumPuppetHaptics; iHaptic++) {
59       mHapticPulseRemaining[iControllerIdx][iHaptic] = 0.0f;
60       mHapticPulseIntensity[iControllerIdx][iHaptic] = 0.0f;
61     }
62   }
63   mDataOffset = 0;
64   mPresentationRequested = false;
65   mFrameSubmitted = false;
66   mFrameAccepted = false;
67   mTimeoutDuration = 10.0f;
68   mWaitRemaining = 0.0f;
69   mBlockedTime = 0.0f;
70   mTimerElapsed = 0.0f;
71   mEnded = true;
72   mEndedWithTimeout = false;
73   mLastRunTimestamp = TimeStamp();
74   mTimerSamples.Clear();
75   mBuffer.Clear();
76 }
77 
RunCommand(uint64_t aCommand,double aDeltaTime)78 bool VRPuppetCommandBuffer::RunCommand(uint64_t aCommand, double aDeltaTime) {
79   /**
80    * Run a single command.  If the command is blocking on a state change and
81    * can't be executed, return false.
82    *
83    * VRPuppetCommandBuffer::RunCommand is only called by
84    *VRPuppetCommandBuffer::Run(), which is already holding the mutex.
85    *
86    * Note that VRPuppetCommandBuffer::RunCommand may potentially be called >1000
87    *times per frame. It might not hurt to add an assert here, but we should
88    *avoid adding code which may potentially malloc (eg string handling) even for
89    *debug builds here.  This function will need to be reasonably fast, even in
90    *debug builds which will be using it during tests.
91    **/
92   switch ((VRPuppet_Command)(aCommand & 0xff00000000000000)) {
93     case VRPuppet_Command::VRPuppet_End:
94       CompleteTest(false);
95       break;
96     case VRPuppet_Command::VRPuppet_ClearAll:
97       memset(&mPendingState, 0, sizeof(VRSystemState));
98       memset(&mCommittedState, 0, sizeof(VRSystemState));
99       mPresentationRequested = false;
100       mFrameSubmitted = false;
101       mFrameAccepted = false;
102       break;
103     case VRPuppet_Command::VRPuppet_ClearController: {
104       uint8_t controllerIdx = aCommand & 0x00000000000000ff;
105       if (controllerIdx < kVRControllerMaxCount) {
106         mPendingState.controllerState[controllerIdx].Clear();
107       }
108     } break;
109     case VRPuppet_Command::VRPuppet_Timeout:
110       mTimeoutDuration = (double)(aCommand & 0x00000000ffffffff) / 1000.0f;
111       break;
112     case VRPuppet_Command::VRPuppet_Wait:
113       if (mWaitRemaining == 0.0f) {
114         mWaitRemaining = (double)(aCommand & 0x00000000ffffffff) / 1000.0f;
115         // Wait timer started, block
116         return false;
117       }
118       mWaitRemaining -= aDeltaTime;
119       if (mWaitRemaining > 0.0f) {
120         // Wait timer still running, block
121         return false;
122       }
123       // Wait timer has elapsed, unblock
124       mWaitRemaining = 0.0f;
125       break;
126     case VRPuppet_Command::VRPuppet_WaitSubmit:
127       if (!mFrameSubmitted) {
128         return false;
129       }
130       break;
131     case VRPuppet_Command::VRPuppet_CaptureFrame:
132       // TODO - Capture the frame and record the output (Bug 1555180)
133       break;
134     case VRPuppet_Command::VRPuppet_AcknowledgeFrame:
135       mFrameSubmitted = false;
136       mFrameAccepted = true;
137       break;
138     case VRPuppet_Command::VRPuppet_RejectFrame:
139       mFrameSubmitted = false;
140       mFrameAccepted = false;
141       break;
142     case VRPuppet_Command::VRPuppet_WaitPresentationStart:
143       if (!mPresentationRequested) {
144         return false;
145       }
146       break;
147     case VRPuppet_Command::VRPuppet_WaitPresentationEnd:
148       if (mPresentationRequested) {
149         return false;
150       }
151       break;
152     case VRPuppet_Command::VRPuppet_WaitHapticIntensity: {
153       // 0x0800cchhvvvvvvvv - VRPuppet_WaitHapticIntensity(c, h, v)
154       uint8_t iControllerIdx = (aCommand & 0x0000ff0000000000) >> 40;
155       if (iControllerIdx >= kVRControllerMaxCount) {
156         // Puppet test is broken, ensure it fails
157         return false;
158       }
159       uint8_t iHapticIdx = (aCommand & 0x000000ff00000000) >> 32;
160       if (iHapticIdx >= kNumPuppetHaptics) {
161         // Puppet test is broken, ensure it fails
162         return false;
163       }
164       uint32_t iHapticIntensity =
165           aCommand & 0x00000000ffffffff;  // interpreted as 16.16 fixed point
166 
167       SimulateHaptics(aDeltaTime);
168       uint64_t iCurrentIntensity =
169           round(mHapticPulseIntensity[iControllerIdx][iHapticIdx] *
170                 (1 << 16));  // convert to 16.16 fixed point
171       if (iCurrentIntensity > 0xffffffff) {
172         iCurrentIntensity = 0xffffffff;
173       }
174       if (iCurrentIntensity != iHapticIntensity) {
175         return false;
176       }
177     } break;
178 
179     case VRPuppet_Command::VRPuppet_StartTimer:
180       mTimerElapsed = 0.0f;
181       break;
182     case VRPuppet_Command::VRPuppet_StopTimer:
183       mTimerSamples.AppendElements(mTimerElapsed);
184       // TODO - Return the timer samples to Javascript once the command buffer
185       // is complete (Bug 1555182)
186       break;
187 
188     case VRPuppet_Command::VRPuppet_UpdateDisplay:
189       mDataOffset = (uint8_t*)&mPendingState.displayState -
190                     (uint8_t*)&mPendingState + (aCommand & 0x00000000ffffffff);
191       break;
192     case VRPuppet_Command::VRPuppet_UpdateSensor:
193       mDataOffset = (uint8_t*)&mPendingState.sensorState -
194                     (uint8_t*)&mPendingState + (aCommand & 0x00000000ffffffff);
195       break;
196     case VRPuppet_Command::VRPuppet_UpdateControllers:
197       mDataOffset = (uint8_t*)&mPendingState.controllerState -
198                     (uint8_t*)&mPendingState + (aCommand & 0x00000000ffffffff);
199       break;
200     case VRPuppet_Command::VRPuppet_Commit:
201       memcpy(&mCommittedState, &mPendingState, sizeof(VRSystemState));
202       break;
203 
204     case VRPuppet_Command::VRPuppet_Data7:
205       WriteData((aCommand & 0x00ff000000000000) >> 48);
206       [[fallthrough]];
207       // Purposefully, no break
208     case VRPuppet_Command::VRPuppet_Data6:
209       WriteData((aCommand & 0x0000ff0000000000) >> 40);
210       [[fallthrough]];
211       // Purposefully, no break
212     case VRPuppet_Command::VRPuppet_Data5:
213       WriteData((aCommand & 0x000000ff00000000) >> 32);
214       [[fallthrough]];
215       // Purposefully, no break
216     case VRPuppet_Command::VRPuppet_Data4:
217       WriteData((aCommand & 0x00000000ff000000) >> 24);
218       [[fallthrough]];
219       // Purposefully, no break
220     case VRPuppet_Command::VRPuppet_Data3:
221       WriteData((aCommand & 0x0000000000ff0000) >> 16);
222       [[fallthrough]];
223       // Purposefully, no break
224     case VRPuppet_Command::VRPuppet_Data2:
225       WriteData((aCommand & 0x000000000000ff00) >> 8);
226       [[fallthrough]];
227       // Purposefully, no break
228     case VRPuppet_Command::VRPuppet_Data1:
229       WriteData(aCommand & 0x00000000000000ff);
230       break;
231   }
232   return true;
233 }
234 
WriteData(uint8_t aData)235 void VRPuppetCommandBuffer::WriteData(uint8_t aData) {
236   if (mDataOffset && mDataOffset < sizeof(VRSystemState)) {
237     ((uint8_t*)&mPendingState)[mDataOffset++] = aData;
238   }
239 }
240 
Run()241 void VRPuppetCommandBuffer::Run() {
242   MutexAutoLock lock(mMutex);
243   TimeStamp now = TimeStamp::Now();
244   double deltaTime = 0.0f;
245   if (!mLastRunTimestamp.IsNull()) {
246     deltaTime = (now - mLastRunTimestamp).ToSeconds();
247   }
248   mLastRunTimestamp = now;
249   mTimerElapsed += deltaTime;
250   size_t transactionLength = 0;
251   while (transactionLength < mBuffer.Length() && !mEnded) {
252     if (RunCommand(mBuffer[transactionLength], deltaTime)) {
253       mBlockedTime = 0.0f;
254       transactionLength++;
255     } else {
256       mBlockedTime += deltaTime;
257       if (mBlockedTime > mTimeoutDuration) {
258         CompleteTest(true);
259       }
260       // If a command is blocked, we don't increment transactionLength,
261       // allowing the command to be retried on the next cycle
262       break;
263     }
264   }
265   mBuffer.RemoveElementsAt(0, transactionLength);
266 }
267 
Run(VRSystemState & aState)268 void VRPuppetCommandBuffer::Run(VRSystemState& aState) {
269   Run();
270   // We don't want to stomp over some members
271   bool bEnumerationCompleted = aState.enumerationCompleted;
272   bool bShutdown = aState.displayState.shutdown;
273   uint32_t minRestartInterval = aState.displayState.minRestartInterval;
274 
275   // Overwrite it all
276   memcpy(&aState, &mCommittedState, sizeof(VRSystemState));
277 
278   // Restore the members
279   aState.enumerationCompleted = bEnumerationCompleted;
280   aState.displayState.shutdown = bShutdown;
281   aState.displayState.minRestartInterval = minRestartInterval;
282 }
283 
StartPresentation()284 void VRPuppetCommandBuffer::StartPresentation() {
285   mPresentationRequested = true;
286   Run();
287 }
288 
StopPresentation()289 void VRPuppetCommandBuffer::StopPresentation() {
290   mPresentationRequested = false;
291   Run();
292 }
293 
SubmitFrame()294 bool VRPuppetCommandBuffer::SubmitFrame() {
295   // Emulate blocking behavior of various XR API's as
296   // described by puppet script
297   mFrameSubmitted = true;
298   mFrameAccepted = false;
299   while (true) {
300     Run();
301     if (!mFrameSubmitted || mEnded) {
302       break;
303     }
304     PR_Sleep(PR_INTERVAL_NO_WAIT);  // Yield
305   }
306 
307   return mFrameAccepted;
308 }
309 
VibrateHaptic(uint32_t aControllerIdx,uint32_t aHapticIndex,float aIntensity,float aDuration)310 void VRPuppetCommandBuffer::VibrateHaptic(uint32_t aControllerIdx,
311                                           uint32_t aHapticIndex,
312                                           float aIntensity, float aDuration) {
313   if (aHapticIndex >= kNumPuppetHaptics ||
314       aControllerIdx >= kVRControllerMaxCount) {
315     return;
316   }
317 
318   // We must Run() before and after updating haptic state to avoid script
319   // deadlocks
320   // The deadlocks would be caused by scripts that include two
321   // VRPuppet_WaitHapticIntensity commands.  If
322   // VRPuppetCommandBuffer::VibrateHaptic() is called twice without advancing
323   // through the command buffer with VRPuppetCommandBuffer::Run() in between,
324   // the first VRPuppet_WaitHapticInensity may not see the transient value that
325   // it is waiting for, thus blocking forever and deadlocking the script.
326   Run();
327   mHapticPulseRemaining[aControllerIdx][aHapticIndex] = aDuration;
328   mHapticPulseIntensity[aControllerIdx][aHapticIndex] = aIntensity;
329   Run();
330 }
331 
StopVibrateHaptic(uint32_t aControllerIdx)332 void VRPuppetCommandBuffer::StopVibrateHaptic(uint32_t aControllerIdx) {
333   if (aControllerIdx >= kVRControllerMaxCount) {
334     return;
335   }
336   // We must Run() before and after updating haptic state to avoid script
337   // deadlocks
338   Run();
339   for (int iHaptic = 0; iHaptic < kNumPuppetHaptics; iHaptic++) {
340     mHapticPulseRemaining[aControllerIdx][iHaptic] = 0.0f;
341     mHapticPulseIntensity[aControllerIdx][iHaptic] = 0.0f;
342   }
343   Run();
344 }
345 
StopAllHaptics()346 void VRPuppetCommandBuffer::StopAllHaptics() {
347   // We must Run() before and after updating haptic state to avoid script
348   // deadlocks
349   Run();
350   for (int iControllerIdx = 0; iControllerIdx < kVRControllerMaxCount;
351        iControllerIdx++) {
352     for (int iHaptic = 0; iHaptic < kNumPuppetHaptics; iHaptic++) {
353       mHapticPulseRemaining[iControllerIdx][iHaptic] = 0.0f;
354       mHapticPulseIntensity[iControllerIdx][iHaptic] = 0.0f;
355     }
356   }
357   Run();
358 }
359 
SimulateHaptics(double aDeltaTime)360 void VRPuppetCommandBuffer::SimulateHaptics(double aDeltaTime) {
361   for (int iControllerIdx = 0; iControllerIdx < kVRControllerMaxCount;
362        iControllerIdx++) {
363     for (int iHaptic = 0; iHaptic < kNumPuppetHaptics; iHaptic++) {
364       if (mHapticPulseIntensity[iControllerIdx][iHaptic] > 0.0f) {
365         mHapticPulseRemaining[iControllerIdx][iHaptic] -= aDeltaTime;
366         if (mHapticPulseRemaining[iControllerIdx][iHaptic] <= 0.0f) {
367           mHapticPulseRemaining[iControllerIdx][iHaptic] = 0.0f;
368           mHapticPulseIntensity[iControllerIdx][iHaptic] = 0.0f;
369         }
370       }
371     }
372   }
373 }
374 
CompleteTest(bool aTimedOut)375 void VRPuppetCommandBuffer::CompleteTest(bool aTimedOut) {
376   mEndedWithTimeout = aTimedOut;
377   mEnded = true;
378 }
379 
380 /**
381  *  Generates a sequence of VRPuppet_Data# commands, as described
382  *  in VRPuppetCommandBuffer.h, to encode the changes to be made to
383  *  a "destination" structure to match the "source" structure.
384  *  As the commands are encoded, the destination structure is updated
385  *  to match the source.
386  *
387  * @param aBuffer
388  *     The buffer in which the commands will be appended.
389  * @param aSrcStart
390  *     Byte pointer to the start of the structure that
391  *     will be copied from.
392  * @param aDstStart
393  *     Byte pointer to the start of the structure that
394  *     will be copied to.
395  * @param aLength
396  *     Length of the structure that will be copied.
397  * @param aUpdateCommand
398  *     VRPuppet_... command indicating which structure is being
399  *     copied:
400  *     VRPuppet_Command::VRPuppet_UpdateDisplay:
401  *         A single VRDisplayState struct
402  *     VRPuppet_Command::VRPuppet_UpdateSensor:
403  *         A single VRHMDSensorState struct
404  *     VRPuppet_Command::VRPuppet_UpdateControllers:
405  *         An array of VRControllerState structs with a
406  *         count of kVRControllerMaxCount
407  */
EncodeStruct(nsTArray<uint64_t> & aBuffer,uint8_t * aSrcStart,uint8_t * aDstStart,size_t aLength,VRPuppet_Command aUpdateCommand)408 void VRPuppetCommandBuffer::EncodeStruct(nsTArray<uint64_t>& aBuffer,
409                                          uint8_t* aSrcStart, uint8_t* aDstStart,
410                                          size_t aLength,
411                                          VRPuppet_Command aUpdateCommand) {
412   // Naive implementation, but will not be executed in realtime, so will not
413   // affect test timer results. Could be improved to avoid unaligned reads and
414   // to use SSE.
415 
416   // Pointer to source byte being compared+copied
417   uint8_t* src = aSrcStart;
418 
419   // Pointer to destination byte being compared+copied
420   uint8_t* dst = aDstStart;
421 
422   // Number of bytes packed into bufData
423   uint8_t bufLen = 0;
424 
425   // 64-bits to be interpreted as up to 7 separate bytes
426   // This will form the lower 56 bits of the command
427   uint64_t bufData = 0;
428 
429   // purgebuffer takes the bytes stored in bufData and generates a VRPuppet
430   // command representing those bytes as "VRPuppet Data".
431   // VRPUppet_Data1 encodes 1 byte
432   // VRPuppet_Data2 encodes 2 bytes
433   // and so on, until..
434   // VRPuppet_Data7 encodes 7 bytes
435   // This command is appended to aBuffer, then bufLen and bufData are reset
436   auto purgeBuffer = [&]() {
437     // Only emit a command if there are data bytes in bufData
438     if (bufLen > 0) {
439       MOZ_ASSERT(bufLen <= 7);
440       uint64_t command = (uint64_t)VRPuppet_Command::VRPuppet_Data1;
441       command += ((uint64_t)VRPuppet_Command::VRPuppet_Data2 -
442                   (uint64_t)VRPuppet_Command::VRPuppet_Data1) *
443                  (bufLen - 1);
444       command |= bufData;
445       aBuffer.AppendElement(command);
446       bufLen = 0;
447       bufData = 0;
448     }
449   };
450 
451   // Loop through the bytes of the structs.
452   // While copying the struct at aSrcStart to aDstStart,
453   // the differences are encoded as VRPuppet commands and
454   // appended to aBuffer.
455   for (size_t i = 0; i < aLength; i++) {
456     if (*src != *dst) {
457       // This byte is different
458 
459       // Copy the byte to the destination
460       *dst = *src;
461 
462       if (bufLen == 0) {
463         // This is the start of a new span of changed bytes
464 
465         // Output a command to specify the offset of the
466         // span.
467         aBuffer.AppendElement((uint64_t)aUpdateCommand + i);
468 
469         // Store this first byte in bufData.
470         // We will batch up to 7 bytes in one VRPuppet_DataXX
471         // command, so we won't emit it yet.
472         bufLen = 1;
473         bufData = *src;
474       } else if (bufLen <= 6) {
475         // This is the continuation of a span of changed bytes.
476         // There is room to add more bytes to bufData.
477 
478         // Store the next byte in bufData.
479         // We will batch up to 7 bytes in one VRPuppet_DataXX
480         // command, so we won't emit it yet.
481         bufData = (bufData << 8) | *src;
482         bufLen++;
483       } else {
484         MOZ_ASSERT(bufLen == 7);
485         // This is the continuation of a span of changed bytes.
486         // There are already 7 bytes in bufData, so we must emit
487         // the VRPuppet_Data7 command for the prior bytes before
488         // starting a new command.
489         aBuffer.AppendElement((uint64_t)VRPuppet_Command::VRPuppet_Data7 +
490                               bufData);
491 
492         // Store this byte to be included in the next VRPuppet_DataXX
493         // command.
494         bufLen = 1;
495         bufData = *src;
496       }
497     } else {
498       // This byte is the same.
499       // If there are bytes in bufData, the span has now ended and we must
500       // emit a VRPuppet_DataXX command for the accumulated bytes.
501       // purgeBuffer will not emit any commands if there are no bytes
502       // accumulated.
503       purgeBuffer();
504     }
505     // Advance to the next source and destination byte.
506     ++src;
507     ++dst;
508   }
509   // In the event that the very last byte of the structs differ, we must
510   // ensure that the accumulated bytes are emitted as a VRPuppet_DataXX
511   // command.
512   purgeBuffer();
513 }
514 
515 }  // namespace mozilla::gfx
516