VRPuppetCommandBuffer.cpp (17869B)
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 */ 16 VRPuppetCommandBuffer& VRPuppetCommandBuffer::Get() { 17 if (sVRPuppetCommandBufferSingleton == nullptr) { 18 sVRPuppetCommandBufferSingleton = new VRPuppetCommandBuffer(); 19 ClearOnShutdown(&sVRPuppetCommandBufferSingleton); 20 } 21 return *sVRPuppetCommandBufferSingleton; 22 } 23 24 /* static */ 25 bool VRPuppetCommandBuffer::IsCreated() { 26 return sVRPuppetCommandBufferSingleton != nullptr; 27 } 28 29 VRPuppetCommandBuffer::VRPuppetCommandBuffer() 30 : mMutex("VRPuppetCommandBuffer::mMutex") { 31 MOZ_COUNT_CTOR(VRPuppetCommandBuffer); 32 MOZ_ASSERT(sVRPuppetCommandBufferSingleton == nullptr); 33 Reset(); 34 } 35 36 VRPuppetCommandBuffer::~VRPuppetCommandBuffer() { 37 MOZ_COUNT_DTOR(VRPuppetCommandBuffer); 38 } 39 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 47 bool VRPuppetCommandBuffer::HasEnded() { 48 MutexAutoLock lock(mMutex); 49 return mEnded; 50 } 51 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 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 235 void VRPuppetCommandBuffer::WriteData(uint8_t aData) { 236 if (mDataOffset && mDataOffset < sizeof(VRSystemState)) { 237 ((uint8_t*)&mPendingState)[mDataOffset++] = aData; 238 } 239 } 240 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 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 284 void VRPuppetCommandBuffer::StartPresentation() { 285 mPresentationRequested = true; 286 Run(); 287 } 288 289 void VRPuppetCommandBuffer::StopPresentation() { 290 mPresentationRequested = false; 291 Run(); 292 } 293 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 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 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 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 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 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 */ 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