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/. */
7 #include "VRPuppetCommandBuffer.h"
9 #include "mozilla/ClearOnShutdown.h"
11 namespace mozilla::gfx
{
13 static StaticRefPtr
<VRPuppetCommandBuffer
> sVRPuppetCommandBufferSingleton
;
16 VRPuppetCommandBuffer
& VRPuppetCommandBuffer::Get() {
17 if (sVRPuppetCommandBufferSingleton
== nullptr) {
18 sVRPuppetCommandBufferSingleton
= new VRPuppetCommandBuffer();
19 ClearOnShutdown(&sVRPuppetCommandBufferSingleton
);
21 return *sVRPuppetCommandBufferSingleton
;
25 bool VRPuppetCommandBuffer::IsCreated() {
26 return sVRPuppetCommandBufferSingleton
!= nullptr;
29 VRPuppetCommandBuffer::VRPuppetCommandBuffer()
30 : mMutex("VRPuppetCommandBuffer::mMutex") {
31 MOZ_COUNT_CTOR(VRPuppetCommandBuffer
);
32 MOZ_ASSERT(sVRPuppetCommandBufferSingleton
== nullptr);
36 VRPuppetCommandBuffer::~VRPuppetCommandBuffer() {
37 MOZ_COUNT_DTOR(VRPuppetCommandBuffer
);
40 void VRPuppetCommandBuffer::Submit(const nsTArray
<uint64_t>& aBuffer
) {
41 MutexAutoLock
lock(mMutex
);
42 mBuffer
.AppendElements(aBuffer
);
44 mEndedWithTimeout
= false;
47 bool VRPuppetCommandBuffer::HasEnded() {
48 MutexAutoLock
lock(mMutex
);
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
;
58 for (int iHaptic
= 0; iHaptic
< kNumPuppetHaptics
; iHaptic
++) {
59 mHapticPulseRemaining
[iControllerIdx
][iHaptic
] = 0.0f
;
60 mHapticPulseIntensity
[iControllerIdx
][iHaptic
] = 0.0f
;
64 mPresentationRequested
= false;
65 mFrameSubmitted
= false;
66 mFrameAccepted
= false;
67 mTimeoutDuration
= 10.0f
;
68 mWaitRemaining
= 0.0f
;
72 mEndedWithTimeout
= false;
73 mLastRunTimestamp
= TimeStamp();
74 mTimerSamples
.Clear();
78 bool VRPuppetCommandBuffer::RunCommand(uint64_t aCommand
, double aDeltaTime
) {
80 * Run a single command. If the command is blocking on a state change and
81 * can't be executed, return false.
83 * VRPuppetCommandBuffer::RunCommand is only called by
84 *VRPuppetCommandBuffer::Run(), which is already holding the mutex.
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.
92 switch ((VRPuppet_Command
)(aCommand
& 0xff00000000000000)) {
93 case VRPuppet_Command::VRPuppet_End
:
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;
103 case VRPuppet_Command::VRPuppet_ClearController
: {
104 uint8_t controllerIdx
= aCommand
& 0x00000000000000ff;
105 if (controllerIdx
< kVRControllerMaxCount
) {
106 mPendingState
.controllerState
[controllerIdx
].Clear();
109 case VRPuppet_Command::VRPuppet_Timeout
:
110 mTimeoutDuration
= (double)(aCommand
& 0x00000000ffffffff) / 1000.0f
;
112 case VRPuppet_Command::VRPuppet_Wait
:
113 if (mWaitRemaining
== 0.0f
) {
114 mWaitRemaining
= (double)(aCommand
& 0x00000000ffffffff) / 1000.0f
;
115 // Wait timer started, block
118 mWaitRemaining
-= aDeltaTime
;
119 if (mWaitRemaining
> 0.0f
) {
120 // Wait timer still running, block
123 // Wait timer has elapsed, unblock
124 mWaitRemaining
= 0.0f
;
126 case VRPuppet_Command::VRPuppet_WaitSubmit
:
127 if (!mFrameSubmitted
) {
131 case VRPuppet_Command::VRPuppet_CaptureFrame
:
132 // TODO - Capture the frame and record the output (Bug 1555180)
134 case VRPuppet_Command::VRPuppet_AcknowledgeFrame
:
135 mFrameSubmitted
= false;
136 mFrameAccepted
= true;
138 case VRPuppet_Command::VRPuppet_RejectFrame
:
139 mFrameSubmitted
= false;
140 mFrameAccepted
= false;
142 case VRPuppet_Command::VRPuppet_WaitPresentationStart
:
143 if (!mPresentationRequested
) {
147 case VRPuppet_Command::VRPuppet_WaitPresentationEnd
:
148 if (mPresentationRequested
) {
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
159 uint8_t iHapticIdx
= (aCommand
& 0x000000ff00000000) >> 32;
160 if (iHapticIdx
>= kNumPuppetHaptics
) {
161 // Puppet test is broken, ensure it fails
164 uint32_t iHapticIntensity
=
165 aCommand
& 0x00000000ffffffff; // interpreted as 16.16 fixed point
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;
174 if (iCurrentIntensity
!= iHapticIntensity
) {
179 case VRPuppet_Command::VRPuppet_StartTimer
:
180 mTimerElapsed
= 0.0f
;
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)
188 case VRPuppet_Command::VRPuppet_UpdateDisplay
:
189 mDataOffset
= (uint8_t*)&mPendingState
.displayState
-
190 (uint8_t*)&mPendingState
+ (aCommand
& 0x00000000ffffffff);
192 case VRPuppet_Command::VRPuppet_UpdateSensor
:
193 mDataOffset
= (uint8_t*)&mPendingState
.sensorState
-
194 (uint8_t*)&mPendingState
+ (aCommand
& 0x00000000ffffffff);
196 case VRPuppet_Command::VRPuppet_UpdateControllers
:
197 mDataOffset
= (uint8_t*)&mPendingState
.controllerState
-
198 (uint8_t*)&mPendingState
+ (aCommand
& 0x00000000ffffffff);
200 case VRPuppet_Command::VRPuppet_Commit
:
201 memcpy(&mCommittedState
, &mPendingState
, sizeof(VRSystemState
));
204 case VRPuppet_Command::VRPuppet_Data7
:
205 WriteData((aCommand
& 0x00ff000000000000) >> 48);
207 // Purposefully, no break
208 case VRPuppet_Command::VRPuppet_Data6
:
209 WriteData((aCommand
& 0x0000ff0000000000) >> 40);
211 // Purposefully, no break
212 case VRPuppet_Command::VRPuppet_Data5
:
213 WriteData((aCommand
& 0x000000ff00000000) >> 32);
215 // Purposefully, no break
216 case VRPuppet_Command::VRPuppet_Data4
:
217 WriteData((aCommand
& 0x00000000ff000000) >> 24);
219 // Purposefully, no break
220 case VRPuppet_Command::VRPuppet_Data3
:
221 WriteData((aCommand
& 0x0000000000ff0000) >> 16);
223 // Purposefully, no break
224 case VRPuppet_Command::VRPuppet_Data2
:
225 WriteData((aCommand
& 0x000000000000ff00) >> 8);
227 // Purposefully, no break
228 case VRPuppet_Command::VRPuppet_Data1
:
229 WriteData(aCommand
& 0x00000000000000ff);
235 void VRPuppetCommandBuffer::WriteData(uint8_t aData
) {
236 if (mDataOffset
&& mDataOffset
< sizeof(VRSystemState
)) {
237 ((uint8_t*)&mPendingState
)[mDataOffset
++] = aData
;
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();
248 mLastRunTimestamp
= now
;
249 mTimerElapsed
+= deltaTime
;
250 size_t transactionLength
= 0;
251 while (transactionLength
< mBuffer
.Length() && !mEnded
) {
252 if (RunCommand(mBuffer
[transactionLength
], deltaTime
)) {
256 mBlockedTime
+= deltaTime
;
257 if (mBlockedTime
> mTimeoutDuration
) {
260 // If a command is blocked, we don't increment transactionLength,
261 // allowing the command to be retried on the next cycle
265 mBuffer
.RemoveElementsAt(0, transactionLength
);
268 void VRPuppetCommandBuffer::Run(VRSystemState
& aState
) {
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
;
276 memcpy(&aState
, &mCommittedState
, sizeof(VRSystemState
));
278 // Restore the members
279 aState
.enumerationCompleted
= bEnumerationCompleted
;
280 aState
.displayState
.shutdown
= bShutdown
;
281 aState
.displayState
.minRestartInterval
= minRestartInterval
;
284 void VRPuppetCommandBuffer::StartPresentation() {
285 mPresentationRequested
= true;
289 void VRPuppetCommandBuffer::StopPresentation() {
290 mPresentationRequested
= false;
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;
301 if (!mFrameSubmitted
|| mEnded
) {
304 PR_Sleep(PR_INTERVAL_NO_WAIT
); // Yield
307 return mFrameAccepted
;
310 void VRPuppetCommandBuffer::VibrateHaptic(uint32_t aControllerIdx
,
311 uint32_t aHapticIndex
,
312 float aIntensity
, float aDuration
) {
313 if (aHapticIndex
>= kNumPuppetHaptics
||
314 aControllerIdx
>= kVRControllerMaxCount
) {
318 // We must Run() before and after updating haptic state to avoid script
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.
327 mHapticPulseRemaining
[aControllerIdx
][aHapticIndex
] = aDuration
;
328 mHapticPulseIntensity
[aControllerIdx
][aHapticIndex
] = aIntensity
;
332 void VRPuppetCommandBuffer::StopVibrateHaptic(uint32_t aControllerIdx
) {
333 if (aControllerIdx
>= kVRControllerMaxCount
) {
336 // We must Run() before and after updating haptic state to avoid script
339 for (int iHaptic
= 0; iHaptic
< kNumPuppetHaptics
; iHaptic
++) {
340 mHapticPulseRemaining
[aControllerIdx
][iHaptic
] = 0.0f
;
341 mHapticPulseIntensity
[aControllerIdx
][iHaptic
] = 0.0f
;
346 void VRPuppetCommandBuffer::StopAllHaptics() {
347 // We must Run() before and after updating haptic state to avoid script
350 for (int iControllerIdx
= 0; iControllerIdx
< kVRControllerMaxCount
;
352 for (int iHaptic
= 0; iHaptic
< kNumPuppetHaptics
; iHaptic
++) {
353 mHapticPulseRemaining
[iControllerIdx
][iHaptic
] = 0.0f
;
354 mHapticPulseIntensity
[iControllerIdx
][iHaptic
] = 0.0f
;
360 void VRPuppetCommandBuffer::SimulateHaptics(double aDeltaTime
) {
361 for (int iControllerIdx
= 0; iControllerIdx
< kVRControllerMaxCount
;
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
;
375 void VRPuppetCommandBuffer::CompleteTest(bool aTimedOut
) {
376 mEndedWithTimeout
= aTimedOut
;
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.
388 * The buffer in which the commands will be appended.
390 * Byte pointer to the start of the structure that
391 * will be copied from.
393 * Byte pointer to the start of the structure that
396 * Length of the structure that will be copied.
397 * @param aUpdateCommand
398 * VRPuppet_... command indicating which structure is being
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
408 void VRPuppetCommandBuffer::EncodeStruct(nsTArray
<uint64_t>& aBuffer
,
409 uint8_t* aSrcStart
, uint8_t* aDstStart
,
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
416 // Pointer to source byte being compared+copied
417 uint8_t* src
= aSrcStart
;
419 // Pointer to destination byte being compared+copied
420 uint8_t* dst
= aDstStart
;
422 // Number of bytes packed into bufData
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;
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
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
) *
445 aBuffer
.AppendElement(command
);
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
++) {
457 // This byte is different
459 // Copy the byte to the destination
463 // This is the start of a new span of changed bytes
465 // Output a command to specify the offset of the
467 aBuffer
.AppendElement((uint64_t)aUpdateCommand
+ i
);
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.
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.
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
;
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
+
492 // Store this byte to be included in the next VRPuppet_DataXX
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
505 // Advance to the next source and destination byte.
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
515 } // namespace mozilla::gfx