ovr_sdk
view LibOVR/Src/CAPI/CAPI_FrameTimeManager.cpp @ 3:f12a8f74fe1f
added the Xcode project
author | John Tsiombikas <nuclear@member.fsf.org> |
---|---|
date | Wed, 21 Jan 2015 11:37:50 +0200 |
parents | |
children |
line source
1 /************************************************************************************
3 Filename : CAPI_FrameTimeManager.cpp
4 Content : Manage frame timing and pose prediction for rendering
5 Created : November 30, 2013
6 Authors : Volga Aksoy, Michael Antonov
8 Copyright : Copyright 2014 Oculus VR, LLC All Rights reserved.
10 Licensed under the Oculus VR Rift SDK License Version 3.2 (the "License");
11 you may not use the Oculus VR Rift SDK except in compliance with the License,
12 which is provided at the time of installation or download, or which
13 otherwise accompanies this software in either electronic or hard copy form.
15 You may obtain a copy of the License at
17 http://www.oculusvr.com/licenses/LICENSE-3.2
19 Unless required by applicable law or agreed to in writing, the Oculus VR SDK
20 distributed under the License is distributed on an "AS IS" BASIS,
21 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22 See the License for the specific language governing permissions and
23 limitations under the License.
25 ************************************************************************************/
27 #include "CAPI_FrameTimeManager.h"
29 #include "../Kernel/OVR_Log.h"
31 namespace OVR { namespace CAPI {
34 //-------------------------------------------------------------------------------------
35 // ***** FrameLatencyTracker
38 FrameLatencyTracker::FrameLatencyTracker()
39 {
40 Reset();
41 }
44 void FrameLatencyTracker::Reset()
45 {
46 TrackerEnabled = true;
47 WaitMode = SampleWait_Zeroes;
48 MatchCount = 0;
49 memset(FrameEndTimes, 0, sizeof(FrameEndTimes));
50 FrameIndex = 0;
51 //FrameDeltas
52 RenderLatencySeconds = 0.0;
53 TimewarpLatencySeconds = 0.0;
54 LatencyRecordTime = 0.0;
56 FrameDeltas.Clear();
57 }
60 unsigned char FrameLatencyTracker::GetNextDrawColor()
61 {
62 if (!TrackerEnabled || (WaitMode == SampleWait_Zeroes) ||
63 (FrameIndex >= FramesTracked))
64 {
65 return (unsigned char)Util::FrameTimeRecord::ReadbackIndexToColor(0);
66 }
68 OVR_ASSERT(FrameIndex < FramesTracked);
69 return (unsigned char)Util::FrameTimeRecord::ReadbackIndexToColor(FrameIndex+1);
70 }
73 void FrameLatencyTracker::SaveDrawColor(unsigned char drawColor, double endFrameTime,
74 double renderIMUTime, double timewarpIMUTime )
75 {
76 if (!TrackerEnabled || (WaitMode == SampleWait_Zeroes))
77 return;
79 if (FrameIndex < FramesTracked)
80 {
81 OVR_ASSERT(Util::FrameTimeRecord::ReadbackIndexToColor(FrameIndex+1) == drawColor);
82 OVR_UNUSED(drawColor);
84 // saves {color, endFrame time}
85 FrameEndTimes[FrameIndex].ReadbackIndex = FrameIndex + 1;
86 FrameEndTimes[FrameIndex].TimeSeconds = endFrameTime;
87 FrameEndTimes[FrameIndex].RenderIMUTimeSeconds = renderIMUTime;
88 FrameEndTimes[FrameIndex].TimewarpIMUTimeSeconds= timewarpIMUTime;
89 FrameEndTimes[FrameIndex].MatchedRecord = false;
90 FrameIndex++;
91 }
92 else
93 {
94 // If the request was outstanding for too long, switch to zero mode to restart.
95 if (endFrameTime > (FrameEndTimes[FrameIndex-1].TimeSeconds + 0.15))
96 {
97 if (MatchCount == 0)
98 {
99 // If nothing was matched, we have no latency reading.
100 RenderLatencySeconds = 0.0;
101 TimewarpLatencySeconds = 0.0;
102 }
104 WaitMode = SampleWait_Zeroes;
105 MatchCount = 0;
106 FrameIndex = 0;
107 }
108 }
109 }
112 void FrameLatencyTracker::MatchRecord(const Util::FrameTimeRecordSet &r)
113 {
114 if (!TrackerEnabled)
115 return;
117 if (WaitMode == SampleWait_Zeroes)
118 {
119 // Do we have all zeros?
120 if (r.IsAllZeroes())
121 {
122 OVR_ASSERT(FrameIndex == 0);
123 WaitMode = SampleWait_Match;
124 MatchCount = 0;
125 }
126 return;
127 }
129 // We are in Match Mode. Wait until all colors are matched or timeout,
130 // at which point we go back to zeros.
132 for (int i = 0; i < FrameIndex; i++)
133 {
134 int recordIndex = 0;
135 int consecutiveMatch = 0;
137 OVR_ASSERT(FrameEndTimes[i].ReadbackIndex != 0);
139 if (r.FindReadbackIndex(&recordIndex, FrameEndTimes[i].ReadbackIndex))
140 {
141 // Advance forward to see that we have several more matches.
142 int ri = recordIndex + 1;
143 int j = i + 1;
145 consecutiveMatch++;
147 for (; (j < FrameIndex) && (ri < Util::FrameTimeRecordSet::RecordCount); j++, ri++)
148 {
149 if (r[ri].ReadbackIndex != FrameEndTimes[j].ReadbackIndex)
150 break;
151 consecutiveMatch++;
152 }
154 // Match at least 2 items in the row, to avoid accidentally matching color.
155 if (consecutiveMatch > 1)
156 {
157 // Record latency values for all but last samples. Keep last 2 samples
158 // for the future to simplify matching.
159 for (int q = 0; q < consecutiveMatch; q++)
160 {
161 const Util::FrameTimeRecord &scanoutFrame = r[recordIndex+q];
162 FrameTimeRecordEx &renderFrame = FrameEndTimes[i+q];
164 if (!renderFrame.MatchedRecord)
165 {
166 double deltaSeconds = scanoutFrame.TimeSeconds - renderFrame.TimeSeconds;
167 if (deltaSeconds > 0.0)
168 {
169 FrameDeltas.AddTimeDelta(deltaSeconds);
171 // FIRMWARE HACK: don't take new readings if they're 10ms higher than previous reading
172 // but only do that for 1 second, after that accept it regardless of the timing difference
173 double newRenderLatency = scanoutFrame.TimeSeconds - renderFrame.RenderIMUTimeSeconds;
174 if( newRenderLatency < RenderLatencySeconds + 0.01 ||
175 scanoutFrame.TimeSeconds > LatencyRecordTime + 1.0)
176 {
177 LatencyRecordTime = scanoutFrame.TimeSeconds;
178 RenderLatencySeconds = scanoutFrame.TimeSeconds - renderFrame.RenderIMUTimeSeconds;
179 TimewarpLatencySeconds = (renderFrame.TimewarpIMUTimeSeconds == 0.0) ? 0.0 :
180 (scanoutFrame.TimeSeconds - renderFrame.TimewarpIMUTimeSeconds);
181 }
182 }
184 renderFrame.MatchedRecord = true;
185 MatchCount++;
186 }
187 }
189 // Exit for.
190 break;
191 }
192 }
193 } // for ( i => FrameIndex )
196 // If we matched all frames, start over.
197 if (MatchCount == FramesTracked)
198 {
199 WaitMode = SampleWait_Zeroes;
200 MatchCount = 0;
201 FrameIndex = 0;
202 }
203 }
205 bool FrameLatencyTracker::IsLatencyTimingAvailable()
206 {
207 return ovr_GetTimeInSeconds() < (LatencyRecordTime + 2.0);
208 }
210 void FrameLatencyTracker::GetLatencyTimings(float& latencyRender, float& latencyTimewarp, float& latencyPostPresent)
211 {
212 if (!IsLatencyTimingAvailable())
213 {
214 latencyRender = 0.0f;
215 latencyTimewarp = 0.0f;
216 latencyPostPresent = 0.0f;
217 }
218 else
219 {
220 latencyRender = (float)RenderLatencySeconds;
221 latencyTimewarp = (float)TimewarpLatencySeconds;
222 latencyPostPresent = (float)FrameDeltas.GetMedianTimeDelta();
223 }
224 }
227 //-------------------------------------------------------------------------------------
228 // ***** FrameTimeManager
230 FrameTimeManager::FrameTimeManager(bool vsyncEnabled) :
231 RenderInfo(),
232 FrameTimeDeltas(),
233 DistortionRenderTimes(),
234 ScreenLatencyTracker(),
235 VsyncEnabled(vsyncEnabled),
236 DynamicPrediction(true),
237 SdkRender(false),
238 //DirectToRift(false), Initialized below.
239 //VSyncToScanoutDelay(0.0), Initialized below.
240 //NoVSyncToScanoutDelay(0.0), Initialized below.
241 ScreenSwitchingDelay(0.0),
242 FrameTiming(),
243 LocklessTiming(),
244 RenderIMUTimeSeconds(0.0),
245 TimewarpIMUTimeSeconds(0.0)
246 {
247 // If driver is in use,
248 DirectToRift = !Display::InCompatibilityMode(false);
249 if (DirectToRift)
250 {
251 // The latest driver provides a post-present vsync-to-scan-out delay
252 // that is roughly zero. The latency tester will provide real numbers
253 // but when it is unavailable for some reason, we should default to
254 // an expected value.
255 VSyncToScanoutDelay = 0.0001f;
256 }
257 else
258 {
259 // HACK: SyncToScanoutDelay observed close to 1 frame in video cards.
260 // Overwritten by dynamic latency measurement on DK2.
261 VSyncToScanoutDelay = 0.013f;
262 }
263 NoVSyncToScanoutDelay = 0.004f;
264 }
266 void FrameTimeManager::Init(HmdRenderInfo& renderInfo)
267 {
268 // Set up prediction distances.
269 // With-Vsync timings.
270 RenderInfo = renderInfo;
272 ScreenSwitchingDelay = RenderInfo.Shutter.PixelSettleTime * 0.5f +
273 RenderInfo.Shutter.PixelPersistence * 0.5f;
274 }
276 void FrameTimeManager::ResetFrameTiming(unsigned frameIndex,
277 bool dynamicPrediction,
278 bool sdkRender)
279 {
280 DynamicPrediction = dynamicPrediction;
281 SdkRender = sdkRender;
283 FrameTimeDeltas.Clear();
284 DistortionRenderTimes.Clear();
285 ScreenLatencyTracker.Reset();
286 //Revisit dynamic pre-Timewarp delay adjustment logic
287 //TimewarpAdjuster.Reset();
289 FrameTiming.FrameIndex = frameIndex;
290 FrameTiming.NextFrameTime = 0.0;
291 FrameTiming.ThisFrameTime = 0.0;
292 FrameTiming.Inputs.FrameDelta = calcFrameDelta();
293 // This one is particularly critical, and has been missed in the past because
294 // this init function wasn't called for app-rendered.
295 FrameTiming.Inputs.ScreenDelay = calcScreenDelay();
296 FrameTiming.Inputs.TimewarpWaitDelta = 0.0f;
298 LocklessTiming.SetState(FrameTiming);
299 }
302 double FrameTimeManager::calcFrameDelta() const
303 {
304 // Timing difference between frame is tracked by FrameTimeDeltas, or
305 // is a hard-coded value of 1/FrameRate.
306 double frameDelta;
308 if (!VsyncEnabled)
309 {
310 frameDelta = 0.0;
311 }
312 else if (FrameTimeDeltas.GetCount() > 3)
313 {
314 frameDelta = FrameTimeDeltas.GetMedianTimeDelta();
315 if (frameDelta > (RenderInfo.Shutter.VsyncToNextVsync + 0.001))
316 frameDelta = RenderInfo.Shutter.VsyncToNextVsync;
317 }
318 else
319 {
320 frameDelta = RenderInfo.Shutter.VsyncToNextVsync;
321 }
323 return frameDelta;
324 }
327 double FrameTimeManager::calcScreenDelay() const
328 {
329 double screenDelay = ScreenSwitchingDelay;
330 double measuredVSyncToScanout;
332 // Use real-time DK2 latency tester HW for prediction if its is working.
333 // Do sanity check under 60 ms
334 if (!VsyncEnabled)
335 {
336 screenDelay += NoVSyncToScanoutDelay;
337 }
338 else if ( DynamicPrediction &&
339 (ScreenLatencyTracker.FrameDeltas.GetCount() > 3) &&
340 (measuredVSyncToScanout = ScreenLatencyTracker.FrameDeltas.GetMedianTimeDelta(),
341 (measuredVSyncToScanout > -0.0001) && (measuredVSyncToScanout < 0.06)) )
342 {
343 screenDelay += measuredVSyncToScanout;
344 }
345 else
346 {
347 screenDelay += VSyncToScanoutDelay;
348 }
350 return screenDelay;
351 }
353 double FrameTimeManager::calcTimewarpWaitDelta() const
354 {
355 // If timewarp timing hasn't been calculated, we should wait.
356 if (!VsyncEnabled)
357 return 0.0;
359 if (SdkRender)
360 {
361 if (NeedDistortionTimeMeasurement())
362 return 0.0;
363 return -(DistortionRenderTimes.GetMedianTimeDelta() + 0.0035);
365 //Revisit dynamic pre-Timewarp delay adjustment logic
366 /*return -(DistortionRenderTimes.GetMedianTimeDelta() + 0.002 +
367 TimewarpAdjuster.GetDelayReduction());*/
368 }
370 // Just a hard-coded "high" value for game-drawn code.
371 // TBD: Just return 0 and let users calculate this themselves?
372 return -0.004;
374 //Revisit dynamic pre-Timewarp delay adjustment logic
375 //return -(0.003 + TimewarpAdjuster.GetDelayReduction());
376 }
378 //Revisit dynamic pre-Timewarp delay adjustment logic
379 /*
380 void FrameTimeManager::updateTimewarpTiming()
381 {
382 // If timewarp timing changes based on this sample, update it.
383 double newTimewarpWaitDelta = calcTimewarpWaitDelta();
384 if (newTimewarpWaitDelta != FrameTiming.Inputs.TimewarpWaitDelta)
385 {
386 FrameTiming.Inputs.TimewarpWaitDelta = newTimewarpWaitDelta;
387 LocklessTiming.SetState(FrameTiming);
388 }
389 }
390 */
392 void FrameTimeManager::Timing::InitTimingFromInputs(const FrameTimeManager::TimingInputs& inputs,
393 HmdShutterTypeEnum shutterType,
394 double thisFrameTime, unsigned int frameIndex)
395 {
396 // ThisFrameTime comes from the end of last frame, unless it it changed.
397 double nextFrameBase;
398 double frameDelta = inputs.FrameDelta;
400 FrameIndex = frameIndex;
402 ThisFrameTime = thisFrameTime;
403 NextFrameTime = ThisFrameTime + frameDelta;
404 nextFrameBase = NextFrameTime + inputs.ScreenDelay;
405 MidpointTime = nextFrameBase + frameDelta * 0.5;
406 TimewarpPointTime = (inputs.TimewarpWaitDelta == 0.0) ?
407 0.0 : (NextFrameTime + inputs.TimewarpWaitDelta);
409 // Calculate absolute points in time when eye rendering or corresponding time-warp
410 // screen edges will become visible.
411 // This only matters with VSync.
412 switch(shutterType)
413 {
414 case HmdShutter_RollingTopToBottom:
415 EyeRenderTimes[0] = MidpointTime;
416 EyeRenderTimes[1] = MidpointTime;
417 TimeWarpStartEndTimes[0][0] = nextFrameBase;
418 TimeWarpStartEndTimes[0][1] = nextFrameBase + frameDelta;
419 TimeWarpStartEndTimes[1][0] = nextFrameBase;
420 TimeWarpStartEndTimes[1][1] = nextFrameBase + frameDelta;
421 break;
422 case HmdShutter_RollingLeftToRight:
423 EyeRenderTimes[0] = nextFrameBase + frameDelta * 0.25;
424 EyeRenderTimes[1] = nextFrameBase + frameDelta * 0.75;
426 /*
427 // TBD: MA: It is probably better if mesh sets it up per-eye.
428 // Would apply if screen is 0 -> 1 for each eye mesh
429 TimeWarpStartEndTimes[0][0] = nextFrameBase;
430 TimeWarpStartEndTimes[0][1] = MidpointTime;
431 TimeWarpStartEndTimes[1][0] = MidpointTime;
432 TimeWarpStartEndTimes[1][1] = nextFrameBase + frameDelta;
433 */
435 // Mesh is set up to vary from Edge of scree 0 -> 1 across both eyes
436 TimeWarpStartEndTimes[0][0] = nextFrameBase;
437 TimeWarpStartEndTimes[0][1] = nextFrameBase + frameDelta;
438 TimeWarpStartEndTimes[1][0] = nextFrameBase;
439 TimeWarpStartEndTimes[1][1] = nextFrameBase + frameDelta;
441 break;
442 case HmdShutter_RollingRightToLeft:
444 EyeRenderTimes[0] = nextFrameBase + frameDelta * 0.75;
445 EyeRenderTimes[1] = nextFrameBase + frameDelta * 0.25;
447 // This is *Correct* with Tom's distortion mesh organization.
448 TimeWarpStartEndTimes[0][0] = nextFrameBase ;
449 TimeWarpStartEndTimes[0][1] = nextFrameBase + frameDelta;
450 TimeWarpStartEndTimes[1][0] = nextFrameBase ;
451 TimeWarpStartEndTimes[1][1] = nextFrameBase + frameDelta;
452 break;
453 case HmdShutter_Global:
454 // TBD
455 EyeRenderTimes[0] = MidpointTime;
456 EyeRenderTimes[1] = MidpointTime;
457 TimeWarpStartEndTimes[0][0] = MidpointTime;
458 TimeWarpStartEndTimes[0][1] = MidpointTime;
459 TimeWarpStartEndTimes[1][0] = MidpointTime;
460 TimeWarpStartEndTimes[1][1] = MidpointTime;
461 break;
462 default:
463 break;
464 }
465 }
468 double FrameTimeManager::BeginFrame(unsigned frameIndex)
469 {
470 RenderIMUTimeSeconds = 0.0;
471 TimewarpIMUTimeSeconds = 0.0;
473 // TPH - putting an assert so this doesn't remain a hidden problem.
474 OVR_ASSERT(FrameTiming.Inputs.ScreenDelay != 0);
476 // ThisFrameTime comes from the end of last frame, unless it it changed.
477 double thisFrameTime = (FrameTiming.NextFrameTime != 0.0) ?
478 FrameTiming.NextFrameTime : ovr_GetTimeInSeconds();
480 // We are starting to process a new frame...
481 FrameTiming.InitTimingFromInputs(FrameTiming.Inputs, RenderInfo.Shutter.Type,
482 thisFrameTime, frameIndex);
484 return FrameTiming.ThisFrameTime;
485 }
488 void FrameTimeManager::EndFrame()
489 {
490 // Record timing since last frame; must be called after Present & sync.
491 FrameTiming.NextFrameTime = ovr_GetTimeInSeconds();
492 if (FrameTiming.ThisFrameTime > 0.0)
493 {
494 //Revisit dynamic pre-Timewarp delay adjustment logic
495 /*
496 double actualFrameDelta = FrameTiming.NextFrameTime - FrameTiming.ThisFrameTime;
498 if (VsyncEnabled)
499 TimewarpAdjuster.UpdateTimewarpWaitIfSkippedFrames(this, actualFrameDelta,
500 FrameTiming.NextFrameTime);
502 FrameTimeDeltas.AddTimeDelta(actualFrameDelta);
503 */
504 FrameTimeDeltas.AddTimeDelta(FrameTiming.NextFrameTime - FrameTiming.ThisFrameTime);
505 FrameTiming.Inputs.FrameDelta = calcFrameDelta();
506 }
508 // Write to Lock-less
509 LocklessTiming.SetState(FrameTiming);
510 }
512 // Thread-safe function to query timing for a future frame
514 FrameTimeManager::Timing FrameTimeManager::GetFrameTiming(unsigned frameIndex)
515 {
516 Timing frameTiming = LocklessTiming.GetState();
518 if (frameTiming.ThisFrameTime == 0.0)
519 {
520 // If timing hasn't been initialized, starting based on "now" is the best guess.
521 frameTiming.InitTimingFromInputs(frameTiming.Inputs, RenderInfo.Shutter.Type,
522 ovr_GetTimeInSeconds(), frameIndex);
523 }
525 else if (frameIndex > frameTiming.FrameIndex)
526 {
527 unsigned frameDelta = frameIndex - frameTiming.FrameIndex;
528 double thisFrameTime = frameTiming.NextFrameTime +
529 double(frameDelta-1) * frameTiming.Inputs.FrameDelta;
530 // Don't run away too far into the future beyond rendering.
531 OVR_DEBUG_LOG_COND(frameDelta >= 6, ("GetFrameTiming is 6 or more frames in future beyond rendering!"));
533 frameTiming.InitTimingFromInputs(frameTiming.Inputs, RenderInfo.Shutter.Type,
534 thisFrameTime, frameIndex);
535 }
537 return frameTiming;
538 }
541 double FrameTimeManager::GetEyePredictionTime(ovrEyeType eye, unsigned int frameIndex)
542 {
543 if (VsyncEnabled)
544 {
545 FrameTimeManager::Timing frameTiming = GetFrameTiming(frameIndex);
547 // Special case: ovrEye_Count predicts to midpoint
548 return (eye == ovrEye_Count) ? frameTiming.MidpointTime : frameTiming.EyeRenderTimes[eye];
549 }
551 // No VSync: Best guess for the near future
552 return ovr_GetTimeInSeconds() + ScreenSwitchingDelay + NoVSyncToScanoutDelay;
553 }
555 ovrTrackingState FrameTimeManager::GetEyePredictionTracking(ovrHmd hmd, ovrEyeType eye, unsigned int frameIndex)
556 {
557 double eyeRenderTime = GetEyePredictionTime(eye, frameIndex);
558 ovrTrackingState eyeState = ovrHmd_GetTrackingState(hmd, eyeRenderTime);
560 // Record view pose sampling time for Latency reporting.
561 if (RenderIMUTimeSeconds == 0.0)
562 {
563 // TODO: Figure out why this are not as accurate as ovr_GetTimeInSeconds()
564 //RenderIMUTimeSeconds = eyeState.RawSensorData.TimeInSeconds;
565 RenderIMUTimeSeconds = ovr_GetTimeInSeconds();
566 }
568 return eyeState;
569 }
571 Posef FrameTimeManager::GetEyePredictionPose(ovrHmd hmd, ovrEyeType eye)
572 {
573 double eyeRenderTime = GetEyePredictionTime(eye, 0);
574 ovrTrackingState eyeState = ovrHmd_GetTrackingState(hmd, eyeRenderTime);
576 // Record view pose sampling time for Latency reporting.
577 if (RenderIMUTimeSeconds == 0.0)
578 {
579 // TODO: Figure out why this are not as accurate as ovr_GetTimeInSeconds()
580 //RenderIMUTimeSeconds = eyeState.RawSensorData.TimeInSeconds;
581 RenderIMUTimeSeconds = ovr_GetTimeInSeconds();
582 }
584 return eyeState.HeadPose.ThePose;
585 }
587 void FrameTimeManager::GetTimewarpPredictions(ovrEyeType eye, double timewarpStartEnd[2])
588 {
589 if (VsyncEnabled)
590 {
591 timewarpStartEnd[0] = FrameTiming.TimeWarpStartEndTimes[eye][0];
592 timewarpStartEnd[1] = FrameTiming.TimeWarpStartEndTimes[eye][1];
593 return;
594 }
596 // Free-running, so this will be displayed immediately.
597 // Unfortunately we have no idea which bit of the screen is actually going to be displayed.
598 // TODO: guess which bit of the screen is being displayed!
599 // (e.g. use DONOTWAIT on present and see when the return isn't WASSTILLWAITING?)
601 // We have no idea where scan-out is currently, so we can't usefully warp the screen spatially.
602 timewarpStartEnd[0] = ovr_GetTimeInSeconds() + ScreenSwitchingDelay + NoVSyncToScanoutDelay;
603 timewarpStartEnd[1] = timewarpStartEnd[0];
604 }
607 void FrameTimeManager::GetTimewarpMatrices(ovrHmd hmd, ovrEyeType eyeId,
608 ovrPosef renderPose, ovrMatrix4f twmOut[2],
609 double debugTimingOffsetInSeconds)
610 {
611 if (!hmd)
612 {
613 return;
614 }
616 double timewarpStartEnd[2] = { 0.0, 0.0 };
617 GetTimewarpPredictions(eyeId, timewarpStartEnd);
619 //TPH, to vary timing, to allow developers to debug, to shunt the predicted time forward
620 //and back, and see if the SDK is truly delivering the correct time. Also to allow
621 //illustration of the detrimental effects when this is not done right.
622 timewarpStartEnd[0] += debugTimingOffsetInSeconds;
623 timewarpStartEnd[1] += debugTimingOffsetInSeconds;
626 //HMDState* p = (HMDState*)hmd;
627 ovrTrackingState startState = ovrHmd_GetTrackingState(hmd, timewarpStartEnd[0]);
628 ovrTrackingState endState = ovrHmd_GetTrackingState(hmd, timewarpStartEnd[1]);
630 if (TimewarpIMUTimeSeconds == 0.0)
631 {
632 // TODO: Figure out why this are not as accurate as ovr_GetTimeInSeconds()
633 //TimewarpIMUTimeSeconds = startState.RawSensorData.TimeInSeconds;
634 TimewarpIMUTimeSeconds = ovr_GetTimeInSeconds();
635 }
637 Quatf quatFromStart = startState.HeadPose.ThePose.Orientation;
638 Quatf quatFromEnd = endState.HeadPose.ThePose.Orientation;
639 Quatf quatFromEye = renderPose.Orientation; //EyeRenderPoses[eyeId].Orientation;
640 quatFromEye.Invert(); // because we need the view matrix, not the camera matrix
642 Quatf timewarpStartQuat = quatFromEye * quatFromStart;
643 Quatf timewarpEndQuat = quatFromEye * quatFromEnd;
645 Matrix4f timewarpStart(timewarpStartQuat);
646 Matrix4f timewarpEnd(timewarpEndQuat);
649 // The real-world orientations have: X=right, Y=up, Z=backwards.
650 // The vectors inside the mesh are in NDC to keep the shader simple: X=right, Y=down, Z=forwards.
651 // So we need to perform a similarity transform on this delta matrix.
652 // The verbose code would look like this:
653 /*
654 Matrix4f matBasisChange;
655 matBasisChange.SetIdentity();
656 matBasisChange.M[0][0] = 1.0f;
657 matBasisChange.M[1][1] = -1.0f;
658 matBasisChange.M[2][2] = -1.0f;
659 Matrix4f matBasisChangeInv = matBasisChange.Inverted();
660 matRenderFromNow = matBasisChangeInv * matRenderFromNow * matBasisChange;
661 */
662 // ...but of course all the above is a constant transform and much more easily done.
663 // We flip the signs of the Y&Z row, then flip the signs of the Y&Z column,
664 // and of course most of the flips cancel:
665 // +++ +-- +--
666 // +++ -> flip Y&Z columns -> +-- -> flip Y&Z rows -> -++
667 // +++ +-- -++
668 timewarpStart.M[0][1] = -timewarpStart.M[0][1];
669 timewarpStart.M[0][2] = -timewarpStart.M[0][2];
670 timewarpStart.M[1][0] = -timewarpStart.M[1][0];
671 timewarpStart.M[2][0] = -timewarpStart.M[2][0];
673 timewarpEnd .M[0][1] = -timewarpEnd .M[0][1];
674 timewarpEnd .M[0][2] = -timewarpEnd .M[0][2];
675 timewarpEnd .M[1][0] = -timewarpEnd .M[1][0];
676 timewarpEnd .M[2][0] = -timewarpEnd .M[2][0];
678 twmOut[0] = timewarpStart;
679 twmOut[1] = timewarpEnd;
680 }
683 // Used by renderer to determine if it should time distortion rendering.
684 bool FrameTimeManager::NeedDistortionTimeMeasurement() const
685 {
686 if (!VsyncEnabled)
687 return false;
688 return DistortionRenderTimes.GetCount() < DistortionRenderTimes.Capacity;
689 }
692 void FrameTimeManager::AddDistortionTimeMeasurement(double distortionTimeSeconds)
693 {
694 DistortionRenderTimes.AddTimeDelta(distortionTimeSeconds);
696 //Revisit dynamic pre-Timewarp delay adjustment logic
697 //updateTimewarpTiming();
699 // If timewarp timing changes based on this sample, update it.
700 double newTimewarpWaitDelta = calcTimewarpWaitDelta();
701 if (newTimewarpWaitDelta != FrameTiming.Inputs.TimewarpWaitDelta)
702 {
703 FrameTiming.Inputs.TimewarpWaitDelta = newTimewarpWaitDelta;
704 LocklessTiming.SetState(FrameTiming);
705 }
706 }
709 void FrameTimeManager::UpdateFrameLatencyTrackingAfterEndFrame(
710 unsigned char frameLatencyTestColor[3],
711 const Util::FrameTimeRecordSet& rs)
712 {
713 // FrameTiming.NextFrameTime in this context (after EndFrame) is the end frame time.
714 ScreenLatencyTracker.SaveDrawColor(frameLatencyTestColor[0],
715 FrameTiming.NextFrameTime,
716 RenderIMUTimeSeconds,
717 TimewarpIMUTimeSeconds);
719 ScreenLatencyTracker.MatchRecord(rs);
721 // If screen delay changed, update timing.
722 double newScreenDelay = calcScreenDelay();
723 if (newScreenDelay != FrameTiming.Inputs.ScreenDelay)
724 {
725 FrameTiming.Inputs.ScreenDelay = newScreenDelay;
726 LocklessTiming.SetState(FrameTiming);
727 }
728 }
731 //-----------------------------------------------------------------------------------
732 //Revisit dynamic pre-Timewarp delay adjustment logic
733 /*
734 void FrameTimeManager::TimewarpDelayAdjuster::Reset()
735 {
736 State = State_WaitingToReduceLevel;
737 DelayLevel = 0;
738 InitialFrameCounter = 0;
739 TimewarpDelayReductionSeconds = 0.0;
740 DelayLevelFinishTime = 0.0;
742 memset(WaitTimeIndexForLevel, 0, sizeof(WaitTimeIndexForLevel));
743 // If we are at level 0, waits are infinite.
744 WaitTimeIndexForLevel[0] = MaxTimeIndex;
745 }
748 void FrameTimeManager::TimewarpDelayAdjuster::
749 UpdateTimewarpWaitIfSkippedFrames(FrameTimeManager* manager,
750 double measuredFrameDelta, double nextFrameTime)
751 {
752 // Times in seconds
753 const static double delayTimingTiers[7] = { 1.0, 5.0, 15.0, 30.0, 60.0, 120.0, 1000000.0 };
755 const double currentFrameDelta = manager->FrameTiming.Inputs.FrameDelta;
758 // Once we detected frame spike, we skip several frames before testing again.
759 if (InitialFrameCounter > 0)
760 {
761 InitialFrameCounter --;
762 return;
763 }
765 // Skipped frame would usually take 2x longer then regular frame
766 if (measuredFrameDelta > currentFrameDelta * 1.8)
767 {
768 if (State == State_WaitingToReduceLevel)
769 {
770 // If we got here, escalate the level again.
771 if (DelayLevel < MaxDelayLevel)
772 {
773 DelayLevel++;
774 InitialFrameCounter = 3;
775 }
776 }
778 else if (State == State_VerifyingAfterReduce)
779 {
780 // So we went down to this level and tried to wait to see if there was
781 // as skipped frame and there is -> go back up a level and incrment its timing tier
782 if (DelayLevel < MaxDelayLevel)
783 {
784 DelayLevel++;
785 State = State_WaitingToReduceLevel;
787 // For higher level delays reductions, i.e. more then half a frame,
788 // we don't go into the infinite wait tier.
789 int maxTimingTier = MaxTimeIndex;
790 if (DelayLevel > MaxInfiniteTimingLevel)
791 maxTimingTier--;
793 if (WaitTimeIndexForLevel[DelayLevel] < maxTimingTier )
794 WaitTimeIndexForLevel[DelayLevel]++;
795 }
796 }
798 DelayLevelFinishTime = nextFrameTime +
799 delayTimingTiers[WaitTimeIndexForLevel[DelayLevel]];
800 TimewarpDelayReductionSeconds = currentFrameDelta * 0.125 * DelayLevel;
801 manager->updateTimewarpTiming();
803 }
805 else if (nextFrameTime > DelayLevelFinishTime)
806 {
807 if (State == State_WaitingToReduceLevel)
808 {
809 if (DelayLevel > 0)
810 {
811 DelayLevel--;
812 State = State_VerifyingAfterReduce;
813 // Always use 1 sec to see if "down sampling mode" caused problems
814 DelayLevelFinishTime = nextFrameTime + 1.0f;
815 }
816 }
817 else if (State == State_VerifyingAfterReduce)
818 {
819 // Prior display level successfully reduced,
820 // try to see we we could go down further after wait.
821 WaitTimeIndexForLevel[DelayLevel+1] = 0;
822 State = State_WaitingToReduceLevel;
823 DelayLevelFinishTime = nextFrameTime +
824 delayTimingTiers[WaitTimeIndexForLevel[DelayLevel]];
825 }
827 // TBD: Update TimeWarpTiming
828 TimewarpDelayReductionSeconds = currentFrameDelta * 0.125 * DelayLevel;
829 manager->updateTimewarpTiming();
830 }
833 //static int oldDelayLevel = 0;
835 //if (oldDelayLevel != DelayLevel)
836 //{
837 //OVR_DEBUG_LOG(("DelayLevel:%d tReduction = %0.5f ", DelayLevel, TimewarpDelayReductionSeconds));
838 //oldDelayLevel = DelayLevel;
839 //}
840 }
841 */
843 //-----------------------------------------------------------------------------------
844 // ***** TimeDeltaCollector
846 void TimeDeltaCollector::AddTimeDelta(double timeSeconds)
847 {
848 // avoid adding invalid timing values
849 if(timeSeconds < 0.0f)
850 return;
852 if (Count == Capacity)
853 {
854 for(int i=0; i< Count-1; i++)
855 TimeBufferSeconds[i] = TimeBufferSeconds[i+1];
856 Count--;
857 }
858 TimeBufferSeconds[Count++] = timeSeconds;
860 ReCalcMedian = true;
861 }
863 // KevinJ: Better median function
864 double CalculateListMedianRecursive(const double inputList[TimeDeltaCollector::Capacity], int inputListLength, int lessThanSum, int greaterThanSum)
865 {
866 double lessThanMedian[TimeDeltaCollector::Capacity], greaterThanMedian[TimeDeltaCollector::Capacity];
867 int lessThanMedianListLength = 0, greaterThanMedianListLength = 0;
868 double median = inputList[0];
869 int i;
870 for (i = 1; i < inputListLength; i++)
871 {
872 // If same value, spread among lists evenly
873 if (inputList[i] < median || ((i & 1) == 0 && inputList[i] == median))
874 lessThanMedian[lessThanMedianListLength++] = inputList[i];
875 else
876 greaterThanMedian[greaterThanMedianListLength++] = inputList[i];
877 }
878 if (lessThanMedianListLength + lessThanSum == greaterThanMedianListLength + greaterThanSum + 1 ||
879 lessThanMedianListLength + lessThanSum == greaterThanMedianListLength + greaterThanSum - 1)
880 return median;
882 if (lessThanMedianListLength + lessThanSum < greaterThanMedianListLength + greaterThanSum)
883 {
884 lessThanMedian[lessThanMedianListLength++] = median;
885 return CalculateListMedianRecursive(greaterThanMedian, greaterThanMedianListLength, lessThanMedianListLength + lessThanSum, greaterThanSum);
886 }
887 else
888 {
889 greaterThanMedian[greaterThanMedianListLength++] = median;
890 return CalculateListMedianRecursive(lessThanMedian, lessThanMedianListLength, lessThanSum, greaterThanMedianListLength + greaterThanSum);
891 }
892 }
893 // KevinJ: Excludes Firmware hack
894 double TimeDeltaCollector::GetMedianTimeDeltaNoFirmwareHack() const
895 {
896 if (ReCalcMedian)
897 {
898 ReCalcMedian = false;
899 Median = CalculateListMedianRecursive(TimeBufferSeconds, Count, 0, 0);
900 }
901 return Median;
902 }
903 double TimeDeltaCollector::GetMedianTimeDelta() const
904 {
905 if(ReCalcMedian)
906 {
907 double SortedList[Capacity];
908 bool used[Capacity];
910 memset(used, 0, sizeof(used));
911 SortedList[0] = 0.0; // In case Count was 0...
913 // Probably the slowest way to find median...
914 for (int i=0; i<Count; i++)
915 {
916 double smallestDelta = 1000000.0;
917 int index = 0;
919 for (int j = 0; j < Count; j++)
920 {
921 if (!used[j])
922 {
923 if (TimeBufferSeconds[j] < smallestDelta)
924 {
925 smallestDelta = TimeBufferSeconds[j];
926 index = j;
927 }
928 }
929 }
931 // Mark as used
932 used[index] = true;
933 SortedList[i] = smallestDelta;
934 }
936 // FIRMWARE HACK: Don't take the actual median, but err on the low time side
937 Median = SortedList[Count/4];
938 ReCalcMedian = false;
939 }
941 return Median;
942 }
945 }} // namespace OVR::CAPI