summaryrefslogtreecommitdiffstats
path: root/LibOVR/Src/CAPI/CAPI_HMDState.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'LibOVR/Src/CAPI/CAPI_HMDState.cpp')
-rw-r--r--LibOVR/Src/CAPI/CAPI_HMDState.cpp804
1 files changed, 804 insertions, 0 deletions
diff --git a/LibOVR/Src/CAPI/CAPI_HMDState.cpp b/LibOVR/Src/CAPI/CAPI_HMDState.cpp
new file mode 100644
index 0000000..fd98225
--- /dev/null
+++ b/LibOVR/Src/CAPI/CAPI_HMDState.cpp
@@ -0,0 +1,804 @@
+/************************************************************************************
+
+Filename : CAPI_HMDState.cpp
+Content : State associated with a single HMD
+Created : January 24, 2014
+Authors : Michael Antonov
+
+Copyright : Copyright 2014 Oculus VR, Inc. All Rights reserved.
+
+Licensed under the Oculus VR Rift SDK License Version 3.1 (the "License");
+you may not use the Oculus VR Rift SDK except in compliance with the License,
+which is provided at the time of installation or download, or which
+otherwise accompanies this software in either electronic or hard copy form.
+
+You may obtain a copy of the License at
+
+http://www.oculusvr.com/licenses/LICENSE-3.1
+
+Unless required by applicable law or agreed to in writing, the Oculus VR SDK
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+************************************************************************************/
+
+#include "CAPI_HMDState.h"
+#include "CAPI_GlobalState.h"
+#include "../OVR_Profile.h"
+
+namespace OVR { namespace CAPI {
+
+//-------------------------------------------------------------------------------------
+// ***** HMDState
+
+
+HMDState::HMDState(HMDDevice* device)
+ : pHMD(device), HMDInfoW(device), HMDInfo(HMDInfoW.h),
+ EnabledHmdCaps(0), HmdCapsAppliedToSensor(0),
+ SensorStarted(0), SensorCreated(0), SensorCaps(0),
+ AddSensorCount(0), AddLatencyTestCount(0), AddLatencyTestDisplayCount(0),
+ RenderState(getThis(), pHMD->GetProfile(), HMDInfoW.h),
+ LastFrameTimeSeconds(0.0f), LastGetFrameTimeSeconds(0.0),
+ LatencyTestActive(false),
+ LatencyTest2Active(false)
+{
+ pLastError = 0;
+ GlobalState::pInstance->AddHMD(this);
+
+ // Should be in renderer?
+ TimeManager.Init(RenderState.RenderInfo);
+
+ EyeRenderActive[0] = false;
+ EyeRenderActive[1] = false;
+
+ LatencyTestDrawColor[0] = 0;
+ LatencyTestDrawColor[1] = 0;
+ LatencyTestDrawColor[2] = 0;
+
+ OVR_CAPI_VISION_CODE( pPoseTracker = 0; )
+
+ RenderingConfigured = false;
+ BeginFrameCalled = false;
+ BeginFrameThreadId = 0;
+ BeginFrameTimingCalled = false;
+}
+
+HMDState::HMDState(ovrHmdType hmdType)
+ : pHMD(0), HMDInfoW(hmdType), HMDInfo(HMDInfoW.h),
+ EnabledHmdCaps(0),
+ SensorStarted(0), SensorCreated(0), SensorCaps(0),
+ AddSensorCount(0), AddLatencyTestCount(0), AddLatencyTestDisplayCount(0),
+ RenderState(getThis(), 0, HMDInfoW.h), // No profile.
+ LastFrameTimeSeconds(0.0), LastGetFrameTimeSeconds(0.0)
+{
+ // TBD: We should probably be looking up the default profile for the given
+ // device type + user.
+
+ pLastError = 0;
+ GlobalState::pInstance->AddHMD(this);
+
+ // Should be in renderer?
+ TimeManager.Init(RenderState.RenderInfo);
+
+ EyeRenderActive[0] = false;
+ EyeRenderActive[1] = false;
+
+ OVR_CAPI_VISION_CODE( pPoseTracker = 0; )
+
+ RenderingConfigured = false;
+ BeginFrameCalled = false;
+ BeginFrameThreadId = 0;
+ BeginFrameTimingCalled = false;
+}
+
+
+HMDState::~HMDState()
+{
+ OVR_ASSERT(GlobalState::pInstance);
+
+ StopSensor();
+ ConfigureRendering(0,0,0,0);
+
+ OVR_CAPI_VISION_CODE( OVR_ASSERT(pPoseTracker == 0); )
+
+ GlobalState::pInstance->RemoveHMD(this);
+}
+
+
+
+//-------------------------------------------------------------------------------------
+// *** Sensor
+
+bool HMDState::StartSensor(unsigned supportedCaps, unsigned requiredCaps)
+{
+ Lock::Locker lockScope(&DevicesLock);
+
+ bool crystalCoveOrBetter = (HMDInfo.HmdType == HmdType_CrystalCoveProto) ||
+ (HMDInfo.HmdType == HmdType_DK2);
+ bool sensorCreatedJustNow = false;
+
+ // TBD: In case of sensor not being immediately available, it would be good to check
+ // yaw config availability to match it with ovrHmdCap_YawCorrection requirement.
+ //
+
+ if (!crystalCoveOrBetter)
+ {
+ if (requiredCaps & ovrSensorCap_Position)
+ {
+ pLastError = "ovrSensorCap_Position not supported on this HMD.";
+ return false;
+ }
+ }
+
+ supportedCaps |= requiredCaps;
+
+ if (pHMD && !pSensor)
+ {
+ // Zero AddSensorCount before creation, in case it fails (or succeeds but then
+ // immediately gets disconnected) followed by another Add notification.
+ AddSensorCount = 0;
+ pSensor = *pHMD->GetSensor();
+ sensorCreatedJustNow= true;
+
+ if (pSensor)
+ {
+ pSensor->SetReportRate(500);
+ SFusion.AttachToSensor(pSensor);
+ applyProfileToSensorFusion();
+ }
+ else
+ {
+ if (requiredCaps & ovrSensorCap_Orientation)
+ {
+ pLastError = "Failed to create sensor.";
+ return false;
+ }
+ }
+ }
+
+
+ if ((requiredCaps & ovrSensorCap_YawCorrection) && !pSensor->IsMagCalibrated())
+ {
+ pLastError = "ovrHmdCap_YawCorrection not available.";
+ if (sensorCreatedJustNow)
+ {
+ SFusion.AttachToSensor(0);
+ SFusion.Reset();
+ pSensor.Clear();
+ }
+ return false;
+ }
+
+ SFusion.SetYawCorrectionEnabled((supportedCaps & ovrSensorCap_YawCorrection) != 0);
+
+ if (pSensor && sensorCreatedJustNow)
+ {
+ LogText("Sensor created.\n");
+ SensorCreated = true;
+ }
+
+ updateDK2FeaturesTiedToSensor(sensorCreatedJustNow);
+
+
+#ifdef OVR_CAPI_VISIONSUPPORT
+
+ if (crystalCoveOrBetter && (supportedCaps & ovrSensorCap_Position))
+ {
+ if (!pPoseTracker)
+ {
+ pPoseTracker = new Vision::PoseTracker(SFusion);
+ if (pPoseTracker)
+ {
+ pPoseTracker->AssociateHMD(pSensor);
+ LogText("Sensor Pose tracker created.\n");
+ }
+ }
+
+ // TBD: How do we verify that position tracking is actually available
+ // i.e. camera is plugged in?
+ }
+ else if (pPoseTracker)
+ {
+ // TBD: Internals not thread safe - must fix!!
+ delete pPoseTracker;
+ pPoseTracker = 0;
+ LogText("Sensor Pose tracker destroyed.\n");
+ }
+
+#endif // OVR_CAPI_VISIONSUPPORT
+
+ SensorCaps = supportedCaps;
+ SensorStarted = true;
+
+ return true;
+}
+
+
+// Stops sensor sampling, shutting down internal resources.
+void HMDState::StopSensor()
+{
+ Lock::Locker lockScope(&DevicesLock);
+
+ if (SensorStarted)
+ {
+#ifdef OVR_CAPI_VISIONSUPPORT
+ if (pPoseTracker)
+ {
+ // TBD: Internals not thread safe - must fix!!
+ delete pPoseTracker;
+ pPoseTracker = 0;
+ LogText("Sensor Pose tracker destroyed.\n");
+ }
+#endif // OVR_CAPI_VISION_CODE
+
+ SFusion.AttachToSensor(0);
+ SFusion.Reset();
+ pSensor.Clear();
+ HmdCapsAppliedToSensor = 0;
+ AddSensorCount = 0;
+ SensorCaps = 0;
+ SensorCreated = false;
+ SensorStarted = false;
+
+ LogText("StopSensor succeeded.\n");
+ }
+}
+
+// Resets sensor orientation.
+void HMDState::ResetSensor()
+{
+ SFusion.Reset();
+}
+
+
+// Returns prediction for time.
+ovrSensorState HMDState::PredictedSensorState(double absTime)
+{
+ SensorState ss;
+
+ // We are trying to keep this path lockless unless we are notified of new device
+ // creation while not having a sensor yet. It's ok to check SensorCreated volatile
+ // flag here, since GetSensorStateAtTime() is internally lockless and safe.
+
+ if (SensorCreated || checkCreateSensor())
+ {
+ ss = SFusion.GetSensorStateAtTime(absTime);
+
+ if (!(ss.StatusFlags & ovrStatus_OrientationTracked))
+ {
+ Lock::Locker lockScope(&DevicesLock);
+
+#ifdef OVR_CAPI_VISIONSUPPORT
+ if (pPoseTracker)
+ {
+ // TBD: Internals not thread safe - must fix!!
+ delete pPoseTracker;
+ pPoseTracker = 0;
+ LogText("Sensor Pose tracker destroyed.\n");
+ }
+#endif // OVR_CAPI_VISION_CODE
+ // Not needed yet; SFusion.AttachToSensor(0);
+ // This seems to reset orientation anyway...
+ pSensor.Clear();
+ SensorCreated = false;
+ HmdCapsAppliedToSensor = 0;
+ }
+ }
+ else
+ {
+ // SensorState() defaults to 0s.
+ // ss.Pose.Orientation = Quatf();
+ // ..
+
+ // John:
+ // We still want valid times so frames will get a delta-time
+ // and allow operation with a joypad when the sensor isn't
+ // connected.
+ ss.Recorded.TimeInSeconds = absTime;
+ ss.Predicted.TimeInSeconds = absTime;
+ }
+
+ ss.StatusFlags |= ovrStatus_HmdConnected;
+ return ss;
+}
+
+
+bool HMDState::checkCreateSensor()
+{
+ if (!(SensorStarted && !SensorCreated && AddSensorCount))
+ return false;
+
+ Lock::Locker lockScope(&DevicesLock);
+
+ // Re-check condition once in the lock, in case the state changed.
+ if (SensorStarted && !SensorCreated && AddSensorCount)
+ {
+ if (pHMD)
+ {
+ AddSensorCount = 0;
+ pSensor = *pHMD->GetSensor();
+ }
+
+ if (pSensor)
+ {
+ pSensor->SetReportRate(500);
+ SFusion.AttachToSensor(pSensor);
+ SFusion.SetYawCorrectionEnabled((SensorCaps & ovrSensorCap_YawCorrection) != 0);
+ applyProfileToSensorFusion();
+
+#ifdef OVR_CAPI_VISIONSUPPORT
+ if (SensorCaps & ovrSensorCap_Position)
+ {
+ pPoseTracker = new Vision::PoseTracker(SFusion);
+ if (pPoseTracker)
+ {
+ pPoseTracker->AssociateHMD(pSensor);
+ }
+ LogText("Sensor Pose tracker created.\n");
+ }
+#endif // OVR_CAPI_VISION_CODE
+
+ LogText("Sensor created.\n");
+
+ SensorCreated = true;
+ return true;
+ }
+ }
+
+ return SensorCreated;
+}
+
+bool HMDState::GetSensorDesc(ovrSensorDesc* descOut)
+{
+ Lock::Locker lockScope(&DevicesLock);
+
+ if (SensorCreated)
+ {
+ OVR_ASSERT(pSensor);
+ OVR::SensorInfo si;
+ pSensor->GetDeviceInfo(&si);
+ descOut->VendorId = si.VendorId;
+ descOut->ProductId = si.ProductId;
+ OVR_ASSERT(si.SerialNumber.GetSize() <= sizeof(descOut->SerialNumber));
+ OVR_strcpy(descOut->SerialNumber, sizeof(descOut->SerialNumber), si.SerialNumber.ToCStr());
+ return true;
+ }
+ return false;
+}
+
+
+void HMDState::applyProfileToSensorFusion()
+{
+ if (!pHMD)
+ return;
+ Profile* profile = pHMD->GetProfile();
+ if (!profile)
+ {
+ OVR_ASSERT(false);
+ return;
+ }
+ SFusion.SetUserHeadDimensions ( *profile, RenderState.RenderInfo );
+}
+
+void HMDState::updateLowPersistenceMode(bool lowPersistence) const
+{
+ OVR_ASSERT(pSensor);
+ DisplayReport dr;
+
+ if (pSensor.GetPtr())
+ {
+ pSensor->GetDisplayReport(&dr);
+
+ dr.Persistence = (UInt16) (dr.TotalRows * (lowPersistence ? 0.18f : 1.0f));
+ dr.Brightness = lowPersistence ? 255 : 0;
+
+ pSensor->SetDisplayReport(dr);
+ }
+}
+
+void HMDState::updateLatencyTestForHmd(bool latencyTesting)
+{
+ if (pSensor.GetPtr())
+ {
+ DisplayReport dr;
+ pSensor->GetDisplayReport(&dr);
+
+ dr.ReadPixel = latencyTesting;
+
+ pSensor->SetDisplayReport(dr);
+ }
+
+ if (latencyTesting)
+ {
+ LatencyUtil2.SetSensorDevice(pSensor.GetPtr());
+ }
+ else
+ {
+ LatencyUtil2.SetSensorDevice(NULL);
+ }
+}
+
+
+void HMDState::updateDK2FeaturesTiedToSensor(bool sensorCreatedJustNow)
+{
+ Lock::Locker lockScope(&DevicesLock);
+
+ if (!SensorCreated || (HMDInfo.HmdType != HmdType_DK2))
+ return;
+
+ // Only send display reports if state changed or sensor initializing first time.
+ if (sensorCreatedJustNow ||
+ ((HmdCapsAppliedToSensor ^ EnabledHmdCaps) & ovrHmdCap_LowPersistence))
+ {
+ updateLowPersistenceMode((EnabledHmdCaps & ovrHmdCap_LowPersistence) ? true : false);
+ }
+
+ if (sensorCreatedJustNow || ((HmdCapsAppliedToSensor ^ EnabledHmdCaps) & ovrHmdCap_LatencyTest))
+ {
+ updateLatencyTestForHmd((EnabledHmdCaps & ovrHmdCap_LatencyTest) != 0);
+ }
+
+ HmdCapsAppliedToSensor = EnabledHmdCaps & (ovrHmdCap_LowPersistence|ovrHmdCap_LatencyTest);
+}
+
+
+
+void HMDState::SetEnabledHmdCaps(unsigned hmdCaps)
+{
+
+ if (HMDInfo.HmdType == HmdType_DK2)
+ {
+ if ((EnabledHmdCaps ^ hmdCaps) & ovrHmdCap_DynamicPrediction)
+ {
+ // DynamicPrediction change
+ TimeManager.ResetFrameTiming(TimeManager.GetFrameTiming().FrameIndex,
+ (hmdCaps & ovrHmdCap_DynamicPrediction) ? true : false,
+ RenderingConfigured);
+ }
+ }
+
+ if ((EnabledHmdCaps ^ hmdCaps) & ovrHmdCap_NoVSync)
+ {
+ TimeManager.SetVsync((hmdCaps & ovrHmdCap_NoVSync) ? false : true);
+ }
+
+
+ EnabledHmdCaps = hmdCaps & ovrHmdCap_Writable_Mask;
+ RenderState.EnabledHmdCaps = EnabledHmdCaps;
+
+ // Unfortunately, LowPersistance and other flags are tied to sensor.
+ // This flag will apply the state of sensor is created; otherwise this will be delayed
+ // till StartSensor.
+ // Such behavior is less then ideal, but should be resolved with the service model.
+
+ updateDK2FeaturesTiedToSensor(false);
+}
+
+
+//-------------------------------------------------------------------------------------
+// ***** Property Access
+
+// TBD: This all needs to be cleaned up and organized into namespaces.
+
+float HMDState::getFloatValue(const char* propertyName, float defaultVal)
+{
+ if (OVR_strcmp(propertyName, "LensSeparation") == 0)
+ {
+ return HMDInfo.LensSeparationInMeters;
+ }
+ else if (OVR_strcmp(propertyName, "CenterPupilDepth") == 0)
+ {
+ return SFusion.GetCenterPupilDepth();
+ }
+ else if (pHMD)
+ {
+ Profile* p = pHMD->GetProfile();
+ if (p)
+ {
+ return p->GetFloatValue(propertyName, defaultVal);
+ }
+ }
+ return defaultVal;
+}
+
+bool HMDState::setFloatValue(const char* propertyName, float value)
+{
+ if (OVR_strcmp(propertyName, "CenterPupilDepth") == 0)
+ {
+ SFusion.SetCenterPupilDepth(value);
+ return true;
+ }
+ return false;
+}
+
+
+static unsigned CopyFloatArrayWithLimit(float dest[], unsigned destSize,
+ float source[], unsigned sourceSize)
+{
+ unsigned count = Alg::Min(destSize, sourceSize);
+ for (unsigned i = 0; i < count; i++)
+ dest[i] = source[i];
+ return count;
+}
+
+
+unsigned HMDState::getFloatArray(const char* propertyName, float values[], unsigned arraySize)
+{
+ if (arraySize)
+ {
+ if (OVR_strcmp(propertyName, "ScreenSize") == 0)
+ {
+ float data[2] = { HMDInfo.ScreenSizeInMeters.w, HMDInfo.ScreenSizeInMeters.h };
+
+ return CopyFloatArrayWithLimit(values, arraySize, data, 2);
+ }
+ else if (OVR_strcmp(propertyName, "DistortionClearColor") == 0)
+ {
+ return CopyFloatArrayWithLimit(values, arraySize, RenderState.ClearColor, 4);
+ }
+ else if (OVR_strcmp(propertyName, "DK2Latency") == 0)
+ {
+ if (HMDInfo.HmdType != HmdType_DK2)
+ return 0;
+
+ float data[3];
+ TimeManager.GetLatencyTimings(data);
+
+ return CopyFloatArrayWithLimit(values, arraySize, data, 3);
+ }
+
+ /*
+ else if (OVR_strcmp(propertyName, "CenterPupilDepth") == 0)
+ {
+ if (arraySize >= 1)
+ {
+ values[0] = SFusion.GetCenterPupilDepth();
+ return 1;
+ }
+ return 0;
+ } */
+ else if (pHMD)
+ {
+ Profile* p = pHMD->GetProfile();
+
+ // TBD: Not quite right. Should update profile interface, so that
+ // we can return 0 in all conditions if property doesn't exist.
+ if (p)
+ {
+ unsigned count = p->GetFloatValues(propertyName, values, arraySize);
+ return count;
+ }
+ }
+ }
+
+ return 0;
+}
+
+bool HMDState::setFloatArray(const char* propertyName, float values[], unsigned arraySize)
+{
+ if (!arraySize)
+ return false;
+
+ if (OVR_strcmp(propertyName, "DistortionClearColor") == 0)
+ {
+ CopyFloatArrayWithLimit(RenderState.ClearColor, 4, values, arraySize);
+ return true;
+ }
+ return false;
+}
+
+
+const char* HMDState::getString(const char* propertyName, const char* defaultVal)
+{
+ if (pHMD)
+ {
+ // For now, just access the profile.
+ Profile* p = pHMD->GetProfile();
+
+ LastGetStringValue[0] = 0;
+ if (p && p->GetValue(propertyName, LastGetStringValue, sizeof(LastGetStringValue)))
+ {
+ return LastGetStringValue;
+ }
+ }
+
+ return defaultVal;
+}
+
+//-------------------------------------------------------------------------------------
+// *** Latency Test
+
+bool HMDState::ProcessLatencyTest(unsigned char rgbColorOut[3])
+{
+ bool result = false;
+
+ // Check create.
+ if (pLatencyTester)
+ {
+ if (pLatencyTester->IsConnected())
+ {
+ Color colorToDisplay;
+
+ LatencyUtil.ProcessInputs();
+ result = LatencyUtil.DisplayScreenColor(colorToDisplay);
+ rgbColorOut[0] = colorToDisplay.R;
+ rgbColorOut[1] = colorToDisplay.G;
+ rgbColorOut[2] = colorToDisplay.B;
+ }
+ else
+ {
+ // Disconnect.
+ LatencyUtil.SetDevice(NULL);
+ pLatencyTester = 0;
+ LogText("LATENCY SENSOR disconnected.\n");
+ }
+ }
+ else if (AddLatencyTestCount > 0)
+ {
+ // This might have some unlikely race condition issue which could cause us to miss a device...
+ AddLatencyTestCount = 0;
+
+ pLatencyTester = *GlobalState::pInstance->GetManager()->
+ EnumerateDevices<LatencyTestDevice>().CreateDevice();
+ if (pLatencyTester)
+ {
+ LatencyUtil.SetDevice(pLatencyTester);
+ LogText("LATENCY TESTER connected\n");
+ }
+ }
+
+ return result;
+}
+
+void HMDState::ProcessLatencyTest2(unsigned char rgbColorOut[3], double startTime)
+{
+ // Check create.
+ if (!(EnabledHmdCaps & ovrHmdCap_LatencyTest))
+ return;
+
+ if (pLatencyTesterDisplay && !LatencyUtil2.HasDisplayDevice())
+ {
+ if (!pLatencyTesterDisplay->IsConnected())
+ {
+ LatencyUtil2.SetDisplayDevice(NULL);
+ }
+ }
+ else if (AddLatencyTestDisplayCount > 0)
+ {
+ // This might have some unlikely race condition issue
+ // which could cause us to miss a device...
+ AddLatencyTestDisplayCount = 0;
+
+ pLatencyTesterDisplay = *GlobalState::pInstance->GetManager()->
+ EnumerateDevices<LatencyTestDevice>().CreateDevice();
+ if (pLatencyTesterDisplay)
+ {
+ LatencyUtil2.SetDisplayDevice(pLatencyTesterDisplay);
+ }
+ }
+
+ if (LatencyUtil2.HasDevice() && pSensor && pSensor->IsConnected())
+ {
+ LatencyUtil2.BeginTest(startTime);
+
+ Color colorToDisplay;
+ LatencyTest2Active = LatencyUtil2.DisplayScreenColor(colorToDisplay);
+ rgbColorOut[0] = colorToDisplay.R;
+ rgbColorOut[1] = colorToDisplay.G;
+ rgbColorOut[2] = colorToDisplay.B;
+ }
+ else
+ {
+ LatencyTest2Active = false;
+ }
+}
+
+//-------------------------------------------------------------------------------------
+// *** Rendering
+
+bool HMDState::ConfigureRendering(ovrEyeRenderDesc eyeRenderDescOut[2],
+ const ovrFovPort eyeFovIn[2],
+ const ovrRenderAPIConfig* apiConfig,
+ unsigned distortionCaps)
+{
+ ThreadChecker::Scope checkScope(&RenderAPIThreadChecker, "ovrHmd_ConfigureRendering");
+
+ // null -> shut down.
+ if (!apiConfig)
+ {
+ if (pRenderer)
+ pRenderer.Clear();
+ RenderingConfigured = false;
+ return true;
+ }
+
+ if (pRenderer &&
+ (apiConfig->Header.API != pRenderer->GetRenderAPI()))
+ {
+ // Shutdown old renderer.
+ if (pRenderer)
+ pRenderer.Clear();
+ }
+
+
+ // Step 1: do basic setup configuration
+ RenderState.setupRenderDesc(eyeRenderDescOut, eyeFovIn);
+ RenderState.EnabledHmdCaps = EnabledHmdCaps; // This is a copy... Any cleaner way?
+ RenderState.DistortionCaps = distortionCaps;
+
+ TimeManager.ResetFrameTiming(0,
+ (EnabledHmdCaps & ovrHmdCap_DynamicPrediction) ? true : false,
+ true);
+
+ LastFrameTimeSeconds = 0.0f;
+
+ // Set RenderingConfigured early to avoid ASSERTs in renderer initialization.
+ RenderingConfigured = true;
+
+ if (!pRenderer)
+ {
+ pRenderer = *DistortionRenderer::APICreateRegistry
+ [apiConfig->Header.API](this, TimeManager, RenderState);
+ }
+
+ if (!pRenderer ||
+ !pRenderer->Initialize(apiConfig, distortionCaps))
+ {
+ RenderingConfigured = false;
+ return false;
+ }
+
+ return true;
+}
+
+
+
+ovrPosef HMDState::BeginEyeRender(ovrEyeType eye)
+{
+ // Debug checks.
+ checkBeginFrameScope("ovrHmd_BeginEyeRender");
+ ThreadChecker::Scope checkScope(&RenderAPIThreadChecker, "ovrHmd_BeginEyeRender");
+
+ // Unknown eyeId provided in ovrHmd_BeginEyeRender
+ OVR_ASSERT_LOG(eye == ovrEye_Left || eye == ovrEye_Right,
+ ("ovrHmd_BeginEyeRender eyeId out of range."));
+ OVR_ASSERT_LOG(EyeRenderActive[eye] == false,
+ ("Multiple calls to ovrHmd_BeginEyeRender for the same eye."));
+
+ EyeRenderActive[eye] = true;
+
+ // Only process latency tester for drawing the left eye (assumes left eye is drawn first)
+ if (pRenderer && eye == 0)
+ {
+ LatencyTestActive = ProcessLatencyTest(LatencyTestDrawColor);
+ }
+
+ return ovrHmd_GetEyePose(this, eye);
+}
+
+
+void HMDState::EndEyeRender(ovrEyeType eye, ovrPosef renderPose, ovrTexture* eyeTexture)
+{
+ // Debug checks.
+ checkBeginFrameScope("ovrHmd_EndEyeRender");
+ ThreadChecker::Scope checkScope(&RenderAPIThreadChecker, "ovrHmd_EndEyeRender");
+
+ if (!EyeRenderActive[eye])
+ {
+ OVR_ASSERT_LOG(false,
+ ("ovrHmd_EndEyeRender called without ovrHmd_BeginEyeRender."));
+ return;
+ }
+
+ RenderState.EyeRenderPoses[eye] = renderPose;
+
+ if (pRenderer)
+ pRenderer->SubmitEye(eye, eyeTexture);
+
+ EyeRenderActive[eye] = false;
+}
+
+}} // namespace OVR::CAPI
+