ovr_sdk
diff LibOVR/Src/CAPI/CAPI_FrameTimeManager.cpp @ 0:1b39a1b46319
initial 0.4.4
author | John Tsiombikas <nuclear@member.fsf.org> |
---|---|
date | Wed, 14 Jan 2015 06:51:16 +0200 |
parents | |
children |
line diff
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/LibOVR/Src/CAPI/CAPI_FrameTimeManager.cpp Wed Jan 14 06:51:16 2015 +0200 1.3 @@ -0,0 +1,946 @@ 1.4 +/************************************************************************************ 1.5 + 1.6 +Filename : CAPI_FrameTimeManager.cpp 1.7 +Content : Manage frame timing and pose prediction for rendering 1.8 +Created : November 30, 2013 1.9 +Authors : Volga Aksoy, Michael Antonov 1.10 + 1.11 +Copyright : Copyright 2014 Oculus VR, LLC All Rights reserved. 1.12 + 1.13 +Licensed under the Oculus VR Rift SDK License Version 3.2 (the "License"); 1.14 +you may not use the Oculus VR Rift SDK except in compliance with the License, 1.15 +which is provided at the time of installation or download, or which 1.16 +otherwise accompanies this software in either electronic or hard copy form. 1.17 + 1.18 +You may obtain a copy of the License at 1.19 + 1.20 +http://www.oculusvr.com/licenses/LICENSE-3.2 1.21 + 1.22 +Unless required by applicable law or agreed to in writing, the Oculus VR SDK 1.23 +distributed under the License is distributed on an "AS IS" BASIS, 1.24 +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1.25 +See the License for the specific language governing permissions and 1.26 +limitations under the License. 1.27 + 1.28 +************************************************************************************/ 1.29 + 1.30 +#include "CAPI_FrameTimeManager.h" 1.31 + 1.32 +#include "../Kernel/OVR_Log.h" 1.33 + 1.34 +namespace OVR { namespace CAPI { 1.35 + 1.36 + 1.37 +//------------------------------------------------------------------------------------- 1.38 +// ***** FrameLatencyTracker 1.39 + 1.40 + 1.41 +FrameLatencyTracker::FrameLatencyTracker() 1.42 +{ 1.43 + Reset(); 1.44 +} 1.45 + 1.46 + 1.47 +void FrameLatencyTracker::Reset() 1.48 +{ 1.49 + TrackerEnabled = true; 1.50 + WaitMode = SampleWait_Zeroes; 1.51 + MatchCount = 0; 1.52 + memset(FrameEndTimes, 0, sizeof(FrameEndTimes)); 1.53 + FrameIndex = 0; 1.54 + //FrameDeltas 1.55 + RenderLatencySeconds = 0.0; 1.56 + TimewarpLatencySeconds = 0.0; 1.57 + LatencyRecordTime = 0.0; 1.58 + 1.59 + FrameDeltas.Clear(); 1.60 +} 1.61 + 1.62 + 1.63 +unsigned char FrameLatencyTracker::GetNextDrawColor() 1.64 +{ 1.65 + if (!TrackerEnabled || (WaitMode == SampleWait_Zeroes) || 1.66 + (FrameIndex >= FramesTracked)) 1.67 + { 1.68 + return (unsigned char)Util::FrameTimeRecord::ReadbackIndexToColor(0); 1.69 + } 1.70 + 1.71 + OVR_ASSERT(FrameIndex < FramesTracked); 1.72 + return (unsigned char)Util::FrameTimeRecord::ReadbackIndexToColor(FrameIndex+1); 1.73 +} 1.74 + 1.75 + 1.76 +void FrameLatencyTracker::SaveDrawColor(unsigned char drawColor, double endFrameTime, 1.77 + double renderIMUTime, double timewarpIMUTime ) 1.78 +{ 1.79 + if (!TrackerEnabled || (WaitMode == SampleWait_Zeroes)) 1.80 + return; 1.81 + 1.82 + if (FrameIndex < FramesTracked) 1.83 + { 1.84 + OVR_ASSERT(Util::FrameTimeRecord::ReadbackIndexToColor(FrameIndex+1) == drawColor); 1.85 + OVR_UNUSED(drawColor); 1.86 + 1.87 + // saves {color, endFrame time} 1.88 + FrameEndTimes[FrameIndex].ReadbackIndex = FrameIndex + 1; 1.89 + FrameEndTimes[FrameIndex].TimeSeconds = endFrameTime; 1.90 + FrameEndTimes[FrameIndex].RenderIMUTimeSeconds = renderIMUTime; 1.91 + FrameEndTimes[FrameIndex].TimewarpIMUTimeSeconds= timewarpIMUTime; 1.92 + FrameEndTimes[FrameIndex].MatchedRecord = false; 1.93 + FrameIndex++; 1.94 + } 1.95 + else 1.96 + { 1.97 + // If the request was outstanding for too long, switch to zero mode to restart. 1.98 + if (endFrameTime > (FrameEndTimes[FrameIndex-1].TimeSeconds + 0.15)) 1.99 + { 1.100 + if (MatchCount == 0) 1.101 + { 1.102 + // If nothing was matched, we have no latency reading. 1.103 + RenderLatencySeconds = 0.0; 1.104 + TimewarpLatencySeconds = 0.0; 1.105 + } 1.106 + 1.107 + WaitMode = SampleWait_Zeroes; 1.108 + MatchCount = 0; 1.109 + FrameIndex = 0; 1.110 + } 1.111 + } 1.112 +} 1.113 + 1.114 + 1.115 +void FrameLatencyTracker::MatchRecord(const Util::FrameTimeRecordSet &r) 1.116 +{ 1.117 + if (!TrackerEnabled) 1.118 + return; 1.119 + 1.120 + if (WaitMode == SampleWait_Zeroes) 1.121 + { 1.122 + // Do we have all zeros? 1.123 + if (r.IsAllZeroes()) 1.124 + { 1.125 + OVR_ASSERT(FrameIndex == 0); 1.126 + WaitMode = SampleWait_Match; 1.127 + MatchCount = 0; 1.128 + } 1.129 + return; 1.130 + } 1.131 + 1.132 + // We are in Match Mode. Wait until all colors are matched or timeout, 1.133 + // at which point we go back to zeros. 1.134 + 1.135 + for (int i = 0; i < FrameIndex; i++) 1.136 + { 1.137 + int recordIndex = 0; 1.138 + int consecutiveMatch = 0; 1.139 + 1.140 + OVR_ASSERT(FrameEndTimes[i].ReadbackIndex != 0); 1.141 + 1.142 + if (r.FindReadbackIndex(&recordIndex, FrameEndTimes[i].ReadbackIndex)) 1.143 + { 1.144 + // Advance forward to see that we have several more matches. 1.145 + int ri = recordIndex + 1; 1.146 + int j = i + 1; 1.147 + 1.148 + consecutiveMatch++; 1.149 + 1.150 + for (; (j < FrameIndex) && (ri < Util::FrameTimeRecordSet::RecordCount); j++, ri++) 1.151 + { 1.152 + if (r[ri].ReadbackIndex != FrameEndTimes[j].ReadbackIndex) 1.153 + break; 1.154 + consecutiveMatch++; 1.155 + } 1.156 + 1.157 + // Match at least 2 items in the row, to avoid accidentally matching color. 1.158 + if (consecutiveMatch > 1) 1.159 + { 1.160 + // Record latency values for all but last samples. Keep last 2 samples 1.161 + // for the future to simplify matching. 1.162 + for (int q = 0; q < consecutiveMatch; q++) 1.163 + { 1.164 + const Util::FrameTimeRecord &scanoutFrame = r[recordIndex+q]; 1.165 + FrameTimeRecordEx &renderFrame = FrameEndTimes[i+q]; 1.166 + 1.167 + if (!renderFrame.MatchedRecord) 1.168 + { 1.169 + double deltaSeconds = scanoutFrame.TimeSeconds - renderFrame.TimeSeconds; 1.170 + if (deltaSeconds > 0.0) 1.171 + { 1.172 + FrameDeltas.AddTimeDelta(deltaSeconds); 1.173 + 1.174 + // FIRMWARE HACK: don't take new readings if they're 10ms higher than previous reading 1.175 + // but only do that for 1 second, after that accept it regardless of the timing difference 1.176 + double newRenderLatency = scanoutFrame.TimeSeconds - renderFrame.RenderIMUTimeSeconds; 1.177 + if( newRenderLatency < RenderLatencySeconds + 0.01 || 1.178 + scanoutFrame.TimeSeconds > LatencyRecordTime + 1.0) 1.179 + { 1.180 + LatencyRecordTime = scanoutFrame.TimeSeconds; 1.181 + RenderLatencySeconds = scanoutFrame.TimeSeconds - renderFrame.RenderIMUTimeSeconds; 1.182 + TimewarpLatencySeconds = (renderFrame.TimewarpIMUTimeSeconds == 0.0) ? 0.0 : 1.183 + (scanoutFrame.TimeSeconds - renderFrame.TimewarpIMUTimeSeconds); 1.184 + } 1.185 + } 1.186 + 1.187 + renderFrame.MatchedRecord = true; 1.188 + MatchCount++; 1.189 + } 1.190 + } 1.191 + 1.192 + // Exit for. 1.193 + break; 1.194 + } 1.195 + } 1.196 + } // for ( i => FrameIndex ) 1.197 + 1.198 + 1.199 + // If we matched all frames, start over. 1.200 + if (MatchCount == FramesTracked) 1.201 + { 1.202 + WaitMode = SampleWait_Zeroes; 1.203 + MatchCount = 0; 1.204 + FrameIndex = 0; 1.205 + } 1.206 +} 1.207 + 1.208 +bool FrameLatencyTracker::IsLatencyTimingAvailable() 1.209 +{ 1.210 + return ovr_GetTimeInSeconds() < (LatencyRecordTime + 2.0); 1.211 +} 1.212 + 1.213 +void FrameLatencyTracker::GetLatencyTimings(float& latencyRender, float& latencyTimewarp, float& latencyPostPresent) 1.214 +{ 1.215 + if (!IsLatencyTimingAvailable()) 1.216 + { 1.217 + latencyRender = 0.0f; 1.218 + latencyTimewarp = 0.0f; 1.219 + latencyPostPresent = 0.0f; 1.220 + } 1.221 + else 1.222 + { 1.223 + latencyRender = (float)RenderLatencySeconds; 1.224 + latencyTimewarp = (float)TimewarpLatencySeconds; 1.225 + latencyPostPresent = (float)FrameDeltas.GetMedianTimeDelta(); 1.226 + } 1.227 +} 1.228 + 1.229 + 1.230 +//------------------------------------------------------------------------------------- 1.231 +// ***** FrameTimeManager 1.232 + 1.233 +FrameTimeManager::FrameTimeManager(bool vsyncEnabled) : 1.234 + RenderInfo(), 1.235 + FrameTimeDeltas(), 1.236 + DistortionRenderTimes(), 1.237 + ScreenLatencyTracker(), 1.238 + VsyncEnabled(vsyncEnabled), 1.239 + DynamicPrediction(true), 1.240 + SdkRender(false), 1.241 + //DirectToRift(false), Initialized below. 1.242 + //VSyncToScanoutDelay(0.0), Initialized below. 1.243 + //NoVSyncToScanoutDelay(0.0), Initialized below. 1.244 + ScreenSwitchingDelay(0.0), 1.245 + FrameTiming(), 1.246 + LocklessTiming(), 1.247 + RenderIMUTimeSeconds(0.0), 1.248 + TimewarpIMUTimeSeconds(0.0) 1.249 +{ 1.250 + // If driver is in use, 1.251 + DirectToRift = !Display::InCompatibilityMode(false); 1.252 + if (DirectToRift) 1.253 + { 1.254 + // The latest driver provides a post-present vsync-to-scan-out delay 1.255 + // that is roughly zero. The latency tester will provide real numbers 1.256 + // but when it is unavailable for some reason, we should default to 1.257 + // an expected value. 1.258 + VSyncToScanoutDelay = 0.0001f; 1.259 + } 1.260 + else 1.261 + { 1.262 + // HACK: SyncToScanoutDelay observed close to 1 frame in video cards. 1.263 + // Overwritten by dynamic latency measurement on DK2. 1.264 + VSyncToScanoutDelay = 0.013f; 1.265 + } 1.266 + NoVSyncToScanoutDelay = 0.004f; 1.267 +} 1.268 + 1.269 +void FrameTimeManager::Init(HmdRenderInfo& renderInfo) 1.270 +{ 1.271 + // Set up prediction distances. 1.272 + // With-Vsync timings. 1.273 + RenderInfo = renderInfo; 1.274 + 1.275 + ScreenSwitchingDelay = RenderInfo.Shutter.PixelSettleTime * 0.5f + 1.276 + RenderInfo.Shutter.PixelPersistence * 0.5f; 1.277 +} 1.278 + 1.279 +void FrameTimeManager::ResetFrameTiming(unsigned frameIndex, 1.280 + bool dynamicPrediction, 1.281 + bool sdkRender) 1.282 +{ 1.283 + DynamicPrediction = dynamicPrediction; 1.284 + SdkRender = sdkRender; 1.285 + 1.286 + FrameTimeDeltas.Clear(); 1.287 + DistortionRenderTimes.Clear(); 1.288 + ScreenLatencyTracker.Reset(); 1.289 + //Revisit dynamic pre-Timewarp delay adjustment logic 1.290 + //TimewarpAdjuster.Reset(); 1.291 + 1.292 + FrameTiming.FrameIndex = frameIndex; 1.293 + FrameTiming.NextFrameTime = 0.0; 1.294 + FrameTiming.ThisFrameTime = 0.0; 1.295 + FrameTiming.Inputs.FrameDelta = calcFrameDelta(); 1.296 + // This one is particularly critical, and has been missed in the past because 1.297 + // this init function wasn't called for app-rendered. 1.298 + FrameTiming.Inputs.ScreenDelay = calcScreenDelay(); 1.299 + FrameTiming.Inputs.TimewarpWaitDelta = 0.0f; 1.300 + 1.301 + LocklessTiming.SetState(FrameTiming); 1.302 +} 1.303 + 1.304 + 1.305 +double FrameTimeManager::calcFrameDelta() const 1.306 +{ 1.307 + // Timing difference between frame is tracked by FrameTimeDeltas, or 1.308 + // is a hard-coded value of 1/FrameRate. 1.309 + double frameDelta; 1.310 + 1.311 + if (!VsyncEnabled) 1.312 + { 1.313 + frameDelta = 0.0; 1.314 + } 1.315 + else if (FrameTimeDeltas.GetCount() > 3) 1.316 + { 1.317 + frameDelta = FrameTimeDeltas.GetMedianTimeDelta(); 1.318 + if (frameDelta > (RenderInfo.Shutter.VsyncToNextVsync + 0.001)) 1.319 + frameDelta = RenderInfo.Shutter.VsyncToNextVsync; 1.320 + } 1.321 + else 1.322 + { 1.323 + frameDelta = RenderInfo.Shutter.VsyncToNextVsync; 1.324 + } 1.325 + 1.326 + return frameDelta; 1.327 +} 1.328 + 1.329 + 1.330 +double FrameTimeManager::calcScreenDelay() const 1.331 +{ 1.332 + double screenDelay = ScreenSwitchingDelay; 1.333 + double measuredVSyncToScanout; 1.334 + 1.335 + // Use real-time DK2 latency tester HW for prediction if its is working. 1.336 + // Do sanity check under 60 ms 1.337 + if (!VsyncEnabled) 1.338 + { 1.339 + screenDelay += NoVSyncToScanoutDelay; 1.340 + } 1.341 + else if ( DynamicPrediction && 1.342 + (ScreenLatencyTracker.FrameDeltas.GetCount() > 3) && 1.343 + (measuredVSyncToScanout = ScreenLatencyTracker.FrameDeltas.GetMedianTimeDelta(), 1.344 + (measuredVSyncToScanout > -0.0001) && (measuredVSyncToScanout < 0.06)) ) 1.345 + { 1.346 + screenDelay += measuredVSyncToScanout; 1.347 + } 1.348 + else 1.349 + { 1.350 + screenDelay += VSyncToScanoutDelay; 1.351 + } 1.352 + 1.353 + return screenDelay; 1.354 +} 1.355 + 1.356 +double FrameTimeManager::calcTimewarpWaitDelta() const 1.357 +{ 1.358 + // If timewarp timing hasn't been calculated, we should wait. 1.359 + if (!VsyncEnabled) 1.360 + return 0.0; 1.361 + 1.362 + if (SdkRender) 1.363 + { 1.364 + if (NeedDistortionTimeMeasurement()) 1.365 + return 0.0; 1.366 + return -(DistortionRenderTimes.GetMedianTimeDelta() + 0.0035); 1.367 + 1.368 + //Revisit dynamic pre-Timewarp delay adjustment logic 1.369 + /*return -(DistortionRenderTimes.GetMedianTimeDelta() + 0.002 + 1.370 + TimewarpAdjuster.GetDelayReduction());*/ 1.371 + } 1.372 + 1.373 + // Just a hard-coded "high" value for game-drawn code. 1.374 + // TBD: Just return 0 and let users calculate this themselves? 1.375 + return -0.004; 1.376 + 1.377 + //Revisit dynamic pre-Timewarp delay adjustment logic 1.378 + //return -(0.003 + TimewarpAdjuster.GetDelayReduction()); 1.379 +} 1.380 + 1.381 +//Revisit dynamic pre-Timewarp delay adjustment logic 1.382 +/* 1.383 +void FrameTimeManager::updateTimewarpTiming() 1.384 +{ 1.385 + // If timewarp timing changes based on this sample, update it. 1.386 + double newTimewarpWaitDelta = calcTimewarpWaitDelta(); 1.387 + if (newTimewarpWaitDelta != FrameTiming.Inputs.TimewarpWaitDelta) 1.388 + { 1.389 + FrameTiming.Inputs.TimewarpWaitDelta = newTimewarpWaitDelta; 1.390 + LocklessTiming.SetState(FrameTiming); 1.391 + } 1.392 +} 1.393 +*/ 1.394 + 1.395 +void FrameTimeManager::Timing::InitTimingFromInputs(const FrameTimeManager::TimingInputs& inputs, 1.396 + HmdShutterTypeEnum shutterType, 1.397 + double thisFrameTime, unsigned int frameIndex) 1.398 +{ 1.399 + // ThisFrameTime comes from the end of last frame, unless it it changed. 1.400 + double nextFrameBase; 1.401 + double frameDelta = inputs.FrameDelta; 1.402 + 1.403 + FrameIndex = frameIndex; 1.404 + 1.405 + ThisFrameTime = thisFrameTime; 1.406 + NextFrameTime = ThisFrameTime + frameDelta; 1.407 + nextFrameBase = NextFrameTime + inputs.ScreenDelay; 1.408 + MidpointTime = nextFrameBase + frameDelta * 0.5; 1.409 + TimewarpPointTime = (inputs.TimewarpWaitDelta == 0.0) ? 1.410 + 0.0 : (NextFrameTime + inputs.TimewarpWaitDelta); 1.411 + 1.412 + // Calculate absolute points in time when eye rendering or corresponding time-warp 1.413 + // screen edges will become visible. 1.414 + // This only matters with VSync. 1.415 + switch(shutterType) 1.416 + { 1.417 + case HmdShutter_RollingTopToBottom: 1.418 + EyeRenderTimes[0] = MidpointTime; 1.419 + EyeRenderTimes[1] = MidpointTime; 1.420 + TimeWarpStartEndTimes[0][0] = nextFrameBase; 1.421 + TimeWarpStartEndTimes[0][1] = nextFrameBase + frameDelta; 1.422 + TimeWarpStartEndTimes[1][0] = nextFrameBase; 1.423 + TimeWarpStartEndTimes[1][1] = nextFrameBase + frameDelta; 1.424 + break; 1.425 + case HmdShutter_RollingLeftToRight: 1.426 + EyeRenderTimes[0] = nextFrameBase + frameDelta * 0.25; 1.427 + EyeRenderTimes[1] = nextFrameBase + frameDelta * 0.75; 1.428 + 1.429 + /* 1.430 + // TBD: MA: It is probably better if mesh sets it up per-eye. 1.431 + // Would apply if screen is 0 -> 1 for each eye mesh 1.432 + TimeWarpStartEndTimes[0][0] = nextFrameBase; 1.433 + TimeWarpStartEndTimes[0][1] = MidpointTime; 1.434 + TimeWarpStartEndTimes[1][0] = MidpointTime; 1.435 + TimeWarpStartEndTimes[1][1] = nextFrameBase + frameDelta; 1.436 + */ 1.437 + 1.438 + // Mesh is set up to vary from Edge of scree 0 -> 1 across both eyes 1.439 + TimeWarpStartEndTimes[0][0] = nextFrameBase; 1.440 + TimeWarpStartEndTimes[0][1] = nextFrameBase + frameDelta; 1.441 + TimeWarpStartEndTimes[1][0] = nextFrameBase; 1.442 + TimeWarpStartEndTimes[1][1] = nextFrameBase + frameDelta; 1.443 + 1.444 + break; 1.445 + case HmdShutter_RollingRightToLeft: 1.446 + 1.447 + EyeRenderTimes[0] = nextFrameBase + frameDelta * 0.75; 1.448 + EyeRenderTimes[1] = nextFrameBase + frameDelta * 0.25; 1.449 + 1.450 + // This is *Correct* with Tom's distortion mesh organization. 1.451 + TimeWarpStartEndTimes[0][0] = nextFrameBase ; 1.452 + TimeWarpStartEndTimes[0][1] = nextFrameBase + frameDelta; 1.453 + TimeWarpStartEndTimes[1][0] = nextFrameBase ; 1.454 + TimeWarpStartEndTimes[1][1] = nextFrameBase + frameDelta; 1.455 + break; 1.456 + case HmdShutter_Global: 1.457 + // TBD 1.458 + EyeRenderTimes[0] = MidpointTime; 1.459 + EyeRenderTimes[1] = MidpointTime; 1.460 + TimeWarpStartEndTimes[0][0] = MidpointTime; 1.461 + TimeWarpStartEndTimes[0][1] = MidpointTime; 1.462 + TimeWarpStartEndTimes[1][0] = MidpointTime; 1.463 + TimeWarpStartEndTimes[1][1] = MidpointTime; 1.464 + break; 1.465 + default: 1.466 + break; 1.467 + } 1.468 +} 1.469 + 1.470 + 1.471 +double FrameTimeManager::BeginFrame(unsigned frameIndex) 1.472 +{ 1.473 + RenderIMUTimeSeconds = 0.0; 1.474 + TimewarpIMUTimeSeconds = 0.0; 1.475 + 1.476 + // TPH - putting an assert so this doesn't remain a hidden problem. 1.477 + OVR_ASSERT(FrameTiming.Inputs.ScreenDelay != 0); 1.478 + 1.479 + // ThisFrameTime comes from the end of last frame, unless it it changed. 1.480 + double thisFrameTime = (FrameTiming.NextFrameTime != 0.0) ? 1.481 + FrameTiming.NextFrameTime : ovr_GetTimeInSeconds(); 1.482 + 1.483 + // We are starting to process a new frame... 1.484 + FrameTiming.InitTimingFromInputs(FrameTiming.Inputs, RenderInfo.Shutter.Type, 1.485 + thisFrameTime, frameIndex); 1.486 + 1.487 + return FrameTiming.ThisFrameTime; 1.488 +} 1.489 + 1.490 + 1.491 +void FrameTimeManager::EndFrame() 1.492 +{ 1.493 + // Record timing since last frame; must be called after Present & sync. 1.494 + FrameTiming.NextFrameTime = ovr_GetTimeInSeconds(); 1.495 + if (FrameTiming.ThisFrameTime > 0.0) 1.496 + { 1.497 + //Revisit dynamic pre-Timewarp delay adjustment logic 1.498 + /* 1.499 + double actualFrameDelta = FrameTiming.NextFrameTime - FrameTiming.ThisFrameTime; 1.500 + 1.501 + if (VsyncEnabled) 1.502 + TimewarpAdjuster.UpdateTimewarpWaitIfSkippedFrames(this, actualFrameDelta, 1.503 + FrameTiming.NextFrameTime); 1.504 + 1.505 + FrameTimeDeltas.AddTimeDelta(actualFrameDelta); 1.506 + */ 1.507 + FrameTimeDeltas.AddTimeDelta(FrameTiming.NextFrameTime - FrameTiming.ThisFrameTime); 1.508 + FrameTiming.Inputs.FrameDelta = calcFrameDelta(); 1.509 + } 1.510 + 1.511 + // Write to Lock-less 1.512 + LocklessTiming.SetState(FrameTiming); 1.513 +} 1.514 + 1.515 +// Thread-safe function to query timing for a future frame 1.516 + 1.517 +FrameTimeManager::Timing FrameTimeManager::GetFrameTiming(unsigned frameIndex) 1.518 +{ 1.519 + Timing frameTiming = LocklessTiming.GetState(); 1.520 + 1.521 + if (frameTiming.ThisFrameTime == 0.0) 1.522 + { 1.523 + // If timing hasn't been initialized, starting based on "now" is the best guess. 1.524 + frameTiming.InitTimingFromInputs(frameTiming.Inputs, RenderInfo.Shutter.Type, 1.525 + ovr_GetTimeInSeconds(), frameIndex); 1.526 + } 1.527 + 1.528 + else if (frameIndex > frameTiming.FrameIndex) 1.529 + { 1.530 + unsigned frameDelta = frameIndex - frameTiming.FrameIndex; 1.531 + double thisFrameTime = frameTiming.NextFrameTime + 1.532 + double(frameDelta-1) * frameTiming.Inputs.FrameDelta; 1.533 + // Don't run away too far into the future beyond rendering. 1.534 + OVR_DEBUG_LOG_COND(frameDelta >= 6, ("GetFrameTiming is 6 or more frames in future beyond rendering!")); 1.535 + 1.536 + frameTiming.InitTimingFromInputs(frameTiming.Inputs, RenderInfo.Shutter.Type, 1.537 + thisFrameTime, frameIndex); 1.538 + } 1.539 + 1.540 + return frameTiming; 1.541 +} 1.542 + 1.543 + 1.544 +double FrameTimeManager::GetEyePredictionTime(ovrEyeType eye, unsigned int frameIndex) 1.545 +{ 1.546 + if (VsyncEnabled) 1.547 + { 1.548 + FrameTimeManager::Timing frameTiming = GetFrameTiming(frameIndex); 1.549 + 1.550 + // Special case: ovrEye_Count predicts to midpoint 1.551 + return (eye == ovrEye_Count) ? frameTiming.MidpointTime : frameTiming.EyeRenderTimes[eye]; 1.552 + } 1.553 + 1.554 + // No VSync: Best guess for the near future 1.555 + return ovr_GetTimeInSeconds() + ScreenSwitchingDelay + NoVSyncToScanoutDelay; 1.556 +} 1.557 + 1.558 +ovrTrackingState FrameTimeManager::GetEyePredictionTracking(ovrHmd hmd, ovrEyeType eye, unsigned int frameIndex) 1.559 +{ 1.560 + double eyeRenderTime = GetEyePredictionTime(eye, frameIndex); 1.561 + ovrTrackingState eyeState = ovrHmd_GetTrackingState(hmd, eyeRenderTime); 1.562 + 1.563 + // Record view pose sampling time for Latency reporting. 1.564 + if (RenderIMUTimeSeconds == 0.0) 1.565 + { 1.566 + // TODO: Figure out why this are not as accurate as ovr_GetTimeInSeconds() 1.567 + //RenderIMUTimeSeconds = eyeState.RawSensorData.TimeInSeconds; 1.568 + RenderIMUTimeSeconds = ovr_GetTimeInSeconds(); 1.569 + } 1.570 + 1.571 + return eyeState; 1.572 +} 1.573 + 1.574 +Posef FrameTimeManager::GetEyePredictionPose(ovrHmd hmd, ovrEyeType eye) 1.575 +{ 1.576 + double eyeRenderTime = GetEyePredictionTime(eye, 0); 1.577 + ovrTrackingState eyeState = ovrHmd_GetTrackingState(hmd, eyeRenderTime); 1.578 + 1.579 + // Record view pose sampling time for Latency reporting. 1.580 + if (RenderIMUTimeSeconds == 0.0) 1.581 + { 1.582 + // TODO: Figure out why this are not as accurate as ovr_GetTimeInSeconds() 1.583 + //RenderIMUTimeSeconds = eyeState.RawSensorData.TimeInSeconds; 1.584 + RenderIMUTimeSeconds = ovr_GetTimeInSeconds(); 1.585 + } 1.586 + 1.587 + return eyeState.HeadPose.ThePose; 1.588 +} 1.589 + 1.590 +void FrameTimeManager::GetTimewarpPredictions(ovrEyeType eye, double timewarpStartEnd[2]) 1.591 +{ 1.592 + if (VsyncEnabled) 1.593 + { 1.594 + timewarpStartEnd[0] = FrameTiming.TimeWarpStartEndTimes[eye][0]; 1.595 + timewarpStartEnd[1] = FrameTiming.TimeWarpStartEndTimes[eye][1]; 1.596 + return; 1.597 + } 1.598 + 1.599 + // Free-running, so this will be displayed immediately. 1.600 + // Unfortunately we have no idea which bit of the screen is actually going to be displayed. 1.601 + // TODO: guess which bit of the screen is being displayed! 1.602 + // (e.g. use DONOTWAIT on present and see when the return isn't WASSTILLWAITING?) 1.603 + 1.604 + // We have no idea where scan-out is currently, so we can't usefully warp the screen spatially. 1.605 + timewarpStartEnd[0] = ovr_GetTimeInSeconds() + ScreenSwitchingDelay + NoVSyncToScanoutDelay; 1.606 + timewarpStartEnd[1] = timewarpStartEnd[0]; 1.607 +} 1.608 + 1.609 + 1.610 +void FrameTimeManager::GetTimewarpMatrices(ovrHmd hmd, ovrEyeType eyeId, 1.611 + ovrPosef renderPose, ovrMatrix4f twmOut[2], 1.612 + double debugTimingOffsetInSeconds) 1.613 +{ 1.614 + if (!hmd) 1.615 + { 1.616 + return; 1.617 + } 1.618 + 1.619 + double timewarpStartEnd[2] = { 0.0, 0.0 }; 1.620 + GetTimewarpPredictions(eyeId, timewarpStartEnd); 1.621 + 1.622 + //TPH, to vary timing, to allow developers to debug, to shunt the predicted time forward 1.623 + //and back, and see if the SDK is truly delivering the correct time. Also to allow 1.624 + //illustration of the detrimental effects when this is not done right. 1.625 + timewarpStartEnd[0] += debugTimingOffsetInSeconds; 1.626 + timewarpStartEnd[1] += debugTimingOffsetInSeconds; 1.627 + 1.628 + 1.629 + //HMDState* p = (HMDState*)hmd; 1.630 + ovrTrackingState startState = ovrHmd_GetTrackingState(hmd, timewarpStartEnd[0]); 1.631 + ovrTrackingState endState = ovrHmd_GetTrackingState(hmd, timewarpStartEnd[1]); 1.632 + 1.633 + if (TimewarpIMUTimeSeconds == 0.0) 1.634 + { 1.635 + // TODO: Figure out why this are not as accurate as ovr_GetTimeInSeconds() 1.636 + //TimewarpIMUTimeSeconds = startState.RawSensorData.TimeInSeconds; 1.637 + TimewarpIMUTimeSeconds = ovr_GetTimeInSeconds(); 1.638 + } 1.639 + 1.640 + Quatf quatFromStart = startState.HeadPose.ThePose.Orientation; 1.641 + Quatf quatFromEnd = endState.HeadPose.ThePose.Orientation; 1.642 + Quatf quatFromEye = renderPose.Orientation; //EyeRenderPoses[eyeId].Orientation; 1.643 + quatFromEye.Invert(); // because we need the view matrix, not the camera matrix 1.644 + 1.645 + Quatf timewarpStartQuat = quatFromEye * quatFromStart; 1.646 + Quatf timewarpEndQuat = quatFromEye * quatFromEnd; 1.647 + 1.648 + Matrix4f timewarpStart(timewarpStartQuat); 1.649 + Matrix4f timewarpEnd(timewarpEndQuat); 1.650 + 1.651 + 1.652 + // The real-world orientations have: X=right, Y=up, Z=backwards. 1.653 + // The vectors inside the mesh are in NDC to keep the shader simple: X=right, Y=down, Z=forwards. 1.654 + // So we need to perform a similarity transform on this delta matrix. 1.655 + // The verbose code would look like this: 1.656 + /* 1.657 + Matrix4f matBasisChange; 1.658 + matBasisChange.SetIdentity(); 1.659 + matBasisChange.M[0][0] = 1.0f; 1.660 + matBasisChange.M[1][1] = -1.0f; 1.661 + matBasisChange.M[2][2] = -1.0f; 1.662 + Matrix4f matBasisChangeInv = matBasisChange.Inverted(); 1.663 + matRenderFromNow = matBasisChangeInv * matRenderFromNow * matBasisChange; 1.664 + */ 1.665 + // ...but of course all the above is a constant transform and much more easily done. 1.666 + // We flip the signs of the Y&Z row, then flip the signs of the Y&Z column, 1.667 + // and of course most of the flips cancel: 1.668 + // +++ +-- +-- 1.669 + // +++ -> flip Y&Z columns -> +-- -> flip Y&Z rows -> -++ 1.670 + // +++ +-- -++ 1.671 + timewarpStart.M[0][1] = -timewarpStart.M[0][1]; 1.672 + timewarpStart.M[0][2] = -timewarpStart.M[0][2]; 1.673 + timewarpStart.M[1][0] = -timewarpStart.M[1][0]; 1.674 + timewarpStart.M[2][0] = -timewarpStart.M[2][0]; 1.675 + 1.676 + timewarpEnd .M[0][1] = -timewarpEnd .M[0][1]; 1.677 + timewarpEnd .M[0][2] = -timewarpEnd .M[0][2]; 1.678 + timewarpEnd .M[1][0] = -timewarpEnd .M[1][0]; 1.679 + timewarpEnd .M[2][0] = -timewarpEnd .M[2][0]; 1.680 + 1.681 + twmOut[0] = timewarpStart; 1.682 + twmOut[1] = timewarpEnd; 1.683 +} 1.684 + 1.685 + 1.686 +// Used by renderer to determine if it should time distortion rendering. 1.687 +bool FrameTimeManager::NeedDistortionTimeMeasurement() const 1.688 +{ 1.689 + if (!VsyncEnabled) 1.690 + return false; 1.691 + return DistortionRenderTimes.GetCount() < DistortionRenderTimes.Capacity; 1.692 +} 1.693 + 1.694 + 1.695 +void FrameTimeManager::AddDistortionTimeMeasurement(double distortionTimeSeconds) 1.696 +{ 1.697 + DistortionRenderTimes.AddTimeDelta(distortionTimeSeconds); 1.698 + 1.699 + //Revisit dynamic pre-Timewarp delay adjustment logic 1.700 + //updateTimewarpTiming(); 1.701 + 1.702 + // If timewarp timing changes based on this sample, update it. 1.703 + double newTimewarpWaitDelta = calcTimewarpWaitDelta(); 1.704 + if (newTimewarpWaitDelta != FrameTiming.Inputs.TimewarpWaitDelta) 1.705 + { 1.706 + FrameTiming.Inputs.TimewarpWaitDelta = newTimewarpWaitDelta; 1.707 + LocklessTiming.SetState(FrameTiming); 1.708 + } 1.709 +} 1.710 + 1.711 + 1.712 +void FrameTimeManager::UpdateFrameLatencyTrackingAfterEndFrame( 1.713 + unsigned char frameLatencyTestColor[3], 1.714 + const Util::FrameTimeRecordSet& rs) 1.715 +{ 1.716 + // FrameTiming.NextFrameTime in this context (after EndFrame) is the end frame time. 1.717 + ScreenLatencyTracker.SaveDrawColor(frameLatencyTestColor[0], 1.718 + FrameTiming.NextFrameTime, 1.719 + RenderIMUTimeSeconds, 1.720 + TimewarpIMUTimeSeconds); 1.721 + 1.722 + ScreenLatencyTracker.MatchRecord(rs); 1.723 + 1.724 + // If screen delay changed, update timing. 1.725 + double newScreenDelay = calcScreenDelay(); 1.726 + if (newScreenDelay != FrameTiming.Inputs.ScreenDelay) 1.727 + { 1.728 + FrameTiming.Inputs.ScreenDelay = newScreenDelay; 1.729 + LocklessTiming.SetState(FrameTiming); 1.730 + } 1.731 +} 1.732 + 1.733 + 1.734 +//----------------------------------------------------------------------------------- 1.735 +//Revisit dynamic pre-Timewarp delay adjustment logic 1.736 +/* 1.737 +void FrameTimeManager::TimewarpDelayAdjuster::Reset() 1.738 +{ 1.739 + State = State_WaitingToReduceLevel; 1.740 + DelayLevel = 0; 1.741 + InitialFrameCounter = 0; 1.742 + TimewarpDelayReductionSeconds = 0.0; 1.743 + DelayLevelFinishTime = 0.0; 1.744 + 1.745 + memset(WaitTimeIndexForLevel, 0, sizeof(WaitTimeIndexForLevel)); 1.746 + // If we are at level 0, waits are infinite. 1.747 + WaitTimeIndexForLevel[0] = MaxTimeIndex; 1.748 +} 1.749 + 1.750 + 1.751 +void FrameTimeManager::TimewarpDelayAdjuster:: 1.752 + UpdateTimewarpWaitIfSkippedFrames(FrameTimeManager* manager, 1.753 + double measuredFrameDelta, double nextFrameTime) 1.754 +{ 1.755 + // Times in seconds 1.756 + const static double delayTimingTiers[7] = { 1.0, 5.0, 15.0, 30.0, 60.0, 120.0, 1000000.0 }; 1.757 + 1.758 + const double currentFrameDelta = manager->FrameTiming.Inputs.FrameDelta; 1.759 + 1.760 + 1.761 + // Once we detected frame spike, we skip several frames before testing again. 1.762 + if (InitialFrameCounter > 0) 1.763 + { 1.764 + InitialFrameCounter --; 1.765 + return; 1.766 + } 1.767 + 1.768 + // Skipped frame would usually take 2x longer then regular frame 1.769 + if (measuredFrameDelta > currentFrameDelta * 1.8) 1.770 + { 1.771 + if (State == State_WaitingToReduceLevel) 1.772 + { 1.773 + // If we got here, escalate the level again. 1.774 + if (DelayLevel < MaxDelayLevel) 1.775 + { 1.776 + DelayLevel++; 1.777 + InitialFrameCounter = 3; 1.778 + } 1.779 + } 1.780 + 1.781 + else if (State == State_VerifyingAfterReduce) 1.782 + { 1.783 + // So we went down to this level and tried to wait to see if there was 1.784 + // as skipped frame and there is -> go back up a level and incrment its timing tier 1.785 + if (DelayLevel < MaxDelayLevel) 1.786 + { 1.787 + DelayLevel++; 1.788 + State = State_WaitingToReduceLevel; 1.789 + 1.790 + // For higher level delays reductions, i.e. more then half a frame, 1.791 + // we don't go into the infinite wait tier. 1.792 + int maxTimingTier = MaxTimeIndex; 1.793 + if (DelayLevel > MaxInfiniteTimingLevel) 1.794 + maxTimingTier--; 1.795 + 1.796 + if (WaitTimeIndexForLevel[DelayLevel] < maxTimingTier ) 1.797 + WaitTimeIndexForLevel[DelayLevel]++; 1.798 + } 1.799 + } 1.800 + 1.801 + DelayLevelFinishTime = nextFrameTime + 1.802 + delayTimingTiers[WaitTimeIndexForLevel[DelayLevel]]; 1.803 + TimewarpDelayReductionSeconds = currentFrameDelta * 0.125 * DelayLevel; 1.804 + manager->updateTimewarpTiming(); 1.805 + 1.806 + } 1.807 + 1.808 + else if (nextFrameTime > DelayLevelFinishTime) 1.809 + { 1.810 + if (State == State_WaitingToReduceLevel) 1.811 + { 1.812 + if (DelayLevel > 0) 1.813 + { 1.814 + DelayLevel--; 1.815 + State = State_VerifyingAfterReduce; 1.816 + // Always use 1 sec to see if "down sampling mode" caused problems 1.817 + DelayLevelFinishTime = nextFrameTime + 1.0f; 1.818 + } 1.819 + } 1.820 + else if (State == State_VerifyingAfterReduce) 1.821 + { 1.822 + // Prior display level successfully reduced, 1.823 + // try to see we we could go down further after wait. 1.824 + WaitTimeIndexForLevel[DelayLevel+1] = 0; 1.825 + State = State_WaitingToReduceLevel; 1.826 + DelayLevelFinishTime = nextFrameTime + 1.827 + delayTimingTiers[WaitTimeIndexForLevel[DelayLevel]]; 1.828 + } 1.829 + 1.830 + // TBD: Update TimeWarpTiming 1.831 + TimewarpDelayReductionSeconds = currentFrameDelta * 0.125 * DelayLevel; 1.832 + manager->updateTimewarpTiming(); 1.833 + } 1.834 + 1.835 + 1.836 + //static int oldDelayLevel = 0; 1.837 + 1.838 + //if (oldDelayLevel != DelayLevel) 1.839 + //{ 1.840 + //OVR_DEBUG_LOG(("DelayLevel:%d tReduction = %0.5f ", DelayLevel, TimewarpDelayReductionSeconds)); 1.841 + //oldDelayLevel = DelayLevel; 1.842 + //} 1.843 + } 1.844 + */ 1.845 + 1.846 +//----------------------------------------------------------------------------------- 1.847 +// ***** TimeDeltaCollector 1.848 + 1.849 +void TimeDeltaCollector::AddTimeDelta(double timeSeconds) 1.850 +{ 1.851 + // avoid adding invalid timing values 1.852 + if(timeSeconds < 0.0f) 1.853 + return; 1.854 + 1.855 + if (Count == Capacity) 1.856 + { 1.857 + for(int i=0; i< Count-1; i++) 1.858 + TimeBufferSeconds[i] = TimeBufferSeconds[i+1]; 1.859 + Count--; 1.860 + } 1.861 + TimeBufferSeconds[Count++] = timeSeconds; 1.862 + 1.863 + ReCalcMedian = true; 1.864 +} 1.865 + 1.866 +// KevinJ: Better median function 1.867 +double CalculateListMedianRecursive(const double inputList[TimeDeltaCollector::Capacity], int inputListLength, int lessThanSum, int greaterThanSum) 1.868 +{ 1.869 + double lessThanMedian[TimeDeltaCollector::Capacity], greaterThanMedian[TimeDeltaCollector::Capacity]; 1.870 + int lessThanMedianListLength = 0, greaterThanMedianListLength = 0; 1.871 + double median = inputList[0]; 1.872 + int i; 1.873 + for (i = 1; i < inputListLength; i++) 1.874 + { 1.875 + // If same value, spread among lists evenly 1.876 + if (inputList[i] < median || ((i & 1) == 0 && inputList[i] == median)) 1.877 + lessThanMedian[lessThanMedianListLength++] = inputList[i]; 1.878 + else 1.879 + greaterThanMedian[greaterThanMedianListLength++] = inputList[i]; 1.880 + } 1.881 + if (lessThanMedianListLength + lessThanSum == greaterThanMedianListLength + greaterThanSum + 1 || 1.882 + lessThanMedianListLength + lessThanSum == greaterThanMedianListLength + greaterThanSum - 1) 1.883 + return median; 1.884 + 1.885 + if (lessThanMedianListLength + lessThanSum < greaterThanMedianListLength + greaterThanSum) 1.886 + { 1.887 + lessThanMedian[lessThanMedianListLength++] = median; 1.888 + return CalculateListMedianRecursive(greaterThanMedian, greaterThanMedianListLength, lessThanMedianListLength + lessThanSum, greaterThanSum); 1.889 + } 1.890 + else 1.891 + { 1.892 + greaterThanMedian[greaterThanMedianListLength++] = median; 1.893 + return CalculateListMedianRecursive(lessThanMedian, lessThanMedianListLength, lessThanSum, greaterThanMedianListLength + greaterThanSum); 1.894 + } 1.895 +} 1.896 +// KevinJ: Excludes Firmware hack 1.897 +double TimeDeltaCollector::GetMedianTimeDeltaNoFirmwareHack() const 1.898 +{ 1.899 + if (ReCalcMedian) 1.900 + { 1.901 + ReCalcMedian = false; 1.902 + Median = CalculateListMedianRecursive(TimeBufferSeconds, Count, 0, 0); 1.903 + } 1.904 + return Median; 1.905 +} 1.906 +double TimeDeltaCollector::GetMedianTimeDelta() const 1.907 +{ 1.908 + if(ReCalcMedian) 1.909 + { 1.910 + double SortedList[Capacity]; 1.911 + bool used[Capacity]; 1.912 + 1.913 + memset(used, 0, sizeof(used)); 1.914 + SortedList[0] = 0.0; // In case Count was 0... 1.915 + 1.916 + // Probably the slowest way to find median... 1.917 + for (int i=0; i<Count; i++) 1.918 + { 1.919 + double smallestDelta = 1000000.0; 1.920 + int index = 0; 1.921 + 1.922 + for (int j = 0; j < Count; j++) 1.923 + { 1.924 + if (!used[j]) 1.925 + { 1.926 + if (TimeBufferSeconds[j] < smallestDelta) 1.927 + { 1.928 + smallestDelta = TimeBufferSeconds[j]; 1.929 + index = j; 1.930 + } 1.931 + } 1.932 + } 1.933 + 1.934 + // Mark as used 1.935 + used[index] = true; 1.936 + SortedList[i] = smallestDelta; 1.937 + } 1.938 + 1.939 + // FIRMWARE HACK: Don't take the actual median, but err on the low time side 1.940 + Median = SortedList[Count/4]; 1.941 + ReCalcMedian = false; 1.942 + } 1.943 + 1.944 + return Median; 1.945 +} 1.946 + 1.947 + 1.948 +}} // namespace OVR::CAPI 1.949 +