aboutsummaryrefslogtreecommitdiffstats
path: root/LibOVR/Src/CAPI/CAPI_FrameTimeManager3.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'LibOVR/Src/CAPI/CAPI_FrameTimeManager3.cpp')
-rw-r--r--LibOVR/Src/CAPI/CAPI_FrameTimeManager3.cpp517
1 files changed, 517 insertions, 0 deletions
diff --git a/LibOVR/Src/CAPI/CAPI_FrameTimeManager3.cpp b/LibOVR/Src/CAPI/CAPI_FrameTimeManager3.cpp
new file mode 100644
index 0000000..d42ba41
--- /dev/null
+++ b/LibOVR/Src/CAPI/CAPI_FrameTimeManager3.cpp
@@ -0,0 +1,517 @@
+/************************************************************************************
+
+Filename : CAPI_FrameTimeManager3.cpp
+Content : Manage frame timing and pose prediction for rendering
+Created : November 30, 2013
+Authors : Volga Aksoy, Michael Antonov
+
+Copyright : Copyright 2014 Oculus VR, Inc. All Rights reserved.
+
+Licensed under the Oculus VR Rift SDK License Version 3.2 (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.2
+
+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 "CAPI_FrameTimeManager3.h"
+
+#include "Kernel/OVR_Log.h"
+
+//#include <dxgi.h>
+
+namespace OVR { namespace CAPI { namespace FTM3 {
+
+
+// Number of frame delta samples to include in the median calculation.
+static const int kFrameDeltaSamples = 12;
+
+
+void LogTime(const char* msg)
+{
+ static double lastTime = 0.0;
+ double now = ovr_GetTimeInSeconds();
+
+ LogText("t=%.3f, dt=%.3f: %s\n", now, now-lastTime, msg);
+ lastTime = now;
+}
+
+
+//-------------------------------------------------------------------------------------
+
+FrameTimeManagerCore::FrameTimeManagerCore(bool vsyncEnabled) :
+ VsyncEnabled(vsyncEnabled),
+ LastTiming(),
+ FrameTimeDeltas(kFrameDeltaSamples)
+{
+}
+
+void FrameTimeManagerCore::Initialize(const Timing& defaultTiming)
+{
+ // Contains default frame delta and indices.
+ // - TBD determine how those are initialized
+ //
+ FrameTimeDeltas.Clear();
+ FrameIndices.Reset();
+
+ LastTiming = defaultTiming;
+ DefaultFrameDelta = defaultTiming.FrameDelta;
+
+ LocklessTiming.SetState(LastTiming);
+}
+
+
+FrameTimeManagerCore::Timing FrameTimeManagerCore::GetAppFrameTiming(unsigned appFrameIndex)
+{
+ // Right now this is required for timing.
+
+ Timing timing = LocklessTiming.GetState();
+
+ // TBD: We need AppFrameIndex and DisplayIndex initialized before this is called.
+
+ OVR_ASSERT(appFrameIndex == 0 || appFrameIndex == timing.AppFrameIndex + 1);
+
+ if (appFrameIndex > timing.AppFrameIndex)
+ {
+ int appFrameDelta = appFrameIndex - timing.AppFrameIndex;
+
+ // Don't run away too far into the future beyond rendering.
+ // OVR_ASSERT(appFrameDelta < 6);
+
+ // Convert to display frames. If we have one-to-one frame sync, appFrameDelta
+ // will be 1.0, so this will give us a correct frame value.
+ int displayFrame = timing.DisplayFrameIndex +
+ (int) (appFrameDelta * ( 1.0 / timing.AppToDisplayFrameRatio));
+
+ double prevFrameSubmitSeconds = (timing.FrameSubmitSeconds == 0.0) ?
+ ovr_GetTimeInSeconds() : timing.FrameSubmitSeconds;
+
+ double displayFrameSubmitTime = prevFrameSubmitSeconds +
+ double(displayFrame-timing.DisplayFrameIndex) *
+ timing.FrameDelta;
+
+ // Initialize to new values.
+ timing.AppFrameIndex = appFrameIndex;
+ timing.DisplayFrameIndex = displayFrame;
+ timing.FrameSubmitSeconds = displayFrameSubmitTime;
+ }
+
+ return timing;
+}
+
+
+// Next: GetDisplayFrameTiming - used for timewarp and late-latching
+//
+FrameTimeManagerCore::Timing FrameTimeManagerCore::GetDisplayFrameTiming(unsigned displayFrameIndex)
+{
+ // Thus assumes caller has checked the 'displayFrameIndex' agains the current
+ // clock/vsync and this is the actual desired value.
+
+ // TBD: Should we use Lockless here? It depends if we plan to access this form different threads.
+ Timing timing = LastTiming;
+
+ if (displayFrameIndex > timing.DisplayFrameIndex)
+ {
+ double prevFrameSubmitSeconds = (timing.FrameSubmitSeconds == 0.0) ?
+ ovr_GetTimeInSeconds() : timing.FrameSubmitSeconds;
+
+ // Initialize to new values.
+ timing.FrameSubmitSeconds = prevFrameSubmitSeconds +
+ double(displayFrameIndex - timing.DisplayFrameIndex) *
+ timing.FrameDelta;
+ timing.DisplayFrameIndex = displayFrameIndex;
+
+ // Last submitted AppFrameIndex is ok.
+ }
+
+ return timing;
+}
+
+
+void FrameTimeManagerCore::SubmitDisplayFrame( unsigned displayFrameIndex,
+ unsigned appFrameIndex,
+ double scanoutStartSeconds )
+{
+ int displayFrameDelta = (displayFrameIndex - LastTiming.DisplayFrameIndex);
+
+ // FIXME: Add queue ahead support here by detecting two frames targeting the same scanout.
+
+ // Update frameDelta tracking.
+ if ((LastTiming.FrameSubmitSeconds > 0.0) && (displayFrameDelta < 2))
+ {
+ if (displayFrameDelta > 0)
+ {
+ double thisFrameDelta = (scanoutStartSeconds - LastTiming.FrameSubmitSeconds) / displayFrameDelta;
+ FrameTimeDeltas.Add(thisFrameDelta);
+ }
+ LastTiming.FrameDelta = calcFrameDelta();
+ }
+
+ // Update indices mapping
+ FrameIndices.Add(displayFrameIndex, appFrameIndex);
+
+ LastTiming.AppFrameIndex = appFrameIndex;
+ LastTiming.DisplayFrameIndex = displayFrameIndex;
+ LastTiming.FrameSubmitSeconds = scanoutStartSeconds;
+ LastTiming.AppToDisplayFrameRatio = FrameIndices.GetAppToDisplayFrameRatio();
+
+ // Update Lockless
+ LocklessTiming.SetState(LastTiming);
+}
+
+
+
+double FrameTimeManagerCore::calcFrameDelta() const
+{
+ // Timing difference between frame is tracked by FrameTimeDeltas, or
+ // is a hard-coded value of 1/FrameRate.
+ double frameDelta;
+
+ if (!VsyncEnabled)
+ {
+ frameDelta = 0.0;
+ }
+ else if (FrameTimeDeltas.GetCount() > 3)
+ {
+ frameDelta = FrameTimeDeltas.GetMedian();
+ if (frameDelta > (DefaultFrameDelta + 0.001))
+ frameDelta = DefaultFrameDelta;
+ }
+ else
+ {
+ frameDelta = DefaultFrameDelta;
+ }
+
+ return frameDelta;
+}
+
+
+
+/*
+
+// ***** Support code
+
+struct FrameStatisticsSampler
+{
+
+ FrameStatisticsSampler(IDXGISwapChain* swapChain, double frameDelta);
+
+ DXGI_FRAME_STATISTICS FStats;
+ double LastReportedFrameSeconds;
+ unsigned CurrentDisplayFrameIndex;
+ unsigned CurrentFrameScanoutSeconds;
+};
+
+FrameStatisticsSampler::FrameStatisticsSampler(IDXGISwapChain* swapChain, double frameDelta)
+{
+ swapChain->GetFrameStatistics(&FStats);
+
+ UINT lastReportedFrameIndex = FStats.SyncRefreshCount;
+ LastReportedFrameSeconds = Timer::GetSecondsFromOSTicks(FStats.SyncQPCTime.QuadPart);
+
+ double currentSeconds = ovr_GetTimeInSeconds();
+
+ // Determine what frame we are currently scanning out and when it started.
+ // This will be a part of input for FrameTimeManager...
+ CurrentDisplayFrameIndex = lastReportedFrameIndex +
+ (unsigned)((currentSeconds - LastReportedFrameSeconds) / frameDelta);
+ CurrentFrameScanoutSeconds = LastReportedFrameSeconds +
+ (CurrentDisplayFrameIndex - lastReportedFrameIndex) * frameDelta;
+}
+
+
+// Default, initial frame timing
+FrameTimeManagerCore::Timing GetDefaultTiming(unsigned appFrameIndex,
+ IDXGISwapChain* swapChain, const HmdRenderInfo& hri)
+{
+ FrameStatisticsSampler fstats(swapChain, hri.Shutter.VsyncToNextVsync);
+ FrameTimeManagerCore::Timing t;
+
+ t.FrameDelta = hri.Shutter.VsyncToNextVsync;
+ t.AppFrameIndex = appFrameIndex;
+ t.DisplayFrameIndex = fstats.CurrentDisplayFrameIndex;
+ t.FrameSubmitSeconds = fstats.CurrentFrameScanoutSeconds;
+ t.AppToDisplayFrameRatio = 1.0;
+
+ return t;
+}
+
+
+void InitializeFrame()
+{
+ FrameStatisticsSampler fstats(pswapChain, ftm->GetFrameDelta());
+
+ unsigned DisplayFrameIndex;
+ //= FI(clock) + 1;
+}
+
+
+
+void UpdateFrame()
+{
+ IDXGISwapChain* pswapChain = 0; // TBD: Pass argument
+ FrameTimeManagerCore* ftm = 0;
+
+ FrameStatisticsSampler fstats(pswapChain, ftm->GetFrameDelta());
+
+ // Note that for rendering, there is actually a separate DisplayFrameIndex to think about.
+
+ // Target 'Display FrameIndex' for next frame. Fix-up if the clock avanced too much.
+ DisplayFrameIndex++;
+ if (fstats.CurrentDisplayFrameIndex >= DisplayFrameIndex)
+ DisplayFrameIndex = fstats.CurrentDisplayFrameIndex + 1;
+
+ GetDisplayFrameTiming(DisplayFrameIndex);
+
+}
+
+FrameTimeManagerCore::ScanoutTimes::ScanoutTimes(
+ const TimingInputs& inputs,
+ double frameSubmitSeconds, HmdShutterTypeEnum shutterType)
+{
+ double frameDelta = inputs.FrameDelta;
+
+ ScanoutStartSeconds = frameSubmitSeconds + inputs.ScreenDelay;
+ ScanoutMidpointSeconds = ScanoutStartSeconds + frameDelta * 0.5;
+
+ //...
+}
+*/
+
+
+//-----------------------------------------------------------------------------------
+// MedianCalculator
+
+MedianCalculator::MedianCalculator(int capacity) :
+ Capacity(capacity)
+{
+ Data.Resize(Capacity);
+ SortBuffer.Resize(Capacity);
+
+ Clear();
+}
+
+void MedianCalculator::Clear()
+{
+ MinValue = 0.;
+ MaxValue = 0.;
+ MeanValue = 0.;
+ MedianValue = 0.;
+ Index = 0;
+ DataCount = 0;
+ Recalculate = false;
+}
+
+void MedianCalculator::Add(double datum)
+{
+ // Write next datum
+ Data[Index] = datum;
+
+ // Loop circular buffer index around
+ if (++Index >= Capacity)
+ {
+ Index = 0;
+ }
+
+ // Update data count
+ if (!AtCapacity())
+ {
+ // Limited to capacity
+ ++DataCount;
+ }
+
+ // Mark as dirty
+ Recalculate = true;
+}
+
+double MedianCalculator::GetMedian()
+{
+ if (Recalculate)
+ {
+ doRecalculate();
+ }
+
+ return MedianValue;
+}
+
+bool MedianCalculator::GetStats(double& minValue, double& maxValue,
+ double& meanValue, double& medianValue)
+{
+ if (GetCount() <= 0)
+ {
+ // Cannot get statistics without one data point.
+ return false;
+ }
+
+ if (Recalculate)
+ {
+ doRecalculate();
+ }
+
+ minValue = MinValue;
+ maxValue = MaxValue;
+ meanValue = MeanValue;
+ medianValue = MedianValue;
+ return true;
+}
+
+/*
+ quick_select()
+
+ Calculates the median.
+
+ This Quickselect routine is based on the algorithm described in
+ "Numerical recipes in C", Second Edition,
+ Cambridge University Press, 1992, Section 8.5, ISBN 0-521-43108-5
+ This code by Nicolas Devillard - 1998. Public domain.
+*/
+
+#define ELEM_SWAP(a,b) {double t=(a);(a)=(b);(b)=t; }
+
+static double quick_select(double arr[], int n)
+{
+ int low, high ;
+ int median;
+ int middle, ll, hh;
+ low = 0 ; high = n-1 ; median = (low + high) / 2;
+ for (;;) {
+ if (high <= low) /* One element only */
+ return arr[median] ;
+ if (high == low + 1) { /* Two elements only */
+ if (arr[low] > arr[high])
+ ELEM_SWAP(arr[low], arr[high]) ;
+ return arr[median] ;
+ }
+ /* Find median of low, middle and high items; swap into position low */
+ middle = (low + high) / 2;
+ if (arr[middle] > arr[high]) ELEM_SWAP(arr[middle], arr[high]) ;
+ if (arr[low] > arr[high]) ELEM_SWAP(arr[low], arr[high]) ;
+ if (arr[middle] > arr[low]) ELEM_SWAP(arr[middle], arr[low]) ;
+ /* Swap low item (now in position middle) into position (low+1) */
+ ELEM_SWAP(arr[middle], arr[low+1]) ;
+ /* Nibble from each end towards middle, swapping items when stuck */
+ ll = low + 1;
+ hh = high;
+ for (;;) {
+ do ll++; while (arr[low] > arr[ll]) ;
+ do hh--; while (arr[hh] > arr[low]) ;
+ if (hh < ll)
+ break;
+ ELEM_SWAP(arr[ll], arr[hh]) ;
+ }
+ /* Swap middle item (in position low) back into correct position */
+ ELEM_SWAP(arr[low], arr[hh]) ;
+ /* Re-set active partition */
+ if (hh <= median)
+ low = ll;
+ if (hh >= median)
+ high = hh - 1;
+ }
+}
+#undef ELEM_SWAP
+
+void MedianCalculator::doRecalculate()
+{
+ Recalculate = false;
+
+ // Set default result
+ MinValue = 0.;
+ MaxValue = 0.;
+ MeanValue = 0.;
+ MedianValue = 0.;
+
+ // Calculate median
+ const int count = GetCount();
+ for (int i = 0; i < count; ++i)
+ SortBuffer[i] = Data[i];
+ MedianValue = quick_select(&SortBuffer[0], count);
+
+ double minValue = Data[0], maxValue = Data[0], sumValue = Data[0];
+
+ for (int i = 1; i < count; ++i)
+ {
+ double value = Data[i];
+
+ if (value < minValue)
+ {
+ minValue = value;
+ }
+
+ if (value > maxValue)
+ {
+ maxValue = value;
+ }
+
+ sumValue += value;
+ }
+
+ MinValue = minValue;
+ MaxValue = maxValue;
+ MeanValue = sumValue / count;
+
+ OVR_ASSERT(MinValue <= MeanValue && MeanValue <= MaxValue);
+ OVR_ASSERT(MinValue <= MedianValue && MedianValue <= MaxValue);
+}
+
+
+//-----------------------------------------------------------------------------------
+// ***** FrameIndexMapper
+
+void FrameIndexMapper::Add(unsigned displayFrameIndex, unsigned appFrameIndex)
+{
+ // Circular buffer; fill up then overwrite tail
+ if (Count == Capacity)
+ {
+ DisplayFrameIndices[StartIndex] = displayFrameIndex;
+ AppFrameIndices[StartIndex] = appFrameIndex;
+ StartIndex++;
+ if (StartIndex == Count)
+ StartIndex = 0;
+ }
+ else
+ {
+ OVR_ASSERT(StartIndex == 0);
+ DisplayFrameIndices[Count] = displayFrameIndex;
+ AppFrameIndices[Count] = appFrameIndex;
+ Count++;
+ }
+}
+
+double FrameIndexMapper::GetAppToDisplayFrameRatio() const
+{
+ // Default ratio is one-to-one. Do aggressive clamping since we
+ // don't want ratio to go too low since we can't predict that far anyway.
+ if (Count < 3)
+ return 1.0;
+
+ unsigned newestIndex = StartIndex + Count - 1;
+ if (newestIndex >= Capacity)
+ newestIndex -= Capacity;
+
+ unsigned displayDelta = DisplayFrameIndices[newestIndex] - DisplayFrameIndices[StartIndex];
+ unsigned appDelta = AppFrameIndices[newestIndex] - AppFrameIndices[StartIndex];
+
+ if (displayDelta < 2)
+ return 1.0f;
+
+ double frameRatio = double(appDelta) / double(displayDelta);
+ if (frameRatio < 0.33f)
+ return 0.33f;
+
+ return frameRatio;
+}
+
+
+
+}}} // namespace OVR::CAPI::FTM2
+