nuclear@0: /************************************************************************************ nuclear@0: nuclear@0: Filename : CAPI_FrameTimeManager.h nuclear@0: Content : Manage frame timing and pose prediction for rendering nuclear@0: Created : November 30, 2013 nuclear@0: Authors : Volga Aksoy, Michael Antonov nuclear@0: nuclear@0: Copyright : Copyright 2014 Oculus VR, LLC All Rights reserved. nuclear@0: nuclear@0: Licensed under the Oculus VR Rift SDK License Version 3.2 (the "License"); nuclear@0: you may not use the Oculus VR Rift SDK except in compliance with the License, nuclear@0: which is provided at the time of installation or download, or which nuclear@0: otherwise accompanies this software in either electronic or hard copy form. nuclear@0: nuclear@0: You may obtain a copy of the License at nuclear@0: nuclear@0: http://www.oculusvr.com/licenses/LICENSE-3.2 nuclear@0: nuclear@0: Unless required by applicable law or agreed to in writing, the Oculus VR SDK nuclear@0: distributed under the License is distributed on an "AS IS" BASIS, nuclear@0: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. nuclear@0: See the License for the specific language governing permissions and nuclear@0: limitations under the License. nuclear@0: nuclear@0: ************************************************************************************/ nuclear@0: nuclear@0: #ifndef OVR_CAPI_FrameTimeManager_h nuclear@0: #define OVR_CAPI_FrameTimeManager_h nuclear@0: nuclear@0: #include "../OVR_CAPI.h" nuclear@0: #include "../Kernel/OVR_Timer.h" nuclear@0: #include "../Kernel/OVR_Math.h" nuclear@0: #include "../Util/Util_Render_Stereo.h" nuclear@0: nuclear@0: namespace OVR { namespace CAPI { nuclear@0: nuclear@0: nuclear@0: //------------------------------------------------------------------------------------- nuclear@0: // ***** TimeDeltaCollector nuclear@0: nuclear@0: // Helper class to collect median times between frames, so that we know nuclear@0: // how long to wait. nuclear@0: struct TimeDeltaCollector nuclear@0: { nuclear@0: TimeDeltaCollector() : Median(-1.0), Count(0), ReCalcMedian(true) { } nuclear@0: nuclear@0: void AddTimeDelta(double timeSeconds); nuclear@0: void Clear() { Count = 0; } nuclear@0: nuclear@0: double GetMedianTimeDelta() const; nuclear@0: double GetMedianTimeDeltaNoFirmwareHack() const; nuclear@0: nuclear@0: double GetCount() const { return Count; } nuclear@0: nuclear@0: enum { Capacity = 12 }; nuclear@0: private: nuclear@0: double TimeBufferSeconds[Capacity]; nuclear@0: mutable double Median; nuclear@0: int Count; nuclear@0: mutable bool ReCalcMedian; nuclear@0: }; nuclear@0: nuclear@0: nuclear@0: //------------------------------------------------------------------------------------- nuclear@0: // ***** FrameLatencyTracker nuclear@0: nuclear@0: // FrameLatencyTracker tracks frame Present to display Scan-out timing, as reported by nuclear@0: // the DK2 internal latency tester pixel read-back. The computed value is used in nuclear@0: // FrameTimeManager for prediction. View Render and TimeWarp to scan-out latencies are nuclear@0: // also reported for debugging. nuclear@0: // nuclear@0: // The class operates by generating color values from GetNextDrawColor() that must nuclear@0: // be rendered on the back end and then looking for matching values in FrameTimeRecordSet nuclear@0: // structure as reported by HW. nuclear@0: nuclear@0: class FrameLatencyTracker nuclear@0: { nuclear@0: public: nuclear@0: nuclear@0: enum { FramesTracked = Util::LT2_IncrementCount-1 }; nuclear@0: nuclear@0: FrameLatencyTracker(); nuclear@0: nuclear@0: // DrawColor == 0 is special in that it doesn't need saving of timestamp nuclear@0: unsigned char GetNextDrawColor(); nuclear@0: nuclear@0: void SaveDrawColor(unsigned char drawColor, double endFrameTime, nuclear@0: double renderIMUTime, double timewarpIMUTime ); nuclear@0: nuclear@0: void MatchRecord(const Util::FrameTimeRecordSet &r); nuclear@0: nuclear@0: bool IsLatencyTimingAvailable(); nuclear@0: void GetLatencyTimings(float& latencyRender, float& latencyTimewarp, float& latencyPostPresent); nuclear@0: nuclear@0: void Reset(); nuclear@0: nuclear@0: public: nuclear@0: nuclear@0: struct FrameTimeRecordEx : public Util::FrameTimeRecord nuclear@0: { nuclear@0: bool MatchedRecord; nuclear@0: double RenderIMUTimeSeconds; nuclear@0: double TimewarpIMUTimeSeconds; nuclear@0: }; nuclear@0: nuclear@0: // True if rendering read-back is enabled. nuclear@0: bool TrackerEnabled; nuclear@0: nuclear@0: enum SampleWaitType { nuclear@0: SampleWait_Zeroes, // We are waiting for a record with all zeros. nuclear@0: SampleWait_Match // We are issuing & matching colors. nuclear@0: }; nuclear@0: nuclear@0: SampleWaitType WaitMode; nuclear@0: int MatchCount; nuclear@0: // Records of frame timings that we are trying to measure. nuclear@0: FrameTimeRecordEx FrameEndTimes[FramesTracked]; nuclear@0: int FrameIndex; nuclear@0: // Median filter for (ScanoutTimeSeconds - PostPresent frame time) nuclear@0: TimeDeltaCollector FrameDeltas; nuclear@0: // Latency reporting results nuclear@0: double RenderLatencySeconds; nuclear@0: double TimewarpLatencySeconds; nuclear@0: double LatencyRecordTime; nuclear@0: }; nuclear@0: nuclear@0: nuclear@0: nuclear@0: //------------------------------------------------------------------------------------- nuclear@0: // ***** FrameTimeManager nuclear@0: nuclear@0: // FrameTimeManager keeps track of rendered frame timing and handles predictions for nuclear@0: // orientations and time-warp. nuclear@0: nuclear@0: class FrameTimeManager nuclear@0: { nuclear@0: public: nuclear@0: FrameTimeManager(bool vsyncEnabled); nuclear@0: nuclear@0: // Data that affects frame timing computation. nuclear@0: struct TimingInputs nuclear@0: { nuclear@0: // Hard-coded value or dynamic as reported by FrameTimeDeltas.GetMedianTimeDelta(). nuclear@0: double FrameDelta; nuclear@0: // Screen delay from present to scan-out, as potentially reported by ScreenLatencyTracker. nuclear@0: double ScreenDelay; nuclear@0: // Negative value of how many seconds before EndFrame we start timewarp. 0.0 if not used. nuclear@0: double TimewarpWaitDelta; nuclear@0: nuclear@0: TimingInputs() nuclear@0: : FrameDelta(0), ScreenDelay(0), TimewarpWaitDelta(0) nuclear@0: { } nuclear@0: }; nuclear@0: nuclear@0: // Timing values for a specific frame. nuclear@0: struct Timing nuclear@0: { nuclear@0: TimingInputs Inputs; nuclear@0: nuclear@0: // Index of a frame that started at ThisFrameTime. nuclear@0: unsigned int FrameIndex; nuclear@0: // Predicted absolute times for when this frame will show up on screen. nuclear@0: // Generally, all values will be >= NextFrameTime, since that's the time we expect next nuclear@0: // vsync to succeed. nuclear@0: double ThisFrameTime; nuclear@0: double TimewarpPointTime; nuclear@0: double NextFrameTime; nuclear@0: double MidpointTime; nuclear@0: double EyeRenderTimes[2]; nuclear@0: double TimeWarpStartEndTimes[2][2]; nuclear@0: nuclear@0: Timing() nuclear@0: { nuclear@0: memset(this, 0, sizeof(Timing)); nuclear@0: } nuclear@0: nuclear@0: void InitTimingFromInputs(const TimingInputs& inputs, HmdShutterTypeEnum shutterType, nuclear@0: double thisFrameTime, unsigned int frameIndex); nuclear@0: }; nuclear@0: nuclear@0: nuclear@0: // Called on startup to provided data on HMD timing. nuclear@0: void Init(HmdRenderInfo& renderInfo); nuclear@0: nuclear@0: // Called with each new ConfigureRendering. nuclear@0: void ResetFrameTiming(unsigned frameIndex, nuclear@0: bool dynamicPrediction, bool sdkRender); nuclear@0: nuclear@0: void SetVsync(bool enabled) { VsyncEnabled = enabled; } nuclear@0: nuclear@0: // BeginFrame returns time of the call nuclear@0: // TBD: Should this be a predicted time value instead ? nuclear@0: double BeginFrame(unsigned frameIndex); nuclear@0: void EndFrame(); nuclear@0: nuclear@0: // Thread-safe function to query timing for a future frame nuclear@0: Timing GetFrameTiming(unsigned frameIndex); nuclear@0: nuclear@0: // if eye == ovrEye_Count, timing is for MidpointTime as opposed to any specific eye nuclear@0: double GetEyePredictionTime(ovrEyeType eye, unsigned int frameIndex); nuclear@0: ovrTrackingState GetEyePredictionTracking(ovrHmd hmd, ovrEyeType eye, unsigned int frameIndex); nuclear@0: Posef GetEyePredictionPose(ovrHmd hmd, ovrEyeType eye); nuclear@0: nuclear@0: void GetTimewarpPredictions(ovrEyeType eye, double timewarpStartEnd[2]); nuclear@0: void GetTimewarpMatrices(ovrHmd hmd, ovrEyeType eye, ovrPosef renderPose, ovrMatrix4f twmOut[2],double debugTimingOffsetInSeconds = 0.0); nuclear@0: nuclear@0: // Used by renderer to determine if it should time distortion rendering. nuclear@0: bool NeedDistortionTimeMeasurement() const; nuclear@0: void AddDistortionTimeMeasurement(double distortionTimeSeconds); nuclear@0: nuclear@0: nuclear@0: // DK2 Latency test interface nuclear@0: nuclear@0: // Get next draw color for DK2 latency tester (3-byte RGB) nuclear@0: void GetFrameLatencyTestDrawColor(unsigned char outColor[3]) nuclear@0: { nuclear@0: outColor[0] = ScreenLatencyTracker.GetNextDrawColor(); nuclear@0: outColor[1] = ScreenLatencyTracker.IsLatencyTimingAvailable() ? 255 : 0; nuclear@0: outColor[2] = ScreenLatencyTracker.IsLatencyTimingAvailable() ? 0 : 255; nuclear@0: } nuclear@0: nuclear@0: // Must be called after EndFrame() to update latency tester timings. nuclear@0: // Must pass color reported by NextFrameColor for this frame. nuclear@0: void UpdateFrameLatencyTrackingAfterEndFrame(unsigned char frameLatencyTestColor[3], nuclear@0: const Util::FrameTimeRecordSet& rs); nuclear@0: nuclear@0: void GetLatencyTimings(float& latencyRender, float& latencyTimewarp, float& latencyPostPresent) nuclear@0: { nuclear@0: return ScreenLatencyTracker.GetLatencyTimings(latencyRender, latencyTimewarp, latencyPostPresent); nuclear@0: } nuclear@0: nuclear@0: const Timing& GetFrameTiming() const { return FrameTiming; } nuclear@0: nuclear@0: private: nuclear@0: double calcFrameDelta() const; nuclear@0: double calcScreenDelay() const; nuclear@0: double calcTimewarpWaitDelta() const; nuclear@0: nuclear@0: //Revisit dynamic pre-Timewarp delay adjustment logic nuclear@0: /* nuclear@0: void updateTimewarpTiming(); nuclear@0: nuclear@0: nuclear@0: nuclear@0: // TimewarpDelayAdjuster implements a simple state machine that reduces the amount nuclear@0: // of time-warp waiting based on skipped frames. nuclear@0: struct TimewarpDelayAdjuster nuclear@0: { nuclear@0: enum StateInLevel nuclear@0: { nuclear@0: // We are ok at this level, and will be waiting for some time before trying to reduce. nuclear@0: State_WaitingToReduceLevel, nuclear@0: // After decrementing a level, we are verifying that this won't cause skipped frames. nuclear@0: State_VerifyingAfterReduce nuclear@0: }; nuclear@0: nuclear@0: enum { nuclear@0: MaxDelayLevel = 5, nuclear@0: MaxInfiniteTimingLevel = 3, nuclear@0: MaxTimeIndex = 6 nuclear@0: }; nuclear@0: nuclear@0: StateInLevel State; nuclear@0: // Current level. Higher levels means larger delay reduction (smaller overall time-warp delay). nuclear@0: int DelayLevel; nuclear@0: // Index for the amount of time we'd wait in this level. If attempt to decrease level fails, nuclear@0: // with is incrementing causing the level to become "sticky". nuclear@0: int WaitTimeIndexForLevel[MaxTimeIndex + 1]; nuclear@0: // We skip few frames after each escalation to avoid too rapid of a reduction. nuclear@0: int InitialFrameCounter; nuclear@0: // What th currect "reduction" currently is. nuclear@0: double TimewarpDelayReductionSeconds; nuclear@0: // When we should try changing the level again. nuclear@0: double DelayLevelFinishTime; nuclear@0: nuclear@0: public: nuclear@0: TimewarpDelayAdjuster() { Reset(); } nuclear@0: nuclear@0: void Reset(); nuclear@0: nuclear@0: void UpdateTimewarpWaitIfSkippedFrames(FrameTimeManager* manager, nuclear@0: double measuredFrameDelta, nuclear@0: double nextFrameTime); nuclear@0: nuclear@0: double GetDelayReduction() const { return TimewarpDelayReductionSeconds; } nuclear@0: }; nuclear@0: */ nuclear@0: nuclear@0: nuclear@0: HmdRenderInfo RenderInfo; nuclear@0: // Timings are collected through a median filter, to avoid outliers. nuclear@0: TimeDeltaCollector FrameTimeDeltas; nuclear@0: TimeDeltaCollector DistortionRenderTimes; nuclear@0: FrameLatencyTracker ScreenLatencyTracker; nuclear@0: nuclear@0: // Timing changes if we have no Vsync (all prediction is reduced to fixed interval). nuclear@0: bool VsyncEnabled; nuclear@0: // Set if we are rendering via the SDK, so DistortionRenderTimes is valid. nuclear@0: bool DynamicPrediction; nuclear@0: // Set if SDk is doing the rendering. nuclear@0: bool SdkRender; nuclear@0: // Direct to rift. nuclear@0: bool DirectToRift; nuclear@0: nuclear@0: // Total frame delay due to VsyncToFirstScanline, persistence and settle time. nuclear@0: // Computed from RenderInfor.Shutter. nuclear@0: double VSyncToScanoutDelay; nuclear@0: double NoVSyncToScanoutDelay; nuclear@0: double ScreenSwitchingDelay; nuclear@0: nuclear@0: //Revisit dynamic pre-Timewarp delay adjustment logic nuclear@0: //TimewarpDelayAdjuster TimewarpAdjuster; nuclear@0: nuclear@0: // Current (or last) frame timing info. Used as a source for LocklessTiming. nuclear@0: Timing FrameTiming; nuclear@0: // TBD: Don't we need NextFrame here as well? nuclear@0: LocklessUpdater LocklessTiming; nuclear@0: nuclear@0: // IMU Read timings nuclear@0: double RenderIMUTimeSeconds; nuclear@0: double TimewarpIMUTimeSeconds; nuclear@0: }; nuclear@0: nuclear@0: nuclear@0: }} // namespace OVR::CAPI nuclear@0: nuclear@0: #endif // OVR_CAPI_FrameTimeManager_h