summaryrefslogtreecommitdiffstats
path: root/gfx/vr/VRPuppetCommandBuffer.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /gfx/vr/VRPuppetCommandBuffer.cpp
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'gfx/vr/VRPuppetCommandBuffer.cpp')
-rw-r--r--gfx/vr/VRPuppetCommandBuffer.cpp515
1 files changed, 515 insertions, 0 deletions
diff --git a/gfx/vr/VRPuppetCommandBuffer.cpp b/gfx/vr/VRPuppetCommandBuffer.cpp
new file mode 100644
index 0000000000..310a233b88
--- /dev/null
+++ b/gfx/vr/VRPuppetCommandBuffer.cpp
@@ -0,0 +1,515 @@
+/* -*- 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/. */
+
+#include "VRPuppetCommandBuffer.h"
+#include "prthread.h"
+#include "mozilla/ClearOnShutdown.h"
+
+namespace mozilla::gfx {
+
+static StaticRefPtr<VRPuppetCommandBuffer> sVRPuppetCommandBufferSingleton;
+
+/* static */
+VRPuppetCommandBuffer& VRPuppetCommandBuffer::Get() {
+ if (sVRPuppetCommandBufferSingleton == nullptr) {
+ sVRPuppetCommandBufferSingleton = new VRPuppetCommandBuffer();
+ ClearOnShutdown(&sVRPuppetCommandBufferSingleton);
+ }
+ return *sVRPuppetCommandBufferSingleton;
+}
+
+/* static */
+bool VRPuppetCommandBuffer::IsCreated() {
+ return sVRPuppetCommandBufferSingleton != nullptr;
+}
+
+VRPuppetCommandBuffer::VRPuppetCommandBuffer()
+ : mMutex("VRPuppetCommandBuffer::mMutex") {
+ MOZ_COUNT_CTOR(VRPuppetCommandBuffer);
+ MOZ_ASSERT(sVRPuppetCommandBufferSingleton == nullptr);
+ Reset();
+}
+
+VRPuppetCommandBuffer::~VRPuppetCommandBuffer() {
+ MOZ_COUNT_DTOR(VRPuppetCommandBuffer);
+}
+
+void VRPuppetCommandBuffer::Submit(const nsTArray<uint64_t>& aBuffer) {
+ MutexAutoLock lock(mMutex);
+ mBuffer.AppendElements(aBuffer);
+ mEnded = false;
+ mEndedWithTimeout = false;
+}
+
+bool VRPuppetCommandBuffer::HasEnded() {
+ MutexAutoLock lock(mMutex);
+ return mEnded;
+}
+
+void VRPuppetCommandBuffer::Reset() {
+ MutexAutoLock lock(mMutex);
+ memset(&mPendingState, 0, sizeof(VRSystemState));
+ memset(&mCommittedState, 0, sizeof(VRSystemState));
+ for (int iControllerIdx = 0; iControllerIdx < kVRControllerMaxCount;
+ iControllerIdx++) {
+ for (int iHaptic = 0; iHaptic < kNumPuppetHaptics; iHaptic++) {
+ mHapticPulseRemaining[iControllerIdx][iHaptic] = 0.0f;
+ mHapticPulseIntensity[iControllerIdx][iHaptic] = 0.0f;
+ }
+ }
+ mDataOffset = 0;
+ mPresentationRequested = false;
+ mFrameSubmitted = false;
+ mFrameAccepted = false;
+ mTimeoutDuration = 10.0f;
+ mWaitRemaining = 0.0f;
+ mBlockedTime = 0.0f;
+ mTimerElapsed = 0.0f;
+ mEnded = true;
+ mEndedWithTimeout = false;
+ mLastRunTimestamp = TimeStamp();
+ mTimerSamples.Clear();
+ mBuffer.Clear();
+}
+
+bool VRPuppetCommandBuffer::RunCommand(uint64_t aCommand, double aDeltaTime) {
+ /**
+ * Run a single command. If the command is blocking on a state change and
+ * can't be executed, return false.
+ *
+ * VRPuppetCommandBuffer::RunCommand is only called by
+ *VRPuppetCommandBuffer::Run(), which is already holding the mutex.
+ *
+ * Note that VRPuppetCommandBuffer::RunCommand may potentially be called >1000
+ *times per frame. It might not hurt to add an assert here, but we should
+ *avoid adding code which may potentially malloc (eg string handling) even for
+ *debug builds here. This function will need to be reasonably fast, even in
+ *debug builds which will be using it during tests.
+ **/
+ switch ((VRPuppet_Command)(aCommand & 0xff00000000000000)) {
+ case VRPuppet_Command::VRPuppet_End:
+ CompleteTest(false);
+ break;
+ case VRPuppet_Command::VRPuppet_ClearAll:
+ memset(&mPendingState, 0, sizeof(VRSystemState));
+ memset(&mCommittedState, 0, sizeof(VRSystemState));
+ mPresentationRequested = false;
+ mFrameSubmitted = false;
+ mFrameAccepted = false;
+ break;
+ case VRPuppet_Command::VRPuppet_ClearController: {
+ uint8_t controllerIdx = aCommand & 0x00000000000000ff;
+ if (controllerIdx < kVRControllerMaxCount) {
+ mPendingState.controllerState[controllerIdx].Clear();
+ }
+ } break;
+ case VRPuppet_Command::VRPuppet_Timeout:
+ mTimeoutDuration = (double)(aCommand & 0x00000000ffffffff) / 1000.0f;
+ break;
+ case VRPuppet_Command::VRPuppet_Wait:
+ if (mWaitRemaining == 0.0f) {
+ mWaitRemaining = (double)(aCommand & 0x00000000ffffffff) / 1000.0f;
+ // Wait timer started, block
+ return false;
+ }
+ mWaitRemaining -= aDeltaTime;
+ if (mWaitRemaining > 0.0f) {
+ // Wait timer still running, block
+ return false;
+ }
+ // Wait timer has elapsed, unblock
+ mWaitRemaining = 0.0f;
+ break;
+ case VRPuppet_Command::VRPuppet_WaitSubmit:
+ if (!mFrameSubmitted) {
+ return false;
+ }
+ break;
+ case VRPuppet_Command::VRPuppet_CaptureFrame:
+ // TODO - Capture the frame and record the output (Bug 1555180)
+ break;
+ case VRPuppet_Command::VRPuppet_AcknowledgeFrame:
+ mFrameSubmitted = false;
+ mFrameAccepted = true;
+ break;
+ case VRPuppet_Command::VRPuppet_RejectFrame:
+ mFrameSubmitted = false;
+ mFrameAccepted = false;
+ break;
+ case VRPuppet_Command::VRPuppet_WaitPresentationStart:
+ if (!mPresentationRequested) {
+ return false;
+ }
+ break;
+ case VRPuppet_Command::VRPuppet_WaitPresentationEnd:
+ if (mPresentationRequested) {
+ return false;
+ }
+ break;
+ case VRPuppet_Command::VRPuppet_WaitHapticIntensity: {
+ // 0x0800cchhvvvvvvvv - VRPuppet_WaitHapticIntensity(c, h, v)
+ uint8_t iControllerIdx = (aCommand & 0x0000ff0000000000) >> 40;
+ if (iControllerIdx >= kVRControllerMaxCount) {
+ // Puppet test is broken, ensure it fails
+ return false;
+ }
+ uint8_t iHapticIdx = (aCommand & 0x000000ff00000000) >> 32;
+ if (iHapticIdx >= kNumPuppetHaptics) {
+ // Puppet test is broken, ensure it fails
+ return false;
+ }
+ uint32_t iHapticIntensity =
+ aCommand & 0x00000000ffffffff; // interpreted as 16.16 fixed point
+
+ SimulateHaptics(aDeltaTime);
+ uint64_t iCurrentIntensity =
+ round(mHapticPulseIntensity[iControllerIdx][iHapticIdx] *
+ (1 << 16)); // convert to 16.16 fixed point
+ if (iCurrentIntensity > 0xffffffff) {
+ iCurrentIntensity = 0xffffffff;
+ }
+ if (iCurrentIntensity != iHapticIntensity) {
+ return false;
+ }
+ } break;
+
+ case VRPuppet_Command::VRPuppet_StartTimer:
+ mTimerElapsed = 0.0f;
+ break;
+ case VRPuppet_Command::VRPuppet_StopTimer:
+ mTimerSamples.AppendElements(mTimerElapsed);
+ // TODO - Return the timer samples to Javascript once the command buffer
+ // is complete (Bug 1555182)
+ break;
+
+ case VRPuppet_Command::VRPuppet_UpdateDisplay:
+ mDataOffset = (uint8_t*)&mPendingState.displayState -
+ (uint8_t*)&mPendingState + (aCommand & 0x00000000ffffffff);
+ break;
+ case VRPuppet_Command::VRPuppet_UpdateSensor:
+ mDataOffset = (uint8_t*)&mPendingState.sensorState -
+ (uint8_t*)&mPendingState + (aCommand & 0x00000000ffffffff);
+ break;
+ case VRPuppet_Command::VRPuppet_UpdateControllers:
+ mDataOffset = (uint8_t*)&mPendingState.controllerState -
+ (uint8_t*)&mPendingState + (aCommand & 0x00000000ffffffff);
+ break;
+ case VRPuppet_Command::VRPuppet_Commit:
+ memcpy(&mCommittedState, &mPendingState, sizeof(VRSystemState));
+ break;
+
+ case VRPuppet_Command::VRPuppet_Data7:
+ WriteData((aCommand & 0x00ff000000000000) >> 48);
+ [[fallthrough]];
+ // Purposefully, no break
+ case VRPuppet_Command::VRPuppet_Data6:
+ WriteData((aCommand & 0x0000ff0000000000) >> 40);
+ [[fallthrough]];
+ // Purposefully, no break
+ case VRPuppet_Command::VRPuppet_Data5:
+ WriteData((aCommand & 0x000000ff00000000) >> 32);
+ [[fallthrough]];
+ // Purposefully, no break
+ case VRPuppet_Command::VRPuppet_Data4:
+ WriteData((aCommand & 0x00000000ff000000) >> 24);
+ [[fallthrough]];
+ // Purposefully, no break
+ case VRPuppet_Command::VRPuppet_Data3:
+ WriteData((aCommand & 0x0000000000ff0000) >> 16);
+ [[fallthrough]];
+ // Purposefully, no break
+ case VRPuppet_Command::VRPuppet_Data2:
+ WriteData((aCommand & 0x000000000000ff00) >> 8);
+ [[fallthrough]];
+ // Purposefully, no break
+ case VRPuppet_Command::VRPuppet_Data1:
+ WriteData(aCommand & 0x00000000000000ff);
+ break;
+ }
+ return true;
+}
+
+void VRPuppetCommandBuffer::WriteData(uint8_t aData) {
+ if (mDataOffset && mDataOffset < sizeof(VRSystemState)) {
+ ((uint8_t*)&mPendingState)[mDataOffset++] = aData;
+ }
+}
+
+void VRPuppetCommandBuffer::Run() {
+ MutexAutoLock lock(mMutex);
+ TimeStamp now = TimeStamp::Now();
+ double deltaTime = 0.0f;
+ if (!mLastRunTimestamp.IsNull()) {
+ deltaTime = (now - mLastRunTimestamp).ToSeconds();
+ }
+ mLastRunTimestamp = now;
+ mTimerElapsed += deltaTime;
+ size_t transactionLength = 0;
+ while (transactionLength < mBuffer.Length() && !mEnded) {
+ if (RunCommand(mBuffer[transactionLength], deltaTime)) {
+ mBlockedTime = 0.0f;
+ transactionLength++;
+ } else {
+ mBlockedTime += deltaTime;
+ if (mBlockedTime > mTimeoutDuration) {
+ CompleteTest(true);
+ }
+ // If a command is blocked, we don't increment transactionLength,
+ // allowing the command to be retried on the next cycle
+ break;
+ }
+ }
+ mBuffer.RemoveElementsAt(0, transactionLength);
+}
+
+void VRPuppetCommandBuffer::Run(VRSystemState& aState) {
+ Run();
+ // We don't want to stomp over some members
+ bool bEnumerationCompleted = aState.enumerationCompleted;
+ bool bShutdown = aState.displayState.shutdown;
+ uint32_t minRestartInterval = aState.displayState.minRestartInterval;
+
+ // Overwrite it all
+ memcpy(&aState, &mCommittedState, sizeof(VRSystemState));
+
+ // Restore the members
+ aState.enumerationCompleted = bEnumerationCompleted;
+ aState.displayState.shutdown = bShutdown;
+ aState.displayState.minRestartInterval = minRestartInterval;
+}
+
+void VRPuppetCommandBuffer::StartPresentation() {
+ mPresentationRequested = true;
+ Run();
+}
+
+void VRPuppetCommandBuffer::StopPresentation() {
+ mPresentationRequested = false;
+ Run();
+}
+
+bool VRPuppetCommandBuffer::SubmitFrame() {
+ // Emulate blocking behavior of various XR API's as
+ // described by puppet script
+ mFrameSubmitted = true;
+ mFrameAccepted = false;
+ while (true) {
+ Run();
+ if (!mFrameSubmitted || mEnded) {
+ break;
+ }
+ PR_Sleep(PR_INTERVAL_NO_WAIT); // Yield
+ }
+
+ return mFrameAccepted;
+}
+
+void VRPuppetCommandBuffer::VibrateHaptic(uint32_t aControllerIdx,
+ uint32_t aHapticIndex,
+ float aIntensity, float aDuration) {
+ if (aHapticIndex >= kNumPuppetHaptics ||
+ aControllerIdx >= kVRControllerMaxCount) {
+ return;
+ }
+
+ // We must Run() before and after updating haptic state to avoid script
+ // deadlocks
+ // The deadlocks would be caused by scripts that include two
+ // VRPuppet_WaitHapticIntensity commands. If
+ // VRPuppetCommandBuffer::VibrateHaptic() is called twice without advancing
+ // through the command buffer with VRPuppetCommandBuffer::Run() in between,
+ // the first VRPuppet_WaitHapticInensity may not see the transient value that
+ // it is waiting for, thus blocking forever and deadlocking the script.
+ Run();
+ mHapticPulseRemaining[aControllerIdx][aHapticIndex] = aDuration;
+ mHapticPulseIntensity[aControllerIdx][aHapticIndex] = aIntensity;
+ Run();
+}
+
+void VRPuppetCommandBuffer::StopVibrateHaptic(uint32_t aControllerIdx) {
+ if (aControllerIdx >= kVRControllerMaxCount) {
+ return;
+ }
+ // We must Run() before and after updating haptic state to avoid script
+ // deadlocks
+ Run();
+ for (int iHaptic = 0; iHaptic < kNumPuppetHaptics; iHaptic++) {
+ mHapticPulseRemaining[aControllerIdx][iHaptic] = 0.0f;
+ mHapticPulseIntensity[aControllerIdx][iHaptic] = 0.0f;
+ }
+ Run();
+}
+
+void VRPuppetCommandBuffer::StopAllHaptics() {
+ // We must Run() before and after updating haptic state to avoid script
+ // deadlocks
+ Run();
+ for (int iControllerIdx = 0; iControllerIdx < kVRControllerMaxCount;
+ iControllerIdx++) {
+ for (int iHaptic = 0; iHaptic < kNumPuppetHaptics; iHaptic++) {
+ mHapticPulseRemaining[iControllerIdx][iHaptic] = 0.0f;
+ mHapticPulseIntensity[iControllerIdx][iHaptic] = 0.0f;
+ }
+ }
+ Run();
+}
+
+void VRPuppetCommandBuffer::SimulateHaptics(double aDeltaTime) {
+ for (int iControllerIdx = 0; iControllerIdx < kVRControllerMaxCount;
+ iControllerIdx++) {
+ for (int iHaptic = 0; iHaptic < kNumPuppetHaptics; iHaptic++) {
+ if (mHapticPulseIntensity[iControllerIdx][iHaptic] > 0.0f) {
+ mHapticPulseRemaining[iControllerIdx][iHaptic] -= aDeltaTime;
+ if (mHapticPulseRemaining[iControllerIdx][iHaptic] <= 0.0f) {
+ mHapticPulseRemaining[iControllerIdx][iHaptic] = 0.0f;
+ mHapticPulseIntensity[iControllerIdx][iHaptic] = 0.0f;
+ }
+ }
+ }
+ }
+}
+
+void VRPuppetCommandBuffer::CompleteTest(bool aTimedOut) {
+ mEndedWithTimeout = aTimedOut;
+ mEnded = true;
+}
+
+/**
+ * Generates a sequence of VRPuppet_Data# commands, as described
+ * in VRPuppetCommandBuffer.h, to encode the changes to be made to
+ * a "destination" structure to match the "source" structure.
+ * As the commands are encoded, the destination structure is updated
+ * to match the source.
+ *
+ * @param aBuffer
+ * The buffer in which the commands will be appended.
+ * @param aSrcStart
+ * Byte pointer to the start of the structure that
+ * will be copied from.
+ * @param aDstStart
+ * Byte pointer to the start of the structure that
+ * will be copied to.
+ * @param aLength
+ * Length of the structure that will be copied.
+ * @param aUpdateCommand
+ * VRPuppet_... command indicating which structure is being
+ * copied:
+ * VRPuppet_Command::VRPuppet_UpdateDisplay:
+ * A single VRDisplayState struct
+ * VRPuppet_Command::VRPuppet_UpdateSensor:
+ * A single VRHMDSensorState struct
+ * VRPuppet_Command::VRPuppet_UpdateControllers:
+ * An array of VRControllerState structs with a
+ * count of kVRControllerMaxCount
+ */
+void VRPuppetCommandBuffer::EncodeStruct(nsTArray<uint64_t>& aBuffer,
+ uint8_t* aSrcStart, uint8_t* aDstStart,
+ size_t aLength,
+ VRPuppet_Command aUpdateCommand) {
+ // Naive implementation, but will not be executed in realtime, so will not
+ // affect test timer results. Could be improved to avoid unaligned reads and
+ // to use SSE.
+
+ // Pointer to source byte being compared+copied
+ uint8_t* src = aSrcStart;
+
+ // Pointer to destination byte being compared+copied
+ uint8_t* dst = aDstStart;
+
+ // Number of bytes packed into bufData
+ uint8_t bufLen = 0;
+
+ // 64-bits to be interpreted as up to 7 separate bytes
+ // This will form the lower 56 bits of the command
+ uint64_t bufData = 0;
+
+ // purgebuffer takes the bytes stored in bufData and generates a VRPuppet
+ // command representing those bytes as "VRPuppet Data".
+ // VRPUppet_Data1 encodes 1 byte
+ // VRPuppet_Data2 encodes 2 bytes
+ // and so on, until..
+ // VRPuppet_Data7 encodes 7 bytes
+ // This command is appended to aBuffer, then bufLen and bufData are reset
+ auto purgeBuffer = [&]() {
+ // Only emit a command if there are data bytes in bufData
+ if (bufLen > 0) {
+ MOZ_ASSERT(bufLen <= 7);
+ uint64_t command = (uint64_t)VRPuppet_Command::VRPuppet_Data1;
+ command += ((uint64_t)VRPuppet_Command::VRPuppet_Data2 -
+ (uint64_t)VRPuppet_Command::VRPuppet_Data1) *
+ (bufLen - 1);
+ command |= bufData;
+ aBuffer.AppendElement(command);
+ bufLen = 0;
+ bufData = 0;
+ }
+ };
+
+ // Loop through the bytes of the structs.
+ // While copying the struct at aSrcStart to aDstStart,
+ // the differences are encoded as VRPuppet commands and
+ // appended to aBuffer.
+ for (size_t i = 0; i < aLength; i++) {
+ if (*src != *dst) {
+ // This byte is different
+
+ // Copy the byte to the destination
+ *dst = *src;
+
+ if (bufLen == 0) {
+ // This is the start of a new span of changed bytes
+
+ // Output a command to specify the offset of the
+ // span.
+ aBuffer.AppendElement((uint64_t)aUpdateCommand + i);
+
+ // Store this first byte in bufData.
+ // We will batch up to 7 bytes in one VRPuppet_DataXX
+ // command, so we won't emit it yet.
+ bufLen = 1;
+ bufData = *src;
+ } else if (bufLen <= 6) {
+ // This is the continuation of a span of changed bytes.
+ // There is room to add more bytes to bufData.
+
+ // Store the next byte in bufData.
+ // We will batch up to 7 bytes in one VRPuppet_DataXX
+ // command, so we won't emit it yet.
+ bufData = (bufData << 8) | *src;
+ bufLen++;
+ } else {
+ MOZ_ASSERT(bufLen == 7);
+ // This is the continuation of a span of changed bytes.
+ // There are already 7 bytes in bufData, so we must emit
+ // the VRPuppet_Data7 command for the prior bytes before
+ // starting a new command.
+ aBuffer.AppendElement((uint64_t)VRPuppet_Command::VRPuppet_Data7 +
+ bufData);
+
+ // Store this byte to be included in the next VRPuppet_DataXX
+ // command.
+ bufLen = 1;
+ bufData = *src;
+ }
+ } else {
+ // This byte is the same.
+ // If there are bytes in bufData, the span has now ended and we must
+ // emit a VRPuppet_DataXX command for the accumulated bytes.
+ // purgeBuffer will not emit any commands if there are no bytes
+ // accumulated.
+ purgeBuffer();
+ }
+ // Advance to the next source and destination byte.
+ ++src;
+ ++dst;
+ }
+ // In the event that the very last byte of the structs differ, we must
+ // ensure that the accumulated bytes are emitted as a VRPuppet_DataXX
+ // command.
+ purgeBuffer();
+}
+
+} // namespace mozilla::gfx