Bug 1760967 [wpt PR 33319] - Pin remaining dependencies in tools/ci/requirements_buil...
[gecko.git] / gfx / vr / VRPuppetCommandBuffer.cpp
blob310a233b8882a781c4899b6f5946a525567d340c
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"
8 #include "prthread.h"
9 #include "mozilla/ClearOnShutdown.h"
11 namespace mozilla::gfx {
13 static StaticRefPtr<VRPuppetCommandBuffer> sVRPuppetCommandBufferSingleton;
15 /* static */
16 VRPuppetCommandBuffer& VRPuppetCommandBuffer::Get() {
17 if (sVRPuppetCommandBufferSingleton == nullptr) {
18 sVRPuppetCommandBufferSingleton = new VRPuppetCommandBuffer();
19 ClearOnShutdown(&sVRPuppetCommandBufferSingleton);
21 return *sVRPuppetCommandBufferSingleton;
24 /* static */
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);
33 Reset();
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);
43 mEnded = false;
44 mEndedWithTimeout = false;
47 bool VRPuppetCommandBuffer::HasEnded() {
48 MutexAutoLock lock(mMutex);
49 return mEnded;
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;
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();
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.
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.
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();
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;
118 mWaitRemaining -= aDeltaTime;
119 if (mWaitRemaining > 0.0f) {
120 // Wait timer still running, block
121 return false;
123 // Wait timer has elapsed, unblock
124 mWaitRemaining = 0.0f;
125 break;
126 case VRPuppet_Command::VRPuppet_WaitSubmit:
127 if (!mFrameSubmitted) {
128 return false;
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;
146 break;
147 case VRPuppet_Command::VRPuppet_WaitPresentationEnd:
148 if (mPresentationRequested) {
149 return false;
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;
159 uint8_t iHapticIdx = (aCommand & 0x000000ff00000000) >> 32;
160 if (iHapticIdx >= kNumPuppetHaptics) {
161 // Puppet test is broken, ensure it fails
162 return false;
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) {
175 return false;
177 } break;
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;
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;
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;
232 return true;
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)) {
253 mBlockedTime = 0.0f;
254 transactionLength++;
255 } else {
256 mBlockedTime += deltaTime;
257 if (mBlockedTime > mTimeoutDuration) {
258 CompleteTest(true);
260 // If a command is blocked, we don't increment transactionLength,
261 // allowing the command to be retried on the next cycle
262 break;
265 mBuffer.RemoveElementsAt(0, transactionLength);
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;
275 // Overwrite it all
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;
286 Run();
289 void VRPuppetCommandBuffer::StopPresentation() {
290 mPresentationRequested = false;
291 Run();
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;
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) {
315 return;
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();
332 void VRPuppetCommandBuffer::StopVibrateHaptic(uint32_t aControllerIdx) {
333 if (aControllerIdx >= kVRControllerMaxCount) {
334 return;
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;
343 Run();
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;
357 Run();
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;
375 void VRPuppetCommandBuffer::CompleteTest(bool aTimedOut) {
376 mEndedWithTimeout = aTimedOut;
377 mEnded = true;
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.
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
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.
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
423 uint8_t bufLen = 0;
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
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;
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
459 // Copy the byte to the destination
460 *dst = *src;
462 if (bufLen == 0) {
463 // This is the start of a new span of changed bytes
465 // Output a command to specify the offset of the
466 // span.
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.
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.
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);
492 // Store this byte to be included in the next VRPuppet_DataXX
493 // command.
494 bufLen = 1;
495 bufData = *src;
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();
505 // Advance to the next source and destination byte.
506 ++src;
507 ++dst;
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();
515 } // namespace mozilla::gfx