aboutsummaryrefslogtreecommitdiffstats
path: root/LibOVR/Src/Util/Util_LatencyTest.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'LibOVR/Src/Util/Util_LatencyTest.cpp')
-rw-r--r--LibOVR/Src/Util/Util_LatencyTest.cpp570
1 files changed, 570 insertions, 0 deletions
diff --git a/LibOVR/Src/Util/Util_LatencyTest.cpp b/LibOVR/Src/Util/Util_LatencyTest.cpp
new file mode 100644
index 0000000..3017c72
--- /dev/null
+++ b/LibOVR/Src/Util/Util_LatencyTest.cpp
@@ -0,0 +1,570 @@
+/************************************************************************************
+
+Filename : Util_LatencyTest.cpp
+Content : Wraps the lower level LatencyTester interface and adds functionality.
+Created : February 14, 2013
+Authors : Lee Cooper
+
+Copyright : Copyright 2014 Oculus VR, Inc. All Rights reserved.
+
+Licensed under the Oculus VR Rift SDK License Version 3.1 (the "License");
+you may not use the Oculus VR Rift SDK except in compliance with the License,
+which is provided at the time of installation or download, or which
+otherwise accompanies this software in either electronic or hard copy form.
+
+You may obtain a copy of the License at
+
+http://www.oculusvr.com/licenses/LICENSE-3.1
+
+Unless required by applicable law or agreed to in writing, the Oculus VR SDK
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+*************************************************************************************/
+
+#include "Util_LatencyTest.h"
+
+#include "../Kernel/OVR_Log.h"
+#include "../Kernel/OVR_Timer.h"
+
+namespace OVR { namespace Util {
+
+static const UInt32 TIME_TO_WAIT_FOR_SETTLE_PRE_CALIBRATION = 16*10;
+static const UInt32 TIME_TO_WAIT_FOR_SETTLE_POST_CALIBRATION = 16*10;
+static const UInt32 TIME_TO_WAIT_FOR_SETTLE_POST_MEASUREMENT = 16*5;
+static const UInt32 TIME_TO_WAIT_FOR_SETTLE_POST_MEASUREMENT_RANDOMNESS = 16*5;
+static const UInt32 DEFAULT_NUMBER_OF_SAMPLES = 10; // For both color 1->2 and color 2->1 transitions.
+static const UInt32 INITIAL_SAMPLES_TO_IGNORE = 4;
+static const UInt32 TIMEOUT_WAITING_FOR_TEST_STARTED = 1000;
+static const UInt32 TIMEOUT_WAITING_FOR_COLOR_DETECTED = 4000;
+static const Color CALIBRATE_BLACK(0, 0, 0);
+static const Color CALIBRATE_WHITE(255, 255, 255);
+static const Color COLOR1(0, 0, 0);
+static const Color COLOR2(255, 255, 255);
+static const Color SENSOR_DETECT_THRESHOLD(128, 255, 255);
+static const float BIG_FLOAT = 1000000.0f;
+static const float SMALL_FLOAT = -1000000.0f;
+
+//-------------------------------------------------------------------------------------
+// ***** LatencyTest
+
+LatencyTest::LatencyTest(LatencyTestDevice* device)
+ : Handler(getThis())
+{
+ if (device != NULL)
+ {
+ SetDevice(device);
+ }
+
+ reset();
+
+ srand(Timer::GetTicksMs());
+}
+
+LatencyTest::~LatencyTest()
+{
+ clearMeasurementResults();
+}
+
+bool LatencyTest::SetDevice(LatencyTestDevice* device)
+{
+
+ if (device != Device)
+ {
+ Handler.RemoveHandlerFromDevices();
+
+ Device = device;
+
+ if (Device != NULL)
+ {
+ Device->AddMessageHandler(&Handler);
+
+ // Set trigger threshold.
+ LatencyTestConfiguration configuration(SENSOR_DETECT_THRESHOLD, false); // No samples streaming.
+ Device->SetConfiguration(configuration, true);
+
+ // Set display to initial (3 dashes).
+ LatencyTestDisplay ltd(2, 0x40400040);
+ Device->SetDisplay(ltd);
+ }
+ }
+
+ return true;
+}
+
+UInt32 LatencyTest::getRandomComponent(UInt32 range)
+{
+ UInt32 val = rand() % range;
+ return val;
+}
+
+void LatencyTest::BeginTest()
+{
+ if (State == State_WaitingForButton)
+ {
+ // Set color to black and wait a while.
+ RenderColor = CALIBRATE_BLACK;
+
+ State = State_WaitingForSettlePreCalibrationColorBlack;
+ OVR_DEBUG_LOG(("State_WaitingForButton -> State_WaitingForSettlePreCalibrationColorBlack."));
+
+ setTimer(TIME_TO_WAIT_FOR_SETTLE_PRE_CALIBRATION);
+ }
+}
+
+void LatencyTest::handleMessage(const Message& msg, LatencyTestMessageType latencyTestMessage)
+{
+ // For debugging.
+/* if (msg.Type == Message_LatencyTestSamples)
+ {
+ MessageLatencyTestSamples* pSamples = (MessageLatencyTestSamples*) &msg;
+
+ if (pSamples->Samples.GetSize() > 0)
+ {
+ // Just show the first one for now.
+ Color c = pSamples->Samples[0];
+ OVR_DEBUG_LOG(("%d %d %d", c.R, c.G, c.B));
+ }
+ return;
+ }
+*/
+
+ if (latencyTestMessage == LatencyTest_Timer)
+ {
+ if (!Device)
+ {
+ reset();
+ return;
+ }
+
+ if (State == State_WaitingForSettlePreCalibrationColorBlack)
+ {
+ // Send calibrate message to device and wait a while.
+ Device->SetCalibrate(CALIBRATE_BLACK);
+
+ State = State_WaitingForSettlePostCalibrationColorBlack;
+ OVR_DEBUG_LOG(("State_WaitingForSettlePreCalibrationColorBlack -> State_WaitingForSettlePostCalibrationColorBlack."));
+
+ setTimer(TIME_TO_WAIT_FOR_SETTLE_POST_CALIBRATION);
+ }
+ else if (State == State_WaitingForSettlePostCalibrationColorBlack)
+ {
+ // Change color to white and wait a while.
+ RenderColor = CALIBRATE_WHITE;
+
+ State = State_WaitingForSettlePreCalibrationColorWhite;
+ OVR_DEBUG_LOG(("State_WaitingForSettlePostCalibrationColorBlack -> State_WaitingForSettlePreCalibrationColorWhite."));
+
+ setTimer(TIME_TO_WAIT_FOR_SETTLE_PRE_CALIBRATION);
+ }
+ else if (State == State_WaitingForSettlePreCalibrationColorWhite)
+ {
+ // Send calibrate message to device and wait a while.
+ Device->SetCalibrate(CALIBRATE_WHITE);
+
+ State = State_WaitingForSettlePostCalibrationColorWhite;
+ OVR_DEBUG_LOG(("State_WaitingForSettlePreCalibrationColorWhite -> State_WaitingForSettlePostCalibrationColorWhite."));
+
+ setTimer(TIME_TO_WAIT_FOR_SETTLE_POST_CALIBRATION);
+ }
+ else if (State == State_WaitingForSettlePostCalibrationColorWhite)
+ {
+ // Calibration is done. Switch to color 1 and wait for it to settle.
+ RenderColor = COLOR1;
+
+ State = State_WaitingForSettlePostMeasurement;
+ OVR_DEBUG_LOG(("State_WaitingForSettlePostCalibrationColorWhite -> State_WaitingForSettlePostMeasurement."));
+
+ UInt32 waitTime = TIME_TO_WAIT_FOR_SETTLE_POST_MEASUREMENT + getRandomComponent(TIME_TO_WAIT_FOR_SETTLE_POST_MEASUREMENT_RANDOMNESS);
+ setTimer(waitTime);
+ }
+ else if (State == State_WaitingForSettlePostMeasurement)
+ {
+ // Prepare for next measurement.
+
+ // Create a new result object.
+ MeasurementResult* pResult = new MeasurementResult();
+ Results.PushBack(pResult);
+
+ State = State_WaitingToTakeMeasurement;
+ OVR_DEBUG_LOG(("State_WaitingForSettlePostMeasurement -> State_WaitingToTakeMeasurement."));
+ }
+ else if (State == State_WaitingForTestStarted)
+ {
+ // We timed out waiting for 'TestStarted'. Abandon this measurement and setup for the next.
+ getActiveResult()->TimedOutWaitingForTestStarted = true;
+
+ State = State_WaitingForSettlePostMeasurement;
+ OVR_DEBUG_LOG(("** Timed out waiting for 'TestStarted'."));
+ OVR_DEBUG_LOG(("State_WaitingForTestStarted -> State_WaitingForSettlePostMeasurement."));
+
+ UInt32 waitTime = TIME_TO_WAIT_FOR_SETTLE_POST_MEASUREMENT + getRandomComponent(TIME_TO_WAIT_FOR_SETTLE_POST_MEASUREMENT_RANDOMNESS);
+ setTimer(waitTime);
+ }
+ else if (State == State_WaitingForColorDetected)
+ {
+ // We timed out waiting for 'ColorDetected'. Abandon this measurement and setup for the next.
+ getActiveResult()->TimedOutWaitingForColorDetected = true;
+
+ State = State_WaitingForSettlePostMeasurement;
+ OVR_DEBUG_LOG(("** Timed out waiting for 'ColorDetected'."));
+ OVR_DEBUG_LOG(("State_WaitingForColorDetected -> State_WaitingForSettlePostMeasurement."));
+
+ UInt32 waitTime = TIME_TO_WAIT_FOR_SETTLE_POST_MEASUREMENT + getRandomComponent(TIME_TO_WAIT_FOR_SETTLE_POST_MEASUREMENT_RANDOMNESS);
+ setTimer(waitTime);
+ }
+ }
+ else if (latencyTestMessage == LatencyTest_ProcessInputs)
+ {
+ if (State == State_WaitingToTakeMeasurement)
+ {
+ if (!Device)
+ {
+ reset();
+ return;
+ }
+
+ // Send 'StartTest' feature report with opposite target color.
+ if (RenderColor == COLOR1)
+ {
+ RenderColor = COLOR2;
+ }
+ else
+ {
+ RenderColor = COLOR1;
+ }
+
+ getActiveResult()->TargetColor = RenderColor;
+
+ // Record time so we can determine usb roundtrip time.
+ getActiveResult()->StartTestSeconds = Timer::GetSeconds();
+
+ Device->SetStartTest(RenderColor);
+
+ State = State_WaitingForTestStarted;
+ OVR_DEBUG_LOG(("State_WaitingToTakeMeasurement -> State_WaitingForTestStarted."));
+
+ setTimer(TIMEOUT_WAITING_FOR_TEST_STARTED);
+
+ LatencyTestDisplay ltd(2, 0x40090040);
+ Device->SetDisplay(ltd);
+ }
+ }
+ else if (msg.Type == Message_LatencyTestButton)
+ {
+ BeginTest();
+ }
+ else if (msg.Type == Message_LatencyTestStarted)
+ {
+ if (State == State_WaitingForTestStarted)
+ {
+ clearTimer();
+
+ // Record time so we can determine usb roundtrip time.
+ getActiveResult()->TestStartedSeconds = Timer::GetSeconds();
+
+ State = State_WaitingForColorDetected;
+ OVR_DEBUG_LOG(("State_WaitingForTestStarted -> State_WaitingForColorDetected."));
+
+ setTimer(TIMEOUT_WAITING_FOR_COLOR_DETECTED);
+ }
+ }
+ else if (msg.Type == Message_LatencyTestColorDetected)
+ {
+ if (State == State_WaitingForColorDetected)
+ {
+ // Record time to detect color.
+ MessageLatencyTestColorDetected* pDetected = (MessageLatencyTestColorDetected*) &msg;
+ UInt16 elapsedTime = pDetected->Elapsed;
+ OVR_DEBUG_LOG(("Time to 'ColorDetected' = %d", elapsedTime));
+
+ getActiveResult()->DeviceMeasuredElapsedMilliS = elapsedTime;
+
+ if (areResultsComplete())
+ {
+ // We're done.
+ processResults();
+ reset();
+ }
+ else
+ {
+ // Run another measurement.
+ State = State_WaitingForSettlePostMeasurement;
+ OVR_DEBUG_LOG(("State_WaitingForColorDetected -> State_WaitingForSettlePostMeasurement."));
+
+ UInt32 waitTime = TIME_TO_WAIT_FOR_SETTLE_POST_MEASUREMENT + getRandomComponent(TIME_TO_WAIT_FOR_SETTLE_POST_MEASUREMENT_RANDOMNESS);
+ setTimer(waitTime);
+
+ LatencyTestDisplay ltd(2, 0x40400040);
+ Device->SetDisplay(ltd);
+ }
+ }
+ }
+ else if (msg.Type == Message_DeviceRemoved)
+ {
+ reset();
+ }
+}
+
+LatencyTest::MeasurementResult* LatencyTest::getActiveResult()
+{
+ OVR_ASSERT(!Results.IsEmpty());
+ return Results.GetLast();
+}
+
+void LatencyTest::setTimer(UInt32 timeMilliS)
+{
+ ActiveTimerMilliS = timeMilliS;
+}
+
+void LatencyTest::clearTimer()
+{
+ ActiveTimerMilliS = 0;
+}
+
+void LatencyTest::reset()
+{
+ clearMeasurementResults();
+ State = State_WaitingForButton;
+
+ HaveOldTime = false;
+ ActiveTimerMilliS = 0;
+}
+
+void LatencyTest::clearMeasurementResults()
+{
+ while(!Results.IsEmpty())
+ {
+ MeasurementResult* pElem = Results.GetFirst();
+ pElem->RemoveNode();
+ delete pElem;
+ }
+}
+
+LatencyTest::LatencyTestHandler::~LatencyTestHandler()
+{
+ RemoveHandlerFromDevices();
+}
+
+void LatencyTest::LatencyTestHandler::OnMessage(const Message& msg)
+{
+ pLatencyTestUtil->handleMessage(msg);
+}
+
+void LatencyTest::ProcessInputs()
+{
+ updateForTimeouts();
+ handleMessage(Message(), LatencyTest_ProcessInputs);
+}
+
+bool LatencyTest::DisplayScreenColor(Color& colorToDisplay)
+{
+ updateForTimeouts();
+
+ if (State == State_WaitingForButton)
+ {
+ return false;
+ }
+
+ colorToDisplay = RenderColor;
+ return true;
+}
+
+const char* LatencyTest::GetResultsString()
+{
+ if (!ResultsString.IsEmpty() && ReturnedResultString != ResultsString.ToCStr())
+ {
+ ReturnedResultString = ResultsString;
+ return ReturnedResultString.ToCStr();
+ }
+
+ return NULL;
+}
+
+bool LatencyTest::areResultsComplete()
+{
+ UInt32 initialMeasurements = 0;
+
+ UInt32 measurements1to2 = 0;
+ UInt32 measurements2to1 = 0;
+
+ MeasurementResult* pCurr = Results.GetFirst();
+ while(true)
+ {
+ // Process.
+ if (!pCurr->TimedOutWaitingForTestStarted &&
+ !pCurr->TimedOutWaitingForColorDetected)
+ {
+ initialMeasurements++;
+
+ if (initialMeasurements > INITIAL_SAMPLES_TO_IGNORE)
+ {
+ if (pCurr->TargetColor == COLOR2)
+ {
+ measurements1to2++;
+ }
+ else
+ {
+ measurements2to1++;
+ }
+ }
+ }
+
+ if (Results.IsLast(pCurr))
+ {
+ break;
+ }
+ pCurr = Results.GetNext(pCurr);
+ }
+
+ if (measurements1to2 >= DEFAULT_NUMBER_OF_SAMPLES &&
+ measurements2to1 >= DEFAULT_NUMBER_OF_SAMPLES)
+ {
+ return true;
+ }
+
+ return false;
+}
+
+void LatencyTest::processResults()
+{
+
+ UInt32 minTime1To2 = UINT_MAX;
+ UInt32 maxTime1To2 = 0;
+ float averageTime1To2 = 0.0f;
+ UInt32 minTime2To1 = UINT_MAX;
+ UInt32 maxTime2To1 = 0;
+ float averageTime2To1 = 0.0f;
+
+ float minUSBTripMilliS = BIG_FLOAT;
+ float maxUSBTripMilliS = SMALL_FLOAT;
+ float averageUSBTripMilliS = 0.0f;
+ UInt32 countUSBTripTime = 0;
+
+ UInt32 measurementsCount = 0;
+ UInt32 measurements1to2 = 0;
+ UInt32 measurements2to1 = 0;
+
+ MeasurementResult* pCurr = Results.GetFirst();
+ UInt32 count = 0;
+ while(true)
+ {
+ count++;
+
+ if (!pCurr->TimedOutWaitingForTestStarted &&
+ !pCurr->TimedOutWaitingForColorDetected)
+ {
+ measurementsCount++;
+
+ if (measurementsCount > INITIAL_SAMPLES_TO_IGNORE)
+ {
+ if (pCurr->TargetColor == COLOR2)
+ {
+ measurements1to2++;
+
+ if (measurements1to2 <= DEFAULT_NUMBER_OF_SAMPLES)
+ {
+ UInt32 elapsed = pCurr->DeviceMeasuredElapsedMilliS;
+
+ minTime1To2 = Alg::Min(elapsed, minTime1To2);
+ maxTime1To2 = Alg::Max(elapsed, maxTime1To2);
+
+ averageTime1To2 += (float) elapsed;
+ }
+ }
+ else
+ {
+ measurements2to1++;
+
+ if (measurements2to1 <= DEFAULT_NUMBER_OF_SAMPLES)
+ {
+ UInt32 elapsed = pCurr->DeviceMeasuredElapsedMilliS;
+
+ minTime2To1 = Alg::Min(elapsed, minTime2To1);
+ maxTime2To1 = Alg::Max(elapsed, maxTime2To1);
+
+ averageTime2To1 += (float) elapsed;
+ }
+ }
+
+ float usbRountripElapsedMilliS = Timer::MsPerSecond * (float) (pCurr->TestStartedSeconds - pCurr->StartTestSeconds);
+ minUSBTripMilliS = Alg::Min(usbRountripElapsedMilliS, minUSBTripMilliS);
+ maxUSBTripMilliS = Alg::Max(usbRountripElapsedMilliS, maxUSBTripMilliS);
+ averageUSBTripMilliS += usbRountripElapsedMilliS;
+ countUSBTripTime++;
+ }
+ }
+
+ if (measurements1to2 >= DEFAULT_NUMBER_OF_SAMPLES &&
+ measurements2to1 >= DEFAULT_NUMBER_OF_SAMPLES)
+ {
+ break;
+ }
+
+ if (Results.IsLast(pCurr))
+ {
+ break;
+ }
+ pCurr = Results.GetNext(pCurr);
+ }
+
+ averageTime1To2 /= (float) DEFAULT_NUMBER_OF_SAMPLES;
+ averageTime2To1 /= (float) DEFAULT_NUMBER_OF_SAMPLES;
+
+ averageUSBTripMilliS /= countUSBTripTime;
+
+ float finalResult = 0.5f * (averageTime1To2 + averageTime2To1);
+ finalResult += averageUSBTripMilliS;
+
+ ResultsString.Clear();
+ ResultsString.AppendFormat("RESULT=%.1f (add half Tracker period) [b->w %d|%.1f|%d] [w->b %d|%.1f|%d] [usb rndtrp %.1f|%.1f|%.1f] [cnt %d] [tmouts %d]",
+ finalResult,
+ minTime1To2, averageTime1To2, maxTime1To2,
+ minTime2To1, averageTime2To1, maxTime2To1,
+ minUSBTripMilliS, averageUSBTripMilliS, maxUSBTripMilliS,
+ DEFAULT_NUMBER_OF_SAMPLES*2, count - measurementsCount);
+
+ // Display result on latency tester display.
+ LatencyTestDisplay ltd(1, (int)finalResult);
+ Device->SetDisplay(ltd);
+}
+
+void LatencyTest::updateForTimeouts()
+{
+ if (!HaveOldTime)
+ {
+ HaveOldTime = true;
+ OldTime = Timer::GetTicksMs();
+ return;
+ }
+
+ UInt32 newTime = Timer::GetTicksMs();
+ UInt32 elapsedMilliS = newTime - OldTime;
+ if (newTime < OldTime)
+ {
+ elapsedMilliS = OldTime - newTime;
+ elapsedMilliS = UINT_MAX - elapsedMilliS;
+ }
+ OldTime = newTime;
+
+ elapsedMilliS = Alg::Min(elapsedMilliS, (UInt32) 100); // Clamp at 100mS in case we're not being called very often.
+
+
+ if (ActiveTimerMilliS == 0)
+ {
+ return;
+ }
+
+ if (elapsedMilliS >= ActiveTimerMilliS)
+ {
+ ActiveTimerMilliS = 0;
+ handleMessage(Message(), LatencyTest_Timer);
+ return;
+ }
+
+ ActiveTimerMilliS -= elapsedMilliS;
+}
+
+}} // namespace OVR::Util